1. 简介
本次作业的内容是找一个已有的软件项目,阅读分析,找出软件尚存的缺陷, 改进其软件做二次开发。我找到了一个简单的C++俄罗斯方块小程序,并为其添加一些功能。
原作者信息:
GitHub:https://github.com/maodeshu/c-work
2. 准备工作
首先要下载SFML多媒体库。这个程序使用的SFML版本是2.5.1 - Visual C++ 15 (2017) – 64位,再按照原文章中的步骤进行属性配置。不过,在我的电脑上,光是将bin文件夹中的所有.dll文件复制到项目的Debug文件夹中,程序仍无法运行。参照网上查找的方法将这些文件复制到C:\Windows\System32 和 C:\Windows\SysWOW64 后,程序才能正常运行。
此外,我还修改了原先的代码中的文件路径,使得后续不再需要因为上级文件夹的名称变动等事项而修改代码。
3. 功能添加
(1) 难度选择
一般来说一款游戏都会有难度选择系统,俄罗斯方块也不例外。我为程序新增了难度选择系统,分为三个等级。不同的难度有不同的收益,因此在不同难度下消除一行的得分也有所区别,分别为5分、10分和15分。
1 //各难度有不同的下降速度
2 const float SPEED_EASY = 0.5;
3 const float SPEED_NORMAL = 0.4;
4 const float SPEED_HARD= 0.3;
5 const float SPEED_QUICK = 0.05;//快速下降
6 float CURRENT_SPEED = SPEED_NORMAL;//玩家所选难度的速度
7 float delay = CURRENT_SPEED;
8 //各难度得分不同
9 const int SCORE_EASY = 5;
10 const int SCORE_NORMAL = 10;
11 const int SCORE_HARD = 15;
12 int CURRENT_SCORE = SCORE_NORMAL;
然后,在main函数中添加一个简单的难度选择界面:
1 /* 修改:添加难度选择 */
2 int difficulty;
3 bool flag_difficulty = false;
4 cout << "请选择难度:\n1:简单\n2:普通\n3:困难" << endl;
5 while (flag_difficulty == false)
6 {
7 cin >> difficulty;
8 switch (difficulty)
9 {
10 case 1:
11 CURRENT_SPEED = SPEED_EASY;
12 CURRENT_SCORE = SCORE_EASY;
13 flag_difficulty = true;
14 break;
15 case 2:
16 CURRENT_SPEED = SPEED_NORMAL;
17 CURRENT_SCORE = SCORE_NORMAL;
18 flag_difficulty = true;
19 break;
20 case 3:
21 CURRENT_SPEED = SPEED_HARD;
22 CURRENT_SCORE = SCORE_HARD;
23 flag_difficulty = true;
24 break;
25 default:
26 cout << "无效选择,请重选。" << endl;
27 break;
28 }
29 }
同时对消除时的得分进行修改:
1 if (count < COL_COUNT)
2 {
3 k--;//拿去覆盖
4 }
5 else
6 {
7 score += CURRENT_SCORE;
8 sou.play();//放消除音效
9 }
这样,就完成了一个简单的难度系统。
下方可见选择简单难度时,消除了两行共获得10分。
(2) 旋转优化
原作者使用了check()函数来判断方块在移动后是否处于游戏区域内。这一函数在方块左右平移、方块落地、和方块旋转的函数中都有被调用。而在平移和旋转(doRotate()函数)
的操作中是这样使用check()的:
1 void doRotate() {
2 if (blockIndex == 7) { // 田字形,不需要旋转
3 return;
4 }
5 //备份当前方块,出问题再回退
6 for (int i = 0; i < 4; i++) {
7 bakBlock[i] = curBlock[i];
8 }
9
10 Point p = curBlock[1]; //旋转中心
11 //a[i].x=p.x-a[i].y+p.x
12 //a[i].y=p.y+a[i].x-p.x;
13 for (int i = 0; i < 4; i++)
14 {
15 struct Point tmp = curBlock[i];
16 curBlock[i].x = p.x - tmp.y + p.y;
17 curBlock[i].y = p.y + tmp.x - p.x;
18 }
19 if (!check()) {
20 for (int i = 0; i < 4; i++) {
21 curBlock[i] = bakBlock[i];//备份
22 }
23 }
24 }
在判断到方块在位置变化后会部分离开游戏区域时,会调用此前创建的方块备份,等同于无效掉玩家试图将方块移出游戏区域的操作。
但这样的使用方式有一个缺陷:当我们在游戏区域的边缘进行方块的旋转时,对于部分摆放方式的方块会出现按下旋转按键,但方块却不旋转的情况。对于俄罗斯方块来讲,在边缘无法旋转方块无疑是降低操作性和游戏体验的。我对旋转部分调用check()的部分做了优化,引入了rotateAdjuster()函数来解决这一问题:
1 void rotateAdjuster() {//防止因旋转后超过边界而导致旋转不生效的问题。
2 for (int i = 0; i < 4; i++) {
3 if (curBlock[i].x < 0 )
4 {
5 for (int cx = 0; cx < 4; cx++)
6 {
7 ++curBlock[cx].x;
8 }
9 }
10 else if (curBlock[i].x >= COL_COUNT)
11 {
12 for (int cx = 0; cx < 4; cx++)
13 {
14 --curBlock[cx].x;
15 }
16 }
17 }
18 }
此外,还新增了checkLegit ()函数,将是否接触顶边或和现有方块重叠的判断拆分出来,在旋转部分单独判断,以防止不正常的旋转结果。
1 bool checkLegit() //检查是否接触顶边/重叠
2 {
3 for (int i = 0; i < 4; i++) {
4 if (curBlock[i].y <= 0 || table[curBlock[i].y][curBlock[i].x])
5 {
6 return true;
7 }
8 }
9 return false;
10 }
修改后的doRotate()函数:
1 void doRotate() {
2 if (blockIndex == 7) { // 田字形,不需要旋转
3 return;
4 }
5 //备份当前方块,出问题再回退
6 for (int i = 0; i < 4; i++) {
7 bakBlock[i] = curBlock[i];
8 }
9
10 Point p = curBlock[1]; //旋转中心
11 //a[i].x=p.x-a[i].y+p.x
12 //a[i].y=p.y+a[i].x-p.x;
13 for (int i = 0; i < 4; i++)
14 {
15 struct Point tmp = curBlock[i];
16 curBlock[i].x = p.x - tmp.y + p.y;
17 curBlock[i].y = p.y + tmp.x - p.x;
18 }
19 if (!check()) {
20 rotateAdjuster();
21 if (checkLegit())
22 {
23 for (int i = 0; i < 4; i++) {
24 curBlock[i] = bakBlock[i];//备份
25 }
26 }
27 }
28 }
修改后效果:
4. 总结
以上就是我对这一开源的俄罗斯方块小程序做的二次开发。通过源代码和游玩,我找到了一个可改善事项和一个问题:
(1)游戏可以设置难度分级
(2)方块位于区域侧边时,无法旋转方块
当然,这一程序还可进行一些改进,例如为其添加下一个下落的方块的预览图。这次实践,我主要是为了探索与思考寻找软件缺陷与尝试二次开发的流程。在这一过程中,我锻炼了自己解决问题和设计功能的能力。
修改后的项目文件:https://wwqu.lanzoum.com/i8qMe0pkc4xa