diff --git a/ARS-pieces.png b/ARS-pieces.png new file mode 100644 index 0000000..68cdf21 Binary files /dev/null and b/ARS-pieces.png differ diff --git a/Program.cs b/Program.cs index 4013cc3..090b114 100644 --- a/Program.cs +++ b/Program.cs @@ -1,323 +1,336 @@ using System; +using System.Media; +using System.Threading; namespace Tetris { - public class Tetris + public class Tetris + { + private static int keyDelay = 50; // Delay between keypress handling (in ms) + private static DateTime lastKeyPressTime = DateTime.Now; + + public static void Main(string[] args) { - private static int keyDelay = 50; // Delay between keypress handling (in ms) - private static DateTime lastKeyPressTime = DateTime.Now; + // Music player +#if _WINDOWS + SoundPlayer player = new SoundPlayer("song.wav"); + player.PlayLooping(); +#endif - public static void Main(string[] args) + //using (var audioFile = new AudioFileReader(audioFile)) using ( + //var outputDevice = new WaveOutEvent()) + //{ + // outputDevice.Init(audioFile); + // outputDevice.Play(); + // while (outputDevice.PlaybackState == PlaybackState.Playing) + // { + // Thread.Sleep(1000); + // } + //} + + Matrix matrix = new Matrix(); + Shape shape = new Shape(matrix); + int previousScore = 0; + + while (true) + { + matrix.Print(); + shape.Draw(); + + HandleInput(shape); + + if (!shape.MoveDown()) { - Matrix matrix = new Matrix(); - Shape shape = new Shape(matrix); - int previousScore = 0; - - while (true) - { - matrix.Print(); - shape.Draw(); - - HandleInput(shape); - - if (!shape.MoveDown()) - { - shape = new Shape(matrix); - matrix.CheckForCompleteLines(); - if (matrix.IsGameOver() == true) - { - Console.WriteLine("Game Over!"); - break; - } - } - - int score = matrix.GetScore(); - if (score >= previousScore + 800) - { - Speed.SpeedUp(); - previousScore = matrix.GetScore(); - } - - System.Threading.Thread.Sleep(Speed.ShowSpeed()); - } + shape = new Shape(matrix); + matrix.CheckForCompleteLines(); + if (matrix.IsGameOver() == true) + { + Console.WriteLine("Game Over!"); + break; + } } - // TODO: Improve input handling - private static void HandleInput(Shape shape) + int score = matrix.GetScore(); + if (score >= previousScore + 800) { - for (int i = 0; i < 5; i++) - { - if (Console.KeyAvailable) - { - ConsoleKeyInfo key = Console.ReadKey(true); - - if (key.Key == ConsoleKey.LeftArrow) - shape.MoveLeft(); - else if (key.Key == ConsoleKey.RightArrow) - shape.MoveRight(); - else if (key.Key == ConsoleKey.UpArrow) - shape.Rotate(); - else if (key.Key == ConsoleKey.DownArrow) - shape.MoveDown(); - } - } + Speed.SpeedUp(); + previousScore = matrix.GetScore(); } + + System.Threading.Thread.Sleep(Speed.ShowSpeed()); +#if _WINDOWS + player.Stop(); +#endif + } } - public class Speed() + // TODO: Improve input handling + private static void HandleInput(Shape shape) { - private static int speed = 400; - - public static int ShowSpeed() + for (int i = 0; i < 5; i++) + { + if (Console.KeyAvailable) { - return speed; - } + ConsoleKeyInfo key = Console.ReadKey(true); - public static void SpeedUp() - { - speed -= 50; + if (key.Key == ConsoleKey.LeftArrow) + shape.MoveLeft(); + else if (key.Key == ConsoleKey.RightArrow) + shape.MoveRight(); + else if (key.Key == ConsoleKey.UpArrow) + shape.Rotate(); + else if (key.Key == ConsoleKey.DownArrow) + shape.MoveDown(); } + } + } + } + + public class Speed + () + { + private static int speed = 400; + + public static int ShowSpeed() { return speed; } + + public static void SpeedUp() { speed -= 50; } + } + + public class Shape + { + private int[,] current_shape; + private int randomRotation; + private int randomShape; + + private static List> shapes = ShapesData.shapes; + private static List shape; + + private int x, y; + private Matrix matrix; + public Shape(Matrix matrix) + { + Random random = new Random(); + randomShape = random.Next(0, shapes.Count); + shape = shapes[randomShape]; + randomRotation = random.Next(0, shape.Count); + current_shape = shape[randomRotation]; + this.matrix = matrix; + matrix.IncreaseScore(10); + // TODO: Implement random color + this.x = 0; // Starting position + this.y = 8; // Center of the grid } - - public class Shape + public void Draw() { - private int[,] current_shape; - private int randomRotation; - private int randomShape; - - private static List> shapes = ShapesData.shapes; - private static List shape; - - private int x, y; - private Matrix matrix; - public Shape(Matrix matrix) - { - Random random = new Random(); - randomShape = random.Next(0, shapes.Count); - shape = shapes[randomShape]; - randomRotation = random.Next(0, shape.Count); - current_shape = shape[randomRotation]; - this.matrix = matrix; - matrix.IncreaseScore(10); - // TODO: Implement random color - this.x = 0; // Starting position - this.y = 8; // Center of the grid - } - - public void Draw() - { - for (int i = 0; i < current_shape.GetLength(0); i++) - { - matrix.SetBlock(x + current_shape[i, 0], y + current_shape[i, 1], '█'); - } - } - - public bool MoveDown() - { - if (CanMove(x + 1, y)) - { - Clear(); - x++; - Draw(); - return true; - } - return false; - } - - public void MoveLeft() - { - if (CanMove(x, y - 1)) - { - Clear(); - y--; - y--; - Draw(); - } - } - - public void MoveRight() - { - if (CanMove(x, y + 1)) - { - Clear(); - y++; - y++; - Draw(); - } - } - - public void Rotate() - { - Clear(); - randomRotation = (randomRotation + 1) % shape.Count; - // !IMPORTANT: Check if rotation is possible - current_shape = shape[randomRotation]; - Draw(); - } - - private bool IsPartOfShape(int matrixX, int matrixY) - { - for (int i = 0; i < current_shape.GetLength(0); i++) - { - if (x + current_shape[i, 0] == matrixX && y + current_shape[i, 1] == matrixY) - { - return true; - } - } - return false; - } - - - private bool CanMove(int newX, int newY) - { - for (int i = 0; i < current_shape.GetLength(0); i++) - { - int newBlockX = newX + current_shape[i, 0]; - int newBlockY = newY + current_shape[i, 1]; - - if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 || newBlockY >= Matrix.WIDTH) - { - return false; - } - if (matrix.GetBlock(newBlockX, newBlockY) != ' ' && !IsPartOfShape(newBlockX, newBlockY)) - { - return false; - } - } - return true; - } - - private void Clear() - { - for (int i = 0; i < current_shape.GetLength(0); i++) - { - matrix.ClearBlock(x + current_shape[i, 0], y + current_shape[i, 1]); - } - } + for (int i = 0; i < current_shape.GetLength(0); i++) + { + matrix.SetBlock(x + current_shape[i, 0], y + current_shape[i, 1], '█'); + } } - public class Matrix + public bool MoveDown() { - public const int HEIGHT = 22; - public const int WIDTH = 20; - private char[,] matrix; - private int score = 0; - - public Matrix() - { - matrix = new char[HEIGHT, WIDTH]; - Clear(); - } - - public void SetBlock(int x, int y, char c) - { - if (IsInBounds(x, y)) - { - matrix[x, y] = c; - } - } - - public void ClearBlock(int x, int y) - { - if (IsInBounds(x, y)) - { - matrix[x, y] = ' '; - } - } - - public char GetBlock(int x, int y) - { - return IsInBounds(x, y) ? matrix[x, y] : 'E'; - } - - public void Print() - { - Console.Clear(); - for (int i = 0; i < HEIGHT; i++) - { - Console.Write("│ "); - for (int j = 0; j < WIDTH; j++) - { - Console.ForegroundColor = matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue; - Console.Write(matrix[i, j]); - } - Console.ResetColor(); - Console.WriteLine(" │"); - } - Console.WriteLine(" ──────────────────────"); - - Console.WriteLine("Score: " + score); - } - - public int GetScore() - { - return score; - } - public void IncreaseScore(int score) - { - this.score += score; - } - - public void CheckForCompleteLines() - { - for (int i = 0; i < HEIGHT; i++) - { - bool isComplete = true; - for (int j = 0; j < WIDTH; j++) - { - if (matrix[i, j] == ' ') - { - isComplete = false; - break; - } - } - if (isComplete) - { - // Remove line - IncreaseScore(100); - for (int j = 0; j < WIDTH; j++) - { - matrix[i, j] = ' '; - } - // Move all lines above one down - for (int k = i; k > 0; k--) - { - for (int j = 0; j < WIDTH; j++) - { - matrix[k, j] = matrix[k - 1, j]; - } - } - } - } - } - - public bool IsGameOver() - { - for (int i = 0; i < WIDTH; i++) - { - if (matrix[0, i] != ' ') - { - return true; - } - } - return false; - } - - public void Clear() - { - for (int i = 0; i < HEIGHT; i++) - { - for (int j = 0; j < WIDTH; j++) - { - matrix[i, j] = ' '; - } - } - } - - private bool IsInBounds(int x, int y) - { - return x >= 0 && x < HEIGHT && y >= 0 && y < WIDTH; - } + if (CanMove(x + 1, y)) + { + Clear(); + x++; + Draw(); + return true; + } + return false; } + + public void MoveLeft() + { + if (CanMove(x, y - 1)) + { + Clear(); + y--; + y--; + Draw(); + } + } + + public void MoveRight() + { + if (CanMove(x, y + 1)) + { + Clear(); + y++; + y++; + Draw(); + } + } + + public void Rotate() + { + Clear(); + randomRotation = (randomRotation + 1) % shape.Count; + // !IMPORTANT: Check if rotation is possible + current_shape = shape[randomRotation]; + Draw(); + } + + private bool IsPartOfShape(int matrixX, int matrixY) + { + for (int i = 0; i < current_shape.GetLength(0); i++) + { + if (x + current_shape[i, 0] == matrixX && + y + current_shape[i, 1] == matrixY) + { + return true; + } + } + return false; + } + + private bool CanMove(int newX, int newY) + { + for (int i = 0; i < current_shape.GetLength(0); i++) + { + int newBlockX = newX + current_shape[i, 0]; + int newBlockY = newY + current_shape[i, 1]; + + if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 || + newBlockY >= Matrix.WIDTH) + { + return false; + } + if (matrix.GetBlock(newBlockX, newBlockY) != ' ' && + !IsPartOfShape(newBlockX, newBlockY)) + { + return false; + } + } + return true; + } + + private void Clear() + { + for (int i = 0; i < current_shape.GetLength(0); i++) + { + matrix.ClearBlock(x + current_shape[i, 0], y + current_shape[i, 1]); + } + } + } + + public class Matrix + { + public const int HEIGHT = 22; + public const int WIDTH = 20; + private char[,] matrix; + private int score = 0; + + public Matrix() + { + matrix = new char[HEIGHT, WIDTH]; + Clear(); + } + + public void SetBlock(int x, int y, char c) + { + if (IsInBounds(x, y)) + { + matrix[x, y] = c; + } + } + + public void ClearBlock(int x, int y) + { + if (IsInBounds(x, y)) + { + matrix[x, y] = ' '; + } + } + + public char GetBlock(int x, int y) + { + return IsInBounds(x, y) ? matrix[x, y] : 'E'; + } + + public void Print() + { + Console.Clear(); + for (int i = 0; i < HEIGHT; i++) + { + Console.Write("│ "); + for (int j = 0; j < WIDTH; j++) + { + Console.ForegroundColor = + matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue; + Console.Write(matrix[i, j]); + } + Console.ResetColor(); + Console.WriteLine(" │"); + } + Console.WriteLine(" ──────────────────────"); + + Console.WriteLine("Score: " + score); + } + + public int GetScore() { return score; } + public void IncreaseScore(int score) { this.score += score; } + + public void CheckForCompleteLines() + { + for (int i = 0; i < HEIGHT; i++) + { + bool isComplete = true; + for (int j = 0; j < WIDTH; j++) + { + if (matrix[i, j] == ' ') + { + isComplete = false; + break; + } + } + if (isComplete) + { + // Remove line + IncreaseScore(100); + for (int j = 0; j < WIDTH; j++) + { + matrix[i, j] = ' '; + } + // Move all lines above one down + for (int k = i; k > 0; k--) + { + for (int j = 0; j < WIDTH; j++) + { + matrix[k, j] = matrix[k - 1, j]; + } + } + } + } + } + + public bool IsGameOver() + { + for (int i = 0; i < WIDTH; i++) + { + if (matrix[0, i] != ' ') + { + return true; + } + } + return false; + } + + public void Clear() + { + for (int i = 0; i < HEIGHT; i++) + { + for (int j = 0; j < WIDTH; j++) + { + matrix[i, j] = ' '; + } + } + } + + private bool IsInBounds(int x, int y) + { + return x >= 0 && x < HEIGHT && y >= 0 && y < WIDTH; + } + } } diff --git a/README.md b/README.md index 6d1bf21..596f22e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Tetris -## Simple tetris game \ No newline at end of file +## Simple tetris game + +- [SRS specifications](./TetrominoSRS.md) diff --git a/ShapesData.cs b/ShapesData.cs new file mode 100644 index 0000000..a51e4da --- /dev/null +++ b/ShapesData.cs @@ -0,0 +1,119 @@ +namespace Tetris; +public static class ShapesData +{ + + public static readonly List> shapes = new List> + { + + // Square block (only one rotation) + new List + { + new int[,] + { + {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3} + } + }, + + // Long block (two rotations) + new List + { + new int[,] + { + {1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, -1}, {1, -2}, {1, 4}, {1, 5} // Horizontal rotation + }, + new int[,] + { + {0, 0}, {1, 0}, {2, 0}, {3, 0}, {0, 1}, {1, 1}, {2, 1}, {3, 1} // Vertical rotation + } + }, + + // L block right (four rotations) + new List + { + new int[,] + { + {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3} // Rotation 180 degrees + }, + new int[,] + { + {1, -2}, {1, -1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, 0}, {-1, 1} // Rotation 90 degrees + }, + new int[,] + { + {1, -2}, {1, -1}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {0, -2}, {0, -1} // Rotation 0 degrees + }, + new int[,] + { + {-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, 2}, {-1, 3} // Rotation 270 degrees + } + }, + + // L block left (four rotations) + new List + { + new int[,] + { + {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, -2}, {1, -1} // Rotation 0 degrees + }, + new int[,] + { + {-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, -1}, {-1, -2} // Rotation 270 degrees + }, + new int[,] + { + {1, -2}, {1, -1}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {0, 2}, {0, 3} // Rotation 90 degrees + }, + new int[,] + { + {-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {1, 2}, {1, 3} // Rotation 180 degrees + } + }, + + // Z block left (two rotations) + new List + { + new int[,] + { + {1, -2}, {1, -1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3} + }, + new int[,] + { + {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, -1}, {0, -2}, {-1, -1}, {-1, -2} + } + }, + + // T block (four rotations) + new List + { + new int[,] + { + {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1} + }, + new int[,] + { + {-1, 0}, {-1, 1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, -1}, {0, -2} + }, + new int[,] + { + {1,-2}, {1,-1}, {1,0}, {1,1}, {1, 2}, {1, 3}, {0, 0}, {0, 1} + }, + new int[,] + { + {-1, 0}, {-1, 1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3} + } + }, + + // Z block right (two rotations) + new List + { + new int[,] + { + {0, -2}, {0, -1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {1, 2}, {1, 3} + }, + new int[,] + { + {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3}, {-1 , 2}, {-1, 3} + } + } + }; +} diff --git a/Tetris.csproj b/Tetris.csproj index 2150e37..bba73d9 100644 --- a/Tetris.csproj +++ b/Tetris.csproj @@ -7,4 +7,13 @@ enable + + _WINDOWS + + + + + + + diff --git a/Tetris.png b/Tetris.png new file mode 100644 index 0000000..e87ac89 Binary files /dev/null and b/Tetris.png differ diff --git a/Tetromino.md b/TetrominoSRS.md similarity index 98% rename from Tetromino.md rename to TetrominoSRS.md index 38d48be..feb1de2 100644 --- a/Tetromino.md +++ b/TetrominoSRS.md @@ -69,6 +69,7 @@ The game will rely on simple keyboard input for interaction and console output f The primary purpose of this project is to create a minimalist version of Tetris that can run in any terminal or command-line interface. This game is designed for users who enjoy retro-style gaming experiences without needing a graphical environment. It also provides a fun way to practice logical thinking and quick decision-making, as the game increases in difficulty with every level. Key purposes: + - Provide an engaging and simple game for retro gamers or users with minimal system resources. - Offer an accessible gaming option that works on most operating systems. - Allow players to compete for high scores in a lightweight, resource-efficient environment. @@ -86,9 +87,11 @@ Functional requirements define the core behavior and features of the Tetris game - The grid should be displayed using characters or symbols for blocks. 2. **Tetromino Types** - - The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z. + - The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z (shapes are specified in the image below) - Tetrominoes should be represented using characters. + ![tetris block images](./ARS-pieces.png) + 3. **Tetromino Movement** - Players must be able to move tetrominoes left and right using keyboard inputs. - The tetromino should fall automatically at a predefined speed. diff --git a/image.png b/image.png new file mode 100644 index 0000000..e69de29