Files
CMLeonOS/Gui/Apps/Tetris.cs

415 lines
12 KiB
C#
Raw Normal View History

2026-04-03 22:24:57 +08:00
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<WindowManager>();
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();
}
}
}
}