新手网站建设教程/seo官网
用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)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征,例如一个人的肤色、眼球…...

PMP考前冲刺3.06 | 2023新征程,一举拿证
题目1-2:1.一名团队成员表示,他们的用户故事要等到迭代结束后才能完成,因为他们的职能经理要求他们协助解决高优先级的生产问题。项目经理应该做什么?A.将问题上报给项目发起人以解决和调整项目燃尽图B.与产品负责人讨论用户故事不…...

buuctf-pwn write-ups (11)
文章目录buu083-x_ctf_b0verfl0wbuu084-picoctf_2018_leak_mebuu085-inndy_echobuu086-hitcontraining_unlinkbuu087-ciscn_2019_final_3buu088-axb_2019_fmt64buu089-wustctf2020_name_your_catbuu090-pwnme1buu091-axb_2019_brop64buu092-[极客大挑战 2019]Not Badbuu083-x_c…...

【VTK】VTK隐藏vtkOutputWindow窗口的正确方法
VTK隐藏vtkOutputWindow窗口 要求隐藏vtkOutputWindow窗口,但是不能把Warning警告和Error错误的信息都给屏蔽了 网上常见的错误方法: 现在百度搜索出来的方法几乎都是在这样做:在main文件中使用vtkOutputWindow::SetGlobalWarningDisplay(0…...

顺序表以及链表的应用及区别(包含OJ讲解)
前面我已经发过怎么实现链表以及顺序表,今天大概的总结一下。 顺序表: 1.能够随时的存取,比较方便。 2.插入删除时,需要挪动数据,比较麻烦,因为是连续存储。 3.存储密度相对于链表来说是比较高的&#…...

JVM简介
一、什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 Java语言的一个非常重要的特点就是与平…...

Leetcode.1653 使字符串平衡的最少删除次数
题目链接 Leetcode.1653 使字符串平衡的最少删除次数 Rating : 1794 题目描述 给你一个字符串 s,它仅包含字符 a和 b 。 你可以删除 s中任意数目的字符,使得 s平衡 。当不存在下标对 (i,j)满足 i < j,且 s[i] b的同…...

leetcode 71~80 学习经历
leetcode 71~80 学习经历71. 简化路径72. 编辑距离73. 矩阵置零74. 搜索二维矩阵75. 颜色分类76. 最小覆盖子串77. 组合78. 子集79. 单词搜索80. 删除有序数组中的重复项 II小结71. 简化路径 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 &am…...

使用metrics-server监控k8s的资源指标
首先,欢迎使用DHorse部署k8s应用。 k8s可以通过top命令来查询pod和node的资源使用情况,如果直接运行该命令,如下所示。 [rootcentos05 deployment]# kubectl top pod W0306 15:23:24.990550 8247 top_pod.go:140] Using json format to …...

【Copula】考虑风光联合出力和相关性的Copula场景生成(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

【java基础】泛型程序设计基础
文章目录泛型是什么自定义泛型类自定义泛型方法类型变量的限定总结泛型是什么 泛型类和泛型方法有类型参数,这使得它们可以准确地描述用特定类型实例化时会发生什么。在没有泛型类之前,程序员必须使用Objct编写适用于多种类型的代码。这很烦琐ÿ…...