#include <iostream>
#include <format>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <random>
#include <ranges>
#include <conio.h>
#include <windows.h>
#include <ctime>
#include <cstdlib>
#include <sstream>
#include <io.h>
#include <fcntl.h>
using namespace std;
// 修改后的游戏区尺寸:宽度40,高度20
const int nFieldWidth = 40;
const int nFieldHeight = 20;
vector<int> field(nFieldWidth * nFieldHeight, 0);
// 7 种俄罗斯方块,每个用 16 个字符表示(4×4 方阵)
vector<wstring> tetromino(7);
// 4×4 方阵旋转函数
static int Rotate(int px, int py, int r)
{
switch (r % 4)
{
case 0: return py * 4 + px; // 0 度
case 1: return 12 + py - (px * 4); // 90 度
case 2: return 15 - (py * 4) - px; // 180 度
case 3: return 3 - py + (px * 4); // 270 度
}
return 0;
}
// 判断方块是否可以放置到 (posX, posY)
static bool DoesPieceFit(int tetrominoIndex, int rotation, int posX, int posY)
{
for (int x = 0; x < 4; x++)
{
for (int y = 0; y < 4; y++)
{
int pi = Rotate(x, y, rotation);
int fi = (posY + y) * nFieldWidth + (posX + x);
if (posX + x >= 0 && posX + x < nFieldWidth &&
posY + y >= 0 && posY + y < nFieldHeight)
{
if (tetromino[tetrominoIndex][pi] == L'X' && field[fi] != 0)
return false;
}
}
}
return true;
}
static int ShowMenu(HANDLE hConsole)
{
// 清屏并重置光标
system("cls");
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
int consoleWidth = csbi.dwSize.X;
int consoleHeight = csbi.dwSize.Y;
int menuY = consoleHeight / 2 - 2;
vector<wstring> options = { L"1. 游戏开始", L"2. 游戏说明", L"3. 游戏结束" };
// 定义不同的颜色
WORD colors[3] = { FOREGROUND_GREEN | FOREGROUND_INTENSITY,
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY,
FOREGROUND_RED | FOREGROUND_INTENSITY };
for (int i = 0; i < 3; i++)
{
int len = options[i].length();
int offsetX = (consoleWidth - len) / 2;
COORD pos = { static_cast<SHORT>(offsetX), static_cast<SHORT>(menuY + i) };
SetConsoleCursorPosition(hConsole, pos);
SetConsoleTextAttribute(hConsole, colors[i]);
wcout << options[i];
}
// 恢复默认颜色
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
// 等待键盘输入
int ch = _getch();
return ch - '0';
}
static void ShowInstructions(HANDLE hConsole)
{
system("cls");
SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
wcout << L"【游戏说明】\n";
wcout << L"1. 用左/右箭头键或A/D 控制方块左右移动。\n";
wcout << L"2. 用上箭头键或Z 控制方块旋转。\n";
wcout << L"3. 用下箭头键或S 控制方块加速下落。\n";
wcout << L"4. 当一整行填满后,该行会消除并获得加分。\n";
wcout << L"按任意键返回菜单...";
(void)_getch();
// 恢复默认颜色
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
static void RunGame(HANDLE hConsole)
{
// 利用 C++ 随机数生成器确保7种不同方块概率均等
mt19937 rng(random_device{}());
uniform_int_distribution<int> piece_dist(0, 6);
// 初始化俄罗斯方块形状
tetromino[0] = L"..X...X...X...X."; // I
tetromino[1] = L"..X..XX...X....."; // T
tetromino[2] = L".....XX..XX....."; // O
tetromino[3] = L"..X..XX..X......"; // S
tetromino[4] = L".X...XX...X....."; // Z
tetromino[5] = L".X...X...XX....."; // J
tetromino[6] = L"..X...X..XX....."; // L
// 初始化游戏区,设置边界(上、左、右和底部为墙体,墙体值为9)
for (int x = 0; x < nFieldWidth; x++)
{
for (int y = 0; y < nFieldHeight; y++)
{
if (x == 0 || x == nFieldWidth - 1 || y == 0 || y == nFieldHeight - 1)
field[static_cast<size_t>(y) * nFieldWidth + x] = 9;
else
field[static_cast<size_t>(y) * nFieldWidth + x] = 0;
}
}
// 定义各个方块 (1~7) 的颜色(固定方块及当前活动块均使用对应颜色)
WORD pieceColors[8] =
{
0,
FOREGROUND_RED | FOREGROUND_INTENSITY, // 1:红色
FOREGROUND_GREEN | FOREGROUND_INTENSITY, // 2:绿色
FOREGROUND_BLUE | FOREGROUND_INTENSITY, // 3:蓝色
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // 4:黄色
FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // 5:紫色(品红)
FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // 6:青色
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // 7:白色
};
// 壁体颜色
WORD wallColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
// 游戏相关变量
bool gameOver = false;
int score = 0;
int currentPiece = piece_dist(rng);
int currentRotation = 0;
// 顶部为墙体,所以初始行设置为1
int currentX = nFieldWidth / 2 - 2;
int currentY = 1;
// 下落速度修改:由20改为10,使得下落速度为之前的两倍
int speed = 10;
int speedCount = 0;
bool forceDown = false;
auto lastTime = chrono::steady_clock::now();
// 游戏主循环
while (!gameOver)
{
this_thread::sleep_for(chrono::milliseconds(50));
speedCount++;
forceDown = (speedCount == speed);
// 键盘输入处理,支持扩展键和普通键
if (_kbhit())
{
int key = _getch();
if (key == 224)
{
int key2 = _getch();
switch (key2)
{
case 75:
if (DoesPieceFit(currentPiece, currentRotation, currentX - 1, currentY))
currentX--;
break;
case 77:
if (DoesPieceFit(currentPiece, currentRotation, currentX + 1, currentY))
currentX++;
break;
case 80:
if (DoesPieceFit(currentPiece, currentRotation, currentX, currentY + 1))
currentY++;
break;
case 72:
if (DoesPieceFit(currentPiece, (currentRotation + 1) % 4, currentX, currentY))
currentRotation = (currentRotation + 1) % 4;
break;
}
}
else
{
switch (key)
{
case 'a': case 'A':
if (DoesPieceFit(currentPiece, currentRotation, currentX - 1, currentY))
currentX--;
break;
case 'd': case 'D':
if (DoesPieceFit(currentPiece, currentRotation, currentX + 1, currentY))
currentX++;
break;
case 's': case 'S':
if (DoesPieceFit(currentPiece, currentRotation, currentX, currentY + 1))
currentY++;
break;
case 'z': case 'Z':
if (DoesPieceFit(currentPiece, (currentRotation + 1) % 4, currentX, currentY))
currentRotation = (currentRotation + 1) % 4;
break;
}
}
}
// 自动下降
if (forceDown)
{
speedCount = 0;
if (DoesPieceFit(currentPiece, currentRotation, currentX, currentY + 1))
currentY++;
else
{
// 固定当前方块到游戏区——固定块统一设置为白色(值7)
for (int px = 0; px < 4; px++)
{
for (int py = 0; py < 4; py++)
{
if (tetromino[currentPiece][Rotate(px, py, currentRotation)] == L'X')
field[static_cast<size_t>(currentY + py) * nFieldWidth + (static_cast<unsigned long long>(currentX) + px)] = 7;
}
}
// 检查是否形成整行
for (int py = 0; py < 4; py++)
{
if (currentY + py < nFieldHeight - 1)
{
bool line = true;
for (int px = 1; px < nFieldWidth - 1; px++)
line &= (field[static_cast<size_t>(currentY + py) * nFieldWidth + px] != 0);
if (line)
{
score += 100;
for (int px = 1; px < nFieldWidth - 1; px++)
field[static_cast<size_t>(currentY + py) * nFieldWidth + px] = 8;
}
}
}
// 删除标记行并向下移动上方块
for (int y = nFieldHeight - 2; y > 0; y--)
{
for (int x = 1; x < nFieldWidth - 1; x++)
{
if (field[static_cast<size_t>(y) * nFieldWidth + x] == 8)
{
for (int ty = y; ty > 1; ty--)
field[static_cast<size_t>(ty) * nFieldWidth + x] = field[(static_cast<std::vector<int, std::allocator<int>>::size_type>(ty) - 1) * nFieldWidth + x];
// 清空第1行内部单元格,保持墙体不变
field[static_cast<unsigned long long>(1) * nFieldWidth + static_cast<std::vector<int, std::allocator<int>>::size_type>(x)] = 0;
}
}
}
// 生成新方块
currentPiece = piece_dist(rng);
currentRotation = 0;
currentX = nFieldWidth / 2 - 2;
currentY = 1;
if (!DoesPieceFit(currentPiece, currentRotation, currentX, currentY))
gameOver = true;
}
}
// 构造显示数组:先拷贝固定方块,再覆盖当前活动块
vector<int> display = field;
for (int px = 0; px < 4; px++)
{
for (int py = 0; py < 4; py++)
{ // 修正此处,由无限循环改为判断 4 行
if (tetromino[currentPiece][Rotate(px, py, currentRotation)] == L'X')
{
int dx = currentX + px;
int dy = currentY + py;
if (dx >= 0 && dx < nFieldWidth && dy >= 0 && dy < nFieldHeight)
display[static_cast<std::vector<int, std::allocator<int>>::size_type>(dy) * nFieldWidth + dx] = currentPiece + 1;
}
}
}
// 获取控制台缓冲区信息以居中游戏区域
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
int consoleWidth = csbi.dwSize.X;
int consoleHeight = csbi.dwSize.Y;
int offsetX = (consoleWidth - nFieldWidth) / 2;
int offsetY = (consoleHeight - nFieldHeight) / 2;
for (int y = 0; y < nFieldHeight; y++)
{
COORD linePos = { static_cast<SHORT>(offsetX), static_cast<SHORT>(offsetY + y) };
SetConsoleCursorPosition(hConsole, linePos);
for (int x = 0; x < nFieldWidth; x++)
{
int value = display[static_cast<std::vector<int, std::allocator<int>>::size_type>(y) * nFieldWidth + x];
if (value == 0)
{
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
wcout << L' ';
}
else if (value == 9)
{
SetConsoleTextAttribute(hConsole, wallColor);
wcout << L'#';
}
else
{
SetConsoleTextAttribute(hConsole, pieceColors[value]);
wcout << L'O';
}
}
}
// 输出分数信息
wstringstream scoreStream;
scoreStream << L"Score: " << score << L"\n";
COORD scorePos = { static_cast<SHORT>(offsetX), static_cast<SHORT>(offsetY + nFieldHeight) };
SetConsoleCursorPosition(hConsole, scorePos);
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
wcout << scoreStream.str();
}
// 游戏结束,显示结束信息
CONSOLE_SCREEN_BUFFER_INFO csbiFinal;
GetConsoleScreenBufferInfo(hConsole, &csbiFinal);
int consoleWidthFinal = csbiFinal.dwSize.X;
int consoleHeightFinal = csbiFinal.dwSize.Y;
int offsetXFinal = (consoleWidthFinal - nFieldWidth) / 2;
int offsetYFinal = (consoleHeightFinal - nFieldHeight) / 2;
COORD cursorPos = { static_cast<SHORT>(offsetXFinal), static_cast<SHORT>(offsetYFinal) };
SetConsoleCursorPosition(hConsole, cursorPos);
wstringstream gameOverStream;
gameOverStream << L"Game Over!! Score: " << score << L"\n";
wcout << gameOverStream.str();
cout << "Press any key to return to menu...";
(void)_getch();
}
int main()
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleOutputCP(CP_UTF8);
_setmode(_fileno(stdout), _O_U16TEXT);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hConsole, &cursorInfo);
cursorInfo.bVisible = false;
SetConsoleCursorInfo(hConsole, &cursorInfo);
while (true)
{
int choice = ShowMenu(hConsole);
if (choice == 1)
{
RunGame(hConsole);
} else
if (choice == 2)
{
ShowInstructions(hConsole);
} else
if (choice == 3)
{
break;
}
}
return 0;
}