consolidate all repos to one for archive
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="TetrisRPS.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:TetrisRPS"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace TetrisRPS
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TetrisRPS
|
||||
{
|
||||
class GameNetwork
|
||||
{
|
||||
TcpListener? tcpListener;
|
||||
TcpClient? tcpClient;
|
||||
NetworkStream? networkStream;
|
||||
|
||||
public GameNetwork() { }
|
||||
|
||||
public void StartListener()
|
||||
{
|
||||
tcpListener = new TcpListener(IPAddress.Any, 8000);
|
||||
tcpListener.Start();
|
||||
tcpClient = tcpListener.AcceptTcpClient();
|
||||
networkStream = tcpClient.GetStream();
|
||||
}
|
||||
|
||||
public void StopListener()
|
||||
{
|
||||
networkStream?.Close();
|
||||
tcpClient?.Close();
|
||||
tcpListener?.Stop();
|
||||
}
|
||||
|
||||
public void StartClient(string ip)
|
||||
{
|
||||
tcpClient = new TcpClient(ip, 8000);
|
||||
networkStream = tcpClient.GetStream();
|
||||
}
|
||||
|
||||
public void StopClient()
|
||||
{
|
||||
networkStream?.Close();
|
||||
tcpClient?.Close();
|
||||
}
|
||||
|
||||
public delegate void EventReceived(int e);
|
||||
public event EventReceived? OnEventReceived;
|
||||
byte[] data = new byte[4];
|
||||
|
||||
public void BeginRead()
|
||||
{
|
||||
if (networkStream == null) return;
|
||||
networkStream?.BeginRead(data, 0, data.Length, OnReadComplete, null);
|
||||
}
|
||||
|
||||
private void OnReadComplete (IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
int bytesRead = networkStream.EndRead(ar);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
int ret = BitConverter.ToInt32(data);
|
||||
OnEventReceived?.Invoke(ret);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { }
|
||||
}
|
||||
|
||||
public void SendData(int key)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(key);
|
||||
networkStream?.Write(bytes, 0, bytes.Length);
|
||||
}catch(Exception e) { }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
<Window x:Class="TetrisRPS.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:TetrisRPS"
|
||||
mc:Ignorable="d"
|
||||
Title="Dual-Tetris" Height="600" Width="900"
|
||||
MinWidth="900" MinHeight="600"
|
||||
Foreground="White" FontWeight="Bold" FontFamily="Segoe UI" FontSize="25"
|
||||
Icon="assets/icon.png" KeyDown="WindowKeyDown">
|
||||
<!--Add the Function for detecing player input-->
|
||||
<!--Key down event handler-->
|
||||
<!--KeyDown="Name of the function for detection"-->
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.Background>
|
||||
<ImageBrush ImageSource="assets\background.jpg"/>
|
||||
</Grid.Background>
|
||||
|
||||
<!--For entering the IP address-->
|
||||
<TextBlock Grid.Row="0" Grid.Column="1"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Center"
|
||||
Text="Enter IP address:"
|
||||
FontSize="20" Margin="0,5,0,50"/>
|
||||
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap" Width="175" Height="25" FontSize="15"
|
||||
FontWeight="Light" Margin="0,15,0,0"
|
||||
Name="IPInput"/>
|
||||
|
||||
<Button Grid.Row="0" Grid.Column="1"
|
||||
VerticalAlignment="Bottom" HorizontalContentAlignment="Center"
|
||||
Content="Start" Width="75" Height="18" FontWeight="Light" FontSize="10"
|
||||
Click="Star_Click"
|
||||
Name="StartButton"
|
||||
/>
|
||||
|
||||
<!--For choosing to either be a host or not-->
|
||||
<TextBlock Grid.Row="0" Grid.Column="3"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Text="SinglePlayer:" FontWeight="Bold" FontSize="20"/>
|
||||
<CheckBox Grid.Row="0" Grid.Column="3" Margin="0,5,0,0"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Name="IsSinglePlayer"
|
||||
/>
|
||||
|
||||
<!--Current players game view-->
|
||||
<Viewbox Grid.Row="2" Grid.Column="1" Margin="0,15,0,15">
|
||||
|
||||
<!--Add the Function for loading the game into the Loaded argument-->
|
||||
<!--The loading argument goes inside the Canvas-->
|
||||
<!--Loaded="Function Name For Loading"-->
|
||||
<Canvas x:Name="firstCanvas"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Height="500"
|
||||
ClipToBounds="True"
|
||||
Background="#545454"/>
|
||||
</Viewbox>
|
||||
|
||||
<!--The block that is currently being held-->
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right">
|
||||
<TextBlock Text="Hold" TextAlignment="Center"/>
|
||||
|
||||
<Image x:Name="holdImage" Margin="20" Width="120"/>
|
||||
</StackPanel>
|
||||
|
||||
<!--The game view of the opposing player-->
|
||||
<Viewbox Grid.Row="2" Grid.Column="3" Margin="0,15,0,15">
|
||||
|
||||
<!--Add the Function for loading the game into the Loaded argument-->
|
||||
<!--The loading argument goes inside the Canvas-->
|
||||
<!--Loaded="Function Name For Loading"-->
|
||||
<Canvas x:Name="secondCanvas"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Height="500"
|
||||
ClipToBounds="True"
|
||||
Background="#545454"/>
|
||||
</Viewbox>
|
||||
|
||||
<!--The opponet of the player-->
|
||||
<!--Dynamically change the text to either player 1 or 2-->
|
||||
<TextBlock x:Name="againstPlayer"
|
||||
Grid.Row="1"
|
||||
Grid.Column="3"
|
||||
Text="Opponent"
|
||||
TextAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<!--Text in the middle "VS"-->
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" TextAlignment="Center" VerticalAlignment="Center" Text="VS"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="4">
|
||||
</StackPanel>
|
||||
|
||||
<!--This is the overlay for when the game is over-->
|
||||
<!--Change the visibility to "Visible" once the game is over-->
|
||||
<Grid x:Name="gameOverScreen"
|
||||
Background="#CC000000"
|
||||
Grid.RowSpan="3"
|
||||
Grid.ColumnSpan="5" Visibility="Hidden">
|
||||
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
|
||||
<!--This is the winning players textBlock change it based on the winner-->
|
||||
<TextBlock x:Name="playerWinText" FontSize="48" TextAlignment="Center"/>
|
||||
|
||||
<!--Play again button-->
|
||||
<!--You can add the Click event/argument to reset the game-->
|
||||
<Button Content="Play Again"
|
||||
Background="LightCyan"
|
||||
Margin="0, 20, 0, 0"
|
||||
Padding="5"
|
||||
Click="End_Click"
|
||||
/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace TetrisRPS
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// Array containg the tile images
|
||||
// Order goes first with the empty tile then corresponds to each blocks ID
|
||||
private readonly ImageSource[] tileImages = new ImageSource[]
|
||||
{
|
||||
new BitmapImage(new Uri("assets/TileEmpty.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileCyan.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileBlue.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileOrange.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileYellow.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileGreen.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TilePurple.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/TileRed.png", UriKind.Relative))
|
||||
};
|
||||
|
||||
// Array contains full picture of the blocks
|
||||
// Used in holding and upcoming block
|
||||
// The order matches each blocks ID
|
||||
private readonly ImageSource[] blockImages = new ImageSource[]
|
||||
{
|
||||
new BitmapImage(new Uri("assets/Block-Empty.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-I.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-J.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-L.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-O.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-S.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-T.png", UriKind.Relative)),
|
||||
new BitmapImage(new Uri("assets/Block-Z.png", UriKind.Relative))
|
||||
};
|
||||
|
||||
// For each of the game grid cells there is and image control
|
||||
private readonly Image[,] firstImageControls;
|
||||
private readonly Image[,] secondImageControls;
|
||||
|
||||
private GameState firstGameState = new GameState();
|
||||
private GameState secondGameState = new GameState();
|
||||
|
||||
DispatcherTimer timer = new DispatcherTimer();
|
||||
|
||||
GameNetwork gameNetwork = new GameNetwork();
|
||||
public const int Tick = 180;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
firstImageControls = SetupGameCanvas(firstGameState.GameGrid, firstCanvas);
|
||||
secondImageControls = SetupGameCanvas(secondGameState.GameGrid, secondCanvas);
|
||||
timer.Tick += Game_Tick;
|
||||
timer.Interval = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
gameNetwork.OnEventReceived += GameNetwork_OnEventReceived;
|
||||
}
|
||||
|
||||
private void GameNetwork_OnEventReceived(int e)
|
||||
{
|
||||
if(e == Tick) secondGameState.MoveBlockDown();
|
||||
secondGameState.MoveBlock(e);
|
||||
if (!secondGameState.IsGameOver) gameNetwork.BeginRead();
|
||||
}
|
||||
|
||||
private void Game_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
if (!firstGameState.IsGameOver && !secondGameState.IsGameOver)
|
||||
{
|
||||
firstGameState.MoveBlockDown();
|
||||
Draw(firstGameState, firstImageControls);
|
||||
DrawHeldBlock(firstGameState.HeldBlock);
|
||||
gameNetwork.SendData(Tick);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameOverScreen.Visibility = Visibility.Visible;
|
||||
if (firstGameState.IsGameOver)
|
||||
playerWinText.Text = "You Lose";
|
||||
else
|
||||
playerWinText.Text = "You Win";
|
||||
|
||||
|
||||
if (!isSingle)
|
||||
{
|
||||
if (isHost)
|
||||
gameNetwork.StopListener();
|
||||
else
|
||||
gameNetwork.StopClient();
|
||||
}
|
||||
|
||||
|
||||
|
||||
timer.Stop();
|
||||
}
|
||||
Draw(secondGameState, secondImageControls);
|
||||
}
|
||||
|
||||
private Image[,] SetupGameCanvas(GameGrid grid, Canvas canvas)
|
||||
{
|
||||
Image[,] ImageControls = new Image[grid.Rows, grid.Columns];
|
||||
int cellSize = 25;
|
||||
|
||||
for (int r = 0; r < grid.Rows; r++)
|
||||
{
|
||||
for (int c = 0; c < grid.Columns; c++)
|
||||
{
|
||||
Image imageControl = new Image
|
||||
{
|
||||
Width = cellSize,
|
||||
Height = cellSize
|
||||
};
|
||||
Canvas.SetTop(imageControl, (r - 2) * cellSize);
|
||||
Canvas.SetLeft(imageControl, c * cellSize);
|
||||
canvas.Children.Add(imageControl);
|
||||
ImageControls[r, c] = imageControl;
|
||||
}
|
||||
}
|
||||
return ImageControls;
|
||||
}
|
||||
|
||||
private void DrawGrid(GameGrid grid, Image[,] control)
|
||||
{
|
||||
for (int r = 0; r < grid.Rows; r++)
|
||||
{
|
||||
for (int c = 0; c < grid.Columns; c++)
|
||||
{
|
||||
int id = grid[r, c];
|
||||
control[r, c].Source = tileImages[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBlock(Block block, Image[,] control)
|
||||
{
|
||||
foreach (Position p in block.TilePositions())
|
||||
{
|
||||
control[p.Row, p.Column].Source = tileImages[block.Id];
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHeldBlock(Block heldBlock)
|
||||
{
|
||||
if (heldBlock == null)
|
||||
{
|
||||
holdImage.Source = blockImages[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
holdImage.Source = blockImages[heldBlock.Id];
|
||||
}
|
||||
}
|
||||
|
||||
private void Draw(GameState gameState, Image[,] control)
|
||||
{
|
||||
DrawGrid(gameState.GameGrid, control);
|
||||
DrawBlock(gameState.currentBlock, control);
|
||||
}
|
||||
|
||||
// Detecting player input
|
||||
private void WindowKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (firstGameState.IsGameOver || secondGameState.IsGameOver)
|
||||
return;
|
||||
firstGameState.MoveBlock((int)e.Key);
|
||||
gameNetwork.SendData((int)e.Key);
|
||||
Draw(firstGameState, firstImageControls);
|
||||
DrawHeldBlock(firstGameState.HeldBlock);
|
||||
Draw(secondGameState, secondImageControls);
|
||||
}
|
||||
|
||||
private void End_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
gameOverScreen.Visibility = Visibility.Hidden;
|
||||
IPInput.IsEnabled = true;
|
||||
StartButton.IsEnabled = true;
|
||||
IsSinglePlayer.IsEnabled = true;
|
||||
|
||||
|
||||
|
||||
Draw(firstGameState, firstImageControls);
|
||||
DrawHeldBlock(firstGameState.HeldBlock);
|
||||
Draw(secondGameState, secondImageControls);
|
||||
}
|
||||
|
||||
private bool isSingle = true;
|
||||
private bool isHost = false;
|
||||
private void Star_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IPInput.IsEnabled = false;
|
||||
StartButton.IsEnabled = false;
|
||||
IsSinglePlayer.IsEnabled = false;
|
||||
this.Focus();
|
||||
|
||||
firstGameState = new GameState();
|
||||
secondGameState = new GameState();
|
||||
|
||||
if (IsSinglePlayer.IsChecked is bool a)
|
||||
{
|
||||
isSingle = a;
|
||||
if (!a)
|
||||
{
|
||||
if (IPInput.Text == "")
|
||||
{
|
||||
gameNetwork.StartListener();
|
||||
isHost = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gameNetwork.StartClient(IPInput.Text);
|
||||
isHost = false;
|
||||
}
|
||||
gameNetwork.BeginRead();
|
||||
}
|
||||
}
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="assets\background.jpg" />
|
||||
<None Remove="assets\Block-Empty.png" />
|
||||
<None Remove="assets\Block-I.png" />
|
||||
<None Remove="assets\Block-J.png" />
|
||||
<None Remove="assets\Block-L.png" />
|
||||
<None Remove="assets\Block-O.png" />
|
||||
<None Remove="assets\Block-S.png" />
|
||||
<None Remove="assets\Block-T.png" />
|
||||
<None Remove="assets\Block-Z.png" />
|
||||
<None Remove="assets\icon.png" />
|
||||
<None Remove="assets\TileBlue.png" />
|
||||
<None Remove="assets\TileCyan.png" />
|
||||
<None Remove="assets\TileEmpty.png" />
|
||||
<None Remove="assets\TileGreen.png" />
|
||||
<None Remove="assets\TileOrange.png" />
|
||||
<None Remove="assets\TilePurple.png" />
|
||||
<None Remove="assets\TileRed.png" />
|
||||
<None Remove="assets\TileYellow.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="assets\background.jpg" />
|
||||
<Resource Include="assets\Block-Empty.png" />
|
||||
<Resource Include="assets\Block-I.png" />
|
||||
<Resource Include="assets\Block-J.png" />
|
||||
<Resource Include="assets\Block-L.png" />
|
||||
<Resource Include="assets\Block-O.png" />
|
||||
<Resource Include="assets\Block-S.png" />
|
||||
<Resource Include="assets\Block-T.png" />
|
||||
<Resource Include="assets\Block-Z.png" />
|
||||
<Resource Include="assets\icon.png" />
|
||||
<Resource Include="assets\TileBlue.png" />
|
||||
<Resource Include="assets\TileCyan.png" />
|
||||
<Resource Include="assets\TileEmpty.png" />
|
||||
<Resource Include="assets\TileGreen.png" />
|
||||
<Resource Include="assets\TileOrange.png" />
|
||||
<Resource Include="assets\TilePurple.png" />
|
||||
<Resource Include="assets\TileRed.png" />
|
||||
<Resource Include="assets\TileYellow.png" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 949 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TetrisRPS;
|
||||
|
||||
public abstract class Block
|
||||
{
|
||||
protected abstract Position[][] Tiles { get; }
|
||||
|
||||
public abstract Position StartOffset { get; }
|
||||
|
||||
public abstract int Id { get; }
|
||||
|
||||
private int rotationState;
|
||||
private Position offset;
|
||||
|
||||
public Block()
|
||||
{
|
||||
offset = new Position(StartOffset.Row, StartOffset.Column);
|
||||
}
|
||||
|
||||
public IEnumerable<Position> TilePositions()
|
||||
{
|
||||
foreach (var tile in Tiles[rotationState])
|
||||
{
|
||||
yield return new Position(tile.Row + offset.Row, tile.Column + offset.Column);
|
||||
}
|
||||
}
|
||||
|
||||
public void RotateCW()
|
||||
{
|
||||
rotationState = (rotationState + 1) % Tiles.Length;
|
||||
}
|
||||
public void RotateCCW()
|
||||
{
|
||||
if (rotationState == 0)
|
||||
{
|
||||
rotationState = Tiles.Length - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
rotationState--;
|
||||
}
|
||||
}
|
||||
//Move by x rows and y columns.
|
||||
public void Move(int row, int column)
|
||||
{
|
||||
offset.Row += row;
|
||||
offset.Column += column;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
rotationState = 0;
|
||||
offset.Row = StartOffset.Row;
|
||||
offset.Column = StartOffset.Column;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.VisualBasic;
|
||||
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class BlockQueue
|
||||
{
|
||||
private readonly Block[] blocks = new Block[]
|
||||
{
|
||||
new IBlock(),
|
||||
new JBlock(),
|
||||
new LBlock(),
|
||||
new OBlock(),
|
||||
new SBlock(),
|
||||
new TBlock(),
|
||||
new ZBlock()
|
||||
};
|
||||
private readonly Random random = new();
|
||||
|
||||
public Block NextBlock { get; private set; }
|
||||
int curBlockID = 0;
|
||||
public BlockQueue()
|
||||
{
|
||||
NextBlock = RandomBlock();
|
||||
}
|
||||
private Block RandomBlock()
|
||||
{
|
||||
curBlockID++;
|
||||
curBlockID %= blocks.Length;
|
||||
return blocks[curBlockID].GetType().GetConstructor(Type.EmptyTypes).Invoke(null) as Block;
|
||||
}
|
||||
|
||||
public Block GetAndReplaceNextBlock()
|
||||
{
|
||||
Block block = NextBlock;
|
||||
do
|
||||
{
|
||||
NextBlock = RandomBlock();
|
||||
} while (block.Id == NextBlock.Id);
|
||||
return block;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
namespace TetrisRPS
|
||||
{
|
||||
public class GameGrid
|
||||
{
|
||||
private readonly int[,] grid;
|
||||
public int Rows { get; }
|
||||
public int Columns { get; }
|
||||
|
||||
// Easy access to the grid values.
|
||||
public int this[int row, int column]
|
||||
{
|
||||
get => grid[row, column];
|
||||
set => grid[row, column] = value;
|
||||
}
|
||||
|
||||
//So it could be made for differently sized grids.
|
||||
public GameGrid(int rows, int columns)
|
||||
{
|
||||
Rows = rows;
|
||||
Columns = columns;
|
||||
grid = new int[rows, columns];
|
||||
}
|
||||
|
||||
public bool IsInsideGrid(int row, int column)
|
||||
{
|
||||
return row >= 0 && row < Rows && column >= 0 && column < Columns;
|
||||
}
|
||||
|
||||
// Must be inside array and the value must be 0. -> all empty values are 0.
|
||||
public bool IsEmpty(int row, int column)
|
||||
{
|
||||
return IsInsideGrid(row, column) && grid[row, column] == 0;
|
||||
}
|
||||
|
||||
//Goes trough the entire row, if it isn't all zeroes it returns false.
|
||||
public bool IsRowFull(int row)
|
||||
{
|
||||
for (int column = 0; column < Columns; column++)
|
||||
{
|
||||
if (grid[row, column] == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Goes trough the entire row, if it isn't all zeroes it returns false.
|
||||
public bool IsRowEmpty(int row)
|
||||
{
|
||||
for (int column = 0; column < Columns; column++)
|
||||
{
|
||||
if (grid[row, column] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Set entire row to 0.
|
||||
private void ClearRow(int row)
|
||||
{
|
||||
for (int column = 0; column < Columns; column++)
|
||||
{
|
||||
grid[row, column] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveRowDown(int row, int numOfRows)
|
||||
{
|
||||
for (int column = 0; column < Columns; column++)
|
||||
{
|
||||
grid[row + numOfRows, column] = grid[row, column];
|
||||
grid[row, column] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int ClearFullRows()
|
||||
{
|
||||
int clearedRows = 0;
|
||||
for (int row = Rows - 1; row >= 0; row--)
|
||||
{
|
||||
if (IsRowFull(row))
|
||||
{
|
||||
ClearRow(row);
|
||||
clearedRows++;
|
||||
}
|
||||
else if (clearedRows > 0)
|
||||
{
|
||||
MoveRowDown(row, clearedRows);
|
||||
}
|
||||
}
|
||||
|
||||
return clearedRows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,189 @@
|
||||
using System.ComponentModel.Design;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class GameState
|
||||
{
|
||||
public Block currentBlock { get; set; }
|
||||
|
||||
public Block CurrentBlock
|
||||
{
|
||||
get => currentBlock;
|
||||
private set
|
||||
{
|
||||
currentBlock = value;
|
||||
currentBlock.Reset();
|
||||
}
|
||||
}
|
||||
public GameGrid GameGrid { get; }
|
||||
public BlockQueue BlockQueue { get; }
|
||||
|
||||
public bool IsGameOver { get; private set; }
|
||||
public Block HeldBlock { get; private set; }
|
||||
public bool CanHold { get; private set; }
|
||||
|
||||
public GameState()
|
||||
{
|
||||
GameGrid = new GameGrid(22, 10);
|
||||
BlockQueue = new BlockQueue();
|
||||
CurrentBlock = BlockQueue.GetAndReplaceNextBlock();
|
||||
CanHold = true;
|
||||
}
|
||||
// Holding the block
|
||||
public void HoldBlock()
|
||||
{
|
||||
if (!CanHold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (HeldBlock == null)
|
||||
{
|
||||
HeldBlock = CurrentBlock;
|
||||
CurrentBlock = BlockQueue.GetAndReplaceNextBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
Block tmp = CurrentBlock;
|
||||
CurrentBlock = HeldBlock;
|
||||
HeldBlock = tmp;
|
||||
}
|
||||
CanHold = false;
|
||||
}
|
||||
|
||||
|
||||
// If any block is out of bounds, it will return true.
|
||||
private bool IsBlockOutOfBounds()
|
||||
{
|
||||
foreach (Position position in CurrentBlock.TilePositions())
|
||||
{
|
||||
if (!GameGrid.IsEmpty(position.Row, position.Column))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// If the block is out of bounds, it will rotate it back.
|
||||
private void RotateBlockCW()
|
||||
{
|
||||
CurrentBlock.RotateCW();
|
||||
if (IsBlockOutOfBounds())
|
||||
{
|
||||
CurrentBlock.RotateCCW();
|
||||
}
|
||||
}
|
||||
private void RotateBlockCCW()
|
||||
{
|
||||
CurrentBlock.RotateCCW();
|
||||
if (IsBlockOutOfBounds())
|
||||
{
|
||||
CurrentBlock.RotateCW();
|
||||
}
|
||||
}
|
||||
// If the block is out of bounds, it will move it back.
|
||||
private void MoveBlockLeft()
|
||||
{
|
||||
CurrentBlock.Move(0, -1);
|
||||
if (IsBlockOutOfBounds())
|
||||
{
|
||||
CurrentBlock.Move(0, 1);
|
||||
}
|
||||
}
|
||||
private void MoveBlockRight()
|
||||
{
|
||||
CurrentBlock.Move(0, 1);
|
||||
if (IsBlockOutOfBounds())
|
||||
{
|
||||
CurrentBlock.Move(0, -1);
|
||||
}
|
||||
}
|
||||
private bool IsGameOverCheck()
|
||||
{
|
||||
return !(GameGrid.IsRowEmpty(0) && GameGrid.IsRowEmpty(1));
|
||||
}
|
||||
|
||||
private void PlaceBlock()
|
||||
{
|
||||
foreach (Position position in CurrentBlock.TilePositions())
|
||||
{
|
||||
GameGrid[position.Row, position.Column] = CurrentBlock.Id;
|
||||
}
|
||||
GameGrid.ClearFullRows();
|
||||
if (IsGameOverCheck())
|
||||
{
|
||||
IsGameOver = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentBlock = BlockQueue.GetAndReplaceNextBlock();
|
||||
CanHold = true;
|
||||
}
|
||||
}
|
||||
public void MoveBlockDown()
|
||||
{
|
||||
CurrentBlock.Move(1, 0);
|
||||
if (IsBlockOutOfBounds())
|
||||
{
|
||||
CurrentBlock.Move(-1, 0);
|
||||
PlaceBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private int TileDropDistance(Position p)
|
||||
{
|
||||
int drop = 0;
|
||||
|
||||
while (GameGrid.IsEmpty(p.Row + drop + 1, p.Column))
|
||||
{
|
||||
drop++;
|
||||
}
|
||||
return drop;
|
||||
}
|
||||
|
||||
private int BlockDropDistance()
|
||||
{
|
||||
int drop = GameGrid.Rows;
|
||||
foreach (Position p in CurrentBlock.TilePositions())
|
||||
{
|
||||
drop = System.Math.Min(drop, TileDropDistance(p));
|
||||
}
|
||||
return drop;
|
||||
}
|
||||
private void DropBlock()
|
||||
{
|
||||
CurrentBlock.Move(BlockDropDistance(), 0);
|
||||
PlaceBlock();
|
||||
}
|
||||
|
||||
public void MoveBlock(int key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case 18:
|
||||
DropBlock();
|
||||
break;
|
||||
case 23:
|
||||
MoveBlockLeft();
|
||||
break;
|
||||
case 24:
|
||||
RotateBlockCW();
|
||||
break;
|
||||
case 25:
|
||||
MoveBlockRight();
|
||||
break;
|
||||
case 26:
|
||||
MoveBlockDown();
|
||||
break;
|
||||
case 46:
|
||||
HoldBlock();
|
||||
break;
|
||||
case 68:
|
||||
RotateBlockCCW();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class IBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(1, 0), new(1, 1), new(1, 2), new(1, 3) },
|
||||
new Position[] { new(0, 2), new(1, 2), new(2, 2), new(3, 2) },
|
||||
new Position[] { new(2, 0), new(2, 1), new(2, 2), new(2, 3) },
|
||||
new Position[] { new(0, 1), new(1, 1), new(2, 1), new(3, 1) }
|
||||
};
|
||||
public override Position StartOffset => new(-1, 3);
|
||||
public override int Id => 1;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class JBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 0), new(1, 0), new(2, 0), new(2, 1) },
|
||||
new Position[] { new(1, 0), new(1, 1), new(1, 2), new(0, 2) },
|
||||
new Position[] { new(0, 1), new(1, 1), new(2, 1), new(0, 0) },
|
||||
new Position[] { new(0, 0), new(0, 1), new(0, 2), new(1, 2 ) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 3);
|
||||
public override int Id => 2;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class LBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 2), new(1, 0), new(1, 1), new(1, 2) },
|
||||
new Position[] { new(0, 1), new(1, 1), new(2, 1), new(2, 2) },
|
||||
new Position[] { new(1, 0), new(1, 1), new(1, 2), new(2, 0) },
|
||||
new Position[] { new(0, 0), new(0, 1), new(1, 1), new(2, 1) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 3);
|
||||
public override int Id => 3;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class OBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 0), new(0, 1), new(1, 0), new(1, 1) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 4);
|
||||
public override int Id => 4;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class Position
|
||||
{
|
||||
public int Row { get; set; }
|
||||
public int Column { get; set; }
|
||||
|
||||
public Position(int row, int column)
|
||||
{
|
||||
Row = row;
|
||||
Column = column;
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class SBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 1), new(0, 2), new(1, 0), new(1, 1) },
|
||||
new Position[] { new(0, 0), new(1, 0), new(1, 1), new(2, 1) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 3);
|
||||
public override int Id => 5;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class TBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 1), new(1, 0), new(1, 1), new(1, 2) },
|
||||
new Position[] { new(0, 1), new(1, 1), new(1, 2), new(2, 1) },
|
||||
new Position[] { new(1, 0), new(1, 1), new(1, 2), new(2, 1) },
|
||||
new Position[] { new(0, 1), new(1, 0), new(1, 1), new(2, 1) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 3);
|
||||
public override int Id => 6;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TetrisRPS
|
||||
{
|
||||
internal class Tetris
|
||||
{
|
||||
public int[,] array = new int[10, 20];
|
||||
// public int holding;
|
||||
// public int next;
|
||||
public int button;
|
||||
public void ButtonPress(int butt)
|
||||
{
|
||||
button = butt;
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace TetrisRPS;
|
||||
|
||||
public class ZBlock : Block
|
||||
{
|
||||
private readonly Position[][] tiles = new Position[][]
|
||||
{
|
||||
new Position[] { new(0, 0), new(0, 1), new(1, 1), new(1, 2) },
|
||||
new Position[] { new(0, 1), new(1, 0), new(1, 1), new(2, 0) }
|
||||
};
|
||||
public override Position StartOffset => new(0, 3);
|
||||
public override int Id => 7;
|
||||
protected override Position[][] Tiles => tiles;
|
||||
}
|