C++实现俄罗斯方块(Windows控制台版)

news2024/12/23 5:09:39

C++实现俄罗斯方块(Windows控制台版)

在油管上看到一个使用C++控制台编写的俄罗斯方块小游戏,源代码200多行,B站上也有相关的讲解视频,非常不错,值得学习。
B站讲解视频地址为:【百万好评】国外技术大神C++游戏编程实战教程,油管580W收藏,新手10小时入门,并快速达到游戏开发能力(中英字幕) B站
CSDN博主千帐灯无此声还为此写了一篇博客:C++实现俄罗斯方块(源码+详解),讲解得已经非常详细了,为此我就不赘余了。
Github源代码地址为:https://github.com/OneLoneCoder/Javidx9/blob/master/SimplyCode/OneLoneCoder_Tetris.cpp

特此贴上对应的C++源代码,记录一下:

/*
	OneLoneCoder.com - Command Line Tetris
	"Put Your Money Where Your Mouth Is" - @Javidx9

	License
	~~~~~~~
	Copyright (C) 2018  Javidx9
	This program comes with ABSOLUTELY NO WARRANTY.
	This is free software, and you are welcome to redistribute it
	under certain conditions; See license for details.
	Original works located at:
	https://www.github.com/onelonecoder
	https://www.onelonecoder.com
	https://www.youtube.com/javidx9

	GNU GPLv3
	https://github.com/OneLoneCoder/videos/blob/master/LICENSE

	From Javidx9 :)
	~~~~~~~~~~~~~~~
	Hello! Ultimately I don't care what you use this for. It's intended to be
	educational, and perhaps to the oddly minded - a little bit of fun.
	Please hack this, change it and use it in any way you see fit. You acknowledge
	that I am not responsible for anything bad that happens as a result of
	your actions. However this code is protected by GNU GPLv3, see the license in the
	github repo. This means you must attribute me if you use it. You can view this
	license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
	Cheers!

	Background
	~~~~~~~~~~
	I made a video "8-Bits of advice for new programmers" (https://youtu.be/vVRCJ52g5m4)
	and suggested that building a tetris clone instead of Dark Sould IV might be a better
	approach to learning to code. Tetris is nice as it makes you think about algorithms.

	Controls are Arrow keys Left, Right & Down. Use Z to rotate the piece.
	You score 25pts per tetronimo, and 2^(number of lines)*100 when you get lines.

	Future Modifications
	~~~~~~~~~~~~~~~~~~~~
	1) Show next block and line counter

	Author
	~~~~~~
	Twitter: @javidx9
	Blog: www.onelonecoder.com

	Video:
	~~~~~~
	https://youtu.be/8OK8_tHeCIA

	Last Updated: 30/03/2017
*/

#include <iostream>
#include <thread>
#include <vector>
using namespace std;

#include <stdio.h>
#include <Windows.h>

int nScreenWidth = 80;			// Console Screen Size X (columns)
int nScreenHeight = 30;			// Console Screen Size Y (rows)
wstring tetromino[7];
int nFieldWidth = 12;			// 表示场地的宽度,它的值为 12。这意味着在水平方向上,场地被分割成了 12 个单元格或列
int nFieldHeight = 18;			// nFieldHeight 表示场地的高度,它的值为 18。这意味着在垂直方向上,场地被分割成了 18 个单元格或行
// 一个指向无符号字符的指针,初始化为 nullptr。这个指针通常用于动态分配内存,并表示场地的状态或布局。
// 通过使用指针,可以在程序运行时为场地分配所需的内存空间
unsigned char* pField = nullptr;

// 方块旋转
// 据给定的方块坐标(px, py)和旋转次数r,返回旋转后方块的索引位置
int Rotate(int px, int py, int r)
{
	int pi = 0;
	switch (r % 4)
	{
	case 0: // 0 degrees			// 0  1  2  3
		pi = py * 4 + px;			// 4  5  6  7
		break;						// 8  9 10 11
		//12 13 14 15

	case 1: // 90 degrees			//12  8  4  0
		pi = 12 + py - (px * 4);	//13  9  5  1
		break;						//14 10  6  2
		//15 11  7  3

	case 2: // 180 degrees			//15 14 13 12
		pi = 15 - (py * 4) - px;	//11 10  9  8
		break;						// 7  6  5  4
		// 3  2  1  0

	case 3: // 270 degrees			// 3  7 11 15
		pi = 3 - py + (px * 4);		// 2  6 10 14
		break;						// 1  5  9 13
	}								// 0  4  8 12

	return pi;
}

