一、运行效果
![](https://img2020.cnblogs.com/blog/1925787/202006/1925787-20200610230712232-1534387019.png)
![](https://img2020.cnblogs.com/blog/1925787/202006/1925787-20200610233314138-20742267.png)
![](https://img2020.cnblogs.com/blog/1925787/202006/1925787-20200610234046369-609761340.png)
![](https://img2020.cnblogs.com/blog/1925787/202006/1925787-20200610230843852-689000362.png)
视频链接
二、代码要点
- 采用Easy_x进行界面渲染,将渲染功能打包在pait类中
class paint
{
public:
void endGame(); //绘制结束界面
void initEnviroment(); //初始化环境
void drawGameBG(); //绘制游戏背景
void drawLeftSide(); //绘制游戏左侧边
void drawRightSide(); //绘制游戏右侧边
void drawLeftMap(); //绘制玩家一的地图
void drawRightMap(); //绘制玩家二的地图
void drawSquareNext(int num); //绘制预览框内的方块
void drawItem(int x, int y, COLORREF c); //绘制方块
void drawSquareNow(int num); //绘制当前“尘埃落定”的全体方块
};
void paint::initEnviroment()
{
// 窗口设置
initgraph(1210, 540);
HWND hwnd = GetHWnd();
SetWindowText(hwnd, L"我罗斯");
SetWindowPos(hwnd, HWND_TOP, 0, 20, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
// 绘图模式设置
setbkmode(TRANSPARENT);
// 随机数种子
srand(time(NULL));
}
- 每一轮游戏的初始化:分数、储存方块信息的“地图”,结束标志重置
- 每一个新方块的初始化:获取方块所需信息——颜色、类型、形状,生成下一方块标志的重置,预览框方块信息的设置
提前将各种方块的信息储存在数组中,通过随机数取模的方式来随机生成方块
now_c_idx[num] = next_c_idx[num];
now_s_idx[num] = next_s_idx[num];
now_d_idx[num] = next_d_idx[num];
next_c_idx[num] = rand() % 7;
next_s_idx[num] = rand() % 7;
next_d_idx[num] = rand() % 4;
- 游戏过程中的方块左移、右移、旋转、下降均需要事先判断操作是否可行,若不可行,则不执行
- 长时间未执行操作,方块将自动下降(利用GetTickCount()函数来获取时间)
bool player::checkPut(int mp_x, int mp_y, int dir_idx)
{
int sq_x[4];
int sq_y[4];
for (int i = 0; i < 4; ++i)
{
sq_x[i] = mp_x + squares[now_s_idx[num]].dir[dir_idx][i][0];
sq_y[i] = mp_y + squares[now_s_idx[num]].dir[dir_idx][i][1];
}
// 【左右越界、下方越界、重复占格】
for (int i = 0; i < 4; ++i)
{
if (sq_x[i] < 0 || sq_x[i] > 9 || sq_y[i] > 14)
return false;
if (sq_y[i] < 0) // 检查坐标合法性
continue;
if (mp[num][sq_x[i]][sq_y[i]])
return false;
}
return true;
}
- 玩家类的各种操作大同小异,但是在下降、消行、给对方增加行、检查是否结束差异较大,所以将这部分剥离出来,构造玩家一、二类公有继承于玩家类,分别编写上述成员函数,便于进行相关操作
- 随着玩家分数的增加,游戏难度需相应提高,设置了默认下降速度改变的机制,但是最终会稳定在某个最大速度值
//检查玩家一是否长时间未执行正确指令
int speed1, speed2;
if (score[0] <= 3000)
speed1 = 1000 - score[0] / 10;
else
speed1 = 700;
if (time_tmp[0] - time_now[0] >= speed1)
{
time_now[0] = time_tmp[0];
one.moveDown();
over = true;
}
//检查玩家二是否长时间未执行正确指令
if (score[1] <= 3000)
speed2 = 1000 - score[1] / 10;
else
speed2 = 700;
if (time_tmp[1] - time_now[1] >= speed2)
{
time_now[1] = time_tmp[1];
two.moveDown();
over = true;
}
- 玩家在游戏过程中消除的行数不同所得分数也不相同,并且多行消除后的得分不是简单的一行消除得分的多次叠加
score[num] += 100.0 * clearNum * (1 + clearNum * 0.25);
- 利用_kbhit()来监听键盘敲击,并且在每次执行完操作后 Sleep(20)来降低CPU占用
//接受指令
if (_kbhit())
{
//兼顾大小写
switch (_getch())
{
case 'W':
case 'w':
one.moveRotate();
break;
case 'A':
case 'a':
one.moveLeft();
break;
case 'D':
case 'd':
one.moveRight();
break;
case 'S':
case 's':
one.moveDown();
break;
case 72:
two.moveRotate();
break;
case 75:
two.moveLeft();
break;
case 77:
two.moveRight();
break;
case 80:
two.moveDown();
break;
}
}
// 降低CPU占用
Sleep(20);
- 每局游戏结束后在屏幕上显示本局游戏的赢家(或者出现平局),并显示询问是否“再来亿局”的弹窗
starpaint.endGame();
if (MessageBox(GetHWnd(), over_tips, L"再来亿局?", MB_ICONQUESTION | MB_YESNO) == IDNO)
break;
- 可以保证游戏的正常运行,实现方块左移、右移、旋转、下降、长时间不操作自动下降、消行操作,并且补充消行后给对方增加相应行数的随机方块功能。
- 侧边方块预览功能正常实现。
- 随着等级的提升方块下降速度加快。
- 每局游戏结束后在屏幕上显示本局游戏的赢家(或者出现平局),实现了“再来亿局”的功能正常使用。
三、依然存在的问题
- 游戏流畅度的进一步优化
- 使用了较多全局变量,未能很好地实现代码的封装性,所以在最后未能分离出类代码文件
四、收获与心得
学习了之前没有接触过的Easy_x的操作,了解到如何利用它来进行页面的渲染。明白了要想真正写出这样一个小游戏并不容易,在一开始,就应该利用流程图、思维导图等工具来对思路进行处理。在实现过程中会碰到各种各样意料之外的问题,不要急于实现全部功能,应该是在已有的、正确的代码基础上进行功能的完善和补充。适时进行代码功能的测试,以便及时修改,否则bug堆积多了,修改时也无从下手。在这个过程中,与组员的分工合作也十分重要,及时与组员保持联系,确认编码上的一些修改等问题。在这次制作“我罗斯”的过程中,慢慢体会到做项目与平常写PTA上的作业的深刻不同,不要只满足于完成PTA这类作业上,而不学习、实践项目的编写,否则将来只会成为一名“面向PTA的程序员”,而不是一个能够编写项目的、合格的程序员。