我罗斯方块最终篇(Player类、Game类)
|--------------------项目GitHub地址--------------------|
我负责的部分
- player类的完成
- game类的完成
- player类的调试
- game类的调试
代码要点
Player类
-
玩家地图map
-
碰撞检测:
bool Player::detectCollision(Block block, int x, int y) { //发生碰撞返回false //未发生返回true for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { // 如果检测到block中该位置为1的话 // 先检测这个位置是否越界 // 再检测这个位置对应的map位置是否也为1 if(block.block[i][j]==1) { if(x + i >= 20 || y + j < 0 || y + j >= 10) { return false; } else if(map[x+i][y+j]==1 && x + i >=0) { return false; } } } } return true; }
-
-
玩家方块管理
-
旋转:利用tempBlock标记,如果旋转后不会碰撞,则保持不变,如果旋转后碰撞则将tempBlock赋值回去
-
左移、右移:利用碰撞检测去检测移动后pos定位点,实现同旋转。
-
下移:检测同左移右移,如果发生碰撞就执行map的更新操作,如果发生碰撞需返回true,代表发生碰撞
for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { if(posX + i >= 0 && posY + j >= 0 && posX + i < 20 && posY + j < 10 && blockNow.block[i][j] == 1) { map[posX+i][posY+j]=1; } } }
-
-
管理用户信息如名字,得分
-
检测消行:检测每一行,如果满足消行,则将该行上面方块向下移动一格,在结尾返回消去的行数,方便双人模式下此消彼长
int Player::detectReductsion() { // 记录消去的行数 int count = 0; for(int i = 0; i < 20; i++) { // 记录单行的方块个数 int tempcount = 0; for(int j = 0; j < 10; j++) { if(map[i][j] == 1) { tempcount++; } } if(tempcount >= 10) { // 增加分数 point+=10; // 增加消除行数 count++; // 移动上面的行 for(int k=i;k>=1;k--) { for(int j=0;j<10;j++) { map[k][j]=map[k-1][j]; } } } } return count; }
-
方块增加:接受一个int类型传参,利用for循环每次在底部增加一行,在增加前先检测map顶部,是否游戏结束
bool Player::addBlock(int num) { for(int i=0;i<num;i++) { for(int t=0;t<10;t++) { if(map[0][t]>0) { fail = true; return false; } } // 移动下面行 for(int m=0; m<19; m++) { for(int n=0; n<10; n++) { map[m][n] = map[m+1][n]; } } // 底部清零 for(int j=0; j<10; j++) { map[19][j] = 0; } // 底部随机增加 for(int j=0; j<10; j++) { map[19][rand()%10]++; } for(int j=0; j<10; j++) { if(map[19][j]) { map[19][j]=1; } } } return true; }
-
玩家状态更新
- 生成新方块
- 玩家恢复数据到游戏开始前
Game类
-
私有成员:itfs -> Interface类的实例化,用于Game的渲染
-
游戏控制:控制游戏进程,包括模式选择,名字输入,游戏结束是否重新开始
-
游戏模式选择
int Game::selectGameType() { // 渲染 itfs.selectPart(); itfs.selectKey1(); int type=1; while(1) { if(_kbhit()){ int key=_getch(); if(key==72&&type!=1){ itfs.selectKey1(); type=1; } if(key==80&&type!=2){ itfs.selectKey2(); type=2; } if(key==13){ return type; } } } }
-
-
单人模式:速度控制,每秒检测50次键盘输入
// speed = 50 void Game::onePlayer(Player player) { // 页面渲染 while(1) { if(_kbhit()) { int key=_getch(); if(key==115) { // S键 if(方块下落) { if(检测消行) { // 渲染失败页面 // 返回 } // 更新方块和map } } else if(key==119) { // W键 // 旋转 } else if(key==97) { // A键 // 左移 } else if(key==100) { // D键 // 右移 } else if(key == 32) { // 暂停处理 } if(有消行) { // 清除map区域 // 更新方块 // 更新map // 更新分数 } else { // 更新方块 } } Sleep(20); if(--temptime == 0) { if(方块下落) { if(检测消行) { // 渲染失败页面 // 返回 } // 更新方块和map } if(有消行) { // 清除map区域 // 更新方块 // 更新map // 更新分数 } else { // 更新方块 } temptime = speed; } } }
-
双人模式:参考单人模式代码,增加在检测消行处增加方块增加功能
int add1 = player1.detectReductsion(); int add2 = player2.detectReductsion(); // 如果有消行,此消彼长,检测玩家2是否失败 if(add1) { if(!player2.addBlock(add1)) { // 失败渲染 itfs.gameResult(player1.getName(), player1.getPoint(), 1); return; } itfs.printPointPlayer1(player1.getPoint()); } // 如果没有,正常渲染玩家1的方块 else { itfs.deleteBlock1(); itfs.drawNowBlock1(player1.getNowBlock(), player1.getX(), player1.getY()); itfs.refreshBlock1(player1.getX(), player1.getY(), player1.getNowBlock()); } // 如果有消行,此消彼长,检测玩家1是否失败 if(add2) { if(!player1.addBlock(add2)) { // 失败渲染 itfs.gameResult(player2.getName(), player2.getPoint(), 1); return; } itfs.printPointPlayer2(player2.getPoint()); } // 如果没有,正常渲染玩家2的方块 else { itfs.deleteBlock2(); itfs.drawNowBlock2(player2.getNowBlock(), player2.getX(), player2.getY()); itfs.refreshBlock2(player2.getX(), player2.getY(), player2.getNowBlock()); } // 如果任何一个人有消行就重新渲染map if(add1 != 0 || add2 != 0) { itfs.clearMap1(player1.map); itfs.printMap1(player1.map); itfs.drawNowBlock1(player1.getNowBlock(), player1.getX(), player1.getY()); itfs.clearMap2(player2.map); itfs.printMap2(player2.map); itfs.drawNowBlock2(player2.getNowBlock(), player2.getX(), player2.getY()); }
遇到的问题
头文件包含问题
由图可见,block被重复包含,所以在编译时会报错,需要用到预编译知识
在Block处定义_BLOCK,用于判断是否已经包含Block类
/*-----------block.h-----------*/
#define _BLOCK
/*-----------player.h----------*/
#ifndef _BLOCK
#include "block.h"
#endif
/*----------interface.h--------*/
#ifndef _BLOCK
#include"block.h"
#endif
这样不会导致Block类在多处被定义
数组越界问题
在完成检测碰撞的测试时,在一个不起眼的地方数组发生了越界。我请求了团队的另外两个人一起查看,由于其中一个人使用的是32位虚拟机,所以在编译时需加上-m32
参数。然而在我这运行程序会出现问题,但是在32位虚拟机上却可以正常运行。这一结果导致思考方向偏离了原先正确的思路,开始相信自己代码没有存在问题,经过一段时间后,最终发现问题在于数组越界,但是这也值得思考32位和64位电脑对于数组越界的检查上的差异。
功能重复实现
在两个多星期之前,尝试过完成检测游戏是否失败的功能,但是在中间一段时间没有去处理该文件,后来对于功能的实现上的思路转变,导致渐渐淡忘这段不起眼的代码,再后来,将代码拼凑起来后,出现问题,却迟迟找不到错误,花费大量时间,最后通过不断测试输出,找到bug。
关于渲染页面闪烁的问题
在每次方块移动,消行,方块碰撞之后都需要渲染新的页面,开始采用的较为简单的,将页面全部清除,再重新渲染,但是会造成页面闪烁的问题,原因是由于清除的打印中间短暂的间隔,使得看上去好像屏幕在闪烁。为了解决这个问题,和负责渲染部分的队友讨论过后,认为只有在消行情况下需要重新渲染整个页面,其他情况下则可部分渲染,只对原先页面的的一小部分改动的地方进行清除和重新渲染,这样可以解决屏幕闪烁的问题,提高了用户的体验。
总结收获
- 这次作业的代码量较大,文件多,需要测试的地方也多。在完成这次作业的过程中,感觉自己对出现的bug的处理能力得到提高,在多文件,代码量巨大的情况下,对功能的实现需要构造清晰的框架,否则容易乱,我觉我在此方面得到了一定的锻炼。
- 这次作业适合另外两位小伙伴一起完成的,虽然先前有过合作开发的经验,到时在这次作业中是首次利用到GitHub这个平台来实现代码的综合管理,学习到了很多先前没有接触过的关于git的知识。在完成作业的过程中,和两位小伙伴之间的交流沟通是必不可少的,在对待问题的讨论和思考上,我见证了我们团队从开始的少言少语到后来的滔滔不绝,这中间的过程是渐渐磨合出来的,对于开发正需要这样的气氛。我认为这锻炼了我和团队沟通能力,对于整个团队来说,我们之间的默契都得到了提升。
- 对于编程工具vscode的使用更加熟练,在完成作业的过程中尝试了使用vscode自带的终端以及git工具,极大的方便了在完成作业的过程中的效率,其中充分的利用了vscode结合git的文档对比功能,查看另外两位小伙伴他们在解决问题时的思路和以及他们的实现代码,从中学习他人的优点。
- 对象面向对象的理解加深了,如果采用面向过程的方式,那么这个程序最后的代码一定会很长且不好理解,拆分成类之后,既方便了分工合作,也使得整体思路清晰,方向确定。
仍然存在的问题
-
关于双人模式下,键盘输入采用_kbhit()函数监听会导致按键发生冲突,当一个人按住键盘不动时,两个人的按键输入都会受到影响,目前还没有较好的解决办法。
思路:多线程、更好的输入函数
-
代码还不够简洁,关于game类还可以将一些功能进行拆分;在游戏重新开始的实现上采用了goto语句,不是很好。
-
在解决屏闪的问题时为了图方便,和队友商量后在Interface类中放入两个记录当前渲染的Block的变量,破坏了类的封装。