// 检查方块是否适合放置在指定位置
/******************************************
* 判断给定的方块是否适合放置在指定的位置(nPosX, nPosY)上。
* 通过遍历方块的每个格子,并将其与场地进行匹配,判断方块是否和场地中的其他方块冲突
* nTetromino:表示方块的类型(编号)
* nRotation:表示方块的旋转状态
* nPosX:表示要放置方块的水平位置(X 坐标)
* nPosY:表示要放置方块的垂直位置(Y 坐标)
*******************************************/
bool DoesPieceFit(int nTetromino, int nRotation, int nPosX, int nPosY)
{
	// All Field cells >0 are occupied
	for (int px = 0; px < 4; px++)	// 循环遍历方块的水平位置
		for (int py = 0; py < 4; py++)	// 循环遍历方块的垂直位置
		{
			// Get index into piece
			// 获取方块内部位置的索引
			int pi = Rotate(px, py, nRotation);

			// Get index into field
			// 获取方块在游戏区域中的索引
			int fi = (nPosY + py) * nFieldWidth + (nPosX + px);

			// Check that test is in bounds. Note out of bounds does
			// not necessarily mean a fail, as the long vertical piece
			// can have cells that lie outside the boundary, so we'll
			// just ignore them
			if (nPosX + px >= 0 && nPosX + px < nFieldWidth)	// 检查方块是否在横向范围内
			{
				if (nPosY + py >= 0 && nPosY + py < nFieldHeight)	// 检查方块是否在纵向范围内
				{
					// In Bounds so do collision check
					if (tetromino[nTetromino][pi] != L'.' && pField[fi] != 0)	// 检查方块和游戏区域是否有重叠
						// 第一个碰撞就返回失败
						return false; // fail on first hit
				}
			}
		}
	// 方块适合放置在指定位置
	return true;
}

