using System; using System.Threading; using System.Diagnostics; namespace Tetris { public static class Tetris { // Static variables private static readonly ConsoleColor[] colors = { ConsoleColor.Red, ConsoleColor.Gray, ConsoleColor.Yellow, ConsoleColor.Green, ConsoleColor.Cyan, ConsoleColor.Blue, ConsoleColor.Magenta }; private static readonly int[][][] blocks = { new[] { new[] { 0, 0 }, new[] { 1, 0 }, new[] { 1, 1 }, new[] { 2, 1 } }, // Spiegel-Z new[] { new[] { 1, 0 }, new[] { 1, 1 }, new[] { 1, 2 }, new[] { 2, 2 } }, // Normal-L new[] { new[] { 0, 0 }, new[] { 1, 0 }, new[] { 0, 1 }, new[] { 1, 1 } }, // Viereck new[] { new[] { 0, 1 }, new[] { 1, 1 }, new[] { 1, 0 }, new[] { 2, 0 } }, // Normal-Z new[] { new[] { 2, 0 }, new[] { 2, 1 }, new[] { 2, 2 }, new[] { 2, 3 } }, // Strich new[] { new[] { 1, 0 }, new[] { 1, 1 }, new[] { 1, 2 }, new[] { 0, 2 } }, // Spiegel-L new[] { new[] { 0, 1 }, new[] { 1, 1 }, new[] { 2, 1 }, new[] { 1, 2 } } }; // T-Block // Global Variables private static int FPS = 60; private static ConsoleColor[,] board = new ConsoleColor[10, 21]; private static readonly ConsoleColor black = ConsoleColor.Black; private static bool canSwap = true, gameOver, startOver = true, hasStarted; private static Player player = new(), next = new(true); public static int selectionIndex, frameCount; private static int score, bounds; public static bool selection; public static void Main() { // Initialize board if (!hasStarted) { initBoard(board); hasStarted = true; } // Initialize stopwatch and Console Color Console.ForegroundColor = ConsoleColor.White; Stopwatch stopwatch = new(); // Outer loop for restarting the Game startOver = true; while (startOver) { // Reset loop Console.Clear(); // Draw line "in the middle" Console.ForegroundColor = ConsoleColor.White; for (int i = 0; i < 21; i++) { Console.SetCursorPosition(10, i+1); Console.Write("#"); } Console.SetCursorPosition(12, 1); Console.Write("Save:"); Console.SetCursorPosition(12, 6); Console.Write("Next:"); // Initially draw board for (int i = 0; i < board.GetLength(0); i++) for (int j = 0; j < board.GetLength(1); j++) if (!board[i, j].Equals(black)) { Console.ForegroundColor = board[i, j]; Console.SetCursorPosition(i, j + 1); Console.Write("O"); } // Inner loop for actual Gameplay score = 0; gameOver = false; ConsoleColor[,] copy = new ConsoleColor[10, 21]; while (!gameOver) { // Handle Keypresses if (Console.KeyAvailable && keyHandler()) { EndGame(); break; } // Move Player down every few frames if (frameCount % 30 == 0) { initBoard(copy); for (int i = 0; i < board.GetLength(0); i++) for (int j = 0; j < board.GetLength(1); j++) { if (copy[i, j] != board[i, j]) copy[i, j] = board[i, j]; } // Display board onto Matrix for (int i = 0; i < board.GetLength(0); i++) for (int j = 0; j < board.GetLength(1); j++) { ConsoleColor current = board[i, j]; if (current != copy[i, j]) { string nchar = (current != black) ? "O" : " "; Console.ForegroundColor = board[i, j]; Console.SetCursorPosition(i, j + 1); Console.Write(nchar); } } // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write(" "); } if (collisionCheck(player.x, ++player.y, player.positions)) { player.y--; placeBlock(); } // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } } // End of Frame frameCount = (frameCount+1)%(FPS*4); // Fix framerate int elapsed = (int)stopwatch.ElapsedMilliseconds; if (elapsed < 1000/FPS) Thread.Sleep(1000/FPS - elapsed); } // If Player has lost but not yet quit if (startOver) { // Start loop to select next step in frameCount = 0; selection = true; while (selection) { // Display Options and Cursor on Matrix Console.WriteLine("Game Over!"); Console.WriteLine(); Console.WriteLine(" ) Weiter!"); Console.WriteLine(" ) Beenden"); Console.WriteLine(); Console.WriteLine($"Score: {score}"); // Handle key presses for continue game or exit if (Console.KeyAvailable) { frameCount = 0; switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: // Exit Game selection = false; startOver = false; break; case ConsoleKey.DownArrow: // Increment index if (selectionIndex < 1) selectionIndex++; break; case ConsoleKey.UpArrow: // Decrement index if (selectionIndex > 0) selectionIndex--; break; case ConsoleKey.Enter: // Enter selected option enterSelection(); break; } } // Exit if no response for 30 secs if (frameCount >= 300) { selection = false; startOver = false; } // Wait for input frameCount++; Thread.Sleep(100); } } } } // Method to initialize the Game board private static void initBoard(ConsoleColor[,] _board) { for (int i = 0; i < board.GetLength(0); i++) for (int j = 0; j < board.GetLength(1); j++) _board[i, j] = black; // P for (int i = 0; i < 8; i++) _board[1, 13 + i] = colors[4]; _board[2, 13] = colors[4]; _board[3, 13] = colors[4]; _board[4, 14] = colors[4]; _board[4, 15] = colors[4]; _board[2, 16] = colors[4]; _board[3, 16] = colors[4]; // T for (int i = 0; i < 8; i++) _board[5, 11 + i] = colors[5]; _board[3, 11] = colors[5]; _board[4, 11] = colors[5]; _board[6, 11] = colors[5]; _board[7, 11] = colors[5]; // B for (int i = 0; i < 8; i++) _board[6, 13 + i] = colors[4]; _board[7, 13] = colors[4]; _board[8, 13] = colors[4]; _board[9, 14] = colors[4]; _board[9, 15] = colors[4]; _board[7, 16] = colors[4]; _board[8, 16] = colors[4]; _board[9, 17] = colors[4]; _board[9, 18] = colors[4]; _board[9, 19] = colors[4]; _board[7, 20] = colors[4]; _board[8, 20] = colors[4]; } // Method to check whether a block at given coordinates collides with any other block or border private static bool collisionCheck(int x, int y, int[][] block) { foreach (int[] b in block) { int nx = x + b[0], ny = y + b[1]; if (nx is > 9 or < 0 || ny is > 20 or < 0 || !board[nx, ny].Equals(black)) return true; } return false; } // Method to place currently held block private static void placeBlock() { // Block placing Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { int x = player.x + current[0]; int y = player.y + current[1]; board[x, y] = player.color; Console.SetCursorPosition(x, y + 1); Console.Write("O"); } // Count completed rows int rows = 0; for (int j = board.GetLength(1)-1; j >= 0;) { bool isRow = true; for (int i = 0; i < board.GetLength(0); i++) if (board[i, j].Equals(black)) { isRow = false; break; } if (isRow) { rows++; for (int i = j-1; i >= 0; i--) for (int k = 0; k < board.GetLength(0); k++) { board[k, i + 1] = board[k, i]; Console.ForegroundColor = board[k, i]; Console.SetCursorPosition(k, i + 2); Console.Write((board[k, i].Equals(black)) ? " " : "O"); } continue; } j--; } // Add to score if (rows >= 4) { int tetris = rows / 4; if (tetris > 1) score += 1200 * tetris; else score += 800; } else if (rows == 3) score += 400; else score += 100 * rows; Console.ForegroundColor = ConsoleColor.White; Console.SetCursorPosition(0, 0); Console.WriteLine($"Score: {score}"); // Remove next from Matrix if (next.memDis != null) { Console.ForegroundColor = black; foreach (int[] b in next.memDis) { Console.SetCursorPosition(13 + b[0], 7 + b[1]); Console.Write(" "); } } // Generating new Block int blockIndex = 0; for (int i = 1; i < blocks.Length; i++) if (blocks[i].Equals(next.positions)) { blockIndex = i; break; } // Generate new player with given parameters player = new( blockIndex, player.memPos, player.memDis, player.memCol ); // Generate new "next Block" with displaySelf State next = new(true); // If new block doesnt collide with any other, keep playing (also re-enable swapping) if (!collisionCheck(player.x, player.y, player.positions)) canSwap = true; // Otherwise reset game, because player has lost else { gameOver = true; initBoard(board); player = new(); } // Remove next from Matrix if (next.memDis != null) { Console.ForegroundColor = next.color; foreach (int[] b in next.memDis) { Console.SetCursorPosition(13 + b[0], 7 + b[1]); Console.Write("O"); } } } private static bool keyHandler() { // If key is pressed, handle logic switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: // Exit program. return true; case ConsoleKey.Spacebar: // Place block on lowest point at current x placeDownBlock(); break; case ConsoleKey.DownArrow: // Lower block by 1 on y-axis moveDownOnce(); break; case ConsoleKey.LeftArrow: // Move block to the left on the x axis moveLeftSide(); break; case ConsoleKey.RightArrow: // Move block to the right on the x axis moveRightSide(); break; case ConsoleKey.A: // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } // Rotate block counter-clockwise calcBounds(); rotateCounterClockwise(); adjustXPos(); // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } break; case ConsoleKey.S: // Swap current block with memory swapMemory(); break; case ConsoleKey.D: // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } // Rotate block clockwise calcBounds(); rotateClockwise(); adjustXPos(); // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } break; } return false; } // Method to choose what a given index has to do public static void enterSelection() { switch (selectionIndex) { case 1: selection = false; startOver = false; break; case 0: selection = false; frameCount = 0; break; } } // Depending on currently used block, save its width and height (they're always the same) public static void calcBounds() { int blockIndex = 0; for (int i = 1; i < blocks.Length; i++) if (blocks[i].Equals(player.positions)) { blockIndex = i; break; } switch (blockIndex) { default: bounds = 3; break; case 2: bounds = 2; break; case 4: bounds = 4; break; } } // Adjust x-positions if necessary due to any previous movement public static void adjustXPos() { foreach (int[] b in player.positions) { int nx = player.x + b[0]; while (nx > 9) { player.x--; nx = player.x + b[0]; } while (nx < 0) { player.x++; nx = player.x + b[0]; } } } // Swap current block with the one in memory public static void swapMemory() { if (canSwap) { // Remove memory from Matrix if (player.memDis != null && player.memCol != null) { Console.ForegroundColor = black; foreach (int[] b in player.memDis) { Console.SetCursorPosition(13 + b[0], 2 + b[1]); Console.Write(" "); } } // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } // Save all data int[][] tempPos = player.positions; int[][]? tempMem = player.memPos; ConsoleColor tempCol1 = player.color; ConsoleColor? tempCol2 = player.memCol; // Generate new Player and assign saved variables player = new(); player.memPos = tempPos; player.memCol = tempCol1; if (tempMem != null && tempCol2 != null) { player.positions = tempMem; player.color = (ConsoleColor)tempCol2; } // Actually clone the content of block in memory to be displayed without rotating later player.memDis = new int[tempPos.Length][]; for (int i = 0; i < tempPos.Length; i++) { player.memDis[i] = new int[tempPos[i].Length]; for (int j = 0; j < tempPos[i].Length; j++) player.memDis[i][j] = tempPos[i][j]; } canSwap = false; // Place memory onto Matrix if (player.memDis != null && player.memCol != null) { Console.ForegroundColor = (ConsoleColor)player.memCol; foreach (int[] b in player.memDis) { Console.SetCursorPosition(13 + b[0], 2 + b[1]); Console.Write("O"); } } // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } } } // Rotate current block Clockwise public static void rotateClockwise() { foreach (int[] a in player.positions) { int temp = a[0]; a[0] = bounds - 1 - a[1]; a[1] = temp; } } // Rotate current block counter-Clockwise public static void rotateCounterClockwise() { foreach (int[] a in player.positions) { int temp = a[1]; a[1] = bounds - 1 - a[0]; a[0] = temp; } } // Move current block to the right public static void moveRightSide() { // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write(" "); } if (collisionCheck(++player.x, player.y, player.positions)) player.x--; // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } } // Move current block to the left public static void moveLeftSide() { // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write(" "); } if (collisionCheck(--player.x, player.y, player.positions)) player.x++; // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } } // Move current block downwards public static void moveDownOnce() { // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write(" "); } if (collisionCheck(player.x, ++player.y, player.positions)) player.y--; // Place player onto Matrix Console.ForegroundColor = player.color; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write("O"); } } // Method to be called when the button for placing a block is pressed public static void placeDownBlock() { // Remove player from Matrix Console.ForegroundColor = black; foreach (int[] current in player.positions) { Console.SetCursorPosition(player.x + current[0], player.y + current[1] + 1); Console.Write(" "); } // Move block to the lowest possible point while (!collisionCheck(player.x, ++player.y, player.positions)) { } player.y--; // Place the block placeBlock(); } // Method to end the Game public static void EndGame() { gameOver = true; startOver = false; } // Data-Class to store a player's variables in private class Player { public int[][] positions; public int[][]? memPos; public int[][]? memDis; public ConsoleColor? memCol; public ConsoleColor color; public int x, y; // Constructor to generate new Player public Player(bool displaySelf=false) { int randInt = (new Random()).Next(7); if (displaySelf) { int[][] tempPos = blocks[randInt]; memDis = new int[tempPos.Length][]; for (int i = 0; i < tempPos.Length; i++) { memDis[i] = new int[tempPos[i].Length]; for (int j = 0; j < tempPos[i].Length; j++) memDis[i][j] = tempPos[i][j]; } } positions = blocks[randInt]; color = colors[randInt]; int xB; switch (randInt) { default: xB = 3; break; case 2: xB = 2; break; case 4: xB = 4; break; } x = 4 - xB / 2; y = 0; } // Constructor to carry over variables public Player(int blockIndex, int[][]? _memPos, int[][]? _memDis, ConsoleColor? _memCol) { memPos = _memPos; memDis = _memDis; memCol = _memCol; positions = blocks[blockIndex]; color = colors[blockIndex]; int xB; switch (blockIndex) { default: xB = 3; break; case 2: xB = 2; break; case 4: xB = 4; break; } x = 4 - xB / 2; y = 0; } } } }