From 836f199ffa6c8b69ac057d3946d68f44397e973e Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Fri, 3 Apr 2026 22:24:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=84=E7=BD=97=E6=96=AF=E6=96=B9=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BuildTime.txt | 2 +- GitCommit.txt | 2 +- Gui/AppManager.cs | 1 + Gui/Apps/Tetris.cs | 414 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 Gui/Apps/Tetris.cs diff --git a/BuildTime.txt b/BuildTime.txt index d17b050..e5f6166 100644 --- a/BuildTime.txt +++ b/BuildTime.txt @@ -1 +1 @@ -2026-04-03 22:07:05 \ No newline at end of file +2026-04-03 22:22:40 \ No newline at end of file diff --git a/GitCommit.txt b/GitCommit.txt index d4958c4..a721cec 100644 --- a/GitCommit.txt +++ b/GitCommit.txt @@ -1 +1 @@ -395df7b \ No newline at end of file +1c1f85c \ No newline at end of file diff --git a/Gui/AppManager.cs b/Gui/AppManager.cs index 1046529..3484988 100644 --- a/Gui/AppManager.cs +++ b/Gui/AppManager.cs @@ -146,6 +146,7 @@ namespace CMLeonOS.Gui RegisterApp(new AppMetadata("CodeBlocks", () => { return new CodeBlocks(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25))); RegisterApp(new AppMetadata("Contacts", () => { return new Contacts(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25))); RegisterApp(new AppMetadata("Sheet Editor", () => { return new SheetEditor(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25))); + RegisterApp(new AppMetadata("Tetris", () => { return new Tetris(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25))); Logger.Logger.Instance.Info("AppManager", $"{AppMetadatas.Count} apps were registered."); diff --git a/Gui/Apps/Tetris.cs b/Gui/Apps/Tetris.cs new file mode 100644 index 0000000..896386e --- /dev/null +++ b/Gui/Apps/Tetris.cs @@ -0,0 +1,414 @@ +using Cosmos.System; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Tetris : Process + { + internal Tetris() : base("Tetris", ProcessType.Application) { } + + private const int BoardWidth = 10; + private const int BoardHeight = 20; + private const int CellSize = 16; + private const int BoardX = 14; + private const int BoardY = 24; + + private readonly WindowManager wm = ProcessManager.GetProcess(); + private readonly Random random = new Random(); + + private AppWindow window; + private readonly int[] board = new int[BoardWidth * BoardHeight]; + + private int[] curX = new int[4]; + private int[] curY = new int[4]; + private int curType = 0; + private int curColor = 1; + private bool gameOver = false; + + private int score = 0; + private int lines = 0; + private int level = 1; + private int frameCounter = 0; + private int dropFrames = 36; + + private bool dirty = true; + + // Each piece has 4 blocks: (x0,y0,x1,y1,x2,y2,x3,y3) + private static readonly int[] PieceTemplates = new int[] + { + // I + -1,0, 0,0, 1,0, 2,0, + // O + 0,0, 1,0, 0,1, 1,1, + // T + -1,0, 0,0, 1,0, 0,1, + // S + 0,0, 1,0, -1,1, 0,1, + // Z + -1,0, 0,0, 0,1, 1,1, + // J + -1,0, 0,0, 1,0, 1,1, + // L + -1,0, 0,0, 1,0, -1,1, + }; + + private static readonly Color[] BlockColors = new Color[] + { + Color.FromArgb(28, 38, 52), // unused + Color.FromArgb(86, 201, 231), // I + Color.FromArgb(246, 218, 87), // O + Color.FromArgb(188, 111, 255), // T + Color.FromArgb(117, 209, 119), // S + Color.FromArgb(239, 100, 100), // Z + Color.FromArgb(88, 142, 255), // J + Color.FromArgb(255, 168, 83), // L + }; + + private static int Index(int x, int y) + { + return (y * BoardWidth) + x; + } + + private bool InBounds(int x, int y) + { + return x >= 0 && x < BoardWidth && y >= 0 && y < BoardHeight; + } + + private bool CanPlace(int[] testX, int[] testY) + { + for (int i = 0; i < 4; i++) + { + int x = testX[i]; + int y = testY[i]; + if (!InBounds(x, y)) + { + return false; + } + if (board[Index(x, y)] != 0) + { + return false; + } + } + return true; + } + + private void SpawnPiece() + { + curType = random.Next(0, 7); + curColor = curType + 1; + + int baseOffset = curType * 8; + int spawnX = BoardWidth / 2; + int spawnY = 1; + for (int i = 0; i < 4; i++) + { + curX[i] = spawnX + PieceTemplates[baseOffset + (i * 2)]; + curY[i] = spawnY + PieceTemplates[baseOffset + (i * 2) + 1]; + } + + if (!CanPlace(curX, curY)) + { + gameOver = true; + } + dirty = true; + } + + private void LockPiece() + { + for (int i = 0; i < 4; i++) + { + if (InBounds(curX[i], curY[i])) + { + board[Index(curX[i], curY[i])] = curColor; + } + } + ClearLines(); + SpawnPiece(); + } + + private void ClearLines() + { + int cleared = 0; + for (int y = BoardHeight - 1; y >= 0; y--) + { + bool full = true; + for (int x = 0; x < BoardWidth; x++) + { + if (board[Index(x, y)] == 0) + { + full = false; + break; + } + } + + if (!full) + { + continue; + } + + for (int ty = y; ty > 0; ty--) + { + for (int x = 0; x < BoardWidth; x++) + { + board[Index(x, ty)] = board[Index(x, ty - 1)]; + } + } + for (int x = 0; x < BoardWidth; x++) + { + board[Index(x, 0)] = 0; + } + + cleared++; + y++; // re-check same line after shift + } + + if (cleared > 0) + { + lines += cleared; + if (cleared == 1) score += 100 * level; + else if (cleared == 2) score += 300 * level; + else if (cleared == 3) score += 500 * level; + else score += 800 * level; + + level = 1 + (lines / 10); + dropFrames = Math.Max(10, 36 - ((level - 1) * 2)); + dirty = true; + } + } + + private void MovePiece(int dx, int dy, bool lockOnFail) + { + if (gameOver) return; + + int[] tx = new int[4]; + int[] ty = new int[4]; + for (int i = 0; i < 4; i++) + { + tx[i] = curX[i] + dx; + ty[i] = curY[i] + dy; + } + + if (CanPlace(tx, ty)) + { + curX = tx; + curY = ty; + dirty = true; + return; + } + + if (lockOnFail && dy > 0) + { + LockPiece(); + } + } + + private void RotatePiece() + { + if (gameOver) return; + if (curType == 1) return; // O + + int pivotX = curX[1]; + int pivotY = curY[1]; + + int[] tx = new int[4]; + int[] ty = new int[4]; + for (int i = 0; i < 4; i++) + { + int relX = curX[i] - pivotX; + int relY = curY[i] - pivotY; + tx[i] = pivotX - relY; + ty[i] = pivotY + relX; + } + + if (CanPlace(tx, ty)) + { + curX = tx; + curY = ty; + dirty = true; + return; + } + + // simple wall kick + for (int shift = -1; shift <= 1; shift += 2) + { + for (int i = 0; i < 4; i++) tx[i] += shift; + if (CanPlace(tx, ty)) + { + curX = tx; + curY = ty; + dirty = true; + return; + } + for (int i = 0; i < 4; i++) tx[i] -= shift; + } + } + + private void HardDrop() + { + if (gameOver) return; + while (true) + { + int oldY = curY[0]; + MovePiece(0, 1, false); + if (curY[0] == oldY) + { + LockPiece(); + break; + } + } + dirty = true; + } + + private void ResetGame() + { + for (int i = 0; i < board.Length; i++) + { + board[i] = 0; + } + score = 0; + lines = 0; + level = 1; + dropFrames = 36; + frameCounter = 0; + gameOver = false; + SpawnPiece(); + dirty = true; + } + + private void OnKeyPressed(KeyEvent key) + { + if (gameOver) + { + if (key.Key == ConsoleKeyEx.R) + { + ResetGame(); + } + return; + } + + switch (key.Key) + { + case ConsoleKeyEx.LeftArrow: + MovePiece(-1, 0, false); + break; + case ConsoleKeyEx.RightArrow: + MovePiece(1, 0, false); + break; + case ConsoleKeyEx.DownArrow: + MovePiece(0, 1, true); + break; + case ConsoleKeyEx.UpArrow: + RotatePiece(); + break; + case ConsoleKeyEx.Spacebar: + HardDrop(); + break; + } + } + + private void DrawCell(int x, int y, Color color) + { + int px = BoardX + (x * CellSize); + int py = BoardY + (y * CellSize); + window.DrawFilledRectangle(px, py, CellSize, CellSize, color); + window.DrawRectangle(px, py, CellSize, CellSize, Color.FromArgb(28, 38, 52)); + } + + private void Render() + { + window.Clear(Color.FromArgb(19, 22, 28)); + + int boardPixelW = BoardWidth * CellSize; + int boardPixelH = BoardHeight * CellSize; + window.DrawFilledRectangle(BoardX - 2, BoardY - 2, boardPixelW + 4, boardPixelH + 4, Color.FromArgb(56, 64, 76)); + window.DrawFilledRectangle(BoardX, BoardY, boardPixelW, boardPixelH, Color.FromArgb(30, 34, 42)); + + for (int y = 0; y < BoardHeight; y++) + { + for (int x = 0; x < BoardWidth; x++) + { + int v = board[Index(x, y)]; + if (v != 0) + { + DrawCell(x, y, BlockColors[v]); + } + else + { + int px = BoardX + (x * CellSize); + int py = BoardY + (y * CellSize); + window.DrawRectangle(px, py, CellSize, CellSize, Color.FromArgb(37, 43, 54)); + } + } + } + + if (!gameOver) + { + for (int i = 0; i < 4; i++) + { + DrawCell(curX[i], curY[i], BlockColors[curColor]); + } + } + + int sideX = BoardX + boardPixelW + 20; + window.DrawString("TETRIS", Color.FromArgb(196, 212, 235), sideX, 30); + window.DrawString("Score: " + score.ToString(), Color.White, sideX, 62); + window.DrawString("Lines: " + lines.ToString(), Color.White, sideX, 82); + window.DrawString("Level: " + level.ToString(), Color.White, sideX, 102); + window.DrawString("Keys:", Color.FromArgb(186, 198, 216), sideX, 136); + window.DrawString("Arrows Move", Color.FromArgb(186, 198, 216), sideX, 154); + window.DrawString("Up Rotate", Color.FromArgb(186, 198, 216), sideX, 172); + window.DrawString("Space Drop", Color.FromArgb(186, 198, 216), sideX, 190); + + if (gameOver) + { + int boxW = 210; + int boxH = 68; + int boxX = (window.Width / 2) - (boxW / 2); + int boxY = (window.Height / 2) - (boxH / 2); + window.DrawFilledRectangle(boxX, boxY, boxW, boxH, Color.FromArgb(18, 18, 22)); + window.DrawRectangle(boxX, boxY, boxW, boxH, Color.FromArgb(210, 96, 96)); + window.DrawString("Game Over", Color.FromArgb(250, 140, 140), boxX + 60, boxY + 14); + window.DrawString("Press R to restart", Color.White, boxX + 38, boxY + 36); + } + + wm.Update(window); + dirty = false; + } + + public override void Start() + { + base.Start(); + + window = new AppWindow(this, 170, 80, 430, 380); + window.Title = "Tetris"; + window.Icon = AppManager.DefaultAppIcon; + window.Closing = TryStop; + window.OnKeyPressed = OnKeyPressed; + wm.AddWindow(window); + + ResetGame(); + Render(); + } + + public override void Run() + { + if (!gameOver) + { + frameCounter++; + if (frameCounter >= dropFrames) + { + frameCounter = 0; + MovePiece(0, 1, true); + } + } + + if (dirty) + { + Render(); + } + } + } +}