/*****************************************************
游戏的主函数。包括创建方块、初始化场地和屏幕,
控制游戏逻辑的循环,处理用户输入,更新方块的位置和状态,
判断方块能否放置,渲染输出到屏幕,计分和游戏结束
*****************************************************/
int main()
{
	// Create Screen Buffer
	// 创建一个带有空格字符初始化的屏幕缓冲区,并将其设置为活动的屏幕缓冲区,以便后续可以将字符输出到控制台屏幕上
	wchar_t* screen = new wchar_t[nScreenWidth * nScreenHeight];
	for (int i = 0; i < nScreenWidth * nScreenHeight; i++) screen[i] = L' ';
	HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	SetConsoleActiveScreenBuffer(hConsole);
	// 用于记录写入到控制台屏幕缓冲区的字节数
	DWORD dwBytesWritten = 0;

	tetromino[0].append(L"..X...X...X...X."); // Tetronimos 4x4
	tetromino[1].append(L"..X..XX...X.....");
	tetromino[2].append(L".....XX..XX.....");
	tetromino[3].append(L"..X..XX..X......");
	tetromino[4].append(L".X...XX...X.....");
	tetromino[5].append(L".X...X...XX.....");
	tetromino[6].append(L"..X...X..XX.....");

	pField = new unsigned char[nFieldWidth * nFieldHeight]; // Create play field buffer
	for (int x = 0; x < nFieldWidth; x++) // Board Boundary
		for (int y = 0; y < nFieldHeight; y++)
			pField[y * nFieldWidth + x] = (x == 0 || x == nFieldWidth - 1 || y == nFieldHeight - 1) ? 9 : 0;

	// Game Logic
	bool bKey[4];
	int nCurrentPiece = 0;
	int nCurrentRotation = 0;
	int nCurrentX = nFieldWidth / 2;
	int nCurrentY = 0;
	int nSpeed = 20;			// 控制方块下落速度的变量,初始化为20
	int nSpeedCount = 0;		// 一个计数器,用于记录方块下落的帧数,初始化为0
	bool bForceDown = false;	// 用于标记是否强制方块向下移动,初始化为false
	bool bRotateHold = true;	// 是否连续旋转,表明用户是否按住了旋转按钮
	int nPieceCount = 0;
	int nScore = 0;
	vector<int> vLines;
	bool bGameOver = false;

	// 绘制游戏界面并更新显示
	while (!bGameOver) // Main Loop
	{
		// Timing =======================
		// 将当前线程暂停执行,等待50毫秒,以控制游戏帧率
		this_thread::sleep_for(50ms); // Small Step = 1 Game Tick
		nSpeedCount++;				  // 将计数器nSpeedCounter的值加1,表示经过了一个帧
		bForceDown = (nSpeedCount == nSpeed);	// 判断计数器是否等于设定数量,如果相等,则将bForceDown设置为true,表示需要强制方块向下移动

		// Input ========================
		for (int k = 0; k < 4; k++)								// R   L   D Z
			// 判断了指定键码对应的按键是否处于按下状态。如果按键被按下,则结果为真,否则为假。
			bKey[k] = (0x8000 & GetAsyncKeyState((unsigned char)("\x27\x25\x28Z"[k]))) != 0;

		// Game Logic ===================
		// 游戏逻辑

		// Handle player movement
		// 处理玩家的移动
		// 按下右键 right
		nCurrentX += (bKey[0] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX + 1, nCurrentY)) ? 1 : 0;
		// 按下左键 left
		nCurrentX -= (bKey[1] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX - 1, nCurrentY)) ? 1 : 0;
		// 按下下键 down
		nCurrentY += (bKey[2] && DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1)) ? 1 : 0;

		// Rotate, but latch to stop wild spinning
		// 按下Z键,旋转
		if (bKey[3])	// 按下Z键
		{
			nCurrentRotation += (bRotateHold && DoesPieceFit(nCurrentPiece, nCurrentRotation + 1, nCurrentX, nCurrentY)) ? 1 : 0;
			bRotateHold = false;
		}
		else
			bRotateHold = true;	// 无法连续旋转

		// Force the piece down the playfield if it's time
		// 如果是时候,将棋子强行推向游戏场地
		if (bForceDown)
		{
			// Update difficulty every 50 pieces
			// 每 50 件更新一次难度
			nSpeedCount = 0;
			nPieceCount++;
			if (nPieceCount % 50 == 0)
				if (nSpeed >= 10) nSpeed--;

			// Test if piece can be moved down
			// 测试是否可以向下移动
			if (DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1))
				nCurrentY++; // It can, so do it!
			else
			{
				// It can't! Lock the piece in place
				// 它不能!将工件锁定到位
				for (int px = 0; px < 4; px++)
					for (int py = 0; py < 4; py++)
						if (tetromino[nCurrentPiece][Rotate(px, py, nCurrentRotation)] != L'.')
							pField[(nCurrentY + py) * nFieldWidth + (nCurrentX + px)] = nCurrentPiece + 1;

				// Check for lines
				for (int py = 0; py < 4; py++)
					if (nCurrentY + py < nFieldHeight - 1)
					{
						bool bLine = true;
						for (int px = 1; px < nFieldWidth - 1; px++)
							bLine &= (pField[(nCurrentY + py) * nFieldWidth + px]) != 0;

						if (bLine)
						{
							// Remove Line, set to =
							for (int px = 1; px < nFieldWidth - 1; px++)
								pField[(nCurrentY + py) * nFieldWidth + px] = 8;
							vLines.push_back(nCurrentY + py);
						}
					}

				nScore += 25;
				if (!vLines.empty())	nScore += (1 << vLines.size()) * 100;

				// Pick New Piece
				nCurrentX = nFieldWidth / 2;
				nCurrentY = 0;
				nCurrentRotation = 0;
				nCurrentPiece = rand() % 7;

				// If piece does not fit straight away, game over!
				bGameOver = !DoesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY);
			}
		}

		// Display ======================

		// Draw Field
		// nFieldWidth游戏界面宽度,nFieldHeight游戏界面高度;注意与屏幕宽度和高度区分
		// nScreenWidth屏幕宽度,nScreenHeight屏幕高度
		for (int x = 0; x < nFieldWidth; x++)
			for (int y = 0; y < nFieldHeight; y++)
				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)] != L'.')	// 检查方块是否存在于当前位置
					// 注意:65代表大写的A
					screen[(nCurrentY + py + 2) * nScreenWidth + (nCurrentX + px + 2)] = nCurrentPiece + 65;	// 将方块绘制到屏幕上(加上适当的偏移量)

		// Draw Score
		swprintf_s(&screen[2 * nScreenWidth + nFieldWidth + 6], 16, L"SCORE: %8d", nScore);

		// Animate Line Completion
		if (!vLines.empty())
		{
			// Display Frame (cheekily to draw lines)
			WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
			this_thread::sleep_for(400ms); // Delay a bit

			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
		// 将字符写入控制台的输出缓冲区
		// 将 screen 数组中的字符数据写入到控制台的输出缓冲区中,并显示在控制台窗口上
		WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
	}

	// Oh Dear
	// 游戏结束 查看分数
	CloseHandle(hConsole);
	cout << "Game Over!! Score:" << nScore << endl;
	system("pause");
	return 0;
}

