用C/C++制作一个简单的俄罗斯方块小游戏
用C/C++制作一个简单的俄罗斯方块小游戏
- 用C/C++制作一个简单的俄罗斯方块小游戏
- 0 准备
- 1 游戏界面设计
- 1.1 界面布局
- 1.2 用 EasyX 显示界面
- 1.3 音乐播放
- 2 方块设计
- 2.1 方块显示
- 2.2 随机生成一个方块
- 2.3 方块记录
- 3 方块移动和旋转
- 3.1 方块的移动
- 3.2 方块的旋转
- 3.3 方块的碰撞和消除
- 3.3.1 碰撞
- 3.3.2 消除
- 3.3.3 分数和下落速度
- 3.3.4 game over
- 4 制作 exe 文件
- 5 总结

0 准备
- 开发环境:Windows
- 编程语言:C/C++
- 开发软件:Visual Studio 2022 (软件安装可参考:链接: Visual Studio 2022 免费版最新版本下载安装教程
- 图形插件:EasyX
EasyX 的安装非常简单,百度搜索以下即可,但是在安装前一定要先安装 Visual Studio。下面的章节给出如何使用它。
1 游戏界面设计
1.1 界面布局
首先,我们要选择一章图片作为游戏的背景,我们可以在图片网站上下载合适的背景图片。
其次,在背景图片上划分出,游戏区和显示区,一般游戏区在正中间,两边为显示区。游戏区用于控制方块的移动、消除和旋转等;显示区用于速度和分数的展示。下面是我设计的游戏界面:

用画图工具打开图片,可以看到背景大小为800*600像素,同时在图片中间设置游戏区(虚线框,大小自定义),添加速度和分数的文本框。
1.2 用 EasyX 显示界面
此时,我相信你应该会建立 VS 工程了,并且安装了 EasyX。
建立一个文件夹 imp ,把1.1小节中的图片放在里面。imp 文件夹和 main.cpp 文件同一目录。
显示图形需要调用头文件 graphics.h。我们需要知道显示的图形的大小,这边是800*600,显示的位置为(0,0)
#include <graphics.h>// 绘图窗口初始化initgraph(imp_width, imp_heght);loadimage(&background, _T("img/background.png"));putimage(0, 0, &background);
显示如下:

这是一个简单的显示案例,后续方块的显示也是用这种方式。
1.3 音乐播放
先用 酷狗 下载一首好听的音乐,然后将音乐放在和 main.cpp 同一目录下。
我这边用了两首音乐,每次打开都是随机播放
程序:
#include "Windows.h"
#include <time.h>#pragma comment (lib, "winmm.lib")void Music::palyMusic()
{int chFlag;srand((unsigned)time(NULL));chFlag = rand() % 2;if (chFlag == 0){//mciSendString("close 1.mp3", NULL, 0, NULL);mciSendString("open 2.mp3", NULL, 0, NULL);mciSendString("play 2.mp3 repeat", NULL, 0, NULL);mciSendString("setaudio 2.mp3 volume to 100", 0, 0, 0);}else{//mciSendString("close 2.mp3", NULL, 0, NULL);mciSendString("open 1.mp3", NULL, 0, NULL);mciSendString("play 1.mp3 repeat", NULL, 0, NULL);mciSendString("setaudio 1.mp3 volume to 100", 0, 0, 0);}
}
2 方块设计
2.1 方块显示
设计7种方块类型:
const int blocks[7][4] = {1,3,5,7, // I2,4,5,7, // Z 1型3,5,4,6, // Z 2型3,5,4,7, // T2,3,5,7, // L3,5,7,6, // J2,3,4,5, // 田};
方块的表示如下图所示:

上面两张图表示在游戏中方块是如何表示的,方块可以由界面的横纵坐标表示,我们可以将下落的初始位置作为横坐标,方块的左边界作为纵坐标。其实就是游戏区的左上角作为坐标。
那么显示的原理知道了,就是操作数组,方块的图形呢?

小方块的显示可以根据这张图,从上图不难看出,小方块的大小为:20*20像素。如果我们要显示第一个小方块,我们可以加载这张图片然后从坐标(0,0)开始,显示长宽为20像素的图片。同意,如果要显示第三个绿色方块,就是从坐标(40,0)开始。
下面是实现的部分程序:
//计算小方块位置
blockType = rand() % 7;
for (int i = 0; i < 4; i++)
{smallBlock[i][0] = blocks[blockType][i] / 2;smallBlock[i][1] = blocks[blockType][i] % 2 + 1;//离左边界一格显示,方便旋转
}//小方块显示函数
void Graph::block()
{IMAGE imgTmp;loadimage(&imgTmp, _T("img/small.png"));SetWorkingImage(&imgTmp);//putimage(0, 0, &imgTmp);for (int i = 0; i < 7; i++) {this->imgs[i] = new IMAGE;getimage(this->imgs[i], i * blocks_size, 0, blocks_size, blocks_size);}SetWorkingImage();
}

2.2 随机生成一个方块
随机生成方块的原理就是将记录正在下落方块的数组清空初始化
void Graph::random()
{blockType = rand() % 7;for (int i = 0; i < 4; i++){smallBlock[i][0] = blocks[blockType][i] / 2;smallBlock[i][1] = blocks[blockType][i] % 2 + 1;//离左边界一格显示,方便变形}colBasis = 0;rowBasis = 0;
}
2.3 方块记录
我们不仅需要对当前正在操作的方块进行记录,还需要对已经下落还未被消除的方块进行记录。可以将游戏区看作一个二维数组,开辟一个 29*14 的二维数组进行记录。
//背景图像大小const unsigned int imp_width = 800;const unsigned int imp_heght = 600;//小方块大小const unsigned int blocks_size = 20;//游戏区边界const unsigned int left_margin = 240;const unsigned int right_margin = 485;const unsigned int down_margin = 570;const unsigned int up_margin = 10;const int rows = 29;const int cols = 14;//记录方块的数组vector<vector<int>> allBlock;
这里有个小技巧,因为方块要显示不同的颜色,因此我们可以用二维数组allBlock的值当作颜色的值
当allBlock[i][j]的值为0时,表示该位置没有方块;
当allBlock[i][j]的值大于0时,表示该位置有方块,显示的颜色用allBlock[i][j]的之表示
//已静止方块显示for (int i = rows-1; i > 3; --i){for (int j = 0; j < cols; ++j){if(allBlock[i][j]!=0)putimage(left_margin + j * blocks_size, up_margin + i * blocks_size, imgs[allBlock[i][j]-1]);}}
3 方块移动和旋转
3.1 方块的移动
方块的移动就是一个核心:方块的移动 = 对数组的操作
方块的下落 = 行坐标+1
方块的左移 = 列坐标-1
方块的右移 = 列坐标+1
前提是需要判断是否出界或者移动的下一个位置是否有方块
程序如下:
void Graph::moveLeft()
{for (int i = 0; i < 4; i++){if (smallBlock[i][1] <= 0 || allBlock[smallBlock[i][0]][smallBlock[i][1] - 1] >= 1)return;}for (int i = 0; i < 4; i++){--smallBlock[i][1];}--colBasis;
}void Graph::moveDown()
{for (int i = 0; i < 4; i++){if (smallBlock[i][0] >= rows)return;}for (int i = 0; i < 4; i++){++smallBlock[i][0];}++rowBasis;
}void Graph::moveRight()
{for (int i = 0; i < 4; i++){if (smallBlock[i][1] >= cols - 1 || allBlock[smallBlock[i][0]][smallBlock[i][1] + 1] >= 1)return;}for (int i = 0; i < 4; i++){++smallBlock[i][1];}++colBasis;
}
当然移动的前提说需要用户按键输入的,所以需要有判断按键输入的函数和读取按键值的函数,我这边使用函数 _kbhit() 来判断是否有按键输入,用函数 _getch() 读取按键值
//控制方块移动if (_kbhit() && graph.startFlag)//如果键盘有输入{graph.keyPlay();}
void Graph::keyPlay()
{int ch = 0;ch = _getch();switch (ch){//WASD键(小写)case 119: changeBlock();//上键break;case 97: moveLeft();//左键break;case 115: moveDown();//下键break;case 100: moveRight();//右键break; //上下左右键case 72: changeBlock();//上键break;case 75: moveLeft();//左键break;case 80: moveDown();//下键break;case 77: moveRight();//右键break;}
}
3.2 方块的旋转

如上图所示,这样可以用几行代码实现了方块的旋转,但是仍然需要注意下面的几个问题:
- 以什么为中心旋转?
- 方块是不断下落的,行和列是一直在变化的
- 在边界处有部分方块是不能旋转的
针对第一个问题,如果想让方块的旋转看起来不那么别捏,以4*4方格的中心旋转是最合适的,即图中的2,3,4,5作为旋转的核心。
针对第二个问题,可以将方块的行列切换至初始位置,再进行上图的公式,然后再切回来,这边可以设置两个变量确定方块离初始位置的距离。
针对第三个问题,将方块的行列号暂存,进行变换,然后再进行检测是否有方块在边界外面,如果有,旋转这步算作废。
旋转的时候初始位置的确定也是非常关键的,因为在边界处有些旋转是做不了的

程序实现:
void Graph::changeBlock()
{int temp[4][2] = { 0 };for (int i = 0; i < 4; i++){//配合偏置,进行方块的旋转temp[i][0] = smallBlock[i][1] - colBasis;temp[i][1] = 3 - (smallBlock[i][0] - rowBasis);temp[i][0] += rowBasis;temp[i][1] += colBasis;//检查合法性if (temp[i][1] == 0 || temp[i][1] == cols - 1)return;}for (int i = 0; i < 4; i++)//若合法,实行{smallBlock[i][0] = temp[i][0];smallBlock[i][1] = temp[i][1];}
}
3.3 方块的碰撞和消除
方块的消除需要考虑下面几个问题
- 碰撞检测
- 一行的消除算法
3.3.1 碰撞
碰撞检测很容易实现,由于左右移动我已经设置了边界检测,这边只需要对四个方块进行判断,也就是是说判断它们下面是否有方块就行。如果有,就返回 1
int Graph::check()
{int row, col;for (int i = 0; i < 4; i++)//若合法,实行{row = smallBlock[i][0]+1;col = smallBlock[i][1];if (row >= this->rows || allBlock[row][col] >= 1){if (rowBasis == 0)return 2;elsereturn 1;}}return 0;
}
3.3.2 消除
对一行的消除,采用一个二维数组对所有的位置进行记录,如果在(i,j)处有方块,则 allBlock[i][j]=1;在碰撞检测完毕之后,对整个数组进行遍历,对每一行移动的行数进行记录,尽量减少时间复杂度。
int clearRowNum[30] = { 0 };
int num=0;
//unordered_map<int, int>map;
if (check()==1)
{for (int i = 0; i < 4; i++) {int row = smallBlock[i][0];int col = smallBlock[i][1];allBlock[row][col] = blockType+1;}//消除一行for (int i = rows-1; i > 3; --i){for (int j = 0; j < cols; ++j){if (allBlock[i][j] == 0){clearRowNum[i] = num;break;}else if (j == cols-1)//该行需要消除{++num;clearRowNum[i] = 0;}}}for (int i = rows - 2; i > 3; --i){if (clearRowNum[i] != 0){for (int j = 0; j < cols; ++j){allBlock[i + clearRowNum[i]][j] = allBlock[i][j];}}}
}
3.3.3 分数和下落速度
同时,在消除函数中可以添加分数计算,速度计算。大致的逻辑是每消除一行,分数变多;分数越高,下落速度越快;
//设置速度,得分越多,速度越快
score += num * cols;speed = 100+score/10;
3.3.4 game over
当小方块处于初始位置时,它的下方有方块时就可以判断 game over了。
if (row >= this->rows || allBlock[row][col] >= 1)
{if (rowBasis == 0)return 2;elsereturn 1;
}
game over 之后,界面会一直显示game over,直到输入 回车键
//游戏结束if (!graph.startFlag){settextcolor(WHITE);settextstyle(40, 0, "黑体");setbkmode(TRANSPARENT);char s[10] = "Game Over";outtextxy(300, 280, s);if (_kbhit() && _getch() == 13)//如果键盘有输入{graph.init();}}

4 制作 exe 文件
如何用 Visual Studio打包项目程序可以参考:
Visual Studio 怎么将项目程序打包成软件
5 总结
最后我想说的是,对方块的移动和旋转,其根本就是在对数组进行操作。
至于后续的一些最高分数记录,下一个方块提示等功能,都是锦上添花的功能,感兴趣的小伙伴可以尝试添加一下。
程序下载:
俄罗斯方块小游戏程序下载
相关文章:
用C/C++制作一个简单的俄罗斯方块小游戏
用C/C制作一个简单的俄罗斯方块小游戏 用C/C制作一个简单的俄罗斯方块小游戏 0 准备1 游戏界面设计 1.1 界面布局1.2 用 EasyX 显示界面1.3 音乐播放 2 方块设计 2.1 方块显示2.2 随机生成一个方块2.3 方块记录 3 方块移动和旋转 3.1 方块的移动3.2 方块的旋转3.3 方块的碰撞和…...
使用免费负载生成器swingbench对oracle数据库进行压力测试(测试Oracle的功能或评估性能)
1.Swingbench 简介 Swingbench 是一个免费负载生成器(和基准测试),旨在对 Oracle 数据库 进行压力测试。目前最新版本 Swingbench 2.6。 SwingBench 由负载生成器,协调器和集群概述组成。该软件可以生成负载 并绘制交易/响应时间…...
【预告】ORACLE Primavera P6 v22.12 虚拟机发布
引言 离ORACLE Primavera P6 EPPM最新系统 v22.12已过去了3个多月,应盆友需要,也为方便大家体验,我近日将构建最新的P6的虚拟环境,届时将分享给大家,最终可通过VMWare vsphere (esxi) / workstation 或Oracle virtua…...
机器学习100天(四十):040 线性支持向量机-公式推导
《机器学习100天》完整目录:目录 机器学习 100 天,今天讲的是:线性支持向量机-公式推导! 首先来看这样一个问题,在二维平面上需要找到一条直线划分正类和负类。 我们找到了 A、B、C 三条直线。这三条直线都能正确分类所有训练样本。但是,哪条直线最好呢?直观上来看,我…...
失败经验之震荡玩家往往死于趋势市场
亏损,是从去年开始的吧。 尤其是去年,仅仅一年,就亏掉了自从交易以来的所有盈利。 现在,我甚至不敢去计算具体的亏损金额。 保守估计,已经亏损100万左右。 现在回想,似乎也是必然。 交易本来就是一个走…...
应用层与传输层~
文章目录应用层自定义应用层协议什么是自定义应用层协议自定义方式运输层运输层概述运输层特点运输层协议UDP协议UDP的特点UDP首部格式校验规则TCP协议TCP的特点TCP协议段格式TCP的性质确认序号超时重传连接管理三次握手四次挥手TCP的状态滑动窗口流量控制拥塞控制延迟应答捎带…...
IO文件操作
认识文件 狭义的文件 存储在硬盘上的数据,以“文件"为单位,进行组织 常见的就是普通的文件 (文本文件,图片, office系列,视频,音频可执行程序…)文件夹也叫做"目录" 也是一种特殊的文件。 广义的文件 操作系统,是要负责管理软硬件资源,操作系统(…...
【构建工具】webpack 3、4 升级指南,摆脱低版本的困扰
一、依赖处理 1.升级通用依赖 借用 ncu 库实现,帮你改写需要升级的package.json 然后再 npm install ncu -u <packages> # 可以指定依赖 ncu # 升级全部依赖大概列了下升级的效果 add-asset-html-webpack-plugin ^2.1.3 → ^5.0.2 clean-webpack-…...
Javaweb第一个项目——实现简单的登陆功能
第一步:打开idea-->文件-->新建 第二步: 在Demo文件夹 点击右键-->添加框架支持-->找到Web应用程序 勾选 第三步:配置Tomcat 第四步:新建一个lib(建在web-INF文件夹下)文件夹 用于存放jar包…...
OpenKruise 开发者不容错过的带薪实习机会!马上加入 LFX Mentorship 计划
LFX Mentorship 计划由 Linux Foundation 组织发起,为像 OpenKruise 这样的 CNCF 托管项目提供了激励开源贡献、扶植社区发展的优秀土壤。参与其中的开发者不仅有机会在经验丰富的社区 Mentor 指导下贡献开源项目、为职业生涯加分,完成工作后还能获得 $3…...
《c++ primer笔记》第八章 IO库
前言 简单看一下就行 文章目录一、IO类1.1基本概念1.2管理输出缓冲二、文件输入输出2.1文件模式三、string流3.1istringstream3.2ostringstream一、IO类 1.1基本概念 我们常见的流有istream和ostream,这两个流都是有关输入和输出的,此外,…...
web开发 用idea创建一个新项目
这个写着就是给自己当备忘录用的QAQ 这个老师上课一通操作啥也没看清…卑微搞了半天看样子是成功了 记录一下省的以后忘了怎么创建(? zufe lxy 2023.3 先行条件是已经自己装好了Tomcat和idea!!(我的idea是申请了教育…...
【FMCW 03】测速
从上一讲 测距 末尾的frame讲起。我们知道一个chirp对应了一个采样后的IF信号,我们将这些采样后的IF信号按chirp的次序排列成一个帧(frame),这就得到了我们实际中接收后处理的FMCW信号。 由于chirp的发射返回时间很短,…...
ERP(企业资源管理)概述
🌟所属专栏:ERP企业资源管理🐔作者简介:rchjr——五带信管菜只因一枚😮前言:该系列将持续更新ERP的相关学习笔记,欢迎和我一样的小白订阅,一起学习共同进步~👉文章简介&a…...
深入理解java虚拟机精华总结:性能监控和故障处理工具、类加载机制
深入理解java虚拟机精华总结:性能监控和故障处理工具、类加载机制性能监控和故障处理工具、类加载机制jpsjstatjinfojmapjhatjstackVisualVM类加载机制类加载的时机类加载的过程加载验证准备解析初始化类加载器类与类加载器双亲委派模型破坏双亲委派模型往期内容&am…...
推荐系统与推荐算法
文章目录第一章1.1推荐系统意义与价值1.2推荐系统历史与框架1.3推荐算法分类第二章2.1协同过滤的基本思想与分类2.2基于用户的协同过滤2.3基于项目的协同过滤2.4基于邻域的评分预测2.5基于二部图的协同过滤第三章3.1基于关联规则的推荐3.2基于矩阵分解的评分预测3.3概率矩阵分解…...
socket 编程实战(编写客户端程序 )
编写客户端程序 接着上一篇:实战服务端程序 接下来我们再编写一个简单地客户端应用程序,客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入。示例代码如下所示: #include…...
“巨亏成名”的魔鬼交易员,你知道几个?
谁说在期货市场上只有赚大钱才能出名?殊不知还有这样一群特殊的交易员靠着巨额亏损而“一战成名”,亏得是老东家元气大伤,外号“魔鬼交易员”——“不亏不成魔”!接下来火象就给大家盘点几位代表性魔鬼交易员,看看他们…...
1380:分糖果(candy)
1380:分糖果(candy) 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 童年的我们,将和朋友分享美好的事物作为自己的快乐。这天,C小朋友得到了Plenty of candies,将要把这些糖果分给要好的朋友们。已知糖果从一个人传…...
数据挖掘(2.1)--数据预处理
一、基础知识 1.数据的基本概念 1.1基础知识 数据是数据对象(Data Objects)及其属性(Attributes)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征,例如一个人的肤色、眼球…...
Spring源码全家桶核心宝典,Java程序员提升基础内功必备!
Spring是我们Java程序员面试和工作都绕不开的重难点。很多粉丝就经常跟我反馈说由Spring衍生出来的一系列框架太多了,根本不知道从何下手;大家学习过程中大都不成体系,但面试的时候都上升到源码级别了,你不光要清楚了解Spring源码…...
NVIDIA Profile Inspector终极指南:解锁700+显卡隐藏设置,提升游戏性能30%
NVIDIA Profile Inspector终极指南:解锁700显卡隐藏设置,提升游戏性能30% 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款强大的开源显卡配置工具…...
AIGC面试指南:从Transformer到扩散模型,系统掌握核心技术与实战
1. 项目概述:一本面向AIGC求职者的实战指南最近几年,AI生成内容(AIGC)领域的热度可以说是“肉眼可见”地飙升。从文本生成、图像创作到视频合成,相关岗位如雨后春笋般涌现,吸引了大量开发者和研究者的目光。…...
为什么92%的AIGC剪辑师仍在用手动导出?揭秘Sora 2直连Premiere的7大底层优化与3个避坑红线
更多请点击: https://intelliparadigm.com 第一章:Sora 2与Premiere直连整合的行业悖论与破局起点 当OpenAI正式释放Sora 2的API文档并开放有限开发者预览时,Adobe Premiere Pro团队内部立即启动了“Project Lumen”——一项旨在实现双向帧级…...
Smoothieware 分支固件编译与配置项深度解析
1. Smoothieware分支固件编译全流程实战 第一次接触Smoothieware_best-for-pnp这个分支时,我完全没想到一个开源3D打印机固件能有这么多隐藏玩法。这个由社区开发者维护的分支,在保留官方核心功能的同时,针对OpenPNP应用场景做了大量优化。最…...
怎么限制用户上传到MongoDB GridFS的文件总容量
GridFS不支持全局容量配额,需在应用层实现配额校验:上传前聚合查询fs.files中指定用户的length总和,判断是否超限,且须防范并发写入导致的超限问题。GridFS 本身不提供全局容量配额机制MongoDB 的 GridFS 是一个文件分片存储规范&…...
Pytorch图像去噪实战(九十三):数据集版本管理实战,保证每次训练数据可追溯、可回滚
Pytorch图像去噪实战(九十三):数据集版本管理实战,保证每次训练数据可追溯、可回滚 一、问题场景:模型效果变好了,但不知道用了哪批数据训练 图像去噪项目进入迭代阶段后,数据会不断变化: 新增用户反馈样本 新增真实噪声数据 删除低质量图片 加入OCR场景样本 加入低光…...
2025届学术党必备的五大AI写作工具实际效果
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 到了2026年,人工智能生成内容也就是AIGC技术,已经深入渗透到内容创作…...
7th grade math (2026.05.15)Binary Linear Equation Group
Binary Linear Equation Group 七年纪(下)数学第十章《二元一次方程组》作业评价参考答案-zwf 错误题型分析...
