From 051d0233502eeb6f27bc457c6311d1f49dc8cc3c Mon Sep 17 00:00:00 2001
From: Jahg
Date: Thu, 26 Oct 2023 22:17:07 -0700
Subject: [PATCH 01/24] Add Tetris
---
.github/workflows/Tetris Build.yml | 20 +
.vscode/launch.json | 10 +
.vscode/tasks.json | 13 +
Projects/Tetris/Program.cs | 768 ++++++++++++++++++++++++
Projects/Tetris/README.md | 36 ++
Projects/Tetris/Tetris.csproj | 10 +
Projects/Website/.vscode/launch.json | 11 +
Projects/Website/.vscode/tasks.json | 41 ++
Projects/Website/Games/Tetris/Tetris.cs | 733 ++++++++++++++++++++++
Projects/Website/Pages/Tetris.razor | 54 ++
Projects/Website/Shared/NavMenu.razor | 5 +
README.md | 1 +
dotnet-console-games.sln | 6 +
dotnet-console-games.slnf | 3 +-
14 files changed, 1710 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/Tetris Build.yml
create mode 100644 Projects/Tetris/Program.cs
create mode 100644 Projects/Tetris/README.md
create mode 100644 Projects/Tetris/Tetris.csproj
create mode 100644 Projects/Website/.vscode/launch.json
create mode 100644 Projects/Website/.vscode/tasks.json
create mode 100644 Projects/Website/Games/Tetris/Tetris.cs
create mode 100644 Projects/Website/Pages/Tetris.razor
diff --git a/.github/workflows/Tetris Build.yml b/.github/workflows/Tetris Build.yml
new file mode 100644
index 00000000..a2ced14f
--- /dev/null
+++ b/.github/workflows/Tetris Build.yml
@@ -0,0 +1,20 @@
+name: Tetris Build
+on:
+ push:
+ paths:
+ - 'Projects/Tetris/**'
+ - '!**.md'
+ pull_request:
+ paths:
+ - 'Projects/Tetris/**'
+ - '!**.md'
+ workflow_dispatch:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - run: dotnet build "Projects\Tetris\Tetris.csproj" --configuration Release
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 476816fe..9d48e2c0 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -492,5 +492,15 @@
"console": "externalTerminal",
"stopAtEntry": false,
},
+ {
+ "name": "Tetris",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "Build Tetris",
+ "program": "${workspaceFolder}/Projects/Tetris/bin/Debug/Tetris.dll",
+ "cwd": "${workspaceFolder}/Projects/Tetris/bin/Debug",
+ "console": "externalTerminal",
+ "stopAtEntry": false,
+ },
],
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2f312b30..8e3d42b3 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -665,5 +665,18 @@
],
"problemMatcher": "$msCompile",
},
+ {
+ "label": "Build Tetris",
+ "command": "dotnet",
+ "type": "process",
+ "args":
+ [
+ "build",
+ "${workspaceFolder}/Projects/Tetris/Tetris.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary",
+ ],
+ "problemMatcher": "$msCompile",
+ },
],
}
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
new file mode 100644
index 00000000..6c71c532
--- /dev/null
+++ b/Projects/Tetris/Program.cs
@@ -0,0 +1,768 @@
+using System.Diagnostics;
+using System.Text;
+
+Console.OutputEncoding = Encoding.UTF8;
+Console.CursorVisible = false;
+Stopwatch Stopwatch = Stopwatch.StartNew();
+
+bool DEBUGCONTROLS = false;
+
+string[] FIELD = new[]
+{
+ "╭──────────────────────────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰──────────────────────────────╯"
+};
+
+string[] NEXTTETROMINO = new[]
+{
+ "╭─────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰─────────╯"
+};
+
+string[] SCORE = new[]{
+ "╭─────────╮",
+ "│ │",
+ "╰─────────╯"
+};
+
+string[] PAUSE = new[]{
+ "█████╗ ███╗ ██╗██╗█████╗█████╗",
+ "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
+ "█████║█████║██║██║ ███╗ █████╗",
+ "██╔══╝██╔██║██║██║ ██╗██╔══╝",
+ "██║ ██║██║█████║█████║█████╗",
+ "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
+};
+
+string[][] TETROMINOS = new[]
+{
+ new[]{
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯"
+ },
+ new[]{
+ "╭─╮ ",
+ "╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮",
+ " ╰─╯",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮",
+ "╰─╯╰─╯",
+ "╭─╮╭─╮",
+ "╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯",
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ "
+ },
+ new[]{
+ " ╭─╮ ",
+ " ╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ ",
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯"
+ },
+};
+
+string[] PLAYFIELD = (string[])FIELD.Clone();
+const int BORDER = 1;
+int FallSpeedMilliSeconds = 1000;
+bool CloseGame = false;
+int Score = 0;
+int PauseCount = 0;
+int TextColor = 0;
+GameStatus GameStatus = GameStatus.Gameover;
+
+Random RamdomGenerator = new();
+
+int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
+int INITIALTETROMINOY = 1;
+Tetromino TETROMINO = new()
+{
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+};
+
+char[][]? LastFrame = null;
+
+AutoResetEvent AutoEvent = new AutoResetEvent(false);
+Timer? FallTimer = null;
+GameStatus = GameStatus.Playing;
+
+Console.WriteLine();
+Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
+Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
+Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
+Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
+Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
+Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
+
+Console.WriteLine();
+Console.WriteLine(" Controls:");
+Console.WriteLine(" WASD or ARROW to move");
+Console.WriteLine(" Q or E to spin left or right");
+Console.WriteLine(" P to paused the game, press enter");
+Console.WriteLine(" key to resume");
+Console.WriteLine(" R to change Text color");
+Console.WriteLine();
+Console.WriteLine(" Press escape to close the game at any time.");
+Console.WriteLine();
+Console.Write(" Press enter to start tetris...");
+Console.CursorVisible = false;
+StartGame();
+Console.Clear();
+
+FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+
+while (!CloseGame)
+{
+ if (CloseGame)
+ {
+ break;
+ }
+
+ PlayerControl();
+ if (GameStatus == GameStatus.Playing)
+ {
+ DrawFrame();
+ SleepAfterRender();
+ }
+}
+
+void PlayerControl()
+{
+ while (Console.KeyAvailable && GameStatus == GameStatus.Playing)
+ {
+ switch (Console.ReadKey(true).Key)
+ {
+ case ConsoleKey.A or ConsoleKey.LeftArrow:
+ if (Collision(Direction.Left)) break;
+ TETROMINO.X -= 3;
+ break;
+ case ConsoleKey.D or ConsoleKey.RightArrow:
+ if (Collision(Direction.Right)) break;
+ TETROMINO.X += 3;
+ break;
+ case ConsoleKey.S or ConsoleKey.DownArrow:
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ break;
+ case ConsoleKey.E:
+ TetrominoSpin(Direction.Right);
+ break;
+ case ConsoleKey.Q:
+ TetrominoSpin(Direction.Left);
+ break;
+ case ConsoleKey.P:
+ PauseGame();
+ break;
+ case ConsoleKey.R:
+ TextColor++;
+ if (TextColor == 16) TextColor = 1;
+ Console.ForegroundColor = (ConsoleColor)TextColor;
+ break;
+
+ //DEBUG
+ case ConsoleKey.Spacebar:
+ if (!DEBUGCONTROLS) return;
+ PLAYFIELD = (string[])FIELD.Clone();
+ break;
+ case ConsoleKey.I:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[0];
+ break;
+ case ConsoleKey.J:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[1];
+ break;
+ case ConsoleKey.L:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[2];
+ break;
+ case ConsoleKey.O:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[3];
+ break;
+ case ConsoleKey.C:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[4];
+ break;
+ case ConsoleKey.T:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[5];
+ break;
+ case ConsoleKey.Z:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[6];
+ break;
+ case ConsoleKey.X:
+ if (!DEBUGCONTROLS) return;
+ Score += 10;
+ break;
+ }
+ }
+}
+
+void DrawFrame()
+{
+ bool collision = false;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = TETROMINO.Shape;
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Save Frame
+ if (collision && LastFrame != null) frame = LastFrame;
+ LastFrame = (char[][])frame.Clone();
+ for (int y = 0; y < LastFrame.Length; y++)
+ {
+ LastFrame[y] = (char[])frame[y].Clone();
+ }
+
+ //Draw Preview
+ for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ {
+ if (CollisionPreview(yField, yScope, shapeScope)) continue;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yField + y;
+
+ if (yScope + shapeScope.Length > tY) continue;
+
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = '•';
+ }
+ }
+
+ break;
+ }
+
+ //Next Square
+ for (int y = 0; y < NEXTTETROMINO.Length; y++)
+ {
+ frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
+ }
+
+ //Score Square
+ for (int y = 0; y < SCORE.Length; y++)
+ {
+ int sY = NEXTTETROMINO.Length + y;
+ frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
+ }
+
+ //Draw Next
+ for (int y = 0; y < TETROMINO.Next.Length; y++)
+ {
+ for (int x = 0; x < TETROMINO.Next[y].Length; x++)
+ {
+ int tY = y + BORDER;
+ int tX = PLAYFIELD[y].Length + x + BORDER;
+ char charTetromino = TETROMINO.Next[y][x];
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Score
+ char[] score = Score.ToString().ToCharArray();
+ for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ {
+ int sY = NEXTTETROMINO.Length + BORDER;
+ int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
+ frame[sY][sX] = score[scoreX];
+ }
+
+ //Draw Pause
+ if (GameStatus == GameStatus.Paused)
+ {
+ for (int y = 0; y < PAUSE.Length; y++)
+ {
+ int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
+ for (int x = 0; x < PAUSE[y].Length; x++)
+ {
+ int fX = x + BORDER;
+
+ if (x >= PLAYFIELD[fY].Length) break;
+
+ frame[fY][fX] = PAUSE[y][x];
+ }
+ }
+ }
+
+ //Create Render
+ StringBuilder render = new();
+ for (int y = 0; y < frame.Length; y++)
+ {
+ render.AppendLine(new string(frame[y]));
+ }
+
+ Console.Clear();
+ Console.Write(render);
+ Console.CursorVisible = false;
+}
+
+bool Collision(Direction direction)
+{
+ int xNew = TETROMINO.X;
+ bool collision = false;
+
+ switch (direction)
+ {
+ case Direction.Right:
+ xNew += 3;
+ if (xNew + TETROMINO.Shape[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ break;
+ case Direction.Left:
+ xNew -= 3;
+ if (xNew < BORDER) collision = true;
+ break;
+ case Direction.None:
+ break;
+ }
+
+ if (collision) return collision;
+
+ for (int y = 0; y < TETROMINO.Shape.Length && !collision; y++)
+ {
+ for (int x = 0; x < TETROMINO.Shape[y].Length; x++)
+ {
+ int tY = TETROMINO.Y + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = TETROMINO.Shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ return collision;
+}
+
+bool CollisionPreview(int initY, int yScope, string[] shape)
+{
+ int xNew = TETROMINO.X;
+
+ for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
+ {
+ for (int y = shape.Length - 1; y >= 0; y -= 2)
+ {
+ for (int x = 0; x < shape[y].Length; x++)
+ {
+ int tY = yUpper + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void Gameover()
+{
+ GameStatus = GameStatus.Gameover;
+ AutoEvent.Dispose();
+ FallTimer.Dispose();
+
+ SleepAfterRender();
+
+ Console.Clear();
+ Console.WriteLine();
+ Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
+ Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
+ Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
+ Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
+ Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
+ Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
+ Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
+ Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
+ Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
+ Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
+ Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
+ Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
+
+ Console.WriteLine();
+ Console.WriteLine($" Final Score: {Score}");
+ Console.WriteLine($" Pause Count: {PauseCount}");
+ Console.WriteLine();
+ Console.WriteLine(" Press enter to play again");
+ Console.WriteLine(" Press escape to close the game");
+ Console.CursorVisible = false;
+ Console.ReadKey();
+ StartGame();
+ RestartGame();
+}
+
+void StartGame(ConsoleKey key = ConsoleKey.Enter)
+{
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = Console.ReadKey(true).Key;
+ if (input is ConsoleKey.Escape)
+ {
+ CloseGame = true;
+ return;
+ }
+ }
+}
+
+void RestartGame()
+{
+ PLAYFIELD = (string[])FIELD.Clone();
+ FallSpeedMilliSeconds = 1000;
+ Score = 0;
+ TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ LastFrame = null;
+ AutoEvent = new AutoResetEvent(false);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+}
+
+void PauseGame()
+{
+ PauseCount++;
+ FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ GameStatus = GameStatus.Paused;
+ DrawFrame();
+
+ ResumeGame();
+}
+
+void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
+{
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = Console.ReadKey(true).Key;
+ if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
+ {
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ return;
+ }
+ }
+}
+
+void TetrominoFall(object? e)
+{
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) TETROMINO.Y = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else TETROMINO.Y += 2;
+
+ //Y Collision
+ for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ {
+ for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ {
+ char exist = TETROMINO.Shape[yCollision][xCollision];
+
+ if (exist == ' ') continue;
+
+ char[] lineYC = PLAYFIELD[TETROMINO.Y + yCollision - 1].ToCharArray();
+
+ if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
+
+ if
+ (
+ lineYC[TETROMINO.X + xCollision] != ' ' &&
+ lineYC[TETROMINO.X + xCollision] != '│' &&
+ LastFrame != null
+ )
+ {
+ for (int y = 0; y < LastFrame.Length; y++)
+ {
+ PLAYFIELD[y] = new string(LastFrame[y]);
+ }
+
+ TETROMINO.X = INITIALTETROMINOX;
+ TETROMINO.Y = INITIALTETROMINOY;
+ TETROMINO.Shape = TETROMINO.Next;
+ TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
+
+ xCollision = TETROMINO.Shape[0].Length;
+ break;
+ }
+ }
+
+ xCollision += 3;
+ }
+
+ //Clean Lines
+ for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ {
+ string line = PLAYFIELD[lineIndex];
+ bool notCompleted = line.Any(e => e == ' ');
+
+ if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+
+ if (!notCompleted)
+ {
+ PLAYFIELD[lineIndex] = "│ │";
+ Score++;
+
+ for (int lineM = lineIndex; lineM >= 1; lineM--)
+ {
+ if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ {
+ PLAYFIELD[lineM] = "│ │";
+ continue;
+ }
+
+ PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ }
+
+ lineIndex++;
+ }
+ }
+
+ //VerifiedCollision
+ if (Collision(Direction.None) && FallTimer != null) Gameover();
+
+ //Change Speed
+ if (Score > 100) return;
+ if (Score < 10) FallSpeedMilliSeconds = 1000;
+ else if (Score < 20) FallSpeedMilliSeconds = 800;
+ else if (Score < 30) FallSpeedMilliSeconds = 800;
+ else if (Score < 40) FallSpeedMilliSeconds = 600;
+ else if (Score < 50) FallSpeedMilliSeconds = 400;
+ else if (Score < 60) FallSpeedMilliSeconds = 200;
+ else if (Score < 70) FallSpeedMilliSeconds = 100;
+ else if (Score < 99) FallSpeedMilliSeconds = 50;
+}
+
+void TetrominoSpin(Direction spinDirection)
+{
+ string[] newShape = new string[TETROMINO.Shape[0].Length / 3 * 2];
+ int newY = 0;
+ int rowEven = 0;
+ int rowOdd = 1;
+
+ //Turn
+ for (int y = 0; y < TETROMINO.Shape.Length;)
+ {
+ switch (spinDirection)
+ {
+ case Direction.Right:
+ SpinRight(newShape, ref newY, rowEven, rowOdd, y);
+ break;
+ case Direction.Left:
+ SpinLeft(newShape, ref newY, rowEven, rowOdd, y);
+ break;
+ }
+
+ newY = 0;
+ rowEven += 2;
+ rowOdd += 2;
+ y += 2;
+ }
+
+ //Verified Collision
+ for (int y = 0; y < newShape.Length - 1; y++)
+ {
+ for (int x = 0; x < newShape[y].Length; x++)
+ {
+ if (newShape[y][x] == ' ') continue;
+
+ char c = PLAYFIELD[TETROMINO.Y + y][TETROMINO.X + x];
+ if (c != ' ') return;
+ }
+ }
+
+ TETROMINO.Shape = newShape;
+}
+
+void SpinLeft(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+{
+ for (int x = TETROMINO.Shape[y].Length - 1; x >= 0; x -= 3)
+ {
+ for (int xS = 2; xS >= 0; xS--)
+ {
+ newShape[newY] += TETROMINO.Shape[rowEven][x - xS];
+ newShape[newY + 1] += TETROMINO.Shape[rowOdd][x - xS];
+ }
+
+ newY += 2;
+ }
+}
+
+void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+{
+ for (int x = 2; x < TETROMINO.Shape[y].Length; x += 3)
+ {
+ if (newShape[newY] == null)
+ {
+ newShape[newY] = "";
+ newShape[newY + 1] = "";
+ }
+
+ for (int xS = 0; xS <= 2; xS++)
+ {
+ newShape[newY] = newShape[newY].Insert(0, TETROMINO.Shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, TETROMINO.Shape[rowOdd][x - xS].ToString());
+ }
+
+ newY += 2;
+ }
+}
+
+void SleepAfterRender()
+{
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 120d) - Stopwatch.Elapsed;
+ if (sleep > TimeSpan.Zero)
+ {
+ Thread.Sleep(sleep);
+ }
+ Stopwatch.Restart();
+}
+
+class Tetromino
+{
+ public required string[] Shape { get; set; }
+ public required string[] Next { get; set; }
+ public int X { get; set; }
+ public int Y { get; set; }
+}
+
+enum Direction
+{
+ Right,
+ Left,
+ None
+}
+
+enum GameStatus
+{
+ Gameover,
+ Playing,
+ Paused
+}
\ No newline at end of file
diff --git a/Projects/Tetris/README.md b/Projects/Tetris/README.md
new file mode 100644
index 00000000..3b4196db
--- /dev/null
+++ b/Projects/Tetris/README.md
@@ -0,0 +1,36 @@
+
+ Tetris
+
+
+
+
+
+
+
+
+
+
+Well, just tetris!
+
+## Inputs
+
+|Key|Action|
+|---|---|
+|`↓`, `S`, `←`, `A`, `→`, `D` |Move |
+|`Q` |Spin Left |
+|`E` |Spin Right |
+|`P` |Pause, enter to resume |
+|`R` |Change Text Color |
+
+Debug controls (only if debug mode its active, it may break the game):
+|Key|Action|
+|---|---|
+|`Spacebar` |Clean Field |
+|`X` |Add 10 score points |
+|`I` |change active tetromino to I|
+|`J` |change active tetromino to J|
+|`L` |change active tetromino to L|
+|`O` |change active tetromino to O|
+|`C` |change active tetromino to S|
+|`T` |change active tetromino to T|
+|`Z` |change active tetromino to Z|
\ No newline at end of file
diff --git a/Projects/Tetris/Tetris.csproj b/Projects/Tetris/Tetris.csproj
new file mode 100644
index 00000000..f02677bf
--- /dev/null
+++ b/Projects/Tetris/Tetris.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
diff --git a/Projects/Website/.vscode/launch.json b/Projects/Website/.vscode/launch.json
new file mode 100644
index 00000000..b0726229
--- /dev/null
+++ b/Projects/Website/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch and Debug Standalone Blazor WebAssembly App",
+ "type": "blazorwasm",
+ "request": "launch",
+ "cwd": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/Website/.vscode/tasks.json b/Projects/Website/.vscode/tasks.json
new file mode 100644
index 00000000..46740de4
--- /dev/null
+++ b/Projects/Website/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/Website.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/Website.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/Website.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/Website/Games/Tetris/Tetris.cs b/Projects/Website/Games/Tetris/Tetris.cs
new file mode 100644
index 00000000..acfccec7
--- /dev/null
+++ b/Projects/Website/Games/Tetris/Tetris.cs
@@ -0,0 +1,733 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Text;
+using System.Linq;
+
+namespace Website.Games.Tetris;
+
+public class Tetris
+{
+ public readonly BlazorConsole Console = new();
+
+ public async Task Run()
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ Console.CursorVisible = false;
+ Stopwatch Stopwatch = Stopwatch.StartNew();
+
+ string[] FIELD = new[]
+ {
+ "╭──────────────────────────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰──────────────────────────────╯"
+ };
+
+ string[] NEXTTETROMINO = new[]
+ {
+ "╭─────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰─────────╯"
+ };
+
+ string[] SCORE = new[]{
+ "╭─────────╮",
+ "│ │",
+ "╰─────────╯"
+ };
+
+ string[] PAUSE = new[]{
+ "█████╗ ███╗ ██╗██╗█████╗█████╗",
+ "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
+ "█████║█████║██║██║ ███╗ █████╗",
+ "██╔══╝██╔██║██║██║ ██╗██╔══╝",
+ "██║ ██║██║█████║█████║█████╗",
+ "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
+ };
+
+ string[][] TETROMINOS = new[]
+ {
+ new[]{
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯"
+ },
+ new[]{
+ "╭─╮ ",
+ "╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮",
+ " ╰─╯",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮",
+ "╰─╯╰─╯",
+ "╭─╮╭─╮",
+ "╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯",
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ "
+ },
+ new[]{
+ " ╭─╮ ",
+ " ╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ ",
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯"
+ },
+ };
+
+ string[] PLAYFIELD = (string[])FIELD.Clone();
+ const int BORDER = 1;
+ int FallSpeedMilliSeconds = 1000;
+ bool CloseGame = false;
+ int Score = 0;
+ int PauseCount = 0;
+ GameStatus GameStatus = GameStatus.Gameover;
+
+ Random RamdomGenerator = new();
+
+ int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
+ int INITIALTETROMINOY = 1;
+ Tetromino TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ char[][]? LastFrame = null;
+
+ AutoResetEvent AutoEvent = new AutoResetEvent(false);
+ Timer? FallTimer = null;
+ GameStatus = GameStatus.Playing;
+
+ await Console.WriteLine();
+ await Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
+ await Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
+ await Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
+ await Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
+ await Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
+ await Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
+
+ await Console.WriteLine();
+ await Console.WriteLine(" Controls:");
+ await Console.WriteLine(" WASD or ARROW to move");
+ await Console.WriteLine(" Q or E to spin left or right");
+ await Console.WriteLine(" P to paused the game, press enter");
+ await Console.WriteLine(" key to resume");
+ await Console.WriteLine();
+ await Console.Write(" Press enter to start tetris...");
+ Console.CursorVisible = false;
+ await StartGame();
+ await Console.Clear();
+
+ FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+
+ while (!CloseGame)
+ {
+ if (CloseGame)
+ {
+ break;
+ }
+
+ await PlayerControl();
+ if (GameStatus == GameStatus.Playing)
+ {
+ await DrawFrame();
+ await SleepAfterRender();
+ }
+ }
+
+ async Task PlayerControl()
+ {
+ while (await Console.KeyAvailable() && GameStatus == GameStatus.Playing)
+ {
+ switch ((await Console.ReadKey(true)).Key)
+ {
+ case ConsoleKey.A or ConsoleKey.LeftArrow:
+ if (Collision(Direction.Left)) break;
+ TETROMINO.X -= 3;
+ break;
+ case ConsoleKey.D or ConsoleKey.RightArrow:
+ if (Collision(Direction.Right)) break;
+ TETROMINO.X += 3;
+ break;
+ case ConsoleKey.S or ConsoleKey.DownArrow:
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ break;
+ case ConsoleKey.E:
+ TetrominoSpin(Direction.Right);
+ break;
+ case ConsoleKey.Q:
+ TetrominoSpin(Direction.Left);
+ break;
+ case ConsoleKey.P:
+ PauseGame();
+ break;
+ }
+ }
+ }
+
+ async Task DrawFrame()
+ {
+ bool collision = false;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = TETROMINO.Shape;
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Save Frame
+ if (collision && LastFrame != null) frame = LastFrame;
+ LastFrame = (char[][])frame.Clone();
+ for (int y = 0; y < LastFrame.Length; y++)
+ {
+ LastFrame[y] = (char[])frame[y].Clone();
+ }
+
+ //Draw Preview
+ for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ {
+ if (CollisionPreview(yField, yScope, shapeScope)) continue;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yField + y;
+
+ if (yScope + shapeScope.Length > tY) continue;
+
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = '•';
+ }
+ }
+
+ break;
+ }
+
+ //Next Square
+ for (int y = 0; y < NEXTTETROMINO.Length; y++)
+ {
+ frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
+ }
+
+ //Score Square
+ for (int y = 0; y < SCORE.Length; y++)
+ {
+ int sY = NEXTTETROMINO.Length + y;
+ frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
+ }
+
+ //Draw Next
+ for (int y = 0; y < TETROMINO.Next.Length; y++)
+ {
+ for (int x = 0; x < TETROMINO.Next[y].Length; x++)
+ {
+ int tY = y + BORDER;
+ int tX = PLAYFIELD[y].Length + x + BORDER;
+ char charTetromino = TETROMINO.Next[y][x];
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Score
+ char[] score = Score.ToString().ToCharArray();
+ for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ {
+ int sY = NEXTTETROMINO.Length + BORDER;
+ int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
+ frame[sY][sX] = score[scoreX];
+ }
+
+ //Draw Pause
+ if (GameStatus == GameStatus.Paused)
+ {
+ for (int y = 0; y < PAUSE.Length; y++)
+ {
+ int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
+ for (int x = 0; x < PAUSE[y].Length; x++)
+ {
+ int fX = x + BORDER;
+
+ if (x >= PLAYFIELD[fY].Length) break;
+
+ frame[fY][fX] = PAUSE[y][x];
+ }
+ }
+ }
+
+ //Create Render
+ StringBuilder render = new();
+ for (int y = 0; y < frame.Length; y++)
+ {
+ render.AppendLine(new string(frame[y]));
+ }
+
+ await Console.Clear();
+ await Console.Write(render);
+ Console.CursorVisible = false;
+ }
+
+ bool Collision(Direction direction)
+ {
+ int xNew = TETROMINO.X;
+ bool collision = false;
+
+ switch (direction)
+ {
+ case Direction.Right:
+ xNew += 3;
+ if (xNew + TETROMINO.Shape[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ break;
+ case Direction.Left:
+ xNew -= 3;
+ if (xNew < BORDER) collision = true;
+ break;
+ case Direction.None:
+ break;
+ }
+
+ if (collision) return collision;
+
+ for (int y = 0; y < TETROMINO.Shape.Length && !collision; y++)
+ {
+ for (int x = 0; x < TETROMINO.Shape[y].Length; x++)
+ {
+ int tY = TETROMINO.Y + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = TETROMINO.Shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ return collision;
+ }
+
+ bool CollisionPreview(int initY, int yScope, string[] shape)
+ {
+ int xNew = TETROMINO.X;
+
+ for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
+ {
+ for (int y = shape.Length - 1; y >= 0; y -= 2)
+ {
+ for (int x = 0; x < shape[y].Length; x++)
+ {
+ int tY = yUpper + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ async void Gameover()
+ {
+ GameStatus = GameStatus.Gameover;
+ AutoEvent.Dispose();
+ FallTimer.Dispose();
+
+ await SleepAfterRender();
+
+ await Console.Clear();
+ await Console.WriteLine();
+ await Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
+ await Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
+ await Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
+ await Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
+ await Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
+ await Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
+ await Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
+ await Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
+ await Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
+ await Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
+ await Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
+ await Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
+
+ await Console.WriteLine();
+ await Console.WriteLine($" Final Score: {Score}");
+ await Console.WriteLine($" Pause Count: {PauseCount}");
+ await Console.WriteLine();
+ await Console.WriteLine(" Press enter to play again");
+ await Console.WriteLine(" Press escape to close the game");
+ Console.CursorVisible = false;
+ await StartGame();
+ RestartGame();
+ }
+
+ async Task StartGame(ConsoleKey key = ConsoleKey.Enter)
+ {
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = (await Console.ReadKey(true)).Key;
+ if (input is ConsoleKey.Escape)
+ {
+ CloseGame = true;
+ return;
+ }
+ }
+ }
+
+ void RestartGame()
+ {
+ PLAYFIELD = (string[])FIELD.Clone();
+ FallSpeedMilliSeconds = 1000;
+ Score = 0;
+ TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ LastFrame = null;
+ AutoEvent = new AutoResetEvent(false);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ }
+
+ async void PauseGame()
+ {
+ PauseCount++;
+ FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ GameStatus = GameStatus.Paused;
+ await DrawFrame();
+
+ await ResumeGame();
+ }
+
+ async Task ResumeGame(ConsoleKey key = ConsoleKey.Enter)
+ {
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = (await Console.ReadKey(true)).Key;
+ if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
+ {
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ return;
+ }
+ }
+ }
+
+ void TetrominoFall(object? e)
+ {
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) TETROMINO.Y = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else TETROMINO.Y += 2;
+
+ //Y Collision
+ for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ {
+ for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ {
+ char exist = TETROMINO.Shape[yCollision][xCollision];
+
+ if (exist == ' ') continue;
+
+ char[] lineYC = PLAYFIELD[TETROMINO.Y + yCollision - 1].ToCharArray();
+
+ if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
+
+ if
+ (
+ lineYC[TETROMINO.X + xCollision] != ' ' &&
+ lineYC[TETROMINO.X + xCollision] != '│' &&
+ LastFrame != null
+ )
+ {
+ for (int y = 0; y < LastFrame.Length; y++)
+ {
+ PLAYFIELD[y] = new string(LastFrame[y]);
+ }
+
+ TETROMINO.X = INITIALTETROMINOX;
+ TETROMINO.Y = INITIALTETROMINOY;
+ TETROMINO.Shape = TETROMINO.Next;
+ TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
+
+ xCollision = TETROMINO.Shape[0].Length;
+ break;
+ }
+ }
+
+ xCollision += 3;
+ }
+
+ //Clean Lines
+ for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ {
+ string line = PLAYFIELD[lineIndex];
+ bool notCompleted = line.Any(e => e == ' ');
+
+ if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+
+ if (!notCompleted)
+ {
+ PLAYFIELD[lineIndex] = "│ │";
+ Score++;
+
+ for (int lineM = lineIndex; lineM >= 1; lineM--)
+ {
+ if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ {
+ PLAYFIELD[lineM] = "│ │";
+ continue;
+ }
+
+ PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ }
+
+ lineIndex++;
+ }
+ }
+
+ //VerifiedCollision
+ if (Collision(Direction.None) && FallTimer != null) Gameover();
+
+ //Change Speed
+ if (Score > 100) return;
+ if (Score < 10) FallSpeedMilliSeconds = 1000;
+ else if (Score < 20) FallSpeedMilliSeconds = 800;
+ else if (Score < 30) FallSpeedMilliSeconds = 800;
+ else if (Score < 40) FallSpeedMilliSeconds = 600;
+ else if (Score < 50) FallSpeedMilliSeconds = 400;
+ else if (Score < 60) FallSpeedMilliSeconds = 200;
+ else if (Score < 70) FallSpeedMilliSeconds = 100;
+ else if (Score < 99) FallSpeedMilliSeconds = 50;
+ }
+
+ void TetrominoSpin(Direction spinDirection)
+ {
+ string[] newShape = new string[TETROMINO.Shape[0].Length / 3 * 2];
+ int newY = 0;
+ int rowEven = 0;
+ int rowOdd = 1;
+
+ //Turn
+ for (int y = 0; y < TETROMINO.Shape.Length;)
+ {
+ switch (spinDirection)
+ {
+ case Direction.Right:
+ SpinRight(newShape, ref newY, rowEven, rowOdd, y);
+ break;
+ case Direction.Left:
+ SpinLeft(newShape, ref newY, rowEven, rowOdd, y);
+ break;
+ }
+
+ newY = 0;
+ rowEven += 2;
+ rowOdd += 2;
+ y += 2;
+ }
+
+ //Verified Collision
+ for (int y = 0; y < newShape.Length - 1; y++)
+ {
+ for (int x = 0; x < newShape[y].Length; x++)
+ {
+ if (newShape[y][x] == ' ') continue;
+
+ char c = PLAYFIELD[TETROMINO.Y + y][TETROMINO.X + x];
+ if (c != ' ') return;
+ }
+ }
+
+ TETROMINO.Shape = newShape;
+ }
+
+ void SpinLeft(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+ {
+ for (int x = TETROMINO.Shape[y].Length - 1; x >= 0; x -= 3)
+ {
+ for (int xS = 2; xS >= 0; xS--)
+ {
+ newShape[newY] += TETROMINO.Shape[rowEven][x - xS];
+ newShape[newY + 1] += TETROMINO.Shape[rowOdd][x - xS];
+ }
+
+ newY += 2;
+ }
+ }
+
+ void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+ {
+ for (int x = 2; x < TETROMINO.Shape[y].Length; x += 3)
+ {
+ if (newShape[newY] == null)
+ {
+ newShape[newY] = "";
+ newShape[newY + 1] = "";
+ }
+
+ for (int xS = 0; xS <= 2; xS++)
+ {
+ newShape[newY] = newShape[newY].Insert(0, TETROMINO.Shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, TETROMINO.Shape[rowOdd][x - xS].ToString());
+ }
+
+ newY += 2;
+ }
+ }
+
+ async Task SleepAfterRender()
+ {
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 120d) - Stopwatch.Elapsed;
+ if (sleep > TimeSpan.Zero)
+ {
+ await Console.RefreshAndDelay(sleep);
+ }
+ Stopwatch.Restart();
+ }
+ }
+
+ class Tetromino
+ {
+ public required string[] Shape { get; set; }
+ public required string[] Next { get; set; }
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ enum Direction
+ {
+ Right,
+ Left,
+ None
+ }
+
+ enum GameStatus
+ {
+ Gameover,
+ Playing,
+ Paused
+ }
+
+}
diff --git a/Projects/Website/Pages/Tetris.razor b/Projects/Website/Pages/Tetris.razor
new file mode 100644
index 00000000..4bc7b4f0
--- /dev/null
+++ b/Projects/Website/Pages/Tetris.razor
@@ -0,0 +1,54 @@
+@using System
+
+@page "/Tetris"
+
+Tetris
+
+Tetris
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⌨ Keyboard input is supported if you click on the game.
+
+
+
+ ↻ You can restart the game by refreshing the page.
+
+
+@code
+{
+ Games.Tetris.Tetris Game;
+ BlazorConsole Console;
+
+ public Tetris()
+ {
+ Game = new();
+ Console = Game.Console;
+ Console.WindowWidth = 43;
+ Console.WindowHeight = 42;
+ Console.TriggerRefresh = StateHasChanged;
+ }
+
+ protected override void OnInitialized() => InvokeAsync(Game.Run);
+}
diff --git a/Projects/Website/Shared/NavMenu.razor b/Projects/Website/Shared/NavMenu.razor
index b2c080a1..4f3b0348 100644
--- a/Projects/Website/Shared/NavMenu.razor
+++ b/Projects/Website/Shared/NavMenu.razor
@@ -248,6 +248,11 @@
Shmup
+
+
+ Tetris
+
+
diff --git a/README.md b/README.md
index 05a836ef..4f6ddc4a 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@
|[Role Playing Game](Projects/Role%20Playing%20Game)|6|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Role%20Playing%20Game) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Role%20Playing%20Game%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
|[Console Monsters](Projects/Console%20Monsters)|7|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Console%20Monsters) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Console%20Monsters%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_Community Collaboration_
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_|
|[Shmup](Projects/Shmup)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Shmup) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Shmup%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_ & _Only Supported On Windows OS (+WEB)_|
+|[Tetris](Projects/Tetris)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Tetris) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Tetris%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_[Community Contribution]()_|
\*_**Weight**: A relative rating for how advanced the source code is._
diff --git a/dotnet-console-games.sln b/dotnet-console-games.sln
index 1cd68ef3..92b38d84 100644
--- a/dotnet-console-games.sln
+++ b/dotnet-console-games.sln
@@ -103,6 +103,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shmup", "Projects\Shmup\Shm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clicker", "Projects\Clicker\Clicker.csproj", "{C408B9C3-5F16-4F0A-B0D0-F39A6F7F0B72}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tetris", "Projects\Tetris\Tetris.csproj", "{4E9F6AA3-7E12-4555-95C2-6D90C8CD3DBB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -309,6 +311,10 @@ Global
{C408B9C3-5F16-4F0A-B0D0-F39A6F7F0B72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C408B9C3-5F16-4F0A-B0D0-F39A6F7F0B72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C408B9C3-5F16-4F0A-B0D0-F39A6F7F0B72}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E9F6AA3-7E12-4555-95C2-6D90C8CD3DBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E9F6AA3-7E12-4555-95C2-6D90C8CD3DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E9F6AA3-7E12-4555-95C2-6D90C8CD3DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E9F6AA3-7E12-4555-95C2-6D90C8CD3DBB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dotnet-console-games.slnf b/dotnet-console-games.slnf
index 4cfb281f..57683bef 100644
--- a/dotnet-console-games.slnf
+++ b/dotnet-console-games.slnf
@@ -50,7 +50,8 @@
"Projects\\Whack A Mole\\Whack A Mole.csproj",
"Projects\\Wordle\\Wordle.csproj",
"Projects\\Wumpus World\\Wumpus World.csproj",
- "Projects\\Yahtzee\\Yahtzee.csproj"
+ "Projects\\Yahtzee\\Yahtzee.csproj",
+ "Projects\\Tetris\\Tetris.csproj"
]
}
}
\ No newline at end of file
From 89ec8e9879fc934f3384c1c3f700290d64d6bff3 Mon Sep 17 00:00:00 2001
From: Jahg
Date: Fri, 27 Oct 2023 10:24:59 -0700
Subject: [PATCH 02/24] Improvements, inmutability and fall logic fix
---
Projects/Tetris/Program.cs | 175 ++++++++++++++++--------
Projects/Website/Games/Tetris/Tetris.cs | 175 ++++++++++++++++--------
README.md | 2 +-
3 files changed, 240 insertions(+), 112 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 6c71c532..151efe53 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -153,8 +153,6 @@
Y = INITIALTETROMINOY
};
-char[][]? LastFrame = null;
-
AutoResetEvent AutoEvent = new AutoResetEvent(false);
Timer? FallTimer = null;
GameStatus = GameStatus.Playing;
@@ -182,7 +180,7 @@
StartGame();
Console.Clear();
-FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
while (!CloseGame)
{
@@ -276,7 +274,8 @@ void DrawFrame()
{
bool collision = false;
int yScope = TETROMINO.Y;
- string[] shapeScope = TETROMINO.Shape;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
char[][] frame = new char[PLAYFIELD.Length][];
//Field
@@ -307,14 +306,6 @@ void DrawFrame()
}
}
- //Save Frame
- if (collision && LastFrame != null) frame = LastFrame;
- LastFrame = (char[][])frame.Clone();
- for (int y = 0; y < LastFrame.Length; y++)
- {
- LastFrame[y] = (char[])frame[y].Clone();
- }
-
//Draw Preview
for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
{
@@ -361,13 +352,13 @@ void DrawFrame()
}
//Draw Next
- for (int y = 0; y < TETROMINO.Next.Length; y++)
+ for (int y = 0; y < nextShapeScope.Length; y++)
{
- for (int x = 0; x < TETROMINO.Next[y].Length; x++)
+ for (int x = 0; x < nextShapeScope[y].Length; x++)
{
int tY = y + BORDER;
int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = TETROMINO.Next[y][x];
+ char charTetromino = nextShapeScope[y][x];
frame[tY][tX] = charTetromino;
}
}
@@ -410,16 +401,58 @@ void DrawFrame()
Console.CursorVisible = false;
}
+char[][] DrawLastFrame(int yS)
+{
+ bool collision = false;
+ int yScope = yS - 2;
+ int xScope = TETROMINO.X;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xScope + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ return frame;
+}
+
bool Collision(Direction direction)
{
int xNew = TETROMINO.X;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
bool collision = false;
switch (direction)
{
case Direction.Right:
xNew += 3;
- if (xNew + TETROMINO.Shape[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
break;
case Direction.Left:
xNew -= 3;
@@ -431,14 +464,14 @@ bool Collision(Direction direction)
if (collision) return collision;
- for (int y = 0; y < TETROMINO.Shape.Length && !collision; y++)
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
{
- for (int x = 0; x < TETROMINO.Shape[y].Length; x++)
+ for (int x = 0; x < shapeScope[y].Length; x++)
{
- int tY = TETROMINO.Y + y;
+ int tY = yScope + y;
int tX = xNew + x;
char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = TETROMINO.Shape[y][x];
+ char charTetromino = shapeScope[y][x];
if (charTetromino == ' ') continue;
@@ -511,7 +544,6 @@ void Gameover()
Console.WriteLine(" Press enter to play again");
Console.WriteLine(" Press escape to close the game");
Console.CursorVisible = false;
- Console.ReadKey();
StartGame();
RestartGame();
}
@@ -543,9 +575,8 @@ void RestartGame()
Y = INITIALTETROMINOY
};
- LastFrame = null;
AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
GameStatus = GameStatus.Playing;
}
@@ -574,10 +605,48 @@ void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
}
}
+void AddScoreChangeSpeed(int value)
+{
+ Score += value;
+
+ if (Score > 100) return;
+
+ switch (Score)
+ {
+ case 10:
+ FallSpeedMilliSeconds = 900;
+ break;
+ case 20:
+ FallSpeedMilliSeconds = 800;
+ break;
+ case 30:
+ FallSpeedMilliSeconds = 700;
+ break;
+ case 40:
+ FallSpeedMilliSeconds = 500;
+ break;
+ case 50:
+ FallSpeedMilliSeconds = 300;
+ break;
+ case 60:
+ FallSpeedMilliSeconds = 200;
+ break;
+ case 70:
+ FallSpeedMilliSeconds = 100;
+ break;
+ case 100:
+ FallSpeedMilliSeconds = 50;
+ break;
+ }
+}
+
void TetrominoFall(object? e)
{
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) TETROMINO.Y = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else TETROMINO.Y += 2;
+ int yAfterFall = TETROMINO.Y;
+ bool collision = false;
+
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else yAfterFall += 2;
//Y Collision
for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
@@ -588,20 +657,20 @@ void TetrominoFall(object? e)
if (exist == ' ') continue;
- char[] lineYC = PLAYFIELD[TETROMINO.Y + yCollision - 1].ToCharArray();
+ char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
if
(
lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│' &&
- LastFrame != null
+ lineYC[TETROMINO.X + xCollision] != '│'
)
{
- for (int y = 0; y < LastFrame.Length; y++)
+ char[][] lastFrame = DrawLastFrame(yAfterFall);
+ for (int y = 0; y < lastFrame.Length; y++)
{
- PLAYFIELD[y] = new string(LastFrame[y]);
+ PLAYFIELD[y] = new string(lastFrame[y]);
}
TETROMINO.X = INITIALTETROMINOX;
@@ -610,6 +679,7 @@ void TetrominoFall(object? e)
TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
xCollision = TETROMINO.Shape[0].Length;
+ collision = true;
break;
}
}
@@ -617,6 +687,8 @@ void TetrominoFall(object? e)
xCollision += 3;
}
+ if (!collision) TETROMINO.Y = yAfterFall;
+
//Clean Lines
for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
{
@@ -628,7 +700,7 @@ void TetrominoFall(object? e)
if (!notCompleted)
{
PLAYFIELD[lineIndex] = "│ │";
- Score++;
+ AddScoreChangeSpeed(1);
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
@@ -647,36 +719,27 @@ void TetrominoFall(object? e)
//VerifiedCollision
if (Collision(Direction.None) && FallTimer != null) Gameover();
-
- //Change Speed
- if (Score > 100) return;
- if (Score < 10) FallSpeedMilliSeconds = 1000;
- else if (Score < 20) FallSpeedMilliSeconds = 800;
- else if (Score < 30) FallSpeedMilliSeconds = 800;
- else if (Score < 40) FallSpeedMilliSeconds = 600;
- else if (Score < 50) FallSpeedMilliSeconds = 400;
- else if (Score < 60) FallSpeedMilliSeconds = 200;
- else if (Score < 70) FallSpeedMilliSeconds = 100;
- else if (Score < 99) FallSpeedMilliSeconds = 50;
}
void TetrominoSpin(Direction spinDirection)
{
- string[] newShape = new string[TETROMINO.Shape[0].Length / 3 * 2];
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int yScope = TETROMINO.Y;
+ string[] newShape = new string[shapeScope[0].Length / 3 * 2];
int newY = 0;
int rowEven = 0;
int rowOdd = 1;
//Turn
- for (int y = 0; y < TETROMINO.Shape.Length;)
+ for (int y = 0; y < shapeScope.Length;)
{
switch (spinDirection)
{
case Direction.Right:
- SpinRight(newShape, ref newY, rowEven, rowOdd, y);
+ SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
break;
case Direction.Left:
- SpinLeft(newShape, ref newY, rowEven, rowOdd, y);
+ SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
break;
}
@@ -693,7 +756,7 @@ void TetrominoSpin(Direction spinDirection)
{
if (newShape[y][x] == ' ') continue;
- char c = PLAYFIELD[TETROMINO.Y + y][TETROMINO.X + x];
+ char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
if (c != ' ') return;
}
}
@@ -701,23 +764,23 @@ void TetrominoSpin(Direction spinDirection)
TETROMINO.Shape = newShape;
}
-void SpinLeft(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = TETROMINO.Shape[y].Length - 1; x >= 0; x -= 3)
+ for (int x = shape[y].Length - 1; x >= 0; x -= 3)
{
for (int xS = 2; xS >= 0; xS--)
{
- newShape[newY] += TETROMINO.Shape[rowEven][x - xS];
- newShape[newY + 1] += TETROMINO.Shape[rowOdd][x - xS];
+ newShape[newY] += shape[rowEven][x - xS];
+ newShape[newY + 1] += shape[rowOdd][x - xS];
}
newY += 2;
}
}
-void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = 2; x < TETROMINO.Shape[y].Length; x += 3)
+ for (int x = 2; x < shape[y].Length; x += 3)
{
if (newShape[newY] == null)
{
@@ -727,8 +790,8 @@ void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
for (int xS = 0; xS <= 2; xS++)
{
- newShape[newY] = newShape[newY].Insert(0, TETROMINO.Shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, TETROMINO.Shape[rowOdd][x - xS].ToString());
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
}
newY += 2;
@@ -737,7 +800,7 @@ void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
void SleepAfterRender()
{
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 120d) - Stopwatch.Elapsed;
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
if (sleep > TimeSpan.Zero)
{
Thread.Sleep(sleep);
diff --git a/Projects/Website/Games/Tetris/Tetris.cs b/Projects/Website/Games/Tetris/Tetris.cs
index acfccec7..a973393e 100644
--- a/Projects/Website/Games/Tetris/Tetris.cs
+++ b/Projects/Website/Games/Tetris/Tetris.cs
@@ -162,8 +162,6 @@ public async Task Run()
Y = INITIALTETROMINOY
};
- char[][]? LastFrame = null;
-
AutoResetEvent AutoEvent = new AutoResetEvent(false);
Timer? FallTimer = null;
GameStatus = GameStatus.Playing;
@@ -188,7 +186,7 @@ public async Task Run()
await StartGame();
await Console.Clear();
- FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
while (!CloseGame)
{
@@ -239,7 +237,8 @@ async Task DrawFrame()
{
bool collision = false;
int yScope = TETROMINO.Y;
- string[] shapeScope = TETROMINO.Shape;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
char[][] frame = new char[PLAYFIELD.Length][];
//Field
@@ -270,14 +269,6 @@ async Task DrawFrame()
}
}
- //Save Frame
- if (collision && LastFrame != null) frame = LastFrame;
- LastFrame = (char[][])frame.Clone();
- for (int y = 0; y < LastFrame.Length; y++)
- {
- LastFrame[y] = (char[])frame[y].Clone();
- }
-
//Draw Preview
for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
{
@@ -324,13 +315,13 @@ async Task DrawFrame()
}
//Draw Next
- for (int y = 0; y < TETROMINO.Next.Length; y++)
+ for (int y = 0; y < nextShapeScope.Length; y++)
{
- for (int x = 0; x < TETROMINO.Next[y].Length; x++)
+ for (int x = 0; x < nextShapeScope[y].Length; x++)
{
int tY = y + BORDER;
int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = TETROMINO.Next[y][x];
+ char charTetromino = nextShapeScope[y][x];
frame[tY][tX] = charTetromino;
}
}
@@ -373,16 +364,58 @@ async Task DrawFrame()
Console.CursorVisible = false;
}
+ char[][] DrawLastFrame(int yS)
+ {
+ bool collision = false;
+ int yScope = yS - 2;
+ int xScope = TETROMINO.X;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xScope + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ return frame;
+ }
+
bool Collision(Direction direction)
{
int xNew = TETROMINO.X;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
bool collision = false;
switch (direction)
{
case Direction.Right:
xNew += 3;
- if (xNew + TETROMINO.Shape[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
break;
case Direction.Left:
xNew -= 3;
@@ -394,14 +427,14 @@ bool Collision(Direction direction)
if (collision) return collision;
- for (int y = 0; y < TETROMINO.Shape.Length && !collision; y++)
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
{
- for (int x = 0; x < TETROMINO.Shape[y].Length; x++)
+ for (int x = 0; x < shapeScope[y].Length; x++)
{
- int tY = TETROMINO.Y + y;
+ int tY = yScope + y;
int tX = xNew + x;
char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = TETROMINO.Shape[y][x];
+ char charTetromino = shapeScope[y][x];
if (charTetromino == ' ') continue;
@@ -505,9 +538,8 @@ void RestartGame()
Y = INITIALTETROMINOY
};
- LastFrame = null;
AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, 1000, FallSpeedMilliSeconds);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
GameStatus = GameStatus.Playing;
}
@@ -536,10 +568,48 @@ async Task ResumeGame(ConsoleKey key = ConsoleKey.Enter)
}
}
+ void AddScoreChangeSpeed(int value)
+ {
+ Score += value;
+
+ if (Score > 100) return;
+
+ switch (Score)
+ {
+ case 10:
+ FallSpeedMilliSeconds = 900;
+ break;
+ case 20:
+ FallSpeedMilliSeconds = 800;
+ break;
+ case 30:
+ FallSpeedMilliSeconds = 700;
+ break;
+ case 40:
+ FallSpeedMilliSeconds = 500;
+ break;
+ case 50:
+ FallSpeedMilliSeconds = 300;
+ break;
+ case 60:
+ FallSpeedMilliSeconds = 200;
+ break;
+ case 70:
+ FallSpeedMilliSeconds = 100;
+ break;
+ case 100:
+ FallSpeedMilliSeconds = 50;
+ break;
+ }
+ }
+
void TetrominoFall(object? e)
{
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) TETROMINO.Y = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else TETROMINO.Y += 2;
+ int yAfterFall = TETROMINO.Y;
+ bool collision = false;
+
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else yAfterFall += 2;
//Y Collision
for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
@@ -550,20 +620,20 @@ void TetrominoFall(object? e)
if (exist == ' ') continue;
- char[] lineYC = PLAYFIELD[TETROMINO.Y + yCollision - 1].ToCharArray();
+ char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
if
(
lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│' &&
- LastFrame != null
+ lineYC[TETROMINO.X + xCollision] != '│'
)
{
- for (int y = 0; y < LastFrame.Length; y++)
+ char[][] lastFrame = DrawLastFrame(yAfterFall);
+ for (int y = 0; y < lastFrame.Length; y++)
{
- PLAYFIELD[y] = new string(LastFrame[y]);
+ PLAYFIELD[y] = new string(lastFrame[y]);
}
TETROMINO.X = INITIALTETROMINOX;
@@ -572,6 +642,7 @@ void TetrominoFall(object? e)
TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
xCollision = TETROMINO.Shape[0].Length;
+ collision = true;
break;
}
}
@@ -579,6 +650,8 @@ void TetrominoFall(object? e)
xCollision += 3;
}
+ if (!collision) TETROMINO.Y = yAfterFall;
+
//Clean Lines
for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
{
@@ -590,7 +663,7 @@ void TetrominoFall(object? e)
if (!notCompleted)
{
PLAYFIELD[lineIndex] = "│ │";
- Score++;
+ AddScoreChangeSpeed(1);
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
@@ -609,36 +682,27 @@ void TetrominoFall(object? e)
//VerifiedCollision
if (Collision(Direction.None) && FallTimer != null) Gameover();
-
- //Change Speed
- if (Score > 100) return;
- if (Score < 10) FallSpeedMilliSeconds = 1000;
- else if (Score < 20) FallSpeedMilliSeconds = 800;
- else if (Score < 30) FallSpeedMilliSeconds = 800;
- else if (Score < 40) FallSpeedMilliSeconds = 600;
- else if (Score < 50) FallSpeedMilliSeconds = 400;
- else if (Score < 60) FallSpeedMilliSeconds = 200;
- else if (Score < 70) FallSpeedMilliSeconds = 100;
- else if (Score < 99) FallSpeedMilliSeconds = 50;
}
void TetrominoSpin(Direction spinDirection)
{
- string[] newShape = new string[TETROMINO.Shape[0].Length / 3 * 2];
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int yScope = TETROMINO.Y;
+ string[] newShape = new string[shapeScope[0].Length / 3 * 2];
int newY = 0;
int rowEven = 0;
int rowOdd = 1;
//Turn
- for (int y = 0; y < TETROMINO.Shape.Length;)
+ for (int y = 0; y < shapeScope.Length;)
{
switch (spinDirection)
{
case Direction.Right:
- SpinRight(newShape, ref newY, rowEven, rowOdd, y);
+ SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
break;
case Direction.Left:
- SpinLeft(newShape, ref newY, rowEven, rowOdd, y);
+ SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
break;
}
@@ -655,7 +719,7 @@ void TetrominoSpin(Direction spinDirection)
{
if (newShape[y][x] == ' ') continue;
- char c = PLAYFIELD[TETROMINO.Y + y][TETROMINO.X + x];
+ char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
if (c != ' ') return;
}
}
@@ -663,23 +727,23 @@ void TetrominoSpin(Direction spinDirection)
TETROMINO.Shape = newShape;
}
- void SpinLeft(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+ void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = TETROMINO.Shape[y].Length - 1; x >= 0; x -= 3)
+ for (int x = shape[y].Length - 1; x >= 0; x -= 3)
{
for (int xS = 2; xS >= 0; xS--)
{
- newShape[newY] += TETROMINO.Shape[rowEven][x - xS];
- newShape[newY + 1] += TETROMINO.Shape[rowOdd][x - xS];
+ newShape[newY] += shape[rowEven][x - xS];
+ newShape[newY + 1] += shape[rowOdd][x - xS];
}
newY += 2;
}
}
- void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
+ void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = 2; x < TETROMINO.Shape[y].Length; x += 3)
+ for (int x = 2; x < shape[y].Length; x += 3)
{
if (newShape[newY] == null)
{
@@ -689,8 +753,8 @@ void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
for (int xS = 0; xS <= 2; xS++)
{
- newShape[newY] = newShape[newY].Insert(0, TETROMINO.Shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, TETROMINO.Shape[rowOdd][x - xS].ToString());
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
}
newY += 2;
@@ -699,13 +763,14 @@ void SpinRight(string[] newShape, ref int newY, int rowEven, int rowOdd, int y)
async Task SleepAfterRender()
{
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 120d) - Stopwatch.Elapsed;
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
if (sleep > TimeSpan.Zero)
{
await Console.RefreshAndDelay(sleep);
}
Stopwatch.Restart();
}
+
}
class Tetromino
diff --git a/README.md b/README.md
index 4f6ddc4a..ea5d9322 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@
|[Role Playing Game](Projects/Role%20Playing%20Game)|6|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Role%20Playing%20Game) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Role%20Playing%20Game%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
|[Console Monsters](Projects/Console%20Monsters)|7|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Console%20Monsters) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Console%20Monsters%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_Community Collaboration_
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_|
|[Shmup](Projects/Shmup)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Shmup) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Shmup%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_ & _Only Supported On Windows OS (+WEB)_|
-|[Tetris](Projects/Tetris)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Tetris) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Tetris%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_[Community Contribution]()_|
+|[Tetris](Projects/Tetris)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Tetris) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Tetris%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/dotnet/dotnet-console-games/pull/89)_|
\*_**Weight**: A relative rating for how advanced the source code is._
From a5ce87f234c51f897c8d2cb17d4f87a4e509306d Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 12:29:32 -0500
Subject: [PATCH 03/24] slnf alphabetical order
---
dotnet-console-games.slnf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dotnet-console-games.slnf b/dotnet-console-games.slnf
index 57683bef..5b642937 100644
--- a/dotnet-console-games.slnf
+++ b/dotnet-console-games.slnf
@@ -42,6 +42,7 @@
"Projects\\Snake\\Snake.csproj",
"Projects\\Sudoku\\Sudoku.csproj",
"Projects\\Tanks\\Tanks.csproj",
+ "Projects\\Tetris\\Tetris.csproj"
"Projects\\Tic Tac Toe\\Tic Tac Toe.csproj",
"Projects\\Tents\\Tents.csproj",
"Projects\\Tower Of Hanoi\\Tower Of Hanoi.csproj",
@@ -51,7 +52,6 @@
"Projects\\Wordle\\Wordle.csproj",
"Projects\\Wumpus World\\Wumpus World.csproj",
"Projects\\Yahtzee\\Yahtzee.csproj",
- "Projects\\Tetris\\Tetris.csproj"
]
}
}
\ No newline at end of file
From 6895b51c52ec4e3e045552d4b0f01d1daaad9e49 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 12:34:15 -0500
Subject: [PATCH 04/24] format documents
---
Projects/Tetris/Program.cs | 1356 ++++++++++----------
Projects/Tetris/Tetris.csproj | 14 +-
Projects/Website/Games/Tetris/Tetris.cs | 1570 +++++++++++------------
3 files changed, 1469 insertions(+), 1471 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 151efe53..b052e062 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -9,127 +9,127 @@
string[] FIELD = new[]
{
- "╭──────────────────────────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰──────────────────────────────╯"
+ "╭──────────────────────────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰──────────────────────────────╯"
};
string[] NEXTTETROMINO = new[]
{
- "╭─────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰─────────╯"
+ "╭─────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰─────────╯"
};
string[] SCORE = new[]{
- "╭─────────╮",
- "│ │",
- "╰─────────╯"
+ "╭─────────╮",
+ "│ │",
+ "╰─────────╯"
};
string[] PAUSE = new[]{
- "█████╗ ███╗ ██╗██╗█████╗█████╗",
- "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
- "█████║█████║██║██║ ███╗ █████╗",
- "██╔══╝██╔██║██║██║ ██╗██╔══╝",
- "██║ ██║██║█████║█████║█████╗",
- "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
+ "█████╗ ███╗ ██╗██╗█████╗█████╗",
+ "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
+ "█████║█████║██║██║ ███╗ █████╗",
+ "██╔══╝██╔██║██║██║ ██╗██╔══╝",
+ "██║ ██║██║█████║█████║█████╗",
+ "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
};
string[][] TETROMINOS = new[]
{
- new[]{
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯"
- },
- new[]{
- "╭─╮ ",
- "╰─╯ ",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- " ╭─╮",
- " ╰─╯",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- "╭─╮╭─╮",
- "╰─╯╰─╯",
- "╭─╮╭─╮",
- "╰─╯╰─╯"
- },
- new[]{
- " ╭─╮╭─╮",
- " ╰─╯╰─╯",
- "╭─╮╭─╮ ",
- "╰─╯╰─╯ "
- },
- new[]{
- " ╭─╮ ",
- " ╰─╯ ",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- "╭─╮╭─╮ ",
- "╰─╯╰─╯ ",
- " ╭─╮╭─╮",
- " ╰─╯╰─╯"
- },
+ new[]{
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯"
+ },
+ new[]{
+ "╭─╮ ",
+ "╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮",
+ " ╰─╯",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮",
+ "╰─╯╰─╯",
+ "╭─╮╭─╮",
+ "╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯",
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ "
+ },
+ new[]{
+ " ╭─╮ ",
+ " ╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ ",
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯"
+ },
};
string[] PLAYFIELD = (string[])FIELD.Clone();
@@ -147,10 +147,10 @@
int INITIALTETROMINOY = 1;
Tetromino TETROMINO = new()
{
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
};
AutoResetEvent AutoEvent = new AutoResetEvent(false);
@@ -184,648 +184,648 @@
while (!CloseGame)
{
- if (CloseGame)
- {
- break;
- }
-
- PlayerControl();
- if (GameStatus == GameStatus.Playing)
- {
- DrawFrame();
- SleepAfterRender();
- }
+ if (CloseGame)
+ {
+ break;
+ }
+
+ PlayerControl();
+ if (GameStatus == GameStatus.Playing)
+ {
+ DrawFrame();
+ SleepAfterRender();
+ }
}
void PlayerControl()
{
- while (Console.KeyAvailable && GameStatus == GameStatus.Playing)
- {
- switch (Console.ReadKey(true).Key)
- {
- case ConsoleKey.A or ConsoleKey.LeftArrow:
- if (Collision(Direction.Left)) break;
- TETROMINO.X -= 3;
- break;
- case ConsoleKey.D or ConsoleKey.RightArrow:
- if (Collision(Direction.Right)) break;
- TETROMINO.X += 3;
- break;
- case ConsoleKey.S or ConsoleKey.DownArrow:
- FallTimer.Change(0, FallSpeedMilliSeconds);
- break;
- case ConsoleKey.E:
- TetrominoSpin(Direction.Right);
- break;
- case ConsoleKey.Q:
- TetrominoSpin(Direction.Left);
- break;
- case ConsoleKey.P:
- PauseGame();
- break;
- case ConsoleKey.R:
- TextColor++;
- if (TextColor == 16) TextColor = 1;
- Console.ForegroundColor = (ConsoleColor)TextColor;
- break;
-
- //DEBUG
- case ConsoleKey.Spacebar:
- if (!DEBUGCONTROLS) return;
- PLAYFIELD = (string[])FIELD.Clone();
- break;
- case ConsoleKey.I:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[0];
- break;
- case ConsoleKey.J:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[1];
- break;
- case ConsoleKey.L:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[2];
- break;
- case ConsoleKey.O:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[3];
- break;
- case ConsoleKey.C:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[4];
- break;
- case ConsoleKey.T:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[5];
- break;
- case ConsoleKey.Z:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[6];
- break;
- case ConsoleKey.X:
- if (!DEBUGCONTROLS) return;
- Score += 10;
- break;
- }
- }
+ while (Console.KeyAvailable && GameStatus == GameStatus.Playing)
+ {
+ switch (Console.ReadKey(true).Key)
+ {
+ case ConsoleKey.A or ConsoleKey.LeftArrow:
+ if (Collision(Direction.Left)) break;
+ TETROMINO.X -= 3;
+ break;
+ case ConsoleKey.D or ConsoleKey.RightArrow:
+ if (Collision(Direction.Right)) break;
+ TETROMINO.X += 3;
+ break;
+ case ConsoleKey.S or ConsoleKey.DownArrow:
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ break;
+ case ConsoleKey.E:
+ TetrominoSpin(Direction.Right);
+ break;
+ case ConsoleKey.Q:
+ TetrominoSpin(Direction.Left);
+ break;
+ case ConsoleKey.P:
+ PauseGame();
+ break;
+ case ConsoleKey.R:
+ TextColor++;
+ if (TextColor == 16) TextColor = 1;
+ Console.ForegroundColor = (ConsoleColor)TextColor;
+ break;
+
+ //DEBUG
+ case ConsoleKey.Spacebar:
+ if (!DEBUGCONTROLS) return;
+ PLAYFIELD = (string[])FIELD.Clone();
+ break;
+ case ConsoleKey.I:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[0];
+ break;
+ case ConsoleKey.J:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[1];
+ break;
+ case ConsoleKey.L:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[2];
+ break;
+ case ConsoleKey.O:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[3];
+ break;
+ case ConsoleKey.C:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[4];
+ break;
+ case ConsoleKey.T:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[5];
+ break;
+ case ConsoleKey.Z:
+ if (!DEBUGCONTROLS) return;
+ TETROMINO.Shape = TETROMINOS[6];
+ break;
+ case ConsoleKey.X:
+ if (!DEBUGCONTROLS) return;
+ Score += 10;
+ break;
+ }
+ }
}
void DrawFrame()
{
- bool collision = false;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
- {
- frame[y] = PLAYFIELD[y].ToCharArray();
- }
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = charTetromino;
- }
- }
-
- //Draw Preview
- for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
- {
- if (CollisionPreview(yField, yScope, shapeScope)) continue;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yField + y;
-
- if (yScope + shapeScope.Length > tY) continue;
-
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = '•';
- }
- }
-
- break;
- }
-
- //Next Square
- for (int y = 0; y < NEXTTETROMINO.Length; y++)
- {
- frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
- }
-
- //Score Square
- for (int y = 0; y < SCORE.Length; y++)
- {
- int sY = NEXTTETROMINO.Length + y;
- frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
- }
-
- //Draw Next
- for (int y = 0; y < nextShapeScope.Length; y++)
- {
- for (int x = 0; x < nextShapeScope[y].Length; x++)
- {
- int tY = y + BORDER;
- int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = nextShapeScope[y][x];
- frame[tY][tX] = charTetromino;
- }
- }
-
- //Draw Score
- char[] score = Score.ToString().ToCharArray();
- for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
- {
- int sY = NEXTTETROMINO.Length + BORDER;
- int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
- frame[sY][sX] = score[scoreX];
- }
-
- //Draw Pause
- if (GameStatus == GameStatus.Paused)
- {
- for (int y = 0; y < PAUSE.Length; y++)
- {
- int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
- for (int x = 0; x < PAUSE[y].Length; x++)
- {
- int fX = x + BORDER;
-
- if (x >= PLAYFIELD[fY].Length) break;
-
- frame[fY][fX] = PAUSE[y][x];
- }
- }
- }
-
- //Create Render
- StringBuilder render = new();
- for (int y = 0; y < frame.Length; y++)
- {
- render.AppendLine(new string(frame[y]));
- }
-
- Console.Clear();
- Console.Write(render);
- Console.CursorVisible = false;
+ bool collision = false;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Preview
+ for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ {
+ if (CollisionPreview(yField, yScope, shapeScope)) continue;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yField + y;
+
+ if (yScope + shapeScope.Length > tY) continue;
+
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = '•';
+ }
+ }
+
+ break;
+ }
+
+ //Next Square
+ for (int y = 0; y < NEXTTETROMINO.Length; y++)
+ {
+ frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
+ }
+
+ //Score Square
+ for (int y = 0; y < SCORE.Length; y++)
+ {
+ int sY = NEXTTETROMINO.Length + y;
+ frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
+ }
+
+ //Draw Next
+ for (int y = 0; y < nextShapeScope.Length; y++)
+ {
+ for (int x = 0; x < nextShapeScope[y].Length; x++)
+ {
+ int tY = y + BORDER;
+ int tX = PLAYFIELD[y].Length + x + BORDER;
+ char charTetromino = nextShapeScope[y][x];
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Score
+ char[] score = Score.ToString().ToCharArray();
+ for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ {
+ int sY = NEXTTETROMINO.Length + BORDER;
+ int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
+ frame[sY][sX] = score[scoreX];
+ }
+
+ //Draw Pause
+ if (GameStatus == GameStatus.Paused)
+ {
+ for (int y = 0; y < PAUSE.Length; y++)
+ {
+ int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
+ for (int x = 0; x < PAUSE[y].Length; x++)
+ {
+ int fX = x + BORDER;
+
+ if (x >= PLAYFIELD[fY].Length) break;
+
+ frame[fY][fX] = PAUSE[y][x];
+ }
+ }
+ }
+
+ //Create Render
+ StringBuilder render = new();
+ for (int y = 0; y < frame.Length; y++)
+ {
+ render.AppendLine(new string(frame[y]));
+ }
+
+ Console.Clear();
+ Console.Write(render);
+ Console.CursorVisible = false;
}
char[][] DrawLastFrame(int yS)
{
- bool collision = false;
- int yScope = yS - 2;
- int xScope = TETROMINO.X;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
- {
- frame[y] = PLAYFIELD[y].ToCharArray();
- }
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = xScope + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = charTetromino;
- }
- }
-
- return frame;
+ bool collision = false;
+ int yScope = yS - 2;
+ int xScope = TETROMINO.X;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xScope + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ return frame;
}
bool Collision(Direction direction)
{
- int xNew = TETROMINO.X;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- bool collision = false;
-
- switch (direction)
- {
- case Direction.Right:
- xNew += 3;
- if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
- break;
- case Direction.Left:
- xNew -= 3;
- if (xNew < BORDER) collision = true;
- break;
- case Direction.None:
- break;
- }
-
- if (collision) return collision;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
- }
- }
-
- return collision;
+ int xNew = TETROMINO.X;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ bool collision = false;
+
+ switch (direction)
+ {
+ case Direction.Right:
+ xNew += 3;
+ if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ break;
+ case Direction.Left:
+ xNew -= 3;
+ if (xNew < BORDER) collision = true;
+ break;
+ case Direction.None:
+ break;
+ }
+
+ if (collision) return collision;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ return collision;
}
bool CollisionPreview(int initY, int yScope, string[] shape)
{
- int xNew = TETROMINO.X;
-
- for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
- {
- for (int y = shape.Length - 1; y >= 0; y -= 2)
- {
- for (int x = 0; x < shape[y].Length; x++)
- {
- int tY = yUpper + y;
- int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shape[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- return true;
- }
- }
- }
- }
-
- return false;
+ int xNew = TETROMINO.X;
+
+ for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
+ {
+ for (int y = shape.Length - 1; y >= 0; y -= 2)
+ {
+ for (int x = 0; x < shape[y].Length; x++)
+ {
+ int tY = yUpper + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
}
void Gameover()
{
- GameStatus = GameStatus.Gameover;
- AutoEvent.Dispose();
- FallTimer.Dispose();
-
- SleepAfterRender();
-
- Console.Clear();
- Console.WriteLine();
- Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
- Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
- Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
- Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
- Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
- Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
- Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
- Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
- Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
- Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
- Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
- Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
-
- Console.WriteLine();
- Console.WriteLine($" Final Score: {Score}");
- Console.WriteLine($" Pause Count: {PauseCount}");
- Console.WriteLine();
- Console.WriteLine(" Press enter to play again");
- Console.WriteLine(" Press escape to close the game");
- Console.CursorVisible = false;
- StartGame();
- RestartGame();
+ GameStatus = GameStatus.Gameover;
+ AutoEvent.Dispose();
+ FallTimer.Dispose();
+
+ SleepAfterRender();
+
+ Console.Clear();
+ Console.WriteLine();
+ Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
+ Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
+ Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
+ Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
+ Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
+ Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
+ Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
+ Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
+ Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
+ Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
+ Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
+ Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
+
+ Console.WriteLine();
+ Console.WriteLine($" Final Score: {Score}");
+ Console.WriteLine($" Pause Count: {PauseCount}");
+ Console.WriteLine();
+ Console.WriteLine(" Press enter to play again");
+ Console.WriteLine(" Press escape to close the game");
+ Console.CursorVisible = false;
+ StartGame();
+ RestartGame();
}
void StartGame(ConsoleKey key = ConsoleKey.Enter)
{
- ConsoleKey input = default;
- while (input != key && !CloseGame)
- {
- input = Console.ReadKey(true).Key;
- if (input is ConsoleKey.Escape)
- {
- CloseGame = true;
- return;
- }
- }
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = Console.ReadKey(true).Key;
+ if (input is ConsoleKey.Escape)
+ {
+ CloseGame = true;
+ return;
+ }
+ }
}
void RestartGame()
{
- PLAYFIELD = (string[])FIELD.Clone();
- FallSpeedMilliSeconds = 1000;
- Score = 0;
- TETROMINO = new()
- {
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
-
- AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
+ PLAYFIELD = (string[])FIELD.Clone();
+ FallSpeedMilliSeconds = 1000;
+ Score = 0;
+ TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ AutoEvent = new AutoResetEvent(false);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
}
void PauseGame()
{
- PauseCount++;
- FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
- GameStatus = GameStatus.Paused;
- DrawFrame();
+ PauseCount++;
+ FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ GameStatus = GameStatus.Paused;
+ DrawFrame();
- ResumeGame();
+ ResumeGame();
}
void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
{
- ConsoleKey input = default;
- while (input != key && !CloseGame)
- {
- input = Console.ReadKey(true).Key;
- if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
- {
- FallTimer.Change(0, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- return;
- }
- }
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = Console.ReadKey(true).Key;
+ if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
+ {
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ return;
+ }
+ }
}
void AddScoreChangeSpeed(int value)
{
- Score += value;
-
- if (Score > 100) return;
-
- switch (Score)
- {
- case 10:
- FallSpeedMilliSeconds = 900;
- break;
- case 20:
- FallSpeedMilliSeconds = 800;
- break;
- case 30:
- FallSpeedMilliSeconds = 700;
- break;
- case 40:
- FallSpeedMilliSeconds = 500;
- break;
- case 50:
- FallSpeedMilliSeconds = 300;
- break;
- case 60:
- FallSpeedMilliSeconds = 200;
- break;
- case 70:
- FallSpeedMilliSeconds = 100;
- break;
- case 100:
- FallSpeedMilliSeconds = 50;
- break;
- }
+ Score += value;
+
+ if (Score > 100) return;
+
+ switch (Score)
+ {
+ case 10:
+ FallSpeedMilliSeconds = 900;
+ break;
+ case 20:
+ FallSpeedMilliSeconds = 800;
+ break;
+ case 30:
+ FallSpeedMilliSeconds = 700;
+ break;
+ case 40:
+ FallSpeedMilliSeconds = 500;
+ break;
+ case 50:
+ FallSpeedMilliSeconds = 300;
+ break;
+ case 60:
+ FallSpeedMilliSeconds = 200;
+ break;
+ case 70:
+ FallSpeedMilliSeconds = 100;
+ break;
+ case 100:
+ FallSpeedMilliSeconds = 50;
+ break;
+ }
}
void TetrominoFall(object? e)
{
- int yAfterFall = TETROMINO.Y;
- bool collision = false;
-
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else yAfterFall += 2;
-
- //Y Collision
- for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
- {
- for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
- {
- char exist = TETROMINO.Shape[yCollision][xCollision];
-
- if (exist == ' ') continue;
-
- char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
-
- if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
-
- if
- (
- lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│'
- )
- {
- char[][] lastFrame = DrawLastFrame(yAfterFall);
- for (int y = 0; y < lastFrame.Length; y++)
- {
- PLAYFIELD[y] = new string(lastFrame[y]);
- }
-
- TETROMINO.X = INITIALTETROMINOX;
- TETROMINO.Y = INITIALTETROMINOY;
- TETROMINO.Shape = TETROMINO.Next;
- TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
-
- xCollision = TETROMINO.Shape[0].Length;
- collision = true;
- break;
- }
- }
-
- xCollision += 3;
- }
-
- if (!collision) TETROMINO.Y = yAfterFall;
-
- //Clean Lines
- for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
- {
- string line = PLAYFIELD[lineIndex];
- bool notCompleted = line.Any(e => e == ' ');
-
- if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
-
- if (!notCompleted)
- {
- PLAYFIELD[lineIndex] = "│ │";
- AddScoreChangeSpeed(1);
-
- for (int lineM = lineIndex; lineM >= 1; lineM--)
- {
- if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
- {
- PLAYFIELD[lineM] = "│ │";
- continue;
- }
-
- PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
- }
-
- lineIndex++;
- }
- }
-
- //VerifiedCollision
- if (Collision(Direction.None) && FallTimer != null) Gameover();
+ int yAfterFall = TETROMINO.Y;
+ bool collision = false;
+
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else yAfterFall += 2;
+
+ //Y Collision
+ for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ {
+ for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ {
+ char exist = TETROMINO.Shape[yCollision][xCollision];
+
+ if (exist == ' ') continue;
+
+ char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
+
+ if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
+
+ if
+ (
+ lineYC[TETROMINO.X + xCollision] != ' ' &&
+ lineYC[TETROMINO.X + xCollision] != '│'
+ )
+ {
+ char[][] lastFrame = DrawLastFrame(yAfterFall);
+ for (int y = 0; y < lastFrame.Length; y++)
+ {
+ PLAYFIELD[y] = new string(lastFrame[y]);
+ }
+
+ TETROMINO.X = INITIALTETROMINOX;
+ TETROMINO.Y = INITIALTETROMINOY;
+ TETROMINO.Shape = TETROMINO.Next;
+ TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
+
+ xCollision = TETROMINO.Shape[0].Length;
+ collision = true;
+ break;
+ }
+ }
+
+ xCollision += 3;
+ }
+
+ if (!collision) TETROMINO.Y = yAfterFall;
+
+ //Clean Lines
+ for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ {
+ string line = PLAYFIELD[lineIndex];
+ bool notCompleted = line.Any(e => e == ' ');
+
+ if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+
+ if (!notCompleted)
+ {
+ PLAYFIELD[lineIndex] = "│ │";
+ AddScoreChangeSpeed(1);
+
+ for (int lineM = lineIndex; lineM >= 1; lineM--)
+ {
+ if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ {
+ PLAYFIELD[lineM] = "│ │";
+ continue;
+ }
+
+ PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ }
+
+ lineIndex++;
+ }
+ }
+
+ //VerifiedCollision
+ if (Collision(Direction.None) && FallTimer != null) Gameover();
}
void TetrominoSpin(Direction spinDirection)
{
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- int yScope = TETROMINO.Y;
- string[] newShape = new string[shapeScope[0].Length / 3 * 2];
- int newY = 0;
- int rowEven = 0;
- int rowOdd = 1;
-
- //Turn
- for (int y = 0; y < shapeScope.Length;)
- {
- switch (spinDirection)
- {
- case Direction.Right:
- SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
- break;
- case Direction.Left:
- SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
- break;
- }
-
- newY = 0;
- rowEven += 2;
- rowOdd += 2;
- y += 2;
- }
-
- //Verified Collision
- for (int y = 0; y < newShape.Length - 1; y++)
- {
- for (int x = 0; x < newShape[y].Length; x++)
- {
- if (newShape[y][x] == ' ') continue;
-
- char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
- if (c != ' ') return;
- }
- }
-
- TETROMINO.Shape = newShape;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int yScope = TETROMINO.Y;
+ string[] newShape = new string[shapeScope[0].Length / 3 * 2];
+ int newY = 0;
+ int rowEven = 0;
+ int rowOdd = 1;
+
+ //Turn
+ for (int y = 0; y < shapeScope.Length;)
+ {
+ switch (spinDirection)
+ {
+ case Direction.Right:
+ SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ break;
+ case Direction.Left:
+ SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ break;
+ }
+
+ newY = 0;
+ rowEven += 2;
+ rowOdd += 2;
+ y += 2;
+ }
+
+ //Verified Collision
+ for (int y = 0; y < newShape.Length - 1; y++)
+ {
+ for (int x = 0; x < newShape[y].Length; x++)
+ {
+ if (newShape[y][x] == ' ') continue;
+
+ char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
+ if (c != ' ') return;
+ }
+ }
+
+ TETROMINO.Shape = newShape;
}
void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = shape[y].Length - 1; x >= 0; x -= 3)
- {
- for (int xS = 2; xS >= 0; xS--)
- {
- newShape[newY] += shape[rowEven][x - xS];
- newShape[newY + 1] += shape[rowOdd][x - xS];
- }
-
- newY += 2;
- }
+ for (int x = shape[y].Length - 1; x >= 0; x -= 3)
+ {
+ for (int xS = 2; xS >= 0; xS--)
+ {
+ newShape[newY] += shape[rowEven][x - xS];
+ newShape[newY + 1] += shape[rowOdd][x - xS];
+ }
+
+ newY += 2;
+ }
}
void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
{
- for (int x = 2; x < shape[y].Length; x += 3)
- {
- if (newShape[newY] == null)
- {
- newShape[newY] = "";
- newShape[newY + 1] = "";
- }
-
- for (int xS = 0; xS <= 2; xS++)
- {
- newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
- }
-
- newY += 2;
- }
+ for (int x = 2; x < shape[y].Length; x += 3)
+ {
+ if (newShape[newY] == null)
+ {
+ newShape[newY] = "";
+ newShape[newY + 1] = "";
+ }
+
+ for (int xS = 0; xS <= 2; xS++)
+ {
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
+ }
+
+ newY += 2;
+ }
}
void SleepAfterRender()
{
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
- if (sleep > TimeSpan.Zero)
- {
- Thread.Sleep(sleep);
- }
- Stopwatch.Restart();
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
+ if (sleep > TimeSpan.Zero)
+ {
+ Thread.Sleep(sleep);
+ }
+ Stopwatch.Restart();
}
class Tetromino
{
- public required string[] Shape { get; set; }
- public required string[] Next { get; set; }
- public int X { get; set; }
- public int Y { get; set; }
+ public required string[] Shape { get; set; }
+ public required string[] Next { get; set; }
+ public int X { get; set; }
+ public int Y { get; set; }
}
enum Direction
{
- Right,
- Left,
- None
+ Right,
+ Left,
+ None
}
enum GameStatus
{
- Gameover,
- Playing,
- Paused
+ Gameover,
+ Playing,
+ Paused
}
\ No newline at end of file
diff --git a/Projects/Tetris/Tetris.csproj b/Projects/Tetris/Tetris.csproj
index f02677bf..1a40ec66 100644
--- a/Projects/Tetris/Tetris.csproj
+++ b/Projects/Tetris/Tetris.csproj
@@ -1,10 +1,8 @@
-
-
- Exe
- net7.0
- enable
- enable
-
-
+
+ Exe
+ net7.0
+ enable
+ enable
+
diff --git a/Projects/Website/Games/Tetris/Tetris.cs b/Projects/Website/Games/Tetris/Tetris.cs
index a973393e..00563e63 100644
--- a/Projects/Website/Games/Tetris/Tetris.cs
+++ b/Projects/Website/Games/Tetris/Tetris.cs
@@ -9,790 +9,790 @@ namespace Website.Games.Tetris;
public class Tetris
{
- public readonly BlazorConsole Console = new();
-
- public async Task Run()
- {
- Console.OutputEncoding = Encoding.UTF8;
- Console.CursorVisible = false;
- Stopwatch Stopwatch = Stopwatch.StartNew();
-
- string[] FIELD = new[]
- {
- "╭──────────────────────────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰──────────────────────────────╯"
- };
-
- string[] NEXTTETROMINO = new[]
- {
- "╭─────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰─────────╯"
- };
-
- string[] SCORE = new[]{
- "╭─────────╮",
- "│ │",
- "╰─────────╯"
- };
-
- string[] PAUSE = new[]{
- "█████╗ ███╗ ██╗██╗█████╗█████╗",
- "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
- "█████║█████║██║██║ ███╗ █████╗",
- "██╔══╝██╔██║██║██║ ██╗██╔══╝",
- "██║ ██║██║█████║█████║█████╗",
- "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
- };
-
- string[][] TETROMINOS = new[]
- {
- new[]{
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯",
- "╭─╮",
- "╰─╯"
- },
- new[]{
- "╭─╮ ",
- "╰─╯ ",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- " ╭─╮",
- " ╰─╯",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- "╭─╮╭─╮",
- "╰─╯╰─╯",
- "╭─╮╭─╮",
- "╰─╯╰─╯"
- },
- new[]{
- " ╭─╮╭─╮",
- " ╰─╯╰─╯",
- "╭─╮╭─╮ ",
- "╰─╯╰─╯ "
- },
- new[]{
- " ╭─╮ ",
- " ╰─╯ ",
- "╭─╮╭─╮╭─╮",
- "╰─╯╰─╯╰─╯"
- },
- new[]{
- "╭─╮╭─╮ ",
- "╰─╯╰─╯ ",
- " ╭─╮╭─╮",
- " ╰─╯╰─╯"
- },
- };
-
- string[] PLAYFIELD = (string[])FIELD.Clone();
- const int BORDER = 1;
- int FallSpeedMilliSeconds = 1000;
- bool CloseGame = false;
- int Score = 0;
- int PauseCount = 0;
- GameStatus GameStatus = GameStatus.Gameover;
-
- Random RamdomGenerator = new();
-
- int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
- int INITIALTETROMINOY = 1;
- Tetromino TETROMINO = new()
- {
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
-
- AutoResetEvent AutoEvent = new AutoResetEvent(false);
- Timer? FallTimer = null;
- GameStatus = GameStatus.Playing;
-
- await Console.WriteLine();
- await Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
- await Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
- await Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
- await Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
- await Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
- await Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
-
- await Console.WriteLine();
- await Console.WriteLine(" Controls:");
- await Console.WriteLine(" WASD or ARROW to move");
- await Console.WriteLine(" Q or E to spin left or right");
- await Console.WriteLine(" P to paused the game, press enter");
- await Console.WriteLine(" key to resume");
- await Console.WriteLine();
- await Console.Write(" Press enter to start tetris...");
- Console.CursorVisible = false;
- await StartGame();
- await Console.Clear();
-
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
-
- while (!CloseGame)
- {
- if (CloseGame)
- {
- break;
- }
-
- await PlayerControl();
- if (GameStatus == GameStatus.Playing)
- {
- await DrawFrame();
- await SleepAfterRender();
- }
- }
-
- async Task PlayerControl()
- {
- while (await Console.KeyAvailable() && GameStatus == GameStatus.Playing)
- {
- switch ((await Console.ReadKey(true)).Key)
- {
- case ConsoleKey.A or ConsoleKey.LeftArrow:
- if (Collision(Direction.Left)) break;
- TETROMINO.X -= 3;
- break;
- case ConsoleKey.D or ConsoleKey.RightArrow:
- if (Collision(Direction.Right)) break;
- TETROMINO.X += 3;
- break;
- case ConsoleKey.S or ConsoleKey.DownArrow:
- FallTimer.Change(0, FallSpeedMilliSeconds);
- break;
- case ConsoleKey.E:
- TetrominoSpin(Direction.Right);
- break;
- case ConsoleKey.Q:
- TetrominoSpin(Direction.Left);
- break;
- case ConsoleKey.P:
- PauseGame();
- break;
- }
- }
- }
-
- async Task DrawFrame()
- {
- bool collision = false;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
- {
- frame[y] = PLAYFIELD[y].ToCharArray();
- }
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = charTetromino;
- }
- }
-
- //Draw Preview
- for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
- {
- if (CollisionPreview(yField, yScope, shapeScope)) continue;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yField + y;
-
- if (yScope + shapeScope.Length > tY) continue;
-
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = '•';
- }
- }
-
- break;
- }
-
- //Next Square
- for (int y = 0; y < NEXTTETROMINO.Length; y++)
- {
- frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
- }
-
- //Score Square
- for (int y = 0; y < SCORE.Length; y++)
- {
- int sY = NEXTTETROMINO.Length + y;
- frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
- }
-
- //Draw Next
- for (int y = 0; y < nextShapeScope.Length; y++)
- {
- for (int x = 0; x < nextShapeScope[y].Length; x++)
- {
- int tY = y + BORDER;
- int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = nextShapeScope[y][x];
- frame[tY][tX] = charTetromino;
- }
- }
-
- //Draw Score
- char[] score = Score.ToString().ToCharArray();
- for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
- {
- int sY = NEXTTETROMINO.Length + BORDER;
- int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
- frame[sY][sX] = score[scoreX];
- }
-
- //Draw Pause
- if (GameStatus == GameStatus.Paused)
- {
- for (int y = 0; y < PAUSE.Length; y++)
- {
- int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
- for (int x = 0; x < PAUSE[y].Length; x++)
- {
- int fX = x + BORDER;
-
- if (x >= PLAYFIELD[fY].Length) break;
-
- frame[fY][fX] = PAUSE[y][x];
- }
- }
- }
-
- //Create Render
- StringBuilder render = new();
- for (int y = 0; y < frame.Length; y++)
- {
- render.AppendLine(new string(frame[y]));
- }
-
- await Console.Clear();
- await Console.Write(render);
- Console.CursorVisible = false;
- }
-
- char[][] DrawLastFrame(int yS)
- {
- bool collision = false;
- int yScope = yS - 2;
- int xScope = TETROMINO.X;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
- {
- frame[y] = PLAYFIELD[y].ToCharArray();
- }
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = xScope + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
-
- frame[tY][tX] = charTetromino;
- }
- }
-
- return frame;
- }
-
- bool Collision(Direction direction)
- {
- int xNew = TETROMINO.X;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- bool collision = false;
-
- switch (direction)
- {
- case Direction.Right:
- xNew += 3;
- if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
- break;
- case Direction.Left:
- xNew -= 3;
- if (xNew < BORDER) collision = true;
- break;
- case Direction.None:
- break;
- }
-
- if (collision) return collision;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
- {
- for (int x = 0; x < shapeScope[y].Length; x++)
- {
- int tY = yScope + y;
- int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- collision = true;
- break;
- }
- }
- }
-
- return collision;
- }
-
- bool CollisionPreview(int initY, int yScope, string[] shape)
- {
- int xNew = TETROMINO.X;
-
- for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
- {
- for (int y = shape.Length - 1; y >= 0; y -= 2)
- {
- for (int x = 0; x < shape[y].Length; x++)
- {
- int tY = yUpper + y;
- int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shape[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- async void Gameover()
- {
- GameStatus = GameStatus.Gameover;
- AutoEvent.Dispose();
- FallTimer.Dispose();
-
- await SleepAfterRender();
-
- await Console.Clear();
- await Console.WriteLine();
- await Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
- await Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
- await Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
- await Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
- await Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
- await Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
- await Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
- await Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
- await Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
- await Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
- await Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
- await Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
-
- await Console.WriteLine();
- await Console.WriteLine($" Final Score: {Score}");
- await Console.WriteLine($" Pause Count: {PauseCount}");
- await Console.WriteLine();
- await Console.WriteLine(" Press enter to play again");
- await Console.WriteLine(" Press escape to close the game");
- Console.CursorVisible = false;
- await StartGame();
- RestartGame();
- }
-
- async Task StartGame(ConsoleKey key = ConsoleKey.Enter)
- {
- ConsoleKey input = default;
- while (input != key && !CloseGame)
- {
- input = (await Console.ReadKey(true)).Key;
- if (input is ConsoleKey.Escape)
- {
- CloseGame = true;
- return;
- }
- }
- }
-
- void RestartGame()
- {
- PLAYFIELD = (string[])FIELD.Clone();
- FallSpeedMilliSeconds = 1000;
- Score = 0;
- TETROMINO = new()
- {
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
-
- AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- }
-
- async void PauseGame()
- {
- PauseCount++;
- FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
- GameStatus = GameStatus.Paused;
- await DrawFrame();
-
- await ResumeGame();
- }
-
- async Task ResumeGame(ConsoleKey key = ConsoleKey.Enter)
- {
- ConsoleKey input = default;
- while (input != key && !CloseGame)
- {
- input = (await Console.ReadKey(true)).Key;
- if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
- {
- FallTimer.Change(0, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- return;
- }
- }
- }
-
- void AddScoreChangeSpeed(int value)
- {
- Score += value;
-
- if (Score > 100) return;
-
- switch (Score)
- {
- case 10:
- FallSpeedMilliSeconds = 900;
- break;
- case 20:
- FallSpeedMilliSeconds = 800;
- break;
- case 30:
- FallSpeedMilliSeconds = 700;
- break;
- case 40:
- FallSpeedMilliSeconds = 500;
- break;
- case 50:
- FallSpeedMilliSeconds = 300;
- break;
- case 60:
- FallSpeedMilliSeconds = 200;
- break;
- case 70:
- FallSpeedMilliSeconds = 100;
- break;
- case 100:
- FallSpeedMilliSeconds = 50;
- break;
- }
- }
-
- void TetrominoFall(object? e)
- {
- int yAfterFall = TETROMINO.Y;
- bool collision = false;
-
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else yAfterFall += 2;
-
- //Y Collision
- for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
- {
- for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
- {
- char exist = TETROMINO.Shape[yCollision][xCollision];
-
- if (exist == ' ') continue;
-
- char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
-
- if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
-
- if
- (
- lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│'
- )
- {
- char[][] lastFrame = DrawLastFrame(yAfterFall);
- for (int y = 0; y < lastFrame.Length; y++)
- {
- PLAYFIELD[y] = new string(lastFrame[y]);
- }
-
- TETROMINO.X = INITIALTETROMINOX;
- TETROMINO.Y = INITIALTETROMINOY;
- TETROMINO.Shape = TETROMINO.Next;
- TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
-
- xCollision = TETROMINO.Shape[0].Length;
- collision = true;
- break;
- }
- }
-
- xCollision += 3;
- }
-
- if (!collision) TETROMINO.Y = yAfterFall;
-
- //Clean Lines
- for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
- {
- string line = PLAYFIELD[lineIndex];
- bool notCompleted = line.Any(e => e == ' ');
-
- if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
-
- if (!notCompleted)
- {
- PLAYFIELD[lineIndex] = "│ │";
- AddScoreChangeSpeed(1);
-
- for (int lineM = lineIndex; lineM >= 1; lineM--)
- {
- if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
- {
- PLAYFIELD[lineM] = "│ │";
- continue;
- }
-
- PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
- }
-
- lineIndex++;
- }
- }
-
- //VerifiedCollision
- if (Collision(Direction.None) && FallTimer != null) Gameover();
- }
-
- void TetrominoSpin(Direction spinDirection)
- {
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- int yScope = TETROMINO.Y;
- string[] newShape = new string[shapeScope[0].Length / 3 * 2];
- int newY = 0;
- int rowEven = 0;
- int rowOdd = 1;
-
- //Turn
- for (int y = 0; y < shapeScope.Length;)
- {
- switch (spinDirection)
- {
- case Direction.Right:
- SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
- break;
- case Direction.Left:
- SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
- break;
- }
-
- newY = 0;
- rowEven += 2;
- rowOdd += 2;
- y += 2;
- }
-
- //Verified Collision
- for (int y = 0; y < newShape.Length - 1; y++)
- {
- for (int x = 0; x < newShape[y].Length; x++)
- {
- if (newShape[y][x] == ' ') continue;
-
- char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
- if (c != ' ') return;
- }
- }
-
- TETROMINO.Shape = newShape;
- }
-
- void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
- {
- for (int x = shape[y].Length - 1; x >= 0; x -= 3)
- {
- for (int xS = 2; xS >= 0; xS--)
- {
- newShape[newY] += shape[rowEven][x - xS];
- newShape[newY + 1] += shape[rowOdd][x - xS];
- }
-
- newY += 2;
- }
- }
-
- void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
- {
- for (int x = 2; x < shape[y].Length; x += 3)
- {
- if (newShape[newY] == null)
- {
- newShape[newY] = "";
- newShape[newY + 1] = "";
- }
-
- for (int xS = 0; xS <= 2; xS++)
- {
- newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
- }
-
- newY += 2;
- }
- }
-
- async Task SleepAfterRender()
- {
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
- if (sleep > TimeSpan.Zero)
- {
- await Console.RefreshAndDelay(sleep);
- }
- Stopwatch.Restart();
- }
-
- }
-
- class Tetromino
- {
- public required string[] Shape { get; set; }
- public required string[] Next { get; set; }
- public int X { get; set; }
- public int Y { get; set; }
- }
-
- enum Direction
- {
- Right,
- Left,
- None
- }
-
- enum GameStatus
- {
- Gameover,
- Playing,
- Paused
- }
+ public readonly BlazorConsole Console = new();
+
+ public async Task Run()
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ Console.CursorVisible = false;
+ Stopwatch Stopwatch = Stopwatch.StartNew();
+
+ string[] FIELD = new[]
+ {
+ "╭──────────────────────────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰──────────────────────────────╯"
+ };
+
+ string[] NEXTTETROMINO = new[]
+ {
+ "╭─────────╮",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "╰─────────╯"
+ };
+
+ string[] SCORE = new[]{
+ "╭─────────╮",
+ "│ │",
+ "╰─────────╯"
+ };
+
+ string[] PAUSE = new[]{
+ "█████╗ ███╗ ██╗██╗█████╗█████╗",
+ "██╔██║██╔██╗██║██║██╔══╝██╔══╝",
+ "█████║█████║██║██║ ███╗ █████╗",
+ "██╔══╝██╔██║██║██║ ██╗██╔══╝",
+ "██║ ██║██║█████║█████║█████╗",
+ "╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
+ };
+
+ string[][] TETROMINOS = new[]
+ {
+ new[]{
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯",
+ "╭─╮",
+ "╰─╯"
+ },
+ new[]{
+ "╭─╮ ",
+ "╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮",
+ " ╰─╯",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮",
+ "╰─╯╰─╯",
+ "╭─╮╭─╮",
+ "╰─╯╰─╯"
+ },
+ new[]{
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯",
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ "
+ },
+ new[]{
+ " ╭─╮ ",
+ " ╰─╯ ",
+ "╭─╮╭─╮╭─╮",
+ "╰─╯╰─╯╰─╯"
+ },
+ new[]{
+ "╭─╮╭─╮ ",
+ "╰─╯╰─╯ ",
+ " ╭─╮╭─╮",
+ " ╰─╯╰─╯"
+ },
+ };
+
+ string[] PLAYFIELD = (string[])FIELD.Clone();
+ const int BORDER = 1;
+ int FallSpeedMilliSeconds = 1000;
+ bool CloseGame = false;
+ int Score = 0;
+ int PauseCount = 0;
+ GameStatus GameStatus = GameStatus.Gameover;
+
+ Random RamdomGenerator = new();
+
+ int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
+ int INITIALTETROMINOY = 1;
+ Tetromino TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ AutoResetEvent AutoEvent = new AutoResetEvent(false);
+ Timer? FallTimer = null;
+ GameStatus = GameStatus.Playing;
+
+ await Console.WriteLine();
+ await Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
+ await Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
+ await Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
+ await Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
+ await Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
+ await Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
+
+ await Console.WriteLine();
+ await Console.WriteLine(" Controls:");
+ await Console.WriteLine(" WASD or ARROW to move");
+ await Console.WriteLine(" Q or E to spin left or right");
+ await Console.WriteLine(" P to paused the game, press enter");
+ await Console.WriteLine(" key to resume");
+ await Console.WriteLine();
+ await Console.Write(" Press enter to start tetris...");
+ Console.CursorVisible = false;
+ await StartGame();
+ await Console.Clear();
+
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
+
+ while (!CloseGame)
+ {
+ if (CloseGame)
+ {
+ break;
+ }
+
+ await PlayerControl();
+ if (GameStatus == GameStatus.Playing)
+ {
+ await DrawFrame();
+ await SleepAfterRender();
+ }
+ }
+
+ async Task PlayerControl()
+ {
+ while (await Console.KeyAvailable() && GameStatus == GameStatus.Playing)
+ {
+ switch ((await Console.ReadKey(true)).Key)
+ {
+ case ConsoleKey.A or ConsoleKey.LeftArrow:
+ if (Collision(Direction.Left)) break;
+ TETROMINO.X -= 3;
+ break;
+ case ConsoleKey.D or ConsoleKey.RightArrow:
+ if (Collision(Direction.Right)) break;
+ TETROMINO.X += 3;
+ break;
+ case ConsoleKey.S or ConsoleKey.DownArrow:
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ break;
+ case ConsoleKey.E:
+ TetrominoSpin(Direction.Right);
+ break;
+ case ConsoleKey.Q:
+ TetrominoSpin(Direction.Left);
+ break;
+ case ConsoleKey.P:
+ PauseGame();
+ break;
+ }
+ }
+ }
+
+ async Task DrawFrame()
+ {
+ bool collision = false;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Preview
+ for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ {
+ if (CollisionPreview(yField, yScope, shapeScope)) continue;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yField + y;
+
+ if (yScope + shapeScope.Length > tY) continue;
+
+ int tX = TETROMINO.X + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = '•';
+ }
+ }
+
+ break;
+ }
+
+ //Next Square
+ for (int y = 0; y < NEXTTETROMINO.Length; y++)
+ {
+ frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
+ }
+
+ //Score Square
+ for (int y = 0; y < SCORE.Length; y++)
+ {
+ int sY = NEXTTETROMINO.Length + y;
+ frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
+ }
+
+ //Draw Next
+ for (int y = 0; y < nextShapeScope.Length; y++)
+ {
+ for (int x = 0; x < nextShapeScope[y].Length; x++)
+ {
+ int tY = y + BORDER;
+ int tX = PLAYFIELD[y].Length + x + BORDER;
+ char charTetromino = nextShapeScope[y][x];
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ //Draw Score
+ char[] score = Score.ToString().ToCharArray();
+ for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ {
+ int sY = NEXTTETROMINO.Length + BORDER;
+ int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
+ frame[sY][sX] = score[scoreX];
+ }
+
+ //Draw Pause
+ if (GameStatus == GameStatus.Paused)
+ {
+ for (int y = 0; y < PAUSE.Length; y++)
+ {
+ int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
+ for (int x = 0; x < PAUSE[y].Length; x++)
+ {
+ int fX = x + BORDER;
+
+ if (x >= PLAYFIELD[fY].Length) break;
+
+ frame[fY][fX] = PAUSE[y][x];
+ }
+ }
+ }
+
+ //Create Render
+ StringBuilder render = new();
+ for (int y = 0; y < frame.Length; y++)
+ {
+ render.AppendLine(new string(frame[y]));
+ }
+
+ await Console.Clear();
+ await Console.Write(render);
+ Console.CursorVisible = false;
+ }
+
+ char[][] DrawLastFrame(int yS)
+ {
+ bool collision = false;
+ int yScope = yS - 2;
+ int xScope = TETROMINO.X;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ char[][] frame = new char[PLAYFIELD.Length][];
+
+ //Field
+ for (int y = 0; y < PLAYFIELD.Length; y++)
+ {
+ frame[y] = PLAYFIELD[y].ToCharArray();
+ }
+
+ //Draw Tetromino
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xScope + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+
+ frame[tY][tX] = charTetromino;
+ }
+ }
+
+ return frame;
+ }
+
+ bool Collision(Direction direction)
+ {
+ int xNew = TETROMINO.X;
+ int yScope = TETROMINO.Y;
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ bool collision = false;
+
+ switch (direction)
+ {
+ case Direction.Right:
+ xNew += 3;
+ if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ break;
+ case Direction.Left:
+ xNew -= 3;
+ if (xNew < BORDER) collision = true;
+ break;
+ case Direction.None:
+ break;
+ }
+
+ if (collision) return collision;
+
+ for (int y = 0; y < shapeScope.Length && !collision; y++)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x++)
+ {
+ int tY = yScope + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shapeScope[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ collision = true;
+ break;
+ }
+ }
+ }
+
+ return collision;
+ }
+
+ bool CollisionPreview(int initY, int yScope, string[] shape)
+ {
+ int xNew = TETROMINO.X;
+
+ for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
+ {
+ for (int y = shape.Length - 1; y >= 0; y -= 2)
+ {
+ for (int x = 0; x < shape[y].Length; x++)
+ {
+ int tY = yUpper + y;
+ int tX = xNew + x;
+ char charToReplace = PLAYFIELD[tY][tX];
+ char charTetromino = shape[y][x];
+
+ if (charTetromino == ' ') continue;
+
+ if (charToReplace != ' ')
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ async void Gameover()
+ {
+ GameStatus = GameStatus.Gameover;
+ AutoEvent.Dispose();
+ FallTimer.Dispose();
+
+ await SleepAfterRender();
+
+ await Console.Clear();
+ await Console.WriteLine();
+ await Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
+ await Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
+ await Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
+ await Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
+ await Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
+ await Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
+ await Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
+ await Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
+ await Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
+ await Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
+ await Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
+ await Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
+
+ await Console.WriteLine();
+ await Console.WriteLine($" Final Score: {Score}");
+ await Console.WriteLine($" Pause Count: {PauseCount}");
+ await Console.WriteLine();
+ await Console.WriteLine(" Press enter to play again");
+ await Console.WriteLine(" Press escape to close the game");
+ Console.CursorVisible = false;
+ await StartGame();
+ RestartGame();
+ }
+
+ async Task StartGame(ConsoleKey key = ConsoleKey.Enter)
+ {
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = (await Console.ReadKey(true)).Key;
+ if (input is ConsoleKey.Escape)
+ {
+ CloseGame = true;
+ return;
+ }
+ }
+ }
+
+ void RestartGame()
+ {
+ PLAYFIELD = (string[])FIELD.Clone();
+ FallSpeedMilliSeconds = 1000;
+ Score = 0;
+ TETROMINO = new()
+ {
+ Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ X = INITIALTETROMINOX,
+ Y = INITIALTETROMINOY
+ };
+
+ AutoEvent = new AutoResetEvent(false);
+ FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ }
+
+ async void PauseGame()
+ {
+ PauseCount++;
+ FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ GameStatus = GameStatus.Paused;
+ await DrawFrame();
+
+ await ResumeGame();
+ }
+
+ async Task ResumeGame(ConsoleKey key = ConsoleKey.Enter)
+ {
+ ConsoleKey input = default;
+ while (input != key && !CloseGame)
+ {
+ input = (await Console.ReadKey(true)).Key;
+ if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
+ {
+ FallTimer.Change(0, FallSpeedMilliSeconds);
+ GameStatus = GameStatus.Playing;
+ return;
+ }
+ }
+ }
+
+ void AddScoreChangeSpeed(int value)
+ {
+ Score += value;
+
+ if (Score > 100) return;
+
+ switch (Score)
+ {
+ case 10:
+ FallSpeedMilliSeconds = 900;
+ break;
+ case 20:
+ FallSpeedMilliSeconds = 800;
+ break;
+ case 30:
+ FallSpeedMilliSeconds = 700;
+ break;
+ case 40:
+ FallSpeedMilliSeconds = 500;
+ break;
+ case 50:
+ FallSpeedMilliSeconds = 300;
+ break;
+ case 60:
+ FallSpeedMilliSeconds = 200;
+ break;
+ case 70:
+ FallSpeedMilliSeconds = 100;
+ break;
+ case 100:
+ FallSpeedMilliSeconds = 50;
+ break;
+ }
+ }
+
+ void TetrominoFall(object? e)
+ {
+ int yAfterFall = TETROMINO.Y;
+ bool collision = false;
+
+ if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
+ else yAfterFall += 2;
+
+ //Y Collision
+ for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ {
+ for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ {
+ char exist = TETROMINO.Shape[yCollision][xCollision];
+
+ if (exist == ' ') continue;
+
+ char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
+
+ if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
+
+ if
+ (
+ lineYC[TETROMINO.X + xCollision] != ' ' &&
+ lineYC[TETROMINO.X + xCollision] != '│'
+ )
+ {
+ char[][] lastFrame = DrawLastFrame(yAfterFall);
+ for (int y = 0; y < lastFrame.Length; y++)
+ {
+ PLAYFIELD[y] = new string(lastFrame[y]);
+ }
+
+ TETROMINO.X = INITIALTETROMINOX;
+ TETROMINO.Y = INITIALTETROMINOY;
+ TETROMINO.Shape = TETROMINO.Next;
+ TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
+
+ xCollision = TETROMINO.Shape[0].Length;
+ collision = true;
+ break;
+ }
+ }
+
+ xCollision += 3;
+ }
+
+ if (!collision) TETROMINO.Y = yAfterFall;
+
+ //Clean Lines
+ for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ {
+ string line = PLAYFIELD[lineIndex];
+ bool notCompleted = line.Any(e => e == ' ');
+
+ if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+
+ if (!notCompleted)
+ {
+ PLAYFIELD[lineIndex] = "│ │";
+ AddScoreChangeSpeed(1);
+
+ for (int lineM = lineIndex; lineM >= 1; lineM--)
+ {
+ if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ {
+ PLAYFIELD[lineM] = "│ │";
+ continue;
+ }
+
+ PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ }
+
+ lineIndex++;
+ }
+ }
+
+ //VerifiedCollision
+ if (Collision(Direction.None) && FallTimer != null) Gameover();
+ }
+
+ void TetrominoSpin(Direction spinDirection)
+ {
+ string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int yScope = TETROMINO.Y;
+ string[] newShape = new string[shapeScope[0].Length / 3 * 2];
+ int newY = 0;
+ int rowEven = 0;
+ int rowOdd = 1;
+
+ //Turn
+ for (int y = 0; y < shapeScope.Length;)
+ {
+ switch (spinDirection)
+ {
+ case Direction.Right:
+ SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ break;
+ case Direction.Left:
+ SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ break;
+ }
+
+ newY = 0;
+ rowEven += 2;
+ rowOdd += 2;
+ y += 2;
+ }
+
+ //Verified Collision
+ for (int y = 0; y < newShape.Length - 1; y++)
+ {
+ for (int x = 0; x < newShape[y].Length; x++)
+ {
+ if (newShape[y][x] == ' ') continue;
+
+ char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
+ if (c != ' ') return;
+ }
+ }
+
+ TETROMINO.Shape = newShape;
+ }
+
+ void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
+ {
+ for (int x = shape[y].Length - 1; x >= 0; x -= 3)
+ {
+ for (int xS = 2; xS >= 0; xS--)
+ {
+ newShape[newY] += shape[rowEven][x - xS];
+ newShape[newY + 1] += shape[rowOdd][x - xS];
+ }
+
+ newY += 2;
+ }
+ }
+
+ void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
+ {
+ for (int x = 2; x < shape[y].Length; x += 3)
+ {
+ if (newShape[newY] == null)
+ {
+ newShape[newY] = "";
+ newShape[newY + 1] = "";
+ }
+
+ for (int xS = 0; xS <= 2; xS++)
+ {
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
+ }
+
+ newY += 2;
+ }
+ }
+
+ async Task SleepAfterRender()
+ {
+ TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
+ if (sleep > TimeSpan.Zero)
+ {
+ await Console.RefreshAndDelay(sleep);
+ }
+ Stopwatch.Restart();
+ }
+
+ }
+
+ class Tetromino
+ {
+ public required string[] Shape { get; set; }
+ public required string[] Next { get; set; }
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ enum Direction
+ {
+ Right,
+ Left,
+ None
+ }
+
+ enum GameStatus
+ {
+ Gameover,
+ Playing,
+ Paused
+ }
}
From 53ddb3c90cb5506230618add220156698251d17c Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 12:37:40 -0500
Subject: [PATCH 05/24] readme updates
---
Projects/Tetris/README.md | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/Projects/Tetris/README.md b/Projects/Tetris/README.md
index 3b4196db..1d91366e 100644
--- a/Projects/Tetris/README.md
+++ b/Projects/Tetris/README.md
@@ -6,13 +6,26 @@
+
+
+ You can play this game in your browser:
+
+
+
+
+
+ Hosted On GitHub Pages
+
+
+> **Note** This game was a *[Community Contribution](https://github.com/dotnet/dotnet-console-games/pull/89)!
+
Well, just tetris!
-## Inputs
+## Input
|Key|Action|
|---|---|
@@ -33,4 +46,12 @@ Debug controls (only if debug mode its active, it may break the game):
|`O` |change active tetromino to O|
|`C` |change active tetromino to S|
|`T` |change active tetromino to T|
-|`Z` |change active tetromino to Z|
\ No newline at end of file
+|`Z` |change active tetromino to Z|
+
+## Downloads
+
+[win-x64](https://github.com/dotnet/dotnet-console-games/raw/binaries/win-x64/Tetris.exe)
+
+[linux-x64](https://github.com/dotnet/dotnet-console-games/raw/binaries/linux-x64/Tetris)
+
+[osx-x64](https://github.com/dotnet/dotnet-console-games/raw/binaries/osx-x64/Tetris)
From 2c8fe718bc2d62fedb166821573b2b60aa40c7bd Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 13:03:13 -0500
Subject: [PATCH 06/24] == -> is && != -> is not
---
Projects/Tetris/Program.cs | 54 ++++++++++++++++++--------------------
1 file changed, 25 insertions(+), 29 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index b052e062..fa81e6e4 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -190,7 +190,7 @@
}
PlayerControl();
- if (GameStatus == GameStatus.Playing)
+ if (GameStatus is GameStatus.Playing)
{
DrawFrame();
SleepAfterRender();
@@ -199,7 +199,7 @@
void PlayerControl()
{
- while (Console.KeyAvailable && GameStatus == GameStatus.Playing)
+ while (Console.KeyAvailable && GameStatus is GameStatus.Playing)
{
switch (Console.ReadKey(true).Key)
{
@@ -225,7 +225,7 @@ void PlayerControl()
break;
case ConsoleKey.R:
TextColor++;
- if (TextColor == 16) TextColor = 1;
+ if (TextColor is 16) TextColor = 1;
Console.ForegroundColor = (ConsoleColor)TextColor;
break;
@@ -294,9 +294,9 @@ void DrawFrame()
char charToReplace = PLAYFIELD[tY][tX];
char charTetromino = shapeScope[y][x];
- if (charTetromino == ' ') continue;
+ if (charTetromino is ' ') continue;
- if (charToReplace != ' ')
+ if (charToReplace is not ' ')
{
collision = true;
break;
@@ -323,9 +323,9 @@ void DrawFrame()
char charToReplace = PLAYFIELD[tY][tX];
char charTetromino = shapeScope[y][x];
- if (charTetromino == ' ') continue;
+ if (charTetromino is ' ') continue;
- if (charToReplace != ' ')
+ if (charToReplace is not ' ')
{
collision = true;
break;
@@ -373,7 +373,7 @@ void DrawFrame()
}
//Draw Pause
- if (GameStatus == GameStatus.Paused)
+ if (GameStatus is GameStatus.Paused)
{
for (int y = 0; y < PAUSE.Length; y++)
{
@@ -426,9 +426,9 @@ char[][] DrawLastFrame(int yS)
char charToReplace = PLAYFIELD[tY][tX];
char charTetromino = shapeScope[y][x];
- if (charTetromino == ' ') continue;
+ if (charTetromino is ' ') continue;
- if (charToReplace != ' ')
+ if (charToReplace is not ' ')
{
collision = true;
break;
@@ -473,9 +473,9 @@ bool Collision(Direction direction)
char charToReplace = PLAYFIELD[tY][tX];
char charTetromino = shapeScope[y][x];
- if (charTetromino == ' ') continue;
+ if (charTetromino is ' ') continue;
- if (charToReplace != ' ')
+ if (charToReplace is not ' ')
{
collision = true;
break;
@@ -501,9 +501,9 @@ bool CollisionPreview(int initY, int yScope, string[] shape)
char charToReplace = PLAYFIELD[tY][tX];
char charTetromino = shape[y][x];
- if (charTetromino == ' ') continue;
+ if (charTetromino is ' ') continue;
- if (charToReplace != ' ')
+ if (charToReplace is not ' ')
{
return true;
}
@@ -596,7 +596,7 @@ void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
while (input != key && !CloseGame)
{
input = Console.ReadKey(true).Key;
- if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
+ if (input is ConsoleKey.Enter && GameStatus is GameStatus.Paused && FallTimer != null)
{
FallTimer.Change(0, FallSpeedMilliSeconds);
GameStatus = GameStatus.Playing;
@@ -655,17 +655,13 @@ void TetrominoFall(object? e)
{
char exist = TETROMINO.Shape[yCollision][xCollision];
- if (exist == ' ') continue;
+ if (exist is ' ') continue;
char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
- if
- (
- lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│'
- )
+ if (lineYC[TETROMINO.X + xCollision] is not ' ' or '│')
{
char[][] lastFrame = DrawLastFrame(yAfterFall);
for (int y = 0; y < lastFrame.Length; y++)
@@ -693,9 +689,9 @@ void TetrominoFall(object? e)
for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
{
string line = PLAYFIELD[lineIndex];
- bool notCompleted = line.Any(e => e == ' ');
+ bool notCompleted = line.Any(e => e is ' ');
- if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+ if (lineIndex is 0 || lineIndex == PLAYFIELD.Length - 1) continue;
if (!notCompleted)
{
@@ -704,7 +700,7 @@ void TetrominoFall(object? e)
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
- if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ if (PLAYFIELD[lineM - 1] is "╭──────────────────────────────╮")
{
PLAYFIELD[lineM] = "│ │";
continue;
@@ -717,8 +713,8 @@ void TetrominoFall(object? e)
}
}
- //VerifiedCollision
- if (Collision(Direction.None) && FallTimer != null) Gameover();
+ //VerifiedCollision
+ if (Collision(Direction.None) && FallTimer is not null) Gameover();
}
void TetrominoSpin(Direction spinDirection)
@@ -754,10 +750,10 @@ void TetrominoSpin(Direction spinDirection)
{
for (int x = 0; x < newShape[y].Length; x++)
{
- if (newShape[y][x] == ' ') continue;
+ if (newShape[y][x] is ' ') continue;
char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
- if (c != ' ') return;
+ if (c is not ' ') return;
}
}
@@ -782,7 +778,7 @@ void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int
{
for (int x = 2; x < shape[y].Length; x += 3)
{
- if (newShape[newY] == null)
+ if (newShape[newY] is null)
{
newShape[newY] = "";
newShape[newY + 1] = "";
From 516121f3057bbe26b029e31575777ec9542f9968 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 13:38:15 -0500
Subject: [PATCH 07/24] Convert.ToInt16 is unnecessary here
---
Projects/Tetris/Program.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index fa81e6e4..b40ee133 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -143,7 +143,7 @@
Random RamdomGenerator = new();
-int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
+int INITIALTETROMINOX = (PLAYFIELD[0].Length / 2) - 3;
int INITIALTETROMINOY = 1;
Tetromino TETROMINO = new()
{
From 97fdeb1c7c78bdb5b8691c6cc367f8356e127953 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 13:44:51 -0500
Subject: [PATCH 08/24] syntax sugar
---
Projects/Tetris/Program.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index b40ee133..49cdb5b0 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -153,7 +153,7 @@
Y = INITIALTETROMINOY
};
-AutoResetEvent AutoEvent = new AutoResetEvent(false);
+AutoResetEvent AutoEvent = new(false);
Timer? FallTimer = null;
GameStatus = GameStatus.Playing;
From a1341181420ee3fedf6780f2f2930052686b63ea Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 13:54:44 -0500
Subject: [PATCH 09/24] switch statement -> switch expression
---
Projects/Tetris/Program.cs | 39 +++++++++++---------------------------
1 file changed, 11 insertions(+), 28 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 49cdb5b0..fab242cb 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -609,35 +609,18 @@ void AddScoreChangeSpeed(int value)
{
Score += value;
- if (Score > 100) return;
-
- switch (Score)
+ FallSpeedMilliSeconds = Score switch
{
- case 10:
- FallSpeedMilliSeconds = 900;
- break;
- case 20:
- FallSpeedMilliSeconds = 800;
- break;
- case 30:
- FallSpeedMilliSeconds = 700;
- break;
- case 40:
- FallSpeedMilliSeconds = 500;
- break;
- case 50:
- FallSpeedMilliSeconds = 300;
- break;
- case 60:
- FallSpeedMilliSeconds = 200;
- break;
- case 70:
- FallSpeedMilliSeconds = 100;
- break;
- case 100:
- FallSpeedMilliSeconds = 50;
- break;
- }
+ > 100 => FallSpeedMilliSeconds = 050,
+ > 070 => FallSpeedMilliSeconds = 100,
+ > 060 => FallSpeedMilliSeconds = 200,
+ > 050 => FallSpeedMilliSeconds = 300,
+ > 040 => FallSpeedMilliSeconds = 500,
+ > 030 => FallSpeedMilliSeconds = 700,
+ > 020 => FallSpeedMilliSeconds = 800,
+ > 010 => FallSpeedMilliSeconds = 900,
+ _ => 1000,
+ };
}
void TetrominoFall(object? e)
From 3aed2caedf83baf56d78bd3fe6f967f52cd3aae6 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 13:57:31 -0500
Subject: [PATCH 10/24] Random.Shared
---
Projects/Tetris/Program.cs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index fab242cb..060140f8 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -141,14 +141,12 @@
int TextColor = 0;
GameStatus GameStatus = GameStatus.Gameover;
-Random RamdomGenerator = new();
-
int INITIALTETROMINOX = (PLAYFIELD[0].Length / 2) - 3;
int INITIALTETROMINOY = 1;
Tetromino TETROMINO = new()
{
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Shape = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
X = INITIALTETROMINOX,
Y = INITIALTETROMINOY
};
@@ -569,8 +567,8 @@ void RestartGame()
Score = 0;
TETROMINO = new()
{
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
+ Shape = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
+ Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
X = INITIALTETROMINOX,
Y = INITIALTETROMINOY
};
@@ -655,7 +653,7 @@ void TetrominoFall(object? e)
TETROMINO.X = INITIALTETROMINOX;
TETROMINO.Y = INITIALTETROMINOY;
TETROMINO.Shape = TETROMINO.Next;
- TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
+ TETROMINO.Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)];
xCollision = TETROMINO.Shape[0].Length;
collision = true;
From ecd2a60480f7bafb84c7c7c6239389529bbd134f Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 14:13:35 -0500
Subject: [PATCH 11/24] raw string literals
---
Projects/Tetris/Program.cs | 78 +++++++++++++++++++-------------------
1 file changed, 40 insertions(+), 38 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 060140f8..ed0ec9d9 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -156,24 +156,25 @@
GameStatus = GameStatus.Playing;
Console.WriteLine();
-Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
-Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
-Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
-Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
-Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
-Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
-
-Console.WriteLine();
-Console.WriteLine(" Controls:");
-Console.WriteLine(" WASD or ARROW to move");
-Console.WriteLine(" Q or E to spin left or right");
-Console.WriteLine(" P to paused the game, press enter");
-Console.WriteLine(" key to resume");
-Console.WriteLine(" R to change Text color");
-Console.WriteLine();
-Console.WriteLine(" Press escape to close the game at any time.");
-Console.WriteLine();
-Console.Write(" Press enter to start tetris...");
+Console.WriteLine("""
+ ██████╗█████╗██████╗█████╗ ██╗█████╗
+ ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝
+ ██║ █████╗ ██║ █████╔╝██║ ███╗
+ ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗
+ ██║ █████╗ ██║ ██║ ██║██║█████║
+ ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝
+
+ Controls:
+ WASD or ARROW to move
+ Q or E to spin left or right
+ P to paused the game, press enter
+ key to resume
+ R to change Text color
+
+ Press escape to close the game at any time.
+
+ Press enter to start tetris...
+ """);
Console.CursorVisible = false;
StartGame();
Console.Clear();
@@ -521,26 +522,27 @@ void Gameover()
SleepAfterRender();
Console.Clear();
- Console.WriteLine();
- Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
- Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
- Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
- Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
- Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
- Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
- Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
- Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
- Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
- Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
- Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
- Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
-
- Console.WriteLine();
- Console.WriteLine($" Final Score: {Score}");
- Console.WriteLine($" Pause Count: {PauseCount}");
- Console.WriteLine();
- Console.WriteLine(" Press enter to play again");
- Console.WriteLine(" Press escape to close the game");
+ Console.Write($"""
+
+ ██████╗ █████╗ ██ ██╗█████╗
+ ██╔════╝ ██╔══██╗███ ███║██╔══╝
+ ██║ ███╗███████║██╔██═██║█████╗
+ ██║ ██║██╔══██║██║ ██║██╔══╝
+ ╚██████╔╝██║ ██║██║ ██║█████╗
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝
+ ██████╗██╗ ██╗█████╗█████╗
+ ██ ██║██║ ██║██╔══╝██╔═██╗
+ ██ ██║██║ ██║█████╗█████╔╝
+ ██ ██║╚██╗██╔╝██╔══╝██╔═██╗
+ ██████║ ╚███╔╝ █████╗██║ ██║
+ ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝
+
+ Final Score: {Score}
+ Pause Count: {PauseCount}
+
+ Press enter to play again
+ Press escape to close the game
+ """);
Console.CursorVisible = false;
StartGame();
RestartGame();
From f61c0b039d3728115a3c1d52239d96cc8ef3a3d0 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 14:23:50 -0500
Subject: [PATCH 12/24] removed unnecessary clones
---
Projects/Tetris/Program.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index ed0ec9d9..80a719d3 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -273,8 +273,8 @@ void DrawFrame()
{
bool collision = false;
int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
+ string[] shapeScope = TETROMINO.Shape;
+ string[] nextShapeScope = TETROMINO.Next;
char[][] frame = new char[PLAYFIELD.Length][];
//Field
From 3c2b387598b4b91099d2e20520a829aa03109125 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Sat, 28 Oct 2023 14:49:32 -0500
Subject: [PATCH 13/24] disable implicit usings (I hate them with a passion) :P
---
Projects/Tetris/Program.cs | 5 ++++-
Projects/Tetris/Tetris.csproj | 4 ++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 80a719d3..e49af5ae 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -1,5 +1,8 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
+using System.Linq;
using System.Text;
+using System.Threading;
Console.OutputEncoding = Encoding.UTF8;
Console.CursorVisible = false;
diff --git a/Projects/Tetris/Tetris.csproj b/Projects/Tetris/Tetris.csproj
index 1a40ec66..0e17b8ef 100644
--- a/Projects/Tetris/Tetris.csproj
+++ b/Projects/Tetris/Tetris.csproj
@@ -1,8 +1,8 @@
-
+
Exe
net7.0
- enable
+ disable
enable
From 0b2860b54652ba0762465dc4edaf893c3c8085df Mon Sep 17 00:00:00 2001
From: Jahg
Date: Sat, 28 Oct 2023 20:57:43 -0700
Subject: [PATCH 14/24] Improved Tetrominos Spin
---
Projects/Tetris/Program.cs | 73 +++++++++++++++++++++++++++++++++-----
1 file changed, 64 insertions(+), 9 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index e49af5ae..c1db059e 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -90,7 +90,7 @@
new[]{
"╭─╮",
"╰─╯",
- "╭─╮",
+ "x─╮",
"╰─╯",
"╭─╮",
"╰─╯",
@@ -100,37 +100,37 @@
new[]{
"╭─╮ ",
"╰─╯ ",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
" ╭─╮",
" ╰─╯",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
"╭─╮╭─╮",
"╰─╯╰─╯",
- "╭─╮╭─╮",
+ "x─╮╭─╮",
"╰─╯╰─╯"
},
new[]{
" ╭─╮╭─╮",
" ╰─╯╰─╯",
- "╭─╮╭─╮ ",
+ "╭─╮x─╮ ",
"╰─╯╰─╯ "
},
new[]{
" ╭─╮ ",
" ╰─╯ ",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
"╭─╮╭─╮ ",
"╰─╯╰─╯ ",
- " ╭─╮╭─╮",
+ " x─╮╭─╮",
" ╰─╯╰─╯"
},
};
@@ -304,6 +304,7 @@ void DrawFrame()
break;
}
+ if (charTetromino is 'x') charTetromino = '╭';
frame[tY][tX] = charTetromino;
}
}
@@ -361,6 +362,7 @@ void DrawFrame()
int tY = y + BORDER;
int tX = PLAYFIELD[y].Length + x + BORDER;
char charTetromino = nextShapeScope[y][x];
+ if (charTetromino is 'x') charTetromino = '╭';
frame[tY][tX] = charTetromino;
}
}
@@ -435,7 +437,8 @@ char[][] DrawLastFrame(int yS)
collision = true;
break;
}
-
+
+ if (charTetromino is 'x') charTetromino = '╭';
frame[tY][tX] = charTetromino;
}
}
@@ -707,6 +710,7 @@ void TetrominoSpin(Direction spinDirection)
{
string[] shapeScope = (string[])TETROMINO.Shape.Clone();
int yScope = TETROMINO.Y;
+ int xScope = TETROMINO.X;
string[] newShape = new string[shapeScope[0].Length / 3 * 2];
int newY = 0;
int rowEven = 0;
@@ -731,6 +735,55 @@ void TetrominoSpin(Direction spinDirection)
y += 2;
}
+ //Old Pivot
+ (int y, int x) offsetOP = (0, 0);
+ for (int y = 0; y < shapeScope.Length; y += 2)
+ {
+ for (int x = 0; x < shapeScope[y].Length; x += 3)
+ {
+ if (shapeScope[y][x] is 'x')
+ {
+ offsetOP = (y / 2, x / 3);
+ y = shapeScope.Length;
+ break;
+ }
+ }
+ }
+
+ //New Pivot
+ (int y, int x) offsetNP = (0, 0);
+ for (int y = 0; y < newShape.Length; y += 2)
+ {
+ for (int x = 0; x < newShape[y].Length; x += 3)
+ {
+ if (newShape[y][x] is 'x')
+ {
+ offsetNP = (y / 2, x / 3);
+ y = newShape.Length;
+ break;
+ }
+ }
+ }
+
+ yScope += (offsetOP.y - offsetNP.y) * 2;
+ xScope += (offsetOP.x - offsetNP.x) * 3;
+
+ //Tetromino Square(O) special case
+ if (newShape.Length / 2 == newShape[0].Length / 3)
+ {
+ yScope = TETROMINO.Y;
+ xScope = TETROMINO.X;
+ }
+ //Tetromino I special case
+ else if (newShape.Length is 8 && newShape[0].Length is 3 && offsetNP.y is 2)
+ {
+ newShape[2] = "x─╮";
+ newShape[4] = "╭─╮";
+ yScope += 2;
+ }
+
+ if (xScope < 1 || yScope < 1) return;
+
//Verified Collision
for (int y = 0; y < newShape.Length - 1; y++)
{
@@ -738,11 +791,13 @@ void TetrominoSpin(Direction spinDirection)
{
if (newShape[y][x] is ' ') continue;
- char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
+ char c = PLAYFIELD[yScope + y][xScope + x];
if (c is not ' ') return;
}
}
+ TETROMINO.Y = yScope;
+ TETROMINO.X = xScope;
TETROMINO.Shape = newShape;
}
From 05463dfdd1f5f9160112e9582731556d9b7cd37d Mon Sep 17 00:00:00 2001
From: Jahg
Date: Sat, 28 Oct 2023 21:25:23 -0700
Subject: [PATCH 15/24] Implemented HardDrop
---
Projects/Tetris/Program.cs | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index c1db059e..d4c55f5f 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -170,6 +170,7 @@
Controls:
WASD or ARROW to move
Q or E to spin left or right
+ Spacebar to HardDrop
P to paused the game, press enter
key to resume
R to change Text color
@@ -230,9 +231,12 @@ void PlayerControl()
if (TextColor is 16) TextColor = 1;
Console.ForegroundColor = (ConsoleColor)TextColor;
break;
+ case ConsoleKey.Spacebar:
+ HardDrop();
+ break;
//DEBUG
- case ConsoleKey.Spacebar:
+ case ConsoleKey.Tab:
if (!DEBUGCONTROLS) return;
PLAYFIELD = (string[])FIELD.Clone();
break;
@@ -312,7 +316,7 @@ void DrawFrame()
//Draw Preview
for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
{
- if (CollisionPreview(yField, yScope, shapeScope)) continue;
+ if (CollisionBottom(yField, yScope, shapeScope)) continue;
for (int y = 0; y < shapeScope.Length && !collision; y++)
{
@@ -437,7 +441,7 @@ char[][] DrawLastFrame(int yS)
collision = true;
break;
}
-
+
if (charTetromino is 'x') charTetromino = '╭';
frame[tY][tX] = charTetromino;
}
@@ -491,7 +495,7 @@ bool Collision(Direction direction)
return collision;
}
-bool CollisionPreview(int initY, int yScope, string[] shape)
+bool CollisionBottom(int initY, int yScope, string[] shape)
{
int xNew = TETROMINO.X;
@@ -706,6 +710,19 @@ void TetrominoFall(object? e)
if (Collision(Direction.None) && FallTimer is not null) Gameover();
}
+void HardDrop()
+{
+ int y = TETROMINO.Y;
+ int x = TETROMINO.X;
+ var shapeScope = TETROMINO.Shape;
+ for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ {
+ if (CollisionBottom(yField, y, shapeScope)) continue;
+ TETROMINO.Y = yField;
+ break;
+ }
+}
+
void TetrominoSpin(Direction spinDirection)
{
string[] shapeScope = (string[])TETROMINO.Shape.Clone();
From 8d4cc2139c27f023084110e2ef659355c50f9d97 Mon Sep 17 00:00:00 2001
From: Jahg
Date: Sat, 28 Oct 2023 21:40:39 -0700
Subject: [PATCH 16/24] Change score -> line clears
---
Projects/Tetris/Program.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index d4c55f5f..728f41fe 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -617,6 +617,7 @@ void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
void AddScoreChangeSpeed(int value)
{
+ if (value >= 4) value++;
Score += value;
FallSpeedMilliSeconds = Score switch
@@ -679,6 +680,7 @@ void TetrominoFall(object? e)
if (!collision) TETROMINO.Y = yAfterFall;
//Clean Lines
+ int clearedLines = 0;
for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
{
string line = PLAYFIELD[lineIndex];
@@ -689,7 +691,7 @@ void TetrominoFall(object? e)
if (!notCompleted)
{
PLAYFIELD[lineIndex] = "│ │";
- AddScoreChangeSpeed(1);
+ clearedLines++;
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
@@ -706,6 +708,8 @@ void TetrominoFall(object? e)
}
}
+ AddScoreChangeSpeed(clearedLines / 2);
+
//VerifiedCollision
if (Collision(Direction.None) && FallTimer is not null) Gameover();
}
From 09e9e1fe401fa17e4dc001e2300aa5e754b885fd Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 17:08:00 -0500
Subject: [PATCH 17/24] refactoring (Timer -> Stopwatch)
---
Projects/Tetris/Program.cs | 944 ++++++++++++++++++-------------------
Projects/Tetris/README.md | 71 ++-
2 files changed, 509 insertions(+), 506 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 728f41fe..43510fa4 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -1,62 +1,20 @@
using System;
using System.Diagnostics;
+using System.Globalization;
using System.Linq;
using System.Text;
-using System.Threading;
-Console.OutputEncoding = Encoding.UTF8;
-Console.CursorVisible = false;
-Stopwatch Stopwatch = Stopwatch.StartNew();
-
-bool DEBUGCONTROLS = false;
+#region Constants
-string[] FIELD = new[]
+string[] emptyField = new string[42];
+emptyField[0] = "╭──────────────────────────────╮";
+for (int i = 1; i < 41; i++)
{
- "╭──────────────────────────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰──────────────────────────────╯"
-};
+ emptyField[i] = "│ │";
+}
+emptyField[^1] = "╰──────────────────────────────╯";
-string[] NEXTTETROMINO = new[]
+string[] nextTetrominoBorder = new[]
{
"╭─────────╮",
"│ │",
@@ -70,13 +28,15 @@
"╰─────────╯"
};
-string[] SCORE = new[]{
+string[] scoreBorder = new[]
+{
"╭─────────╮",
"│ │",
"╰─────────╯"
};
-string[] PAUSE = new[]{
+string[] pauseRender = new[]
+{
"█████╗ ███╗ ██╗██╗█████╗█████╗",
"██╔██║██╔██╗██║██║██╔══╝██╔══╝",
"█████║█████║██║██║ ███╗ █████╗",
@@ -85,7 +45,7 @@
"╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
};
-string[][] TETROMINOS = new[]
+string[][] tetrominos = new[]
{
new[]{
"╭─╮",
@@ -135,143 +95,235 @@
},
};
-string[] PLAYFIELD = (string[])FIELD.Clone();
-const int BORDER = 1;
-int FallSpeedMilliSeconds = 1000;
-bool CloseGame = false;
-int Score = 0;
-int PauseCount = 0;
-int TextColor = 0;
-GameStatus GameStatus = GameStatus.Gameover;
-
-int INITIALTETROMINOX = (PLAYFIELD[0].Length / 2) - 3;
-int INITIALTETROMINOY = 1;
-Tetromino TETROMINO = new()
-{
- Shape = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
-};
+const int borderSize = 1;
-AutoResetEvent AutoEvent = new(false);
-Timer? FallTimer = null;
-GameStatus = GameStatus.Playing;
-
-Console.WriteLine();
-Console.WriteLine("""
- ██████╗█████╗██████╗█████╗ ██╗█████╗
- ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝
- ██║ █████╗ ██║ █████╔╝██║ ███╗
- ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗
- ██║ █████╗ ██║ ██║ ██║██║█████║
- ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝
-
- Controls:
- WASD or ARROW to move
- Q or E to spin left or right
- Spacebar to HardDrop
- P to paused the game, press enter
- key to resume
- R to change Text color
-
- Press escape to close the game at any time.
-
- Press enter to start tetris...
- """);
-Console.CursorVisible = false;
-StartGame();
-Console.Clear();
+int initialX = (emptyField[0].Length / 2) - 3;
+int initialY = 1;
+
+int consoleWidthMin = 44;
+int consoleHeightMin = 43;
-FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
+#endregion
-while (!CloseGame)
+Stopwatch timer = new();
+bool closeRequested = false;
+bool gameOver;
+int score = 0;
+TimeSpan fallSpeed;
+string[] field;
+Tetromino tetromino;
+int consoleWidth = Console.WindowWidth;
+int consoleHeight = Console.WindowHeight;
+bool consoleTooSmallScreen = false;
+
+Console.OutputEncoding = Encoding.UTF8;
+while (!closeRequested)
{
- if (CloseGame)
+ Console.Clear();
+ Console.Write("""
+
+ ██████╗█████╗██████╗█████╗ ██╗█████╗
+ ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝
+ ██║ █████╗ ██║ █████╔╝██║ ███╗
+ ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗
+ ██║ █████╗ ██║ ██║ ██║██║█████║
+ ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝
+
+ Controls:
+
+ [A] or [←] move left
+ [D] or [→] move right
+ [S] or [↓] fall faster
+ [Q] spin left
+ [E] spin right
+ [Spacebar] drop
+ [P] pause and unpause
+ [Escape] close game
+ [Enter] start game
+ """);
+ bool mainMenuScreen = true;
+ while (!closeRequested && mainMenuScreen)
+ {
+ Console.CursorVisible = false;
+ switch (Console.ReadKey(true).Key)
+ {
+ case ConsoleKey.Enter: mainMenuScreen = false; break;
+ case ConsoleKey.Escape: closeRequested = true; break;
+ }
+ }
+ Initialize();
+ Console.Clear();
+ DrawFrame();
+ while (!closeRequested && !gameOver)
+ {
+ // if user changed the size of the console, we need to clear the console
+ if (consoleWidth != Console.WindowWidth || consoleHeight != Console.WindowHeight)
+ {
+ consoleWidth = Console.WindowWidth;
+ consoleHeight = Console.WindowHeight;
+ if (!consoleTooSmallScreen)
+ {
+ Console.Clear();
+ DrawFrame();
+ }
+ else
+ {
+ consoleTooSmallScreen = false;
+ }
+ }
+
+ // if the console isn't big enough to render the game, pause the game and tell the user
+ if (consoleWidth < consoleWidthMin || consoleHeight < consoleHeightMin)
+ {
+ if (!consoleTooSmallScreen)
+ {
+ Console.Clear();
+ Console.Write($"Please increase size of console to at least {consoleWidthMin}x{consoleHeightMin}. Current size is {consoleWidth}x{consoleHeight}.");
+ timer.Stop();
+ consoleTooSmallScreen = true;
+ }
+ }
+ else if (consoleTooSmallScreen)
+ {
+ consoleTooSmallScreen = false;
+ Console.Clear();
+ DrawFrame();
+ }
+
+ HandlePlayerInput();
+ if (closeRequested || gameOver)
+ {
+ break;
+ }
+ if (timer.IsRunning && timer.Elapsed > fallSpeed)
+ {
+ TetrominoFall();
+ if (closeRequested || gameOver)
+ {
+ break;
+ }
+ DrawFrame();
+ }
+ }
+ if (closeRequested)
{
break;
}
+ Console.Clear();
+ Console.Write($"""
- PlayerControl();
- if (GameStatus is GameStatus.Playing)
- {
- DrawFrame();
- SleepAfterRender();
+ ██████╗ █████╗ ██ ██╗█████╗
+ ██╔════╝ ██╔══██╗███ ███║██╔══╝
+ ██║ ███╗███████║██╔██═██║█████╗
+ ██║ ██║██╔══██║██║ ██║██╔══╝
+ ╚██████╔╝██║ ██║██║ ██║█████╗
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝
+ ██████╗██╗ ██╗█████╗█████╗
+ ██ ██║██║ ██║██╔══╝██╔═██╗
+ ██ ██║██║ ██║█████╗█████╔╝
+ ██ ██║╚██╗██╔╝██╔══╝██╔═██╗
+ ██████║ ╚███╔╝ █████╗██║ ██║
+ ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝
+
+ Final Score: {score}
+
+ Press enter to play again
+ Press escape to close the game
+ """);
+ Console.CursorVisible = false;
+ bool gameOverScreen = true;
+ while (!closeRequested && gameOverScreen)
+ {
+ Console.CursorVisible = false;
+ switch (Console.ReadKey(true).Key)
+ {
+ case ConsoleKey.Enter: gameOverScreen = false; break;
+ case ConsoleKey.Escape: closeRequested = true; break;
+ }
}
}
+Console.Clear();
+Console.WriteLine("Tetris was closed.");
+Console.CursorVisible = true;
-void PlayerControl()
+void Initialize()
{
- while (Console.KeyAvailable && GameStatus is GameStatus.Playing)
+ gameOver = false;
+ score = 0;
+ field = emptyField[..];
+ initialX = (field[0].Length / 2) - 3;
+ initialY = 1;
+ tetromino = new()
+ {
+ Shape = tetrominos[Random.Shared.Next(0, tetrominos.Length)],
+ Next = tetrominos[Random.Shared.Next(0, tetrominos.Length)],
+ X = initialX,
+ Y = initialY
+ };
+ fallSpeed = GetFallSpeed();
+ timer.Restart();
+}
+
+void HandlePlayerInput()
+{
+ while (Console.KeyAvailable && !closeRequested)
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.A or ConsoleKey.LeftArrow:
- if (Collision(Direction.Left)) break;
- TETROMINO.X -= 3;
+ if (timer.IsRunning && !Collision(Direction.Left))
+ {
+ tetromino.X -= 3;
+ }
+ DrawFrame();
break;
case ConsoleKey.D or ConsoleKey.RightArrow:
- if (Collision(Direction.Right)) break;
- TETROMINO.X += 3;
+ if (timer.IsRunning && !Collision(Direction.Right))
+ {
+ tetromino.X += 3;
+ }
+ DrawFrame();
break;
case ConsoleKey.S or ConsoleKey.DownArrow:
- FallTimer.Change(0, FallSpeedMilliSeconds);
+ if (timer.IsRunning)
+ {
+ TetrominoFall();
+ }
break;
case ConsoleKey.E:
- TetrominoSpin(Direction.Right);
+ if (timer.IsRunning)
+ {
+ TetrominoSpin(Direction.Right);
+ DrawFrame();
+ }
break;
case ConsoleKey.Q:
- TetrominoSpin(Direction.Left);
+ if (timer.IsRunning)
+ {
+ TetrominoSpin(Direction.Left);
+ DrawFrame();
+ }
break;
case ConsoleKey.P:
- PauseGame();
- break;
- case ConsoleKey.R:
- TextColor++;
- if (TextColor is 16) TextColor = 1;
- Console.ForegroundColor = (ConsoleColor)TextColor;
+ if (timer.IsRunning)
+ {
+ timer.Stop();
+ }
+ else if (!consoleTooSmallScreen)
+ {
+ timer.Start();
+ }
+ DrawFrame();
break;
case ConsoleKey.Spacebar:
- HardDrop();
- break;
-
- //DEBUG
- case ConsoleKey.Tab:
- if (!DEBUGCONTROLS) return;
- PLAYFIELD = (string[])FIELD.Clone();
- break;
- case ConsoleKey.I:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[0];
- break;
- case ConsoleKey.J:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[1];
- break;
- case ConsoleKey.L:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[2];
- break;
- case ConsoleKey.O:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[3];
- break;
- case ConsoleKey.C:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[4];
- break;
- case ConsoleKey.T:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[5];
- break;
- case ConsoleKey.Z:
- if (!DEBUGCONTROLS) return;
- TETROMINO.Shape = TETROMINOS[6];
- break;
- case ConsoleKey.X:
- if (!DEBUGCONTROLS) return;
- Score += 10;
+ if (timer.IsRunning)
+ {
+ HardDrop();
+ }
break;
+ case ConsoleKey.Escape:
+ closeRequested = true;
+ return;
}
}
}
@@ -279,132 +331,131 @@ void PlayerControl()
void DrawFrame()
{
bool collision = false;
- int yScope = TETROMINO.Y;
- string[] shapeScope = TETROMINO.Shape;
- string[] nextShapeScope = TETROMINO.Next;
- char[][] frame = new char[PLAYFIELD.Length][];
+ char[][] frame = new char[field.Length][];
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
+ // Field
+ for (int y = 0; y < field.Length; y++)
{
- frame[y] = PLAYFIELD[y].ToCharArray();
+ frame[y] = field[y].ToCharArray();
}
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ // Tetromino
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
- int tY = yScope + y;
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino is ' ') continue;
-
+ int tY = tetromino.Y + y;
+ int tX = tetromino.X + x;
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
if (charToReplace is not ' ')
{
collision = true;
break;
}
-
- if (charTetromino is 'x') charTetromino = '╭';
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
- //Draw Preview
- for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ // Draw Preview
+ for (int yField = field.Length - tetromino.Shape.Length - borderSize; yField >= 0; yField -= 2)
{
- if (CollisionBottom(yField, yScope, shapeScope)) continue;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ if (CollisionBottom(yField, tetromino.Y, tetromino.Shape))
+ {
+ continue;
+ }
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
int tY = yField + y;
-
- if (yScope + shapeScope.Length > tY) continue;
-
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino is ' ') continue;
-
+ if (tetromino.Y + tetromino.Shape.Length > tY)
+ {
+ continue;
+ }
+ int tX = tetromino.X + x;
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
if (charToReplace is not ' ')
{
collision = true;
break;
}
-
frame[tY][tX] = '•';
}
}
-
break;
}
- //Next Square
- for (int y = 0; y < NEXTTETROMINO.Length; y++)
+ // Next
+ for (int y = 0; y < nextTetrominoBorder.Length; y++)
{
- frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
+ frame[y] = frame[y].Concat(nextTetrominoBorder[y]).ToArray();
}
-
- //Score Square
- for (int y = 0; y < SCORE.Length; y++)
- {
- int sY = NEXTTETROMINO.Length + y;
- frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
- }
-
- //Draw Next
- for (int y = 0; y < nextShapeScope.Length; y++)
+ for (int y = 0; y < tetromino.Next.Length; y++)
{
- for (int x = 0; x < nextShapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Next[y].Length; x++)
{
- int tY = y + BORDER;
- int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = nextShapeScope[y][x];
- if (charTetromino is 'x') charTetromino = '╭';
+ int tY = y + borderSize;
+ int tX = field[y].Length + x + borderSize;
+ char charTetromino = tetromino.Next[y][x];
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
- //Draw Score
- char[] score = Score.ToString().ToCharArray();
- for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ // Score
+ for (int y = 0; y < scoreBorder.Length; y++)
+ {
+ int sY = nextTetrominoBorder.Length + y;
+ frame[sY] = frame[sY].Concat(scoreBorder[y]).ToArray();
+ }
+ char[] scoreRender = score.ToString(CultureInfo.InvariantCulture).ToCharArray();
+ for (int scoreX = scoreRender.Length - 1; scoreX >= 0; scoreX--)
{
- int sY = NEXTTETROMINO.Length + BORDER;
- int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
- frame[sY][sX] = score[scoreX];
+ int sY = nextTetrominoBorder.Length + borderSize;
+ int sX = frame[sY].Length - (scoreRender.Length - scoreX) - borderSize;
+ frame[sY][sX] = scoreRender[scoreX];
}
- //Draw Pause
- if (GameStatus is GameStatus.Paused)
+ // Pause
+ if (!timer.IsRunning)
{
- for (int y = 0; y < PAUSE.Length; y++)
+ for (int y = 0; y < pauseRender.Length; y++)
{
- int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
- for (int x = 0; x < PAUSE[y].Length; x++)
+ int fY = (field.Length / 2) + y - pauseRender.Length;
+ for (int x = 0; x < pauseRender[y].Length; x++)
{
- int fX = x + BORDER;
+ int fX = x + borderSize;
- if (x >= PLAYFIELD[fY].Length) break;
+ if (x >= field[fY].Length) break;
- frame[fY][fX] = PAUSE[y][x];
+ frame[fY][fX] = pauseRender[y][x];
}
}
}
- //Create Render
StringBuilder render = new();
for (int y = 0; y < frame.Length; y++)
{
render.AppendLine(new string(frame[y]));
}
-
- Console.Clear();
+ Console.SetCursorPosition(0, 0);
Console.Write(render);
Console.CursorVisible = false;
}
@@ -413,77 +464,78 @@ char[][] DrawLastFrame(int yS)
{
bool collision = false;
int yScope = yS - 2;
- int xScope = TETROMINO.X;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
+ int xScope = tetromino.X;
+ char[][] frame = new char[field.Length][];
+ for (int y = 0; y < field.Length; y++)
{
- frame[y] = PLAYFIELD[y].ToCharArray();
+ frame[y] = field[y].ToCharArray();
}
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
int tY = yScope + y;
int tX = xScope + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino is ' ') continue;
-
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
if (charToReplace is not ' ')
{
collision = true;
break;
}
-
- if (charTetromino is 'x') charTetromino = '╭';
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
-
return frame;
}
bool Collision(Direction direction)
{
- int xNew = TETROMINO.X;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int xNew = tetromino.X;
bool collision = false;
-
switch (direction)
{
case Direction.Right:
xNew += 3;
- if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ if (xNew + tetromino.Shape[0].Length > field[0].Length - borderSize)
+ {
+ collision = true;
+ }
break;
case Direction.Left:
xNew -= 3;
- if (xNew < BORDER) collision = true;
+ if (xNew < borderSize)
+ {
+ collision = true;
+ }
break;
case Direction.None:
break;
}
-
- if (collision) return collision;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ if (collision)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ return collision;
+ }
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
+ {
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
- int tY = yScope + y;
+ int tY = tetromino.Y + y;
int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino is ' ') continue;
-
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
if (charToReplace is not ' ')
{
collision = true;
@@ -491,14 +543,12 @@ bool Collision(Direction direction)
}
}
}
-
return collision;
}
bool CollisionBottom(int initY, int yScope, string[] shape)
{
- int xNew = TETROMINO.X;
-
+ int xNew = tetromino.X;
for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
{
for (int y = shape.Length - 1; y >= 0; y -= 2)
@@ -507,11 +557,12 @@ bool CollisionBottom(int initY, int yScope, string[] shape)
{
int tY = yUpper + y;
int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
+ char charToReplace = field[tY][tX];
char charTetromino = shape[y][x];
-
- if (charTetromino is ' ') continue;
-
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
if (charToReplace is not ' ')
{
return true;
@@ -519,259 +570,189 @@ bool CollisionBottom(int initY, int yScope, string[] shape)
}
}
}
-
return false;
}
-void Gameover()
+TimeSpan GetFallSpeed() =>
+ TimeSpan.FromMilliseconds(
+ score switch
+ {
+ > 162 => 100,
+ > 144 => 200,
+ > 126 => 300,
+ > 108 => 400,
+ > 090 => 500,
+ > 072 => 600,
+ > 054 => 700,
+ > 036 => 800,
+ > 018 => 900,
+ _ => 1000,
+ });
+
+void TetrominoFall()
{
- GameStatus = GameStatus.Gameover;
- AutoEvent.Dispose();
- FallTimer.Dispose();
-
- SleepAfterRender();
-
- Console.Clear();
- Console.Write($"""
-
- ██████╗ █████╗ ██ ██╗█████╗
- ██╔════╝ ██╔══██╗███ ███║██╔══╝
- ██║ ███╗███████║██╔██═██║█████╗
- ██║ ██║██╔══██║██║ ██║██╔══╝
- ╚██████╔╝██║ ██║██║ ██║█████╗
- ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝
- ██████╗██╗ ██╗█████╗█████╗
- ██ ██║██║ ██║██╔══╝██╔═██╗
- ██ ██║██║ ██║█████╗█████╔╝
- ██ ██║╚██╗██╔╝██╔══╝██╔═██╗
- ██████║ ╚███╔╝ █████╗██║ ██║
- ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝
-
- Final Score: {Score}
- Pause Count: {PauseCount}
-
- Press enter to play again
- Press escape to close the game
- """);
- Console.CursorVisible = false;
- StartGame();
- RestartGame();
-}
+ int yAfterFall = tetromino.Y;
+ bool collision = false;
-void StartGame(ConsoleKey key = ConsoleKey.Enter)
-{
- ConsoleKey input = default;
- while (input != key && !CloseGame)
+ if (tetromino.Y + tetromino.Shape.Length + 2 > field.Length)
{
- input = Console.ReadKey(true).Key;
- if (input is ConsoleKey.Escape)
- {
- CloseGame = true;
- return;
- }
+ yAfterFall = field.Length - tetromino.Shape.Length + 1;
}
-}
-
-void RestartGame()
-{
- PLAYFIELD = (string[])FIELD.Clone();
- FallSpeedMilliSeconds = 1000;
- Score = 0;
- TETROMINO = new()
- {
- Shape = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
-
- AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
-}
-
-void PauseGame()
-{
- PauseCount++;
- FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
- GameStatus = GameStatus.Paused;
- DrawFrame();
-
- ResumeGame();
-}
-
-void ResumeGame(ConsoleKey key = ConsoleKey.Enter)
-{
- ConsoleKey input = default;
- while (input != key && !CloseGame)
+ else
{
- input = Console.ReadKey(true).Key;
- if (input is ConsoleKey.Enter && GameStatus is GameStatus.Paused && FallTimer != null)
- {
- FallTimer.Change(0, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- return;
- }
+ yAfterFall += 2;
}
-}
-
-void AddScoreChangeSpeed(int value)
-{
- if (value >= 4) value++;
- Score += value;
-
- FallSpeedMilliSeconds = Score switch
- {
- > 100 => FallSpeedMilliSeconds = 050,
- > 070 => FallSpeedMilliSeconds = 100,
- > 060 => FallSpeedMilliSeconds = 200,
- > 050 => FallSpeedMilliSeconds = 300,
- > 040 => FallSpeedMilliSeconds = 500,
- > 030 => FallSpeedMilliSeconds = 700,
- > 020 => FallSpeedMilliSeconds = 800,
- > 010 => FallSpeedMilliSeconds = 900,
- _ => 1000,
- };
-}
-
-void TetrominoFall(object? e)
-{
- int yAfterFall = TETROMINO.Y;
- bool collision = false;
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else yAfterFall += 2;
-
- //Y Collision
- for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ // Y Collision
+ for (int xCollision = 0; xCollision < tetromino.Shape[0].Length;)
{
- for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ for (int yCollision = tetromino.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
{
- char exist = TETROMINO.Shape[yCollision][xCollision];
-
- if (exist is ' ') continue;
-
- char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
-
- if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
-
- if (lineYC[TETROMINO.X + xCollision] is not ' ' or '│')
+ char exist = tetromino.Shape[yCollision][xCollision];
+ if (exist is ' ')
+ {
+ continue;
+ }
+ char[] lineYC = field[yAfterFall + yCollision - 1].ToCharArray();
+ if (tetromino.X + xCollision < 0 || tetromino.X + xCollision > lineYC.Length)
+ {
+ continue;
+ }
+ if (lineYC[tetromino.X + xCollision] is not ' ' or '│')
{
char[][] lastFrame = DrawLastFrame(yAfterFall);
for (int y = 0; y < lastFrame.Length; y++)
{
- PLAYFIELD[y] = new string(lastFrame[y]);
+ field[y] = new string(lastFrame[y]);
}
-
- TETROMINO.X = INITIALTETROMINOX;
- TETROMINO.Y = INITIALTETROMINOY;
- TETROMINO.Shape = TETROMINO.Next;
- TETROMINO.Next = TETROMINOS[Random.Shared.Next(0, TETROMINOS.Length)];
-
- xCollision = TETROMINO.Shape[0].Length;
+ tetromino.X = initialX;
+ tetromino.Y = initialY;
+ tetromino.Shape = tetromino.Next;
+ tetromino.Next = tetrominos[Random.Shared.Next(0, tetrominos.Length)];
+ xCollision = tetromino.Shape[0].Length;
collision = true;
break;
}
}
-
xCollision += 3;
}
- if (!collision) TETROMINO.Y = yAfterFall;
+ if (!collision)
+ {
+ tetromino.Y = yAfterFall;
+ }
- //Clean Lines
+ // Clean Lines
int clearedLines = 0;
- for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ for (int lineIndex = field.Length - 1; lineIndex >= 0; lineIndex--)
{
- string line = PLAYFIELD[lineIndex];
+ string line = field[lineIndex];
bool notCompleted = line.Any(e => e is ' ');
-
- if (lineIndex is 0 || lineIndex == PLAYFIELD.Length - 1) continue;
-
+ if (lineIndex is 0 || lineIndex == field.Length - 1)
+ {
+ continue;
+ }
if (!notCompleted)
{
- PLAYFIELD[lineIndex] = "│ │";
+ field[lineIndex] = "│ │";
clearedLines++;
-
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
- if (PLAYFIELD[lineM - 1] is "╭──────────────────────────────╮")
+ if (field[lineM - 1] is "╭──────────────────────────────╮")
{
- PLAYFIELD[lineM] = "│ │";
+ field[lineM] = "│ │";
continue;
}
-
- PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ field[lineM] = field[lineM - 1];
}
-
lineIndex++;
}
}
-
- AddScoreChangeSpeed(clearedLines / 2);
-
- //VerifiedCollision
- if (Collision(Direction.None) && FallTimer is not null) Gameover();
+ clearedLines /= 2;
+ if (clearedLines > 0)
+ {
+ int value = clearedLines /= 2 switch
+ {
+ 1 => 1,
+ 2 => 3,
+ 3 => 6,
+ 4 => 9,
+ _ => throw new NotImplementedException(),
+ };
+ score += value;
+ fallSpeed = GetFallSpeed();
+ }
+ if (Collision(Direction.None))
+ {
+ gameOver = true;
+ }
+ else
+ {
+ DrawFrame();
+ timer.Restart();
+ }
}
void HardDrop()
{
- int y = TETROMINO.Y;
- int x = TETROMINO.X;
- var shapeScope = TETROMINO.Shape;
- for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ int y = tetromino.Y;
+ int x = tetromino.X;
+ for (int yField = field.Length - tetromino.Shape.Length - borderSize; yField >= 0; yField -= 2)
{
- if (CollisionBottom(yField, y, shapeScope)) continue;
- TETROMINO.Y = yField;
+ if (CollisionBottom(yField, y, tetromino.Shape))
+ {
+ continue;
+ }
+ tetromino.Y = yField;
break;
}
+ DrawFrame();
+ timer.Restart();
}
void TetrominoSpin(Direction spinDirection)
{
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- int yScope = TETROMINO.Y;
- int xScope = TETROMINO.X;
- string[] newShape = new string[shapeScope[0].Length / 3 * 2];
+ int yScope = tetromino.Y;
+ int xScope = tetromino.X;
+ string[] newShape = new string[tetromino.Shape[0].Length / 3 * 2];
int newY = 0;
int rowEven = 0;
int rowOdd = 1;
- //Turn
- for (int y = 0; y < shapeScope.Length;)
+ // Turn
+ for (int y = 0; y < tetromino.Shape.Length;)
{
switch (spinDirection)
{
case Direction.Right:
- SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ SpinRight(newShape, tetromino.Shape, ref newY, rowEven, rowOdd, y);
break;
case Direction.Left:
- SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ SpinLeft(newShape, tetromino.Shape, ref newY, rowEven, rowOdd, y);
break;
}
-
newY = 0;
rowEven += 2;
rowOdd += 2;
y += 2;
}
- //Old Pivot
+ // Old Pivot
(int y, int x) offsetOP = (0, 0);
- for (int y = 0; y < shapeScope.Length; y += 2)
+ for (int y = 0; y < tetromino.Shape.Length; y += 2)
{
- for (int x = 0; x < shapeScope[y].Length; x += 3)
+ for (int x = 0; x < tetromino.Shape[y].Length; x += 3)
{
- if (shapeScope[y][x] is 'x')
+ if (tetromino.Shape[y][x] is 'x')
{
offsetOP = (y / 2, x / 3);
- y = shapeScope.Length;
+ y = tetromino.Shape.Length;
break;
}
}
}
- //New Pivot
+ // New Pivot
(int y, int x) offsetNP = (0, 0);
for (int y = 0; y < newShape.Length; y += 2)
{
@@ -789,13 +770,13 @@ void TetrominoSpin(Direction spinDirection)
yScope += (offsetOP.y - offsetNP.y) * 2;
xScope += (offsetOP.x - offsetNP.x) * 3;
- //Tetromino Square(O) special case
+ // Tetromino Square(O) special case
if (newShape.Length / 2 == newShape[0].Length / 3)
{
- yScope = TETROMINO.Y;
- xScope = TETROMINO.X;
+ yScope = tetromino.Y;
+ xScope = tetromino.X;
}
- //Tetromino I special case
+ // Tetromino I special case
else if (newShape.Length is 8 && newShape[0].Length is 3 && offsetNP.y is 2)
{
newShape[2] = "x─╮";
@@ -803,23 +784,30 @@ void TetrominoSpin(Direction spinDirection)
yScope += 2;
}
- if (xScope < 1 || yScope < 1) return;
+ if (xScope < 1 || yScope < 1)
+ {
+ return;
+ }
- //Verified Collision
+ // Verified Collision
for (int y = 0; y < newShape.Length - 1; y++)
{
for (int x = 0; x < newShape[y].Length; x++)
{
- if (newShape[y][x] is ' ') continue;
-
- char c = PLAYFIELD[yScope + y][xScope + x];
- if (c is not ' ') return;
+ if (newShape[y][x] is ' ')
+ {
+ continue;
+ }
+ char c = field[yScope + y][xScope + x];
+ if (c is not ' ')
+ {
+ return;
+ }
}
}
-
- TETROMINO.Y = yScope;
- TETROMINO.X = xScope;
- TETROMINO.Shape = newShape;
+ tetromino.Y = yScope;
+ tetromino.X = xScope;
+ tetromino.Shape = newShape;
}
void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
@@ -831,7 +819,6 @@ void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int
newShape[newY] += shape[rowEven][x - xS];
newShape[newY + 1] += shape[rowOdd][x - xS];
}
-
newY += 2;
}
}
@@ -845,27 +832,15 @@ void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int
newShape[newY] = "";
newShape[newY + 1] = "";
}
-
for (int xS = 0; xS <= 2; xS++)
{
- newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString(CultureInfo.InvariantCulture));
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString(CultureInfo.InvariantCulture));
}
-
newY += 2;
}
}
-void SleepAfterRender()
-{
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
- if (sleep > TimeSpan.Zero)
- {
- Thread.Sleep(sleep);
- }
- Stopwatch.Restart();
-}
-
class Tetromino
{
public required string[] Shape { get; set; }
@@ -876,14 +851,7 @@ class Tetromino
enum Direction
{
+ None,
Right,
Left,
- None
}
-
-enum GameStatus
-{
- Gameover,
- Playing,
- Paused
-}
\ No newline at end of file
diff --git a/Projects/Tetris/README.md b/Projects/Tetris/README.md
index 1d91366e..7cac7dd6 100644
--- a/Projects/Tetris/README.md
+++ b/Projects/Tetris/README.md
@@ -25,28 +25,63 @@
Well, just tetris!
-## Input
+```
+╭──────────────────────────────╮╭─────────╮
+│ ││ ╭─╮ │
+│ ││ ╰─╯ │
+│ ││╭─╮╭─╮╭─╮│
+│ ││╰─╯╰─╯╰─╯│
+│ ││ │
+│ ││ │
+│ ││ │
+│ ││ │
+│ │╰─────────╯
+│ │╭─────────╮
+│ ││ 12│
+│ │╰─────────╯
+│ ╭─╮ │
+│ ╰─╯ │
+│ ╭─╮ │
+│ ╰─╯ │
+│ ╭─╮ │
+│ ╰─╯ │
+│ ╭─╮ │
+│ ╰─╯ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ │
+│ ••• │
+│ ••• │
+│ ╭─╮╭─╮╭─╮••• │
+│ ╰─╯╰─╯╰─╯••• │
+│ ╭─╮╭─╮╭─╮╭─╮••• │
+│ ╰─╯╰─╯╰─╯╰─╯••• │
+│ ╭─╮╭─╮╭─╮╭─╮╭─╮•••╭─╮ ╭─╮│
+│ ╰─╯╰─╯╰─╯╰─╯╰─╯•••╰─╯ ╰─╯│
+╰──────────────────────────────╯
+```
-|Key|Action|
-|---|---|
-|`↓`, `S`, `←`, `A`, `→`, `D` |Move |
-|`Q` |Spin Left |
-|`E` |Spin Right |
-|`P` |Pause, enter to resume |
-|`R` |Change Text Color |
+## Input
-Debug controls (only if debug mode its active, it may break the game):
|Key|Action|
|---|---|
-|`Spacebar` |Clean Field |
-|`X` |Add 10 score points |
-|`I` |change active tetromino to I|
-|`J` |change active tetromino to J|
-|`L` |change active tetromino to L|
-|`O` |change active tetromino to O|
-|`C` |change active tetromino to S|
-|`T` |change active tetromino to T|
-|`Z` |change active tetromino to Z|
+| `←` or `A` | Move Left |
+| `→` or `D` | Move Right |
+| `↓` or `S` | Fall Faster |
+| `Q` | Spin Left |
+| `E` | Spin Right |
+| `P` | Pause or Resume |
+| `Enter` | Confirm |
+| `Escape` | Close Game |
## Downloads
From c5ba97dcd35045f2b0a71e76cb7c8e0609e98669 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 17:18:11 -0500
Subject: [PATCH 18/24] pause while console small fix
---
Projects/Tetris/Program.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 43510fa4..19f50644 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -308,12 +308,13 @@ void HandlePlayerInput()
if (timer.IsRunning)
{
timer.Stop();
+ DrawFrame();
}
else if (!consoleTooSmallScreen)
{
timer.Start();
+ DrawFrame();
}
- DrawFrame();
break;
case ConsoleKey.Spacebar:
if (timer.IsRunning)
From c974ec1aee958eab8f7b24f804839608a1483df5 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 17:42:19 -0500
Subject: [PATCH 19/24] game over screen and synch to web and clearedLines bug
fix
---
Projects/Tetris/Program.cs | 6 +-
Projects/Website/Games/Tetris/Tetris.cs | 963 +++++++++++++-----------
Projects/Website/Pages/Tetris.razor | 6 +-
3 files changed, 524 insertions(+), 451 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 19f50644..2543ef4c 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -227,8 +227,8 @@ [Enter] start game
Final Score: {score}
- Press enter to play again
- Press escape to close the game
+ [Enter] return to menu
+ [Escape] close game
""");
Console.CursorVisible = false;
bool gameOverScreen = true;
@@ -672,7 +672,7 @@ void TetrominoFall()
clearedLines /= 2;
if (clearedLines > 0)
{
- int value = clearedLines /= 2 switch
+ int value = clearedLines switch
{
1 => 1,
2 => 3,
diff --git a/Projects/Website/Games/Tetris/Tetris.cs b/Projects/Website/Games/Tetris/Tetris.cs
index 00563e63..ce6b007c 100644
--- a/Projects/Website/Games/Tetris/Tetris.cs
+++ b/Projects/Website/Games/Tetris/Tetris.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Text;
using System.Linq;
+using System.Globalization;
namespace Website.Games.Tetris;
@@ -13,57 +14,17 @@ public class Tetris
public async Task Run()
{
- Console.OutputEncoding = Encoding.UTF8;
- Console.CursorVisible = false;
- Stopwatch Stopwatch = Stopwatch.StartNew();
+ #region Constants
- string[] FIELD = new[]
+ string[] emptyField = new string[42];
+ emptyField[0] = "╭──────────────────────────────╮";
+ for (int i = 1; i < 41; i++)
{
- "╭──────────────────────────────╮",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "│ │",
- "╰──────────────────────────────╯"
- };
+ emptyField[i] = "│ │";
+ }
+ emptyField[^1] = "╰──────────────────────────────╯";
- string[] NEXTTETROMINO = new[]
+ string[] nextTetrominoBorder = new[]
{
"╭─────────╮",
"│ │",
@@ -77,13 +38,15 @@ public async Task Run()
"╰─────────╯"
};
- string[] SCORE = new[]{
+ string[] scoreBorder = new[]
+ {
"╭─────────╮",
"│ │",
"╰─────────╯"
};
- string[] PAUSE = new[]{
+ string[] pauseRender = new[]
+ {
"█████╗ ███╗ ██╗██╗█████╗█████╗",
"██╔██║██╔██╗██║██║██╔══╝██╔══╝",
"█████║█████║██║██║ ███╗ █████╗",
@@ -92,12 +55,12 @@ public async Task Run()
"╚═╝ ╚═╝╚═╝╚════╝╚════╝╚════╝",
};
- string[][] TETROMINOS = new[]
+ string[][] tetrominos = new[]
{
new[]{
"╭─╮",
"╰─╯",
- "╭─╮",
+ "x─╮",
"╰─╯",
"╭─╮",
"╰─╯",
@@ -107,128 +70,272 @@ public async Task Run()
new[]{
"╭─╮ ",
"╰─╯ ",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
" ╭─╮",
" ╰─╯",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
"╭─╮╭─╮",
"╰─╯╰─╯",
- "╭─╮╭─╮",
+ "x─╮╭─╮",
"╰─╯╰─╯"
},
new[]{
" ╭─╮╭─╮",
" ╰─╯╰─╯",
- "╭─╮╭─╮ ",
+ "╭─╮x─╮ ",
"╰─╯╰─╯ "
},
new[]{
" ╭─╮ ",
" ╰─╯ ",
- "╭─╮╭─╮╭─╮",
+ "╭─╮x─╮╭─╮",
"╰─╯╰─╯╰─╯"
},
new[]{
"╭─╮╭─╮ ",
"╰─╯╰─╯ ",
- " ╭─╮╭─╮",
+ " x─╮╭─╮",
" ╰─╯╰─╯"
},
};
- string[] PLAYFIELD = (string[])FIELD.Clone();
- const int BORDER = 1;
- int FallSpeedMilliSeconds = 1000;
- bool CloseGame = false;
- int Score = 0;
- int PauseCount = 0;
- GameStatus GameStatus = GameStatus.Gameover;
+ const int borderSize = 1;
- Random RamdomGenerator = new();
+ int initialX = (emptyField[0].Length / 2) - 3;
+ int initialY = 1;
- int INITIALTETROMINOX = Convert.ToInt16(PLAYFIELD[0].Length / 2) - 3;
- int INITIALTETROMINOY = 1;
- Tetromino TETROMINO = new()
- {
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
+ int consoleWidthMin = 44;
+ int consoleHeightMin = 43;
- AutoResetEvent AutoEvent = new AutoResetEvent(false);
- Timer? FallTimer = null;
- GameStatus = GameStatus.Playing;
-
- await Console.WriteLine();
- await Console.WriteLine(" ██████╗█████╗██████╗█████╗ ██╗█████╗");
- await Console.WriteLine(" ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝");
- await Console.WriteLine(" ██║ █████╗ ██║ █████╔╝██║ ███╗ ");
- await Console.WriteLine(" ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗");
- await Console.WriteLine(" ██║ █████╗ ██║ ██║ ██║██║█████║");
- await Console.WriteLine(" ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝");
-
- await Console.WriteLine();
- await Console.WriteLine(" Controls:");
- await Console.WriteLine(" WASD or ARROW to move");
- await Console.WriteLine(" Q or E to spin left or right");
- await Console.WriteLine(" P to paused the game, press enter");
- await Console.WriteLine(" key to resume");
- await Console.WriteLine();
- await Console.Write(" Press enter to start tetris...");
- Console.CursorVisible = false;
- await StartGame();
- await Console.Clear();
+ #endregion
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
+ Stopwatch timer = new();
+ bool closeRequested = false;
+ bool gameOver;
+ int score = 0;
+ TimeSpan fallSpeed;
+ string[] field;
+ Tetromino tetromino;
+ int consoleWidth = Console.WindowWidth;
+ int consoleHeight = Console.WindowHeight;
+ bool consoleTooSmallScreen = false;
- while (!CloseGame)
+ Console.OutputEncoding = Encoding.UTF8;
+ while (!closeRequested)
{
- if (CloseGame)
+ await Console.Clear();
+ await Console.Write("""
+
+ ██████╗█████╗██████╗█████╗ ██╗█████╗
+ ╚═██╔═╝██╔══╝╚═██╔═╝██╔═██╗██║██╔══╝
+ ██║ █████╗ ██║ █████╔╝██║ ███╗
+ ██║ ██╔══╝ ██║ ██╔═██╗██║ ██╗
+ ██║ █████╗ ██║ ██║ ██║██║█████║
+ ╚═╝ ╚════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚════╝
+
+ Controls:
+
+ [A] or [←] move left
+ [D] or [→] move right
+ [S] or [↓] fall faster
+ [Q] spin left
+ [E] spin right
+ [Spacebar] drop
+ [P] pause and unpause
+ [Escape] close game
+ [Enter] start game
+ """);
+ bool mainMenuScreen = true;
+ while (!closeRequested && mainMenuScreen)
{
- break;
+ Console.CursorVisible = false;
+ switch ((await Console.ReadKey(true)).Key)
+ {
+ case ConsoleKey.Enter: mainMenuScreen = false; break;
+ case ConsoleKey.Escape: closeRequested = true; break;
+ }
}
+ Initialize();
+ await Console.Clear();
+ await DrawFrame();
+ while (!closeRequested && !gameOver)
+ {
+ // if user changed the size of the console, we need to clear the console
+ if (consoleWidth != Console.WindowWidth || consoleHeight != Console.WindowHeight)
+ {
+ consoleWidth = Console.WindowWidth;
+ consoleHeight = Console.WindowHeight;
+ if (!consoleTooSmallScreen)
+ {
+ await Console.Clear();
+ await DrawFrame();
+ }
+ else
+ {
+ consoleTooSmallScreen = false;
+ }
+ }
+
+ // if the console isn't big enough to render the game, pause the game and tell the user
+ if (consoleWidth < consoleWidthMin || consoleHeight < consoleHeightMin)
+ {
+ if (!consoleTooSmallScreen)
+ {
+ await Console.Clear();
+ await Console.Write($"Please increase size of console to at least {consoleWidthMin}x{consoleHeightMin}. Current size is {consoleWidth}x{consoleHeight}.");
+ timer.Stop();
+ consoleTooSmallScreen = true;
+ }
+ }
+ else if (consoleTooSmallScreen)
+ {
+ consoleTooSmallScreen = false;
+ await Console.Clear();
+ await DrawFrame();
+ }
- await PlayerControl();
- if (GameStatus == GameStatus.Playing)
+ await HandlePlayerInput();
+ if (closeRequested || gameOver)
+ {
+ break;
+ }
+ if (timer.IsRunning && timer.Elapsed > fallSpeed)
+ {
+ await TetrominoFall();
+ if (closeRequested || gameOver)
+ {
+ break;
+ }
+ await DrawFrame();
+ }
+ }
+ if (closeRequested)
{
- await DrawFrame();
- await SleepAfterRender();
+ break;
+ }
+ await Console.Clear();
+ await Console.Write($"""
+
+ ██████╗ █████╗ ██ ██╗█████╗
+ ██╔════╝ ██╔══██╗███ ███║██╔══╝
+ ██║ ███╗███████║██╔██═██║█████╗
+ ██║ ██║██╔══██║██║ ██║██╔══╝
+ ╚██████╔╝██║ ██║██║ ██║█████╗
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝
+ ██████╗██╗ ██╗█████╗█████╗
+ ██ ██║██║ ██║██╔══╝██╔═██╗
+ ██ ██║██║ ██║█████╗█████╔╝
+ ██ ██║╚██╗██╔╝██╔══╝██╔═██╗
+ ██████║ ╚███╔╝ █████╗██║ ██║
+ ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝
+
+ Final Score: {score}
+
+ [Enter] return to menu
+ [Escape] close game
+ """);
+ Console.CursorVisible = false;
+ bool gameOverScreen = true;
+ while (!closeRequested && gameOverScreen)
+ {
+ Console.CursorVisible = false;
+ switch ((await Console.ReadKey(true)).Key)
+ {
+ case ConsoleKey.Enter: gameOverScreen = false; break;
+ case ConsoleKey.Escape: closeRequested = true; break;
+ }
}
}
+ await Console.Clear();
+ await Console.WriteLine("Tetris was closed.");
+ Console.CursorVisible = true;
+ await Console.Refresh();
+
+ void Initialize()
+ {
+ gameOver = false;
+ score = 0;
+ field = emptyField[..];
+ initialX = (field[0].Length / 2) - 3;
+ initialY = 1;
+ tetromino = new()
+ {
+ Shape = tetrominos[Random.Shared.Next(0, tetrominos.Length)],
+ Next = tetrominos[Random.Shared.Next(0, tetrominos.Length)],
+ X = initialX,
+ Y = initialY
+ };
+ fallSpeed = GetFallSpeed();
+ timer.Restart();
+ }
- async Task PlayerControl()
+ async Task HandlePlayerInput()
{
- while (await Console.KeyAvailable() && GameStatus == GameStatus.Playing)
+ while ((await Console.KeyAvailable()) && !closeRequested)
{
switch ((await Console.ReadKey(true)).Key)
{
case ConsoleKey.A or ConsoleKey.LeftArrow:
- if (Collision(Direction.Left)) break;
- TETROMINO.X -= 3;
+ if (timer.IsRunning && !Collision(Direction.Left))
+ {
+ tetromino.X -= 3;
+ }
+ await DrawFrame();
break;
case ConsoleKey.D or ConsoleKey.RightArrow:
- if (Collision(Direction.Right)) break;
- TETROMINO.X += 3;
+ if (timer.IsRunning && !Collision(Direction.Right))
+ {
+ tetromino.X += 3;
+ }
+ await DrawFrame();
break;
case ConsoleKey.S or ConsoleKey.DownArrow:
- FallTimer.Change(0, FallSpeedMilliSeconds);
+ if (timer.IsRunning)
+ {
+ await TetrominoFall();
+ }
break;
case ConsoleKey.E:
- TetrominoSpin(Direction.Right);
+ if (timer.IsRunning)
+ {
+ TetrominoSpin(Direction.Right);
+ await DrawFrame();
+ }
break;
case ConsoleKey.Q:
- TetrominoSpin(Direction.Left);
+ if (timer.IsRunning)
+ {
+ TetrominoSpin(Direction.Left);
+ await DrawFrame();
+ }
break;
case ConsoleKey.P:
- PauseGame();
+ if (timer.IsRunning)
+ {
+ timer.Stop();
+ await DrawFrame();
+ }
+ else if (!consoleTooSmallScreen)
+ {
+ timer.Start();
+ await DrawFrame();
+ }
break;
+ case ConsoleKey.Spacebar:
+ if (timer.IsRunning)
+ {
+ await HardDrop();
+ }
+ break;
+ case ConsoleKey.Escape:
+ closeRequested = true;
+ return;
}
}
}
@@ -236,130 +343,131 @@ async Task PlayerControl()
async Task DrawFrame()
{
bool collision = false;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
+ char[][] frame = new char[field.Length][];
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
+ // Field
+ for (int y = 0; y < field.Length; y++)
{
- frame[y] = PLAYFIELD[y].ToCharArray();
+ frame[y] = field[y].ToCharArray();
}
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ // Tetromino
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
- int tY = yScope + y;
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
+ int tY = tetromino.Y + y;
+ int tX = tetromino.X + x;
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
+ if (charToReplace is not ' ')
{
collision = true;
break;
}
-
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
- //Draw Preview
- for (int yField = PLAYFIELD.Length - shapeScope.Length - BORDER; yField >= 0; yField -= 2)
+ // Draw Preview
+ for (int yField = field.Length - tetromino.Shape.Length - borderSize; yField >= 0; yField -= 2)
{
- if (CollisionPreview(yField, yScope, shapeScope)) continue;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ if (CollisionBottom(yField, tetromino.Y, tetromino.Shape))
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ continue;
+ }
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
+ {
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
int tY = yField + y;
-
- if (yScope + shapeScope.Length > tY) continue;
-
- int tX = TETROMINO.X + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
+ if (tetromino.Y + tetromino.Shape.Length > tY)
+ {
+ continue;
+ }
+ int tX = tetromino.X + x;
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
+ if (charToReplace is not ' ')
{
collision = true;
break;
}
-
frame[tY][tX] = '•';
}
}
-
break;
}
- //Next Square
- for (int y = 0; y < NEXTTETROMINO.Length; y++)
- {
- frame[y] = frame[y].Concat(NEXTTETROMINO[y]).ToArray();
- }
-
- //Score Square
- for (int y = 0; y < SCORE.Length; y++)
+ // Next
+ for (int y = 0; y < nextTetrominoBorder.Length; y++)
{
- int sY = NEXTTETROMINO.Length + y;
- frame[sY] = frame[sY].Concat(SCORE[y]).ToArray();
+ frame[y] = frame[y].Concat(nextTetrominoBorder[y]).ToArray();
}
-
- //Draw Next
- for (int y = 0; y < nextShapeScope.Length; y++)
+ for (int y = 0; y < tetromino.Next.Length; y++)
{
- for (int x = 0; x < nextShapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Next[y].Length; x++)
{
- int tY = y + BORDER;
- int tX = PLAYFIELD[y].Length + x + BORDER;
- char charTetromino = nextShapeScope[y][x];
+ int tY = y + borderSize;
+ int tX = field[y].Length + x + borderSize;
+ char charTetromino = tetromino.Next[y][x];
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
- //Draw Score
- char[] score = Score.ToString().ToCharArray();
- for (int scoreX = score.Length - 1; scoreX >= 0; scoreX--)
+ // Score
+ for (int y = 0; y < scoreBorder.Length; y++)
+ {
+ int sY = nextTetrominoBorder.Length + y;
+ frame[sY] = frame[sY].Concat(scoreBorder[y]).ToArray();
+ }
+ char[] scoreRender = score.ToString(CultureInfo.InvariantCulture).ToCharArray();
+ for (int scoreX = scoreRender.Length - 1; scoreX >= 0; scoreX--)
{
- int sY = NEXTTETROMINO.Length + BORDER;
- int sX = frame[sY].Length - (score.Length - scoreX) - BORDER;
- frame[sY][sX] = score[scoreX];
+ int sY = nextTetrominoBorder.Length + borderSize;
+ int sX = frame[sY].Length - (scoreRender.Length - scoreX) - borderSize;
+ frame[sY][sX] = scoreRender[scoreX];
}
- //Draw Pause
- if (GameStatus == GameStatus.Paused)
+ // Pause
+ if (!timer.IsRunning)
{
- for (int y = 0; y < PAUSE.Length; y++)
+ for (int y = 0; y < pauseRender.Length; y++)
{
- int fY = (PLAYFIELD.Length / 2) + y - PAUSE.Length;
- for (int x = 0; x < PAUSE[y].Length; x++)
+ int fY = (field.Length / 2) + y - pauseRender.Length;
+ for (int x = 0; x < pauseRender[y].Length; x++)
{
- int fX = x + BORDER;
+ int fX = x + borderSize;
- if (x >= PLAYFIELD[fY].Length) break;
+ if (x >= field[fY].Length) break;
- frame[fY][fX] = PAUSE[y][x];
+ frame[fY][fX] = pauseRender[y][x];
}
}
}
- //Create Render
StringBuilder render = new();
for (int y = 0; y < frame.Length; y++)
{
render.AppendLine(new string(frame[y]));
}
-
- await Console.Clear();
+ await Console.SetCursorPosition(0, 0);
await Console.Write(render);
Console.CursorVisible = false;
}
@@ -368,91 +476,91 @@ char[][] DrawLastFrame(int yS)
{
bool collision = false;
int yScope = yS - 2;
- int xScope = TETROMINO.X;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- string[] nextShapeScope = (string[])TETROMINO.Next.Clone();
- char[][] frame = new char[PLAYFIELD.Length][];
-
- //Field
- for (int y = 0; y < PLAYFIELD.Length; y++)
+ int xScope = tetromino.X;
+ char[][] frame = new char[field.Length][];
+ for (int y = 0; y < field.Length; y++)
{
- frame[y] = PLAYFIELD[y].ToCharArray();
+ frame[y] = field[y].ToCharArray();
}
-
- //Draw Tetromino
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
int tY = yScope + y;
int tX = xScope + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
+ if (charToReplace is not ' ')
{
collision = true;
break;
}
-
+ if (charTetromino is 'x')
+ {
+ charTetromino = '╭';
+ }
frame[tY][tX] = charTetromino;
}
}
-
return frame;
}
bool Collision(Direction direction)
{
- int xNew = TETROMINO.X;
- int yScope = TETROMINO.Y;
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
+ int xNew = tetromino.X;
bool collision = false;
-
switch (direction)
{
case Direction.Right:
xNew += 3;
- if (xNew + shapeScope[0].Length > PLAYFIELD[0].Length - BORDER) collision = true;
+ if (xNew + tetromino.Shape[0].Length > field[0].Length - borderSize)
+ {
+ collision = true;
+ }
break;
case Direction.Left:
xNew -= 3;
- if (xNew < BORDER) collision = true;
+ if (xNew < borderSize)
+ {
+ collision = true;
+ }
break;
case Direction.None:
break;
}
-
- if (collision) return collision;
-
- for (int y = 0; y < shapeScope.Length && !collision; y++)
+ if (collision)
+ {
+ return collision;
+ }
+ for (int y = 0; y < tetromino.Shape.Length && !collision; y++)
{
- for (int x = 0; x < shapeScope[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x++)
{
- int tY = yScope + y;
+ int tY = tetromino.Y + y;
int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
- char charTetromino = shapeScope[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
+ char charToReplace = field[tY][tX];
+ char charTetromino = tetromino.Shape[y][x];
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
+ if (charToReplace is not ' ')
{
collision = true;
break;
}
}
}
-
return collision;
}
- bool CollisionPreview(int initY, int yScope, string[] shape)
+ bool CollisionBottom(int initY, int yScope, string[] shape)
{
- int xNew = TETROMINO.X;
-
+ int xNew = tetromino.X;
for (int yUpper = initY; yUpper >= yScope; yUpper -= 2)
{
for (int y = shape.Length - 1; y >= 0; y -= 2)
@@ -461,270 +569,257 @@ bool CollisionPreview(int initY, int yScope, string[] shape)
{
int tY = yUpper + y;
int tX = xNew + x;
- char charToReplace = PLAYFIELD[tY][tX];
+ char charToReplace = field[tY][tX];
char charTetromino = shape[y][x];
-
- if (charTetromino == ' ') continue;
-
- if (charToReplace != ' ')
+ if (charTetromino is ' ')
+ {
+ continue;
+ }
+ if (charToReplace is not ' ')
{
return true;
}
}
}
}
-
return false;
}
- async void Gameover()
- {
- GameStatus = GameStatus.Gameover;
- AutoEvent.Dispose();
- FallTimer.Dispose();
-
- await SleepAfterRender();
-
- await Console.Clear();
- await Console.WriteLine();
- await Console.WriteLine(" ██████╗ █████╗ ██ ██╗█████╗");
- await Console.WriteLine(" ██╔════╝ ██╔══██╗███ ███║██╔══╝");
- await Console.WriteLine(" ██║ ███╗███████║██╔██═██║█████╗");
- await Console.WriteLine(" ██║ ██║██╔══██║██║ ██║██╔══╝");
- await Console.WriteLine(" ╚██████╔╝██║ ██║██║ ██║█████╗");
- await Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚════╝");
- await Console.WriteLine(" ██████╗██╗ ██╗█████╗█████╗ ");
- await Console.WriteLine(" ██ ██║██║ ██║██╔══╝██╔═██╗ ");
- await Console.WriteLine(" ██ ██║██║ ██║█████╗█████╔╝ ");
- await Console.WriteLine(" ██ ██║╚██╗██╔╝██╔══╝██╔═██╗ ");
- await Console.WriteLine(" ██████║ ╚███╔╝ █████╗██║ ██║ ");
- await Console.WriteLine(" ╚═════╝ ╚══╝ ╚════╝╚═╝ ╚═╝ ");
-
- await Console.WriteLine();
- await Console.WriteLine($" Final Score: {Score}");
- await Console.WriteLine($" Pause Count: {PauseCount}");
- await Console.WriteLine();
- await Console.WriteLine(" Press enter to play again");
- await Console.WriteLine(" Press escape to close the game");
- Console.CursorVisible = false;
- await StartGame();
- RestartGame();
- }
-
- async Task StartGame(ConsoleKey key = ConsoleKey.Enter)
- {
- ConsoleKey input = default;
- while (input != key && !CloseGame)
- {
- input = (await Console.ReadKey(true)).Key;
- if (input is ConsoleKey.Escape)
+ TimeSpan GetFallSpeed() =>
+ TimeSpan.FromMilliseconds(
+ score switch
{
- CloseGame = true;
- return;
- }
- }
- }
-
- void RestartGame()
- {
- PLAYFIELD = (string[])FIELD.Clone();
- FallSpeedMilliSeconds = 1000;
- Score = 0;
- TETROMINO = new()
- {
- Shape = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)],
- X = INITIALTETROMINOX,
- Y = INITIALTETROMINOY
- };
-
- AutoEvent = new AutoResetEvent(false);
- FallTimer = new Timer(TetrominoFall, AutoEvent, FallSpeedMilliSeconds, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- }
-
- async void PauseGame()
+ > 162 => 100,
+ > 144 => 200,
+ > 126 => 300,
+ > 108 => 400,
+ > 090 => 500,
+ > 072 => 600,
+ > 054 => 700,
+ > 036 => 800,
+ > 018 => 900,
+ _ => 1000,
+ });
+
+ async Task TetrominoFall()
{
- PauseCount++;
- FallTimer.Change(Timeout.Infinite, Timeout.Infinite);
- GameStatus = GameStatus.Paused;
- await DrawFrame();
-
- await ResumeGame();
- }
+ int yAfterFall = tetromino.Y;
+ bool collision = false;
- async Task ResumeGame(ConsoleKey key = ConsoleKey.Enter)
- {
- ConsoleKey input = default;
- while (input != key && !CloseGame)
+ if (tetromino.Y + tetromino.Shape.Length + 2 > field.Length)
{
- input = (await Console.ReadKey(true)).Key;
- if (input is ConsoleKey.Enter && GameStatus == GameStatus.Paused && FallTimer != null)
- {
- FallTimer.Change(0, FallSpeedMilliSeconds);
- GameStatus = GameStatus.Playing;
- return;
- }
+ yAfterFall = field.Length - tetromino.Shape.Length + 1;
}
- }
-
- void AddScoreChangeSpeed(int value)
- {
- Score += value;
-
- if (Score > 100) return;
-
- switch (Score)
+ else
{
- case 10:
- FallSpeedMilliSeconds = 900;
- break;
- case 20:
- FallSpeedMilliSeconds = 800;
- break;
- case 30:
- FallSpeedMilliSeconds = 700;
- break;
- case 40:
- FallSpeedMilliSeconds = 500;
- break;
- case 50:
- FallSpeedMilliSeconds = 300;
- break;
- case 60:
- FallSpeedMilliSeconds = 200;
- break;
- case 70:
- FallSpeedMilliSeconds = 100;
- break;
- case 100:
- FallSpeedMilliSeconds = 50;
- break;
+ yAfterFall += 2;
}
- }
-
- void TetrominoFall(object? e)
- {
- int yAfterFall = TETROMINO.Y;
- bool collision = false;
- if (TETROMINO.Y + TETROMINO.Shape.Length + 2 > PLAYFIELD.Length) yAfterFall = PLAYFIELD.Length - TETROMINO.Shape.Length + 1;
- else yAfterFall += 2;
-
- //Y Collision
- for (int xCollision = 0; xCollision < TETROMINO.Shape[0].Length;)
+ // Y Collision
+ for (int xCollision = 0; xCollision < tetromino.Shape[0].Length;)
{
- for (int yCollision = TETROMINO.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
+ for (int yCollision = tetromino.Shape.Length - 1; yCollision >= 0; yCollision -= 2)
{
- char exist = TETROMINO.Shape[yCollision][xCollision];
-
- if (exist == ' ') continue;
-
- char[] lineYC = PLAYFIELD[yAfterFall + yCollision - 1].ToCharArray();
-
- if (TETROMINO.X + xCollision < 0 || TETROMINO.X + xCollision > lineYC.Length) continue;
-
- if
- (
- lineYC[TETROMINO.X + xCollision] != ' ' &&
- lineYC[TETROMINO.X + xCollision] != '│'
- )
+ char exist = tetromino.Shape[yCollision][xCollision];
+ if (exist is ' ')
+ {
+ continue;
+ }
+ char[] lineYC = field[yAfterFall + yCollision - 1].ToCharArray();
+ if (tetromino.X + xCollision < 0 || tetromino.X + xCollision > lineYC.Length)
+ {
+ continue;
+ }
+ if (lineYC[tetromino.X + xCollision] is not ' ' or '│')
{
char[][] lastFrame = DrawLastFrame(yAfterFall);
for (int y = 0; y < lastFrame.Length; y++)
{
- PLAYFIELD[y] = new string(lastFrame[y]);
+ field[y] = new string(lastFrame[y]);
}
-
- TETROMINO.X = INITIALTETROMINOX;
- TETROMINO.Y = INITIALTETROMINOY;
- TETROMINO.Shape = TETROMINO.Next;
- TETROMINO.Next = TETROMINOS[RamdomGenerator.Next(0, TETROMINOS.Length)];
-
- xCollision = TETROMINO.Shape[0].Length;
+ tetromino.X = initialX;
+ tetromino.Y = initialY;
+ tetromino.Shape = tetromino.Next;
+ tetromino.Next = tetrominos[Random.Shared.Next(0, tetrominos.Length)];
+ xCollision = tetromino.Shape[0].Length;
collision = true;
break;
}
}
-
xCollision += 3;
}
- if (!collision) TETROMINO.Y = yAfterFall;
-
- //Clean Lines
- for (var lineIndex = PLAYFIELD.Length - 1; lineIndex >= 0; lineIndex--)
+ if (!collision)
{
- string line = PLAYFIELD[lineIndex];
- bool notCompleted = line.Any(e => e == ' ');
-
- if (lineIndex == 0 || lineIndex == PLAYFIELD.Length - 1) continue;
+ tetromino.Y = yAfterFall;
+ }
+ // Clean Lines
+ int clearedLines = 0;
+ for (int lineIndex = field.Length - 1; lineIndex >= 0; lineIndex--)
+ {
+ string line = field[lineIndex];
+ bool notCompleted = line.Any(e => e is ' ');
+ if (lineIndex is 0 || lineIndex == field.Length - 1)
+ {
+ continue;
+ }
if (!notCompleted)
{
- PLAYFIELD[lineIndex] = "│ │";
- AddScoreChangeSpeed(1);
-
+ field[lineIndex] = "│ │";
+ clearedLines++;
for (int lineM = lineIndex; lineM >= 1; lineM--)
{
- if (PLAYFIELD[lineM - 1] == "╭──────────────────────────────╮")
+ if (field[lineM - 1] is "╭──────────────────────────────╮")
{
- PLAYFIELD[lineM] = "│ │";
+ field[lineM] = "│ │";
continue;
}
-
- PLAYFIELD[lineM] = PLAYFIELD[lineM - 1];
+ field[lineM] = field[lineM - 1];
}
-
lineIndex++;
}
}
+ clearedLines /= 2;
+ if (clearedLines > 0)
+ {
+ int value = clearedLines switch
+ {
+ 1 => 1,
+ 2 => 3,
+ 3 => 6,
+ 4 => 9,
+ _ => throw new NotImplementedException(),
+ };
+ score += value;
+ fallSpeed = GetFallSpeed();
+ }
+ if (Collision(Direction.None))
+ {
+ gameOver = true;
+ }
+ else
+ {
+ await DrawFrame();
+ timer.Restart();
+ }
+ }
- //VerifiedCollision
- if (Collision(Direction.None) && FallTimer != null) Gameover();
+ async Task HardDrop()
+ {
+ int y = tetromino.Y;
+ int x = tetromino.X;
+ for (int yField = field.Length - tetromino.Shape.Length - borderSize; yField >= 0; yField -= 2)
+ {
+ if (CollisionBottom(yField, y, tetromino.Shape))
+ {
+ continue;
+ }
+ tetromino.Y = yField;
+ break;
+ }
+ await DrawFrame();
+ timer.Restart();
}
void TetrominoSpin(Direction spinDirection)
{
- string[] shapeScope = (string[])TETROMINO.Shape.Clone();
- int yScope = TETROMINO.Y;
- string[] newShape = new string[shapeScope[0].Length / 3 * 2];
+ int yScope = tetromino.Y;
+ int xScope = tetromino.X;
+ string[] newShape = new string[tetromino.Shape[0].Length / 3 * 2];
int newY = 0;
int rowEven = 0;
int rowOdd = 1;
- //Turn
- for (int y = 0; y < shapeScope.Length;)
+ // Turn
+ for (int y = 0; y < tetromino.Shape.Length;)
{
switch (spinDirection)
{
case Direction.Right:
- SpinRight(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ SpinRight(newShape, tetromino.Shape, ref newY, rowEven, rowOdd, y);
break;
case Direction.Left:
- SpinLeft(newShape, shapeScope, ref newY, rowEven, rowOdd, y);
+ SpinLeft(newShape, tetromino.Shape, ref newY, rowEven, rowOdd, y);
break;
}
-
newY = 0;
rowEven += 2;
rowOdd += 2;
y += 2;
}
- //Verified Collision
- for (int y = 0; y < newShape.Length - 1; y++)
+ // Old Pivot
+ (int y, int x) offsetOP = (0, 0);
+ for (int y = 0; y < tetromino.Shape.Length; y += 2)
{
- for (int x = 0; x < newShape[y].Length; x++)
+ for (int x = 0; x < tetromino.Shape[y].Length; x += 3)
{
- if (newShape[y][x] == ' ') continue;
+ if (tetromino.Shape[y][x] is 'x')
+ {
+ offsetOP = (y / 2, x / 3);
+ y = tetromino.Shape.Length;
+ break;
+ }
+ }
+ }
- char c = PLAYFIELD[yScope + y][TETROMINO.X + x];
- if (c != ' ') return;
+ // New Pivot
+ (int y, int x) offsetNP = (0, 0);
+ for (int y = 0; y < newShape.Length; y += 2)
+ {
+ for (int x = 0; x < newShape[y].Length; x += 3)
+ {
+ if (newShape[y][x] is 'x')
+ {
+ offsetNP = (y / 2, x / 3);
+ y = newShape.Length;
+ break;
+ }
}
}
- TETROMINO.Shape = newShape;
+ yScope += (offsetOP.y - offsetNP.y) * 2;
+ xScope += (offsetOP.x - offsetNP.x) * 3;
+
+ // Tetromino Square(O) special case
+ if (newShape.Length / 2 == newShape[0].Length / 3)
+ {
+ yScope = tetromino.Y;
+ xScope = tetromino.X;
+ }
+ // Tetromino I special case
+ else if (newShape.Length is 8 && newShape[0].Length is 3 && offsetNP.y is 2)
+ {
+ newShape[2] = "x─╮";
+ newShape[4] = "╭─╮";
+ yScope += 2;
+ }
+
+ if (xScope < 1 || yScope < 1)
+ {
+ return;
+ }
+
+ // Verified Collision
+ for (int y = 0; y < newShape.Length - 1; y++)
+ {
+ for (int x = 0; x < newShape[y].Length; x++)
+ {
+ if (newShape[y][x] is ' ')
+ {
+ continue;
+ }
+ char c = field[yScope + y][xScope + x];
+ if (c is not ' ')
+ {
+ return;
+ }
+ }
+ }
+ tetromino.Y = yScope;
+ tetromino.X = xScope;
+ tetromino.Shape = newShape;
}
void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int rowOdd, int y)
@@ -736,7 +831,6 @@ void SpinLeft(string[] newShape, string[] shape, ref int newY, int rowEven, int
newShape[newY] += shape[rowEven][x - xS];
newShape[newY + 1] += shape[rowOdd][x - xS];
}
-
newY += 2;
}
}
@@ -745,32 +839,19 @@ void SpinRight(string[] newShape, string[] shape, ref int newY, int rowEven, int
{
for (int x = 2; x < shape[y].Length; x += 3)
{
- if (newShape[newY] == null)
+ if (newShape[newY] is null)
{
newShape[newY] = "";
newShape[newY + 1] = "";
}
-
for (int xS = 0; xS <= 2; xS++)
{
- newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString());
- newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString());
+ newShape[newY] = newShape[newY].Insert(0, shape[rowEven][x - xS].ToString(CultureInfo.InvariantCulture));
+ newShape[newY + 1] = newShape[newY + 1].Insert(0, shape[rowOdd][x - xS].ToString(CultureInfo.InvariantCulture));
}
-
newY += 2;
}
}
-
- async Task SleepAfterRender()
- {
- TimeSpan sleep = TimeSpan.FromSeconds(1d / 60d) - Stopwatch.Elapsed;
- if (sleep > TimeSpan.Zero)
- {
- await Console.RefreshAndDelay(sleep);
- }
- Stopwatch.Restart();
- }
-
}
class Tetromino
@@ -783,16 +864,8 @@ class Tetromino
enum Direction
{
+ None,
Right,
Left,
- None
}
-
- enum GameStatus
- {
- Gameover,
- Playing,
- Paused
- }
-
}
diff --git a/Projects/Website/Pages/Tetris.razor b/Projects/Website/Pages/Tetris.razor
index 4bc7b4f0..6a0de575 100644
--- a/Projects/Website/Pages/Tetris.razor
+++ b/Projects/Website/Pages/Tetris.razor
@@ -17,7 +17,6 @@
-
@@ -25,6 +24,7 @@
+
@@ -45,8 +45,8 @@
{
Game = new();
Console = Game.Console;
- Console.WindowWidth = 43;
- Console.WindowHeight = 42;
+ Console.WindowWidth = 45;
+ Console.WindowHeight = 44;
Console.TriggerRefresh = StateHasChanged;
}
From 29dab220226e73b7ecc5a1c8ca55dccf50c80518 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 18:04:07 -0500
Subject: [PATCH 20/24] tetris weight = 5
---
.vscode/launch.json | 20 ++++++++++----------
Projects/Website/Shared/NavMenu.razor | 10 +++++-----
README.md | 2 +-
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 9d48e2c0..f22e1c47 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -462,6 +462,16 @@
"console": "externalTerminal",
"stopAtEntry": false,
},
+ {
+ "name": "Tetris",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "Build Tetris",
+ "program": "${workspaceFolder}/Projects/Tetris/bin/Debug/Tetris.dll",
+ "cwd": "${workspaceFolder}/Projects/Tetris/bin/Debug",
+ "console": "externalTerminal",
+ "stopAtEntry": false,
+ },
{
"name": "Role Playing Game",
"type": "coreclr",
@@ -492,15 +502,5 @@
"console": "externalTerminal",
"stopAtEntry": false,
},
- {
- "name": "Tetris",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "Build Tetris",
- "program": "${workspaceFolder}/Projects/Tetris/bin/Debug/Tetris.dll",
- "cwd": "${workspaceFolder}/Projects/Tetris/bin/Debug",
- "console": "externalTerminal",
- "stopAtEntry": false,
- },
],
}
diff --git a/Projects/Website/Shared/NavMenu.razor b/Projects/Website/Shared/NavMenu.razor
index 4f3b0348..db4f7628 100644
--- a/Projects/Website/Shared/NavMenu.razor
+++ b/Projects/Website/Shared/NavMenu.razor
@@ -233,6 +233,11 @@
Gravity
+
+
+ Tetris
+
+
Role Playing Game
@@ -248,11 +253,6 @@
Shmup
-
-
- Tetris
-
-
diff --git a/README.md b/README.md
index ea5d9322..f001b076 100644
--- a/README.md
+++ b/README.md
@@ -70,10 +70,10 @@
|[Maze](Projects/Maze)|5|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Maze) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Maze%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
|[PacMan](Projects/PacMan)|5|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/PacMan) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/PacMan%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
|[Gravity](Projects/Gravity)|5|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Gravity) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Gravity%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
+|[Tetris](Projects/Tetris)|5|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Tetris) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Tetris%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/dotnet/dotnet-console-games/pull/89)_|
|[Role Playing Game](Projects/Role%20Playing%20Game)|6|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Role%20Playing%20Game) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Role%20Playing%20Game%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)|
|[Console Monsters](Projects/Console%20Monsters)|7|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Console%20Monsters) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Console%20Monsters%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_Community Collaboration_
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_|
|[Shmup](Projects/Shmup)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Shmup) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Shmup%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
[![Warning](https://raw.githubusercontent.com/dotnet/dotnet-console-games/main/.github/resources/warning-icon.svg)](#) _Work In Progress_ & _Only Supported On Windows OS (+WEB)_|
-|[Tetris](Projects/Tetris)|?|[![Play Now](.github/resources/play-badge.svg)](https://dotnet.github.io/dotnet-console-games/Tetris) [![Status](https://github.com/dotnet/dotnet-console-games/workflows/Tetris%20Build/badge.svg)](https://github.com/dotnet/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/dotnet/dotnet-console-games/pull/89)_|
\*_**Weight**: A relative rating for how advanced the source code is._
From 07fc5b0d334d46f6ad809daecb736bec6ae2ddad Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 18:08:50 -0500
Subject: [PATCH 21/24] task.json order
---
.vscode/tasks.json | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 8e3d42b3..f66b39b7 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -640,25 +640,25 @@
"problemMatcher": "$msCompile",
},
{
- "label": "Build Solution",
+ "label": "Build Tetris",
"command": "dotnet",
"type": "process",
"args":
[
"build",
- "${workspaceFolder}",
+ "${workspaceFolder}/Projects/Tetris/Tetris.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
],
"problemMatcher": "$msCompile",
},
{
- "label": "Restore Solution",
+ "label": "Build Solution",
"command": "dotnet",
"type": "process",
"args":
[
- "restore",
+ "build",
"${workspaceFolder}",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
@@ -666,13 +666,13 @@
"problemMatcher": "$msCompile",
},
{
- "label": "Build Tetris",
+ "label": "Restore Solution",
"command": "dotnet",
"type": "process",
"args":
[
- "build",
- "${workspaceFolder}/Projects/Tetris/Tetris.csproj",
+ "restore",
+ "${workspaceFolder}",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
],
From a0883be3f5fba64c5ade0b3a39b60bfca00e0b93 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 18:10:21 -0500
Subject: [PATCH 22/24] unpause -> resume
---
Projects/Tetris/Program.cs | 2 +-
Projects/Website/Games/Tetris/Tetris.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Projects/Tetris/Program.cs b/Projects/Tetris/Program.cs
index 2543ef4c..05646a0e 100644
--- a/Projects/Tetris/Program.cs
+++ b/Projects/Tetris/Program.cs
@@ -137,7 +137,7 @@ [S] or [↓] fall faster
[Q] spin left
[E] spin right
[Spacebar] drop
- [P] pause and unpause
+ [P] pause and resume
[Escape] close game
[Enter] start game
""");
diff --git a/Projects/Website/Games/Tetris/Tetris.cs b/Projects/Website/Games/Tetris/Tetris.cs
index ce6b007c..1597199d 100644
--- a/Projects/Website/Games/Tetris/Tetris.cs
+++ b/Projects/Website/Games/Tetris/Tetris.cs
@@ -147,7 +147,7 @@ [S] or [↓] fall faster
[Q] spin left
[E] spin right
[Spacebar] drop
- [P] pause and unpause
+ [P] pause and resume
[Escape] close game
[Enter] start game
""");
From 45b05392bbece18be80911fbb2947369177aca0f Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 18:17:31 -0500
Subject: [PATCH 23/24] removed /.vscode/
---
Projects/Website/.vscode/launch.json | 11 --------
Projects/Website/.vscode/tasks.json | 41 ----------------------------
2 files changed, 52 deletions(-)
delete mode 100644 Projects/Website/.vscode/launch.json
delete mode 100644 Projects/Website/.vscode/tasks.json
diff --git a/Projects/Website/.vscode/launch.json b/Projects/Website/.vscode/launch.json
deleted file mode 100644
index b0726229..00000000
--- a/Projects/Website/.vscode/launch.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Launch and Debug Standalone Blazor WebAssembly App",
- "type": "blazorwasm",
- "request": "launch",
- "cwd": "${workspaceFolder}"
- }
- ]
-}
\ No newline at end of file
diff --git a/Projects/Website/.vscode/tasks.json b/Projects/Website/.vscode/tasks.json
deleted file mode 100644
index 46740de4..00000000
--- a/Projects/Website/.vscode/tasks.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/Website.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "publish",
- "command": "dotnet",
- "type": "process",
- "args": [
- "publish",
- "${workspaceFolder}/Website.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "--project",
- "${workspaceFolder}/Website.csproj"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
-}
\ No newline at end of file
From f65c14cf142009790f1fe9c336e8d63c43f831d1 Mon Sep 17 00:00:00 2001
From: ZacharyPatten
Date: Tue, 31 Oct 2023 18:19:07 -0500
Subject: [PATCH 24/24] slnf fix
---
dotnet-console-games.slnf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dotnet-console-games.slnf b/dotnet-console-games.slnf
index 5b642937..e6060bff 100644
--- a/dotnet-console-games.slnf
+++ b/dotnet-console-games.slnf
@@ -42,7 +42,7 @@
"Projects\\Snake\\Snake.csproj",
"Projects\\Sudoku\\Sudoku.csproj",
"Projects\\Tanks\\Tanks.csproj",
- "Projects\\Tetris\\Tetris.csproj"
+ "Projects\\Tetris\\Tetris.csproj",
"Projects\\Tic Tac Toe\\Tic Tac Toe.csproj",
"Projects\\Tents\\Tents.csproj",
"Projects\\Tower Of Hanoi\\Tower Of Hanoi.csproj",