VS2022中运行上述代码:

C++控制台版俄罗斯方块
其中有一点需要注意:窗口大小的问题,git clone源码100%相同,但输出不一样。

窗口大小的问题。
Windows11中Win+R键打开cmd命令行窗口,鼠标移动到窗口上方白色横条处,右键 - 设置 - 启动 - 启动大小👇改成下面这个,列改成80,行改成30,如下图所示:
在这里插入图片描述
对应代码中的:

int nScreenWidth = 80;			// Console Screen Size X (columns)
int nScreenHeight = 30;			// Console Screen Size Y (rows)

参考资料

  • 【百万好评】国外技术大神C++游戏编程实战教程,油管580W收藏,新手10小时入门,并快速达到游戏开发能力(中英字幕) B站
  • C++实现俄罗斯方块(源码+详解)
  • OneLoneCoder_Tetris.cpp
  • 如何做一个俄罗斯方块4:形状碰撞检测(上)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unet改进27:添加DGCST|Vision Transformer与DGSM模块集成在一起的创新结构

本文内容:在不同位置添加DGCST 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 随着移动计算技术的快速发展,在移动设备上部署高效的目标检测算法成为计算机视觉的一个关键研究领域。本研究的重点是优化YOLOv7算法,以提高其在移动平台上的运行效率和速度…

【Linux】传输层协议——UDP

零、传输层的作用是负责数据能够从发送端传输到接收端 一、再来认识一下端口号 端口号&#xff08;Port&#xff09;标识了一个主机进行通信的不同的应用程序。在TCP/IP协议中&#xff0c;用“源IP”&#xff0c;“源端口号”&#xff0c;“目的IP”&#xff0c;“目的端口号”…

Request Response

1 前言 1.1 内容概要 理解Request、Response和HTTP报文之间的关系掌握通过Request能够获得的信息 请求URL、URI、请求协议请求头、客户机和主机请求参数 掌握通过Response能够完成的设置 响应中文乱码问题响应&#xff08;Json&#xff09;字符串、图片&#xff08;文件&a…

【网络】UDP协议的简单使用

目录 服务器 客户端 测试 UDP是基于socket进行网络通信的&#xff0c;那我们这篇博客就来介绍一下基于UDP通信的基本流程&#xff0c;先让服务端和客户端进行简单的跨网络通信。 服务器 首先我们需要创建UDP套接字&#xff0c;用到的接口是 man socket 如果要使用UDP通信&am…

【Python知识宝库】面向对象编程:Python类的深度剖析

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、类的定义二、类的属性1. 类属性2. 实例属性 三、类的方法1. 实例方法2. 类方法 四、继承五、总结 前言 面向…

【C++ Primer Plus习题】12.3

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include "stock20.h&quo…

嵌入式OpenHarmony源码基本原理详解

大家好,今天主要给大家分享一下,如何分析与使用OpenHarmony源码,欢迎交流学习。 第一:OpenHarmony源码简介 在3.0版本中,Lite系统(即轻量系统和小型系统)、标准系统各有一套独立的构建入口和上层的构建流程,但在3.2版本中,两者开始互相借鉴,取长补短并实现了融合统一;…

【环境领域EI稳定 I 院士主讲】第九届能源与环境研究进展国际学术会议(ICAEER 2024)

ICAEER 2024会议投稿经过2-3位组委会专家严格审核之后&#xff0c;符合Springer ESE征稿要求的论文将由斯普林格&#xff08;Springer-Nature&#xff09;旗下的 Environmental Science and Engineering (ISSN: 1863-5520) 出版&#xff0c;出版后提交至EI Compendex&#xff…

初步了解VTK装配体

VTK还不太了解&#xff0c;根据资料&#xff0c; vtk.vtkAssembly 是 VTK库中的一个重要类&#xff0c;允许通过将多个vtkActor对象组合在一起来创建复杂的3D模型。 import vtk import math from vtk.util.colors import *filenames ["cylinder.stl","sphere…

