以下是我学习并参照油管上的一位博主(博主名:javidx9)的视频和GitHub,补充了一些注释后的代码,在vs studio 2022中可以跑,实现的是简单的Tetris游戏功能,各位可以看一乐~
// 左、右、下键分别控制方块移动,Z键使方块顺时针旋转,方块的自由下落速度会随游戏的进行加快
// 若显示的不好可以更改终端的启动大小,我这里是列80行40
#include <iostream>
#include <thread>
#include <random>
#include <Windows.h>
using namespace std;
// wstring是宽字符串数据类型
wstring tetromino[7];
bool bKey[4];
// 用来检测Z键是否按下,来锁定按键,使一次只旋转一次
bool bRotateHold = false;
// 实现方块自动下落的功能,每20*50ms下落一次
int nSpeed = 20;
// 用来计数20&50ms中的20
int nSpeedCount = 0;
bool bForceDown = false;
// vector<int>是一个大小可变的int数组
vector<int> vLines;
int nPieceCount = 0;
int nScore = 0;
int nScreenWidth = 80;
int nScreenHeight = 30;
int nFieldWidth = 12;
int nFieldHeight = 18;
// 'unsigned char *'是一个无符号字符(unsigned char, 一种数据类型) 指针(*,指向另一个变量的地址,可直接访问而无需知道具体数值)
// 'pField'是指针的名称,'nullptr'即设定其为空指针
unsigned char* pField = nullptr;
int Rotate(int px, int py, int r)
{
switch (r % 4)
{
case 0: return py * 4 + px; // 0 degrees
case 1: return 12 + py - (px * 4); // 90 degrees
case 2: return 15 - (py * 4) - px; // 180 degrees
case 3: return 3 - py + (px * 4); // 270 degrees
}
return 0;
}
bool DoesPieceFit(int nTetromino, int nRotation, int nPosX, int nPosY)
{
for (int px = 0; px < 4; px++)
{
for (int py = 0; py < 4; py++)
{
// Rotation
int pi = Rotate(px, py, nRotation);
// Location
int fi = (nPosY + py) * nFieldWidth + (nPosX + px);
// 检测是否超过边界
if ((nPosY + py) < nFieldHeight && (nPosY + py) >= 0)
{
if ((nPosX + px) < nFieldWidth && (nPosX + px) >= 0)
{
// 检测是否有重叠
if (pField[fi] != 0 && tetromino[nTetromino][pi] == L'X')
{
return false;
}
}
}
}
}
return true;
}
int main()
{
// 控制台屏幕缓冲区:存储屏幕上的信息,但与屏幕显示信息是分离的,使得在屏幕上进行操作而不影响用户看到的界面
// 创建一个新的控制台屏幕缓冲区(ConsoleScreenBuffer——hConsole)
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
// 将hConsole设置为活动缓冲区(当前控制台窗口的显示内容)
SetConsoleActiveScreenBuffer(hConsole);
// DWORD是无符号双字(32 位)整数类型
DWORD dwBytesWritten = 0;
// 动态分配(new)大小为pFieldWidth * pFieldHeight个unsigned char类型数据的内存,并将其地址赋给指针pField(new是动态分配的关键字)
pField = new unsigned char[nFieldWidth * nFieldHeight];
for (int x = 0; x < nFieldWidth; x++)
{
for (int y = 0; y < nFieldHeight; y++)
{
// 这里用到了三元条件运算符(condition) ? value_if_true : value_if_false
// 检测是否是边界,用'9'表示边界
pField[y * nFieldWidth + x] = (x == 0 || x == nFieldWidth - 1 || y == nFieldHeight - 1) ? 9 : 0;
}
}
wchar_t* screen = new wchar_t[nScreenWidth * nScreenHeight];
for (int i = 0; i < nScreenWidth * nScreenHeight; i++) screen[i] = L' ';
//Create assets
tetromino[0].append(L"..X.");
tetromino[0].append(L"..X.");
tetromino[0].append(L"..X.");
tetromino[0].append(L"..X.");
tetromino[1].append(L".X..");
tetromino[1].append(L".X..");
tetromino[1].append(L".XX.");
tetromino[1].append(L"....");
tetromino[2].append(L"..X.");
tetromino[2].append(L".XX.");
tetromino[2].append(L".X..");
tetromino[2].append(L"....");
tetromino[3].append(L".X..");
tetromino[3].append(L".XX.");
tetromino[3].append(L"..X.");
tetromino[3].append(L"....");
tetromino[4].append(L"....");
tetromino[4].append(L".XX.");
tetromino[4].append(L".XX.");
tetromino[4].append(L"....");
tetromino[5].append(L"..X.");
tetromino[5].append(L".XX.");
tetromino[5].append(L"..X.");
tetromino[5].append(L"....");
tetromino[6].append(L"..X.");
tetromino[6].append(L"..X.");
tetromino[6].append(L".XX.");
tetromino[6].append(L"....");
bool bGameOver = false;
int nCurrentPiece = 1;
int nCurrentRotation = 0;
// 初始时从中间顶部位置掉落
int nCurrentX = nFieldWidth / 2;
int nCurrentY = 0;
while (!bGameOver)
{
// Game Timing =============================
//this_thread 是C++11中的一个命名空间,包含了与线程相关的函数和类
this_thread::sleep_for(50ms);
nSpeedCount++;
bForceDown = (nSpeedCount == nSpeed);
// Input ===================================
for (int i = 0; i < 4; i++)
{
// 在二进制表示中,0x8000 对应的是一个 16 位的数值,其最高位(最左侧的位)为 1,其余位都为 0
// '\x27' '\x25' '\x28' 'Z'分别代表右、左、下、Z(旋转键),GetAsyncKeyState为取 按下为1,不按为0(即最高位)
// &是按位与操作(看最高位)
bKey[i] = (0x8000 & GetAsyncKeyState((unsigned char)("\x27\x25\x28Z"[i]))) != 0;
}
// Game Logic ==============================
// 若Right键被按下
if (bKey[0])
{
if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX + 1, nCurrentY))
{
nCurrentX = nCurrentX + 1;
}
}
// 若Left键被按下
if (bKey[1])
{
if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX - 1, nCurrentY))
{
nCurrentX = nCurrentX - 1;
}
}
// 若Down键被按下
if (bKey[2])
{
if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1))
{
nCurrentY = nCurrentY + 1;
}
}
// 若Z键被按下
if (bKey[3])
{
if (DoesPieceFit(nCurrentPiece, nCurrentRotation + 1, nCurrentX, nCurrentY))
{
if (!bRotateHold)
nCurrentRotation = nCurrentRotation + 1;
bRotateHold = true;
}
}
else
{
bRotateHold = false;
}
// 实现方块自由下落的功能
if (bForceDown)
{
if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1))
{
// 还能继续下落
nCurrentY = nCurrentY + 1;
}
else
{
// 锁定方块
for (int px = 0; px < 4; px++)
{
for (int py = 0; py < 4; py++)
{
if (tetromino[nCurrentPiece][Rotate(px, py, nCurrentRotation)] == L'X')
{
pField[(nCurrentY + py) * nFieldWidth + (nCurrentX + px)] = nCurrentPiece + 1;
}
}
}
// 每放下10个块加一次速度(走一步的时间为nSpeed*50ms)
nPieceCount++;
if (nPieceCount % 10 == 0)
{
if (nSpeed >= 10)
{
nSpeed--;
}
}
// 下落一块加一分
nScore += 25;
// 检测是否有已填满的一行
for (int py = 0; py < 4; py++)
{
if (nCurrentY + py < nFieldHeight - 1)
{
bool bLine = true;
for (int px = 0; px < nFieldWidth - 1; px++)
{
// 检测出一行全满且非最后边缘一行
bLine &= pField[(nCurrentY + py) * nFieldWidth + px] != 0;
}
if (bLine)
{
// 把这一行变为“=”
for (int px = 1; px < nFieldWidth - 1; px++)
{
pField[(nCurrentY + py) * nFieldWidth + px] = 8;
}
vLines.push_back(nCurrentY + py);
}
// 消行加分,消得越多呈指数式增长
if (!vLines.empty())
{
// 1<<为二进制中的数字1左移,相当于*2
nScore += (1 << vLines.size()) * 100;
}
}
}
// 选择下一个下落块
nCurrentPiece = rand() % 7;
nCurrentRotation = 0;
nCurrentX = nFieldWidth / 2;
nCurrentY = 0;
// 如果下一个下落块放不进去了
bGameOver = (!DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY));
}
nSpeedCount = 0;
}
// Render Output ==========================
// Draw Field
for (int x = 0; x < nFieldWidth; x++)
{
for (int y = 0; y < nFieldHeight; y++)
{
// 根据pField[y * nFieldWidth + x]计算出的值和映射关系(依次为0123456...)取" ABCDEFG+#"中的值进行赋值
screen[(y + 2) * nScreenWidth + (x + 2)] = L" ABCDEFG=#"[pField[y * nFieldWidth + x]];
}
}
// Draw Current Piece
for (int px = 0; px < 4; px++)
{
for (int py = 0; py < 4; py++)
{
if (tetromino[nCurrentPiece][Rotate(px, py, nCurrentRotation)] == 'X')
{
screen[(nCurrentY + py + 2) * nScreenWidth + (nCurrentX + px + 2)] = nCurrentPiece + 65;
}
}
}
// Draw Score
swprintf_s(&screen[1 * nScreenWidth + nFieldWidth + 12], 16, L"SCORE: %8d", nScore);
// 消去这一行并迭代
if (!vLines.empty())
{
WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0, 0 }, &dwBytesWritten);
this_thread::sleep_for(400ms);
for (auto& v : vLines)
{
for (int px = 1; px < nFieldWidth - 1; px++)
{
for (int py = v; py > 0; py--)
{
pField[py * nFieldWidth + px] = pField[(py - 1) * nFieldWidth + px];
}
pField[px] = 0;
}
}
vLines.clear();
}
// Display Frame
WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0, 0 }, &dwBytesWritten);
}
// Game Over
// 整理控制台,因为在抓住屏幕缓冲器的时候不能使用cout
CloseHandle(hConsole);
cout << "Game Over! Score: " << nScore << endl;
system("pause");
}
这是游戏界面:

有任何疑问和代码问题欢迎探讨交流,一起共同进步!
本文档展示了如何在VisualStudio2022中基于C++编写一个简单的Tetris游戏,包括控制方块移动、旋转、自动下落以及键盘事件处理。源代码包含游戏逻辑、内存分配和控制台输出部分。
2万+






