C++实现——简易版俄罗斯方块

本文档展示了如何在VisualStudio2022中基于C++编写一个简单的Tetris游戏,包括控制方块移动、旋转、自动下落以及键盘事件处理。源代码包含游戏逻辑、内存分配和控制台输出部分。

以下是我学习并参照油管上的一位博主(博主名: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");
}

这是游戏界面:

有任何疑问和代码问题欢迎探讨交流,一起共同进步!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值