作业描述 | 详情 |
---|---|
这个作业属于哪个课程 | 2020面向对象程序设计 |
这个作业要求在哪里 | 我罗斯方块最终篇 |
这个作业的目标 | 1.代码的 git 仓库链接。 2.运行截图/运行视频。 3.代码要点。 4.收获与心得. 5.依然存在的问题。 |
作业正文 | 如下 |
小组成员 | 031902535陈玉娜 031902537江舒颖 031902538李晓芳 |
项目GitHub地址 | https://github.com/dump16/Tetris |
运行截图/运行视频
1.启动界面
2.结束界面
3.基础操作.gif
4.结束.gif
代码要点
Block类:
方块类主要负责设置、操作、判定、绘制方块
1.MoveDown 下落
方块的下落本质上是不断擦除、重绘的过程。
用一个 44 矩阵表示动态方块,左上角的点为基础点,用基础点的行、列坐标确定动态方块在地图上移动的位置。当方块触底后,就将动态方块固定在地图上,产生新的动态方块。
游戏池的设计是 1022 方格,实际的游戏池地图是 10*26 方格,顶部的四行是隐藏的,只有当方块下落到第五行时,才开始绘制方块。
setfillcolor(dynamic_color);
setlinecolor(WHITE);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (dynamic_shape[i][j] == 1 && row + i >= 4) {
fillrectangle(map_x + UNIT * (column + j) + 1, map_y + UNIT * (row + i) + 1,
map_x + UNIT * (column + j + 1) - 1, map_y + UNIT * (row + i + 1) - 1);
}
}
}
2.MoveLeft 左移、MoveRight 右移
改变基础点的行列坐标,擦除、重绘方块,在下落的基础上实现。
3.Rotate 旋转
旋转一次,矩阵顺时针旋转 90°。
int temp[4][4] = { 0 };
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp[i][j] = dynamic_shape[3 - j][i];
}
}
memcpy(dynamic_shape, temp, sizeof(temp));
存在的缺陷是,违背了物理逻辑:旋转路径中不能有障碍物存在,否则,方块是转不动的。
4.DeleteLine 消行、AddLine 增行
将地图矩阵循环赋值,实现整行的消除(增添),然后刷新游戏池。
for (int i = r; i > 3; i--) {
for (int j = 0; j < 10; j++) {
map[i][j] = map[i - 1][j];
}
}
5.DrawBlock 绘制
利用 easyx 的 fillrectangle( ) 函数绘制方块。
擦除本质上是背景色方块的重绘,用以覆盖已绘制的方块。
6.View预测
实际上,先有预测方块,后有动态方块。
srand((unsigned)time(NULL));
view_type = rand() % 7;
随机产生方块的类型,设置方块的颜色、形状。
switch (view_type) {
case 0:// O型
view_shape[1][1] = view_shape[1][2] = view_shape[2][1] = view_shape[2][2] = 1;
view_color = BLUE;
break;
case 1:// I型
view_shape[0][1] = view_shape[1][1] = view_shape[2][1] = view_shape[3][1] = 1;
view_color = RED;
break;
case 2:// S型
view_shape[1][1] = view_shape[1][2] = view_shape[2][0] = view_shape[2][1] = 1;
view_color = GREEN;
break;
case 3:// Z型
view_shape[1][0] = view_shape[1][1] = view_shape[2][1] = view_shape[2][2] = 1;
view_color = CYAN;
break;
case 4:// L型
view_shape[0][1] = view_shape[1][1] = view_shape[2][1] = view_shape[2][2] = 1;
view_color = MAGENTA;
break;
case 5:// J型
view_shape[0][2] = view_shape[1][2] = view_shape[2][1] = view_shape[2][2] = 1;
view_color = YELLOW;
break;
case 6:// T型
view_shape[1][0] = view_shape[1][1] = view_shape[1][2] = view_shape[2][1] = 1;
view_color = BROWN;
break;
}
当新方块在游戏池中绘制时,在预测框中绘制下一个方块。新方块是将预测方块赋值给动态方块。
Game类:
游戏类控制游戏的开始、暂停、继续、结束、退出
1.HandleInput 处理键盘输入
监听键盘输入
switch (_getch()) { // 获取键码
// Player A 操作响应
case 'W':
case 'w': block_a.Rotate(); break;
case 'A':
case 'a': block_a.MoveLeft(); break;
case 'S':
case 's': block_a.Sink(); break;
case 'D':
case 'd': block_a.MoveRight(); break;
// Player B 操作响应
case 0:
case 0xE0:
switch (_getch()) {
case 72: block_b.Rotate(); break; // ↑
case 75: block_b.MoveLeft(); break; // ←
case 80: block_b.Sink(); break; // ↓
case 77: block_b.MoveRight(); break; // →
}
break;
// 控制游戏
case 27: Quit(); break; // Esc 键 退出
case 32: system("pause"); break; // 空格键 暂停
case 13: NewGame(); break; // 回车键 新游戏
}
2.NewGame 新游戏
串联方块类的逻辑
while (GameOver() == 0) { //游戏未结束
while (1) {// 不触底
if (_kbhit()) { // 有按键
HandleInput();
}
else if (block_a.IsBottom()) {
for (int i = 0; i < block_a.FixBlock(); i++) {
block_b.AddLine();
} // 方块触底,固定方块;若满行,实现己方消行,对方增行
block_a.NewBlock();
break;
}
else if (block_b.IsBottom()) {
for (int i = 0; i < block_b.FixBlock(); i++) {
block_a.AddLine();
}
block_b.NewBlock();
break;
}
else {
block_a.MoveDown(); // 下落
block_b.MoveDown();
}
}
}
Render类:
渲染类绘制启动、运行、结束界面
收获与心得
在将单人俄罗斯方块转变为双人俄罗斯方块的时候,尝试使用多线程,但遇到诸多问题,例如:颜色冲突,玩家间地图干扰等等。遂放弃多线程的方法,利用游戏类把方块类的逻辑串联起来。许多课本上没接触到的新东西也通过这个的小组合作开始慢慢尝试,体会到了自学能力真的对于个人今后的发展非常重要,还需要时刻保持对于学习的兴趣,比如做一些可以运用所学知识并且自己感兴趣的东西。大家都通过这个合作不断磨合并且发现了自己的优点和缺点。
并且在这次的作业中,让我对“兴趣是最好的老师”这句话感触颇深。在刚开始的时候,对于制作游戏这一事情感到新奇,虽然之前没有做过,感觉可能会很难,但是还是报以极大热情。然而随着时间渐逝,繁重的课业,作业进程推进的缓慢,一次次失败的尝试,都让人觉得沮丧、有很大压力,甚至一度想要放弃。在这种时候,只能靠“完成作业”来作为驱动力,让人觉得很难受。然而,在大家的合作下,看着一点点完成的作业,感觉又有了动力。在其他人持之以恒完成作业、不断学习,想到自己也要努力、不能拖其他人后腿,这时候感觉又有了很大动力,又重新燃起了兴趣。“兴趣是最好的老师”,没有兴趣,只剩下“完成作业"的心态是很难真正做好一件事。
依然存在的问题
1.未实现精确延时
方块落定后,就不能微小移动,无法实现将方块放置到更优位置的功能。
2.方块下落的预测
下落预测能够防止方块沉底出现错格误差,实现这个功能的方法有两个:一是在游戏池中绘制方格线,二是绘制预测下落位置的方块框。
出于美观性的考虑,第一种方法暂未采用,第二种暂未实现。
3.按键冲突
长按任一按键,会导致其他按键的输入出错。
4.玩家交互设计
设想实现玩家输入名称,未实现。
5.界面美观性问题
似乎启动界面比运行界面美观许多(?)
6.游戏体验
卡顿卡顿卡顿卡顿卡顿卡顿卡顿