C++11 --- 智能指针

序言 在使用 C / C 进行编程时&#xff0c;许多场景都需要我们在堆上申请空间&#xff0c;堆内存的申请和释放都需要我们自己进行手动管理。这就存在容易造成堆内存泄露&#xff08;忘记释放&#xff09;&#xff0c;二次释放&#xff0c;程序发生异常时内存泄露等问题&#xf…

QML入门之创建可重用的组件(一)

我们在日常开发中都会封装一些组件以便于项目内重复利用。QML创建可重用组件一般有两种方法。 自定义Item使用Component创建自定义组件 自定义Item 以一个自定义按钮举例&#xff1a; import QtQuick 2.12Rectangle {id: root// 自定义属性property string btnDis: qsTr(&qu…

Windows环境利用VS2022编译 libvpx 源码教程

libvpx libvpx 是一个开源的视频编码库&#xff0c;由 WebM 项目开发和维护&#xff0c;专门用于 VP8 和 VP9 视频编码格式的编解码处理。它支持高质量的视频压缩&#xff0c;广泛应用于视频会议、在线教育、视频直播服务等多种场景中。libvpx 的特点包括跨平台兼容性、硬件加速…

JavaSE-易错题集-002

1. 下面有关java基本类型的默认值和取值范围&#xff0c;说法错误的是&#xff1f; A 字节型的类型默认值是0&#xff0c;取值范围是-2^7—2^7-1 B boolean类型默认值是false&#xff0c;取值范围是true\false C 字符型类型默认是0&#xff0c;取值范围是-2^15 —2^15-1 D l…

iOS——retain和release底层原理

retain实现原理 retain的源码&#xff1a; //使用此方法等价于使用[this retain] inline id objc_object::retain() {//确保对象不是tagged pointerASSERT(!isTaggedPointer());return rootRetain(false, RRVariant::FastOrMsgSend); }ALWAYS_INLINE id objc_object::rootR…

关系代数 | 数据库SQL

文章目录 关系运算符笛卡尔积笛卡尔积应用 运算符符号含义集合运算符并∪交∩差-笛卡尔积专门的关系运算符选择σ投影π连接⋈除 关系运算符 笛卡尔积 集合运算符中&#xff0c;主要对笛卡尔积做解释&#xff1a; 在数学中&#xff0c;两个集合X和Y的笛卡儿积&#xff08;英语…

【Linux】进程控制(一)

1. 进程创建 &#xff08;一&#xff09;认识fork函数 从已存在进程中创建一个新进程&#xff08;新进程为子进程&#xff0c;而原进程为父进程&#xff09; 进程调用fork&#xff0c;当控制转移到内核中的fork代码后&#xff0c;内核做&#xff1a; 分配新的内存块和内核数…

Allegro PCB--报错

1。 走线上打孔 问题&#xff1a;在走线上打的Via&#xff0c;我通过"Assign net to Via", 给与网络。成功后。 跑Tools\Database check\ Update all DRC(including batch), Via 网络又没有了 原因& 解决方法&#xff1a; VIA没有和走线完全重合 换个方法&#x…

【吊打面试官系列-Redis面试题】说说 Redis 哈希槽的概念?

大家好&#xff0c;我是锋哥。今天分享关于 【说说 Redis 哈希槽的概念&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 说说 Redis 哈希槽的概念&#xff1f; Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念&#xff0c;Redis 集群有 16384 个哈希槽&a…

高精度加法,减法,乘法,除法

加法&#xff1a; 大整数该如何储存&#xff1f; 用数组储存&#xff1a; 把个位放在数下标为0的位置&#xff0c;十位放在数组下标为1的位置&#xff08;也就是高位放在数组的后面&#xff09; 因为这样&#xff0c;如果需要增加一位最高位&#xff0c;那我们就可以直接在…

C语言小游戏--贪吃蛇实现

C语言小游戏--贪吃蛇实现 1.游戏实现背景2.Win32 API介绍2.1什么是Win32 API2.2控制台程序(Console)2.3控制台屏幕的坐标COORD2.4GetStdHandle2.4.1函数语法2.4.2函数的使用 2.5GetConsoleCursorInfo2.5.1函数语法2.5.2函数的使用 2.6CONSOLE_CURSOR_INFO2.6.1结构体结构2.6.2结…