我罗斯方块最终篇
这个作业属于哪个课程 | 2020年面向对象程序设计 |
这个作业的要求在哪里 | 我罗斯方块最终篇 |
这个作业的目标 | 代码的 git 仓库链接,运行截图/运行视频,代码要点收获与心得,依然存在的问题。 |
项目地址 | https://github.com/Taketo-i/Tetris |
小组成员 |
031902631 周盛霖 031902616 翁子龙 |
一.运行视频:
链接(内含演示视频和完整的游戏)
提取码: uvmw
顺便放几张截图
二.代码要点
代码已全部上传至GitHub,这边主要提一下上次汇报之后完成的核心部分。
消行函数。若存在满行,则把该行的上方全部往下移一行,逐层覆盖。最后返回的是消去的行数cnt,以供增行函数判断要增加几行。
int Map::Clear_Line() { int cnt = 0; for (int i = 1; i < MAXY - 1; i++) { int flag = 1; for (int j = 1; j < MAXX - 1; j++) { if (map[i][j] == 0) { flag = 0; break; } } if (flag == 1) { for (int yy = i; yy > 0; yy--) { for (int j = 1; j < MAXX - 1; j++) { map[yy][j] = map[yy - 1][j]; } } cnt++; } } return cnt; }
增行函数。在最底下随机增加一行,以防欧皇遇到增加空行,特意设定成遇到空行时重新生成((
int Map::getTop() { //寻找最顶行所在的行数 int top; for (top = 0; top < MAXY - 1; top++) { for (int j = 1; j < MAXX - 1; j++) { if (map[top][j] == 1) { return top; } } } } void Map::Add_line(int cnt) { int top = getTop(); while (cnt-- && top > 0) { for (int i = top - 1; i < MAXY - 2; i++) { for (int j = 1; j < MAXX - 1; j++) { map[i][j] = map[i + 1][j]; } } top--; int flag = 0; //用于判断,以防出现生成空行的情况 int j = 2; while (!flag) { for (int i = 1; i < MAXX - 1; i++) { map[MAXY - j][i] = rand() % 2; if (map[MAXY - j][i]) { flag = 1; } } } j++; } }
融合函数:将下落到底的方块融入地图中。写完后才发觉应该把方块类设置成地图类的友元类,这样能简化不少操作= = (但也懒得改了)
void fuse(Map &M, Block &A) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (A.shape[i][j] == 1) { M.map[A.y + i][A.x + j] = 1; } } } }
欢迎界面及游戏结束界面。一开始没有先清屏,导致一些显示上的问题,现已解决。
bool Game::Welcome() { settextcolor(WHITE); settextstyle(60, 0, _T("黑体")); outtextxy(440, 100, "我罗斯方块"); settextstyle(30, 0, "黑体"); outtextxy(200, 280, "我罗斯方块是俄罗斯方块的改进版"); outtextxy(200, 380, "当一名玩家消去一行,就会让另一名玩家最底下增加随机一行"); outtextxy(350, 500, "空格 : 开始 esc : 退出"); char input; while (1) { input = getch(); if (input == ' ') { Game::clearScreen(); return true; } if (input == VK_ESCAPE) { Game::clearScreen(); return false; } } } bool Game::GameOver(Map P1_map) { Sleep(500); Game::clearScreen(); settextcolor(WHITE); settextstyle(60, 0, _T("黑体")); outtextxy(440, 200, "GameOver"); if (P1_map.getTop()) { outtextxy(470, 300, "P1 win!"); } else { outtextxy(470, 300, "P2 win!"); } outtextxy(200, 500, "空格 : 重来 esc : 退出"); FlushBatchDraw(); char input; while (1) { input = getch(); if (input == ' ') { Game::clearScreen(); return false; } if (input == VK_ESCAPE) { Game::clearScreen(); return true; } } } void Game::clearScreen() { setbkcolor(BLACK); cleardevice(); }
最后就是将各种运动函数整合到update_with_input()和update_without_input()中了。
easyX的动画制作相关的内容我是看这个视频学习的https://b23.tv/kYLBFB, 但时间有限(懒)只看了其中一部分简单的游戏的制作过程,所以在具体运用中出现了各种强行解决问题_(:з」∠)_
比如说,我是按照这个框架设计游戏主体循环的:
int main() { while (1) { update_with_input(); update_without_input(); Sleep(n毫秒); } return 0; }
但遇到了问题:
一.这个框架的意思是在每n毫秒多一点点内只会进行一次的更新,自然下落和键盘输入都只能有一次。
为解决这个问题,只好再设置了一个参数down,每次循环后down++,加至>8的时候归零并下落一格。然后再缩短休眠时间,才暴力实现了一次下落的间隔内可进行多次操作。
二.需要很好地调整沉睡的时间。
因为我们是用GetAsyncKeyState函数来接收键盘输入,故休眠时间过短则会导致多次操作(比如按了一下 w 却变形了不止一次)。
且休眠时间也不能太长,否则操作可能会有延时(这点在 暂停/开始 操作体现得最为明显),手速快的话甚至有的操作会被忽略。
调了好久最后选了休眠120毫秒,不过偶尔还是会出现多(两)次操作的情况。
总的来说,这个框架并没能做到间隔极微小地实时更新输入。只不过休眠时间短,加上我的各种暴力措施,看上去就像是做到了。(如果你以1ms/每次 的手速敲键盘,你就会发现你敲的一百多下中只有一下是有意义的)
然后也有进行一些优化,比如平衡各种方块的生成概率(主要是大幅削减“田”的出现概率),调节方块随机生成的颜色的范围确保其不会和背景色太过接近等等。
依然存在的问题
①很致命的一个问题:方块在完全进入地图之前无法左右移动。因为方块是从画面外掉下来的,刚开始写地图数组时忘记给场外也设定数据了(场外相当于map[-1],map[-2],越界了),故移动判断函数不起作用,无法移动。
一开始甚至连下落都不行,因为还没进场就被判断成无法下落了orz
后经我的暴力操作后可以正常下落了,但在场外左右移动还是很难实现。因为时间有限(麻烦)暂时也没法重写地图数组了,先将就一下吧。
②没能很好地发挥出面向对象的优势。纵观全程也只用到了类的最基本内容,其他东西基本还是上学期学的,所以代码量较大。
③不够美观没有背景音乐等等。
收获与心得
周盛霖:
第一次接触大程序,感受到了和pta作业的明显区别:对实际运用的要求高了很多,模块化的设计显得很重要,很多东西都是从零开始,需要较强的自学能力等等。
同时初步学习了easyX的使用,亲身体验了小游戏的开发过程,体验到了组团写代码的力量,总的来说是收获满满w
最重要的还是明白了学习编程不能止步于学校布置的题目,应想办法将所学内容运用于现实生活中,发挥编程的实际意义并提高自己的编程能力。
翁子龙:
我主要负责了我罗斯方块的判断代码部分,这部分主要用于判断方块能否移动或变形。在编写判断部分时由于方块种类和变形状态的不同,每种方块的不同状态有不同的判断方式,在这上面花了不少精力。我还负责了欢迎界面以及游戏结束界面的编写,用于开始和结束游戏。
编写程序仅仅靠已掌握的知识是基本上是从零开始,有挺多部分都是需要自己学习的,编程是一个需要在不断学习中不断获取新知识再将知识进行运用的过程。
在编写程序时,团队配合挺重要的,团队人员间是需要进行沟通和配合,才能够完成编写的任务。在编写过程中,我受到了我的队友的很大帮助。
今后可能需要编写大型程序时这点应该会更明显。