一、项目介绍
这是一个可以单人进行的俄罗斯方块小游戏。
按左右键移动方块,按上键可以旋转方块,按下键可以加速方块的下落(需要控制好按下的时长,否则下一个方块也会加速落下)。方块碰到屏幕底部,或者碰到已经堆积的方块就会停下,此时上方会落下下一个方块。右侧会对下一个方块的种类进行提示。
编译环境:visual c++ 6.0
第三方库:Easyx2022 注意需要提前安装easyX,如没有基础可以先了解easyX图形编程
二、运行截图
三、源码解析
游戏逻辑:
1. 生成界面,初始化程序
2. 游戏开始循环。每次循环检测输入按键做出反应,之后睡眠50毫秒,再开始下一次循环。
3. 按住上键调用旋转的函数。
4. 按住左右键,改变方块的横坐标,调用对应函数。
5. Esc 键退出程序。
6. 在循环当中,利用计时器判断是否过了500毫秒,如果过了则下落一格。下落一格则检测底部是否碰撞,碰撞则重新生成方块,改变对下一个方块的提示,清除连成一行的方块并得分,检测游戏是否结束。
7. 检测是否按住下键,按住则加速下落。
8. 失败后循环结束,退出游戏。
根据这些我们需要完成的功能,我们定义两个类。其一为游戏类,内置地图,分数,时间等整局游戏的变量,以及设置地图,判读满行,清除行等操作函数。
另一个类是方块类,设置方块的坐标,类型,旋转方向,颜色等特征,以及对应的初始化,添加,移动,旋转等操作函数。
接下来是相关步骤的详细解析。
界面生成以及初始化
SetWindowText(initgraph(350, 440), "俄罗斯方块dotcpp.com"); // 设置绘图颜色 setbkcolor(WHITE); cleardevice(); setlinecolor(BLACK); // 生成游戏界面和数据 srand(time(NULL)); Block::generateBlockData(); Game game; game.drawMap(); game.drawPrompt(); Block b(game); Block nextBlock(game, 11, 2); // 下一方块 clock_t start = 0; // 时钟开始时间 clock_t end; // 时钟结束时间 ExMessage msg; nextBlock.draw();
这里用到了一些Easyx当中的函数。如无注明,后文中非本程序定义的函数也都位于Easyx当中。
setbkcolor()用于设置当前设备绘图背景色。
Cleardevice()使用当前背景色清空绘图设备.
Setlinecolor()用于设置当前设备画线颜色。
ExMessage结构体用于保存鼠标信息。
在此处我们还定义了几个相关的函数。
Block::generateBlockData()用于设定不同种类方块的信息。blockData是一个三维数组,用于保存所有方块的数据,第一位是方块种类,二三位则是横纵坐标,调节这些数据与游戏内容一致。
roundrect用于画无填充的圆角矩形。
rectangle用于画无填充的矩形。
setfillcolor用于设置当前设备填充颜色。
fillrectangle用于画有边框的填充矩形。
game.drawPrompt()用于绘制提示界面,主要是各种提示。
其中最主要用到的outtextxy是在x,y坐标处输出文字。
其余的还有settextstyle设置当前文字样式。
gettextstyle获取当前文字样式。
settextcolor设置当前文字颜色。
Block::draw()用来绘制方块。
还是用到了setfillcolor和fillrectangle,根据方块数据对应的xy坐标,用双层循环的方式输出结果。注意在Y坐标为负时不绘制。
2. 游戏循环
while (true) { b.clear(); clearrectangle(20, 20, 220, 420); game.drawMap(); …… b.draw(); game.clearLine(); FlushBatchDraw(); // 刷新缓冲区 Sleep(50); // 每 50 毫秒接收一次按键 }
b是Block类的一个对象。
clearrectangle用于清空矩形区域。
FlushBatchDraw用于执行未完成的绘制任务。
其中也用到了Block类中定义的两个函数Block::draw和Block::clear。前者已经在前文中提到过,而后者则是把绘制函数换成了clearrectangle,以此来清除绘制的方块。通过调用clear再调用draw,可以及时地显示出方块的变化。
在之后还调用了Game::clearLine()这个函数。其目的是判断哪一行已经满了,并将其清除以及增加得分。
void Game::clearLine() { int line = -1; // 判断哪一行满行 for (int j = 0; j < MAP_HEIGHT; j++) { if (checkLine(j)) { line = j; break; } } if (line != -1) { // 将上一行移至满行 for (int j = line; j > 0; j--) { for (int i = 0; i < MAP_WIDTH; i++) { map[i][j] = map[i][j - 1]; } } score += 10; // 将游戏分数加 10 } drawPrompt(); }
首先用循环判定哪一行是满的,其中用到了Game::checkLine(),其内部也是一个循环,依次检测某一y坐标对应的所有x坐标是否都为1。如果检测到某行是满的,跳出循环,继续函数的后续部分。不用担心没有判断所有行是否填满,因为在50毫秒之后,该函数会再次被调用。
然后,将被消除那一行上方的所有行都向下移动一行。循环从j行开始,以y坐标递减的方式执行,让map变量(所有方块的位置)对应坐标位置的数据等于其y坐标减一的数据。最后如果消除,则将游戏分数加10。调用绘制提示界面的函数drawPrompt(),将得分显示在旁边。
3. 按下上键
这里先看我们如何获取键盘信息。
while (peekmessage(&msg, EM_KEY) && msg.message == WM_KEYDOWN)
{
switch (msg.vkcode)
{
peekmessage用于获取一个消息,并立即返回。其中参数&msg表示用指针的形式保存获取到的信息,而 EM_KEY意味这是键盘信息。这个函数获取消息的返回值为True,如果没有获取到,则返回False。
右边的msg为ExMessage结构体的对象,msg.message == WM_KEYDOWN表明获取到的信息为键盘按下。上面的代码意味着,如果你向程序发出了指令,并且该指令是键盘按下时,循环才会执行。
msg.vkcode代表按键的虚拟键码。很显然,
case 'W':
case VK_UP:
b.rotate();
break;
意味着如果你按下上键或者W键,才会执行b.rotate()。
b.rotate()用于改变b(Block)类型的数据。执行会改变其中block[4][4]这个二维数组,这个数组用1,0来表示在对应位置是否存在方块。对每种情况分类讨论,得出旋转之后的结果。
4.按下左右键,方块左右移动
// 左键移动
case 'A':
case VK_LEFT:
b.move(1);
break;
// 右键移动
case 'D':
case VK_RIGHT:
b.move(2);
break;
在Block类中,我们定义了move函数。其中只有一个参数,0 表示下移一格,1 表示左移一格,2 表示右移一格,当下移检测到碰撞时返回 true。
switch (direction)
{
case 0:
y++;
if (checkCollision())
{
y--;
return true;
}
break;
其逻辑很简单,就是根据输入的不同情况,改变x或者y的坐标(x,y是整个大方块的坐标)。之后进行碰撞检测,如果碰撞,取消移动。上面是其中下方碰撞情况,注意如果是向左或向右移动,则需要返回False,因为返回True时,方块便落到地图上了。
Block::checkCollision()用于碰撞检测。 bool Block::checkCollision() const { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { // 判断方块是否与地图发生碰撞,顶部不判断 if ((game.getMap(x + i, y + j) || 20 + BLOCK_WIDTH * (x + i) < 20 || 20 + BLOCK_WIDTH * (x + i) + BLOCK_WIDTH > 220 || 20 + BLOCK_WIDTH * (j + y) + BLOCK_WIDTH > 420) && block[i][j]) { return true; } } } return false; }
用双重循环,判定每一个小方块是否超出地图边界,或者与地图上方块重叠。
四、完整源码
本文固定URL:https://www.dotcpp.com/course/1230
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程