****大学
《程序设计课程设计》
报告
1 课程设计需求
编写一个 2048 游戏,且使用图形界面。
游戏规则为:
① 游戏开始时,初始化一个 16 方格的棋盘,并在棋盘内随机出现两个数字,出现的数字只能是 2 或 4。
② 玩家可以选择上下左右四个方向,若棋盘内的数字出现位移或合并,视为有效移动。
③ 玩家选择的方向上若有相同的数字则合并,每次有效移动可以同时合并,但不可以连续合并。
④ 合并所得的所有新生成数字相加即为此次移动的有效得分。
⑤ 玩家选择的方向行或列前方有空格则出现位移。
⑥ 每有效移动一步,棋盘的空位随机出现一个数字(依然为 2 或 4)。
⑦ 棋盘被数字填满,无法进行有效移动,判负,游戏结束。
⑧ 棋盘上出现 2048,判胜,游戏结束。
2设计
(1)总体思路
根据上述需求,先利用随机数调整2和4的出现概率,再在棋盘中随机2个空位填补数字2或4,即初始化棋盘。
采用文件流相关操作记录历史最高分,若玩家从未玩过,则默认最高分为0。游戏过程中需要进行当前分数(Score)和历史最高分(Best)大小比较,以便随时更新历史最高分。
再利用循环结构实现玩家操作(重新开始,退出游戏,移动),移动操作需要实现上移、下移、左移、右移。
重新开始(设定为N或n):需要再次初始化棋盘。
退出游戏(设定为Z或z):结束程序运行进程。
移动操作(设定以W,S,A,D或者键盘自带方向键作为移动方向键):对移动操作是否有效进行判断,有效则累加此次移动的分数,并判断是否出现2048,出现则游戏胜利,否则在棋盘空位随机出现一个数字(依然为 2 或 4);在分数累加后与历史最高分数比较,判断是否更新历史最高分;当棋盘被填满且无法合并数字,即移动操作无效时,游戏结束。
游戏胜利:界面上方出现Win字样。
游戏失败:界面上方出现Lose字样。
(2)具体流程图如下:
(3)界面设计
采用easyx绘制图形界面:界面下方是4×4大小的棋盘,并对棋盘填充色彩,且不同数字对应不同色彩;上方是数据显示界面,显示当前分数和历史最高分,以及重新开始和退出游戏的操作提示。
表格1不同数字对应颜色
枚举的color数组对应下标 |
对应的数字 |
对应的RGB |
t0 |
0 |
RGB(205, 193,180) |
t1 |
2 |
RGB(238, 228,218) |
t2 |
4 |
RGB(237, 224,200) |
t3 |
8 |
RGB(242, 177,121) |
t4 |
16 |
RGB(245, 149, 99) |
t5 |
32 |
RGB(246, 124, 95) |
t6 |
64 |
RGB(246, 94, 59) |
t7 |
128 |
RGB(242, 177,121) |
t8 |
256 |
RGB(237, 204, 97) |
t9 |
512 |
RGB(255, 0, 128) |
t10 |
1024 |
RGB(145, 0, 72) |
t11 |
2048 |
RGB(242, 17, 158) |
(4)构思
表格2常量汇总
常量 |
Row |
Col |
Width |
Gap |
数值 |
4 |
4 |
105 px |
15 px |
描述 |
棋盘行数 |
棋盘列数 |
单独一个正方形格子的边长 |
格子间的距离 |
表格3全局变量
全局变量 |
score |
Best |
table[Row][Col] |
gameOver |
类型 |
int |
int |
二维数组 |
bool |
初始值 |
0 |
0 |
{ } |
false |
描述 |
当前总分 |
历史最佳分数 |
棋盘 |
判断游戏是否继续 |
3 项目实现与运行结果
调试结果和分析:
(1)首次运行,进入游戏:
可以看到在棋盘中随机2处出现数字2(因为设定出现2的概率大于4),历史最高分(Best)也是默认为0,因还未移动,所以当前得分(Score)也为0.
(2)移动数次后:
移动过程中分数一直变化,因为移动后Score>Best始终成立,所以Best随时跟随Score变化。
(3)游戏失败时,得分为660分(注:最后一次滑动是向右滑动):
因为此次滑动是向右边滑动,所以虽然上下方向可以合并2个16,但因为右滑,无法进行数字合并,而且棋盘已满,故游戏判负,显示Lose字样。
(4)再重新开始游戏:
重开后,棋盘也照样在随机2处出现数字。且历史最高分(Best)变为之前的660分,而当前得分(Score)为0.
(5)再次移动数次:
移动过程中,因为Score暂时未超过Best,所以Best不变,而Score变化。
当Score超过Best后,Best会随着Score一同增加。
(6)在中途时,选择重新开始:
在上一步得到880分后,重新开始(键入N或者n)后,棋盘随机2处出现数字,Best是之前的最高分880,Score为0.
(7)移动数次后再退出游戏:
进行数次移动操作后,Score为252分,然后退出游戏(键入Z或z),游戏关闭,并调出控制台(因为调试程序时,选择不关闭控制台;若想退出游戏后,直接退出所有程序,则需要在initgraph(500, 630)函数中传入第3个参数1,因为默认第三个参数为0,表示退出游戏后调出控制台)。
(8)游戏胜利时:
由于技术水平有限,暂时无法提供通关截屏。
至此,已基本将所有调试做完。
4 课程设计过程问题分析
(1)怎样利用easyx绘制图形化界面?
通过网上查询资料,主要在网站EasyX文档(https://docs.easyx.cn/zh-cn/reference),再浏览主要的绘制函数,包括填充背景色彩、设置字体颜色,大小、显示字符串等,一步步学以致用,并通过结合网站提供的实例,逐渐掌握使用的方法。
(2)完成数据的收集,以及构思整个程序如何书写。
主要是需要搜集每个数字对应颜色,可以直接上网收集每个数字所对应的颜色,但我选择使用色彩吸取相关工具,在实践中逐步掌握色彩吸取工具的用法,并越发熟练。然后是构思程序设计,在多次阅读完题设需求后,最好在画图工具上一步步梳理题干;理清程序的进行步骤,明白程序的进程;该用何种方式才能完成题目要求;在使用这种方法时,是否需要一个变量来跟进程序运行进程,以便对实现某些操作:比如此次课设,要随时检验移动的有效性以及游戏是否结束,所以我采用创建一个变量来控制,如果移动无效,该变量改变后,就可满足游戏结束的条件。
最终理清程序该如何进行后,得出具体的流程图,对书写程序有很大帮助。
(3)如何具体实现移动操作?
在实现移动操作的过程中,因为各种原因,导致程序异常、运行失败、崩溃等问题。主要在于如何实现合并数字的操作,而且不能在一次移动中连续合并数字。因为上移,下移,左移,右移都是一个原理,所以先挑选右移入手,其他的移动便不攻自破。
当数字下移时,需要考虑如下场景:相邻位置数字相同时的合并操作,如[2,2,4,4]à[0,4,0,8]这种情况;数字无法合并时,如[0,2,0,8]à[0,0,2,8]这种情况。
所以选择下面这种解法:
void moveRight()
{
for (int i = Row - 1; i >= 0;i--)
{
int t = Row - 1;
for (int next = Col - 2; next >= 0;next--)
{
if (table[i][next] !=0)
{
if (table[i][t] == 0)
{
table[i][t]= table[i][next];
table[i][next]= 0;
}
else if (table[i][next] ==table[i][t])
{
table[i][t]*= 2;
score+= table[i][t];
table[i][next]= 0;
t--;
}
else
{
table[i][t- 1] = table[i][next];
if (t - 1 != next)
{
table[i][next]= 0;
}
t--;
}
}
}
}
}
通过前后两个变量是否为0,是否相等,考虑各种情况下的右移合并操作。
5 总结与心得体会
通过本次课程设计,对C++语言的应用以及实操有了更多的了解,提高了自身的逻辑思维能力;在查找资料的过程中,逐渐学会如何自学,自学能力进一步加强;在此基础上,还学会了如何运用esayx工具绘制简易游戏界面,以及熟练掌握了色彩吸取相关工具的快捷使用方法;能通过些许代码实现需求,程序每次运行成功总能带来不少喜悦,加强了继续下去,不断攻克难题的信心。文章来源:https://www.toymoban.com/news/detail-520741.html
具体代码:文章来源地址https://www.toymoban.com/news/detail-520741.html
#define _CRT_SECURE_NO_WARNINGS 1 // VS高版本编译器需要
#include<iostream>
#include<stdio.h>
#include<string>
#include<math.h>
#include<ctime>
#include <stdlib.h>
#include<easyx.h> // 图形化界面采用easyx
#include <conio.h>
#include<fstream>
using namespace std;
#define Row 4 // 行数
#define Col 4 // 列数
#define Width 105 // 格子边长
#define Gap 15 // 格子间距
enum color // 枚举相应颜色
{
t0 = RGB(205, 193, 180), // 0
t1 = RGB(238, 228, 218), // 2
t2 = RGB(237, 224, 200), // 4
t3 = RGB(242, 177, 121), // 8
t4 = RGB(245, 149, 99), // 16
t5 = RGB(246, 124, 95), // 32
t6 = RGB(246, 94, 59), // 64
t7 = RGB(242, 177, 121), // 128
t8 = RGB(237, 204, 97), // 256
t9 = RGB(255, 0, 128), // 512
t10 = RGB(145, 0, 72), // 1024
t11 = RGB(242, 17, 158) // 2048
};
color colors[] = { t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11 }; // 对应数字的背景颜色
int table[Row][Col] = {};
int score = 0; // 当前总分
int Best = 0; // 历史最佳分数
void over(); // 判断是否出现2048获胜
bool gameOver = false; // 判断游戏是否继续
void startagain(); // 是否重新开始
bool find0(); // 确认有无空位
int random(); // 出现随机数字2或4
void init(int n); // 数字出现的个数
void display(); // 展示
void record(); // 记录最高分
void update(); // 更新最高分
void move(); // 移动
void moveUp();
void moveDown();
void moveLeft();
void moveRight();
int main()
{
initgraph(500, 630); // 窗口分辨率
init(2); // 初始化棋盘
update();
do {
while (!gameOver)
{
display();
move();
over();
update();
}
if (gameOver)
{
startagain();
}
} while (!gameOver);
closegraph();
return 0;
}
int random()
{
srand((unsigned int)time(NULL));
if (rand() % 10 > 6) // 调整2和4出现概率
{
return 4;
}
else
{
return 2;
}
}
bool find0() // 确认有无空位增加
{
for (int j = 0; j < Row; j++)
{
for (int t = 0; t < Col; t++)
{
if (table[j][t] == 0)
{
return true;
}
}
}
settextcolor(RGB(252, 85, 49));
settextstyle(100, 0, _T("微软雅黑"));
outtextxy(Width * 2 - Gap * 3, Gap * 4, _T("Lose"));
gameOver = true;
return false;
}
void init(int n) // 数字出现的个数
{
srand((unsigned int)time(NULL));
int init_row = 0;
int init_col = 0;
if (find0() == true)
{
for (int i = 0; i < n; )
{
init_row = rand() % Row;
init_col = rand() % Col;
if (table[init_row][init_col] == 0)
{
table[init_row][init_col] = random();
i++;
}
}
}
}
void over() // 判断是否2048获胜
{
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if (table[i][j] == 2048)
{
settextcolor(RGB(252, 85, 49));
settextstyle(100, 0, _T("微软雅黑"));
outtextxy(Width * 2 - Gap * 3, Gap * 4, _T("Win"));
gameOver = true;
}
}
}
}
void startagain() // 是否重新开始
{
char key = _getch();
switch (key)
{
case 'N':
case 'n':
{
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
table[i][j] = 0;
}
}
init(2); // 初始化
score = 0;
graphdefaults(); // 设置默认字体
display();
gameOver = false;
break;
}
default:
{
break;
}
}
}
void display() // 展示
{
setbkcolor(RGB(187, 173, 160)); // 设置背景颜色
cleardevice();
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
// 计算每个格子左上角坐标
int x = Gap + (Width + Gap) * j; // 横坐标
int y = 130 + Gap + (Width + Gap) * i; // 纵坐标
int index = table[i][j] ? log2(table[i][j]) : 0; // 取color数组下标
setfillcolor(colors[index]); // 填充格子颜色
solidroundrect(x, y, x + Width, y + Width, 10, 10); // 绘制格子
if (table[i][j])
{
TCHAR s[5];
_stprintf(s, _T("%d"), table[i][j]);
LOGFONT f;
gettextstyle(&f);
f.lfHeight = 60;
f.lfWeight = 600;
_tcscpy(f.lfFaceName, _T("Microsoft Yahei"));
f.lfQuality = ANTIALIASED_QUALITY;
settextstyle(&f);
setbkmode(TRANSPARENT);
int widthspace = (Width - textwidth(s)) / 2; // 格子内左右间隔,以实现数字居中
int heightspace = (Width - textheight(s)) / 2; // 格子内上下间隔
outtextxy(x + widthspace, y + heightspace, s);
}
}
}
settextstyle(20, 0, _T("微软雅黑"));
outtextxy(Width * 3 + Width / 2, Width - Gap, _T("重新开始:N"));
outtextxy(Width * 3 + Width / 2, Width, _T("退出游戏:Z"));
TCHAR s1[5], s2[5];
_stprintf(s1, _T("%d"), score); // 展示分数
_stprintf(s2, _T("%d"), Best);
settextstyle(37, 0, _T("微软雅黑"));
setbkmode(TRANSPARENT);
outtextxy(Gap, Gap * 3, s1);
outtextxy(Gap, 0, _T("Score"));
outtextxy(Gap+Width*3, Gap * 3, s2);
outtextxy(Gap + Width * 3, 0, _T("Best"));
}
void moveUp()
{
for (int i = 0; i < Col; i++)
{
int t = 0;
for (int next = 1; next < Row; next++)
{
if (table[next][i] != 0)
{
if (table[t][i] == 0)
{
table[t][i] = table[next][i];
table[next][i] = 0;
}
else if (table[next][i] == table[t][i])
{
table[t][i] *= 2;
score += table[t][i];
table[next][i] = 0;
t++;
}
else
{
table[t + 1][i] = table[next][i];
if (t + 1 != next)
{
table[next][i] = 0;
}
t++;
}
}
}
}
}
void moveDown()
{
for (int i = Col - 1; i >= 0; i--)
{
int t = Col - 1;
for (int next = Row - 2; next >= 0; next--)
{
if (table[next][i] != 0)
{
if (table[t][i] == 0)
{
table[t][i] = table[next][i];
table[next][i] = 0;
}
else if (table[next][i] == table[t][i])
{
table[t][i] *= 2;
score += table[t][i];
table[next][i] = 0;
t--;
}
else
{
table[t - 1][i] = table[next][i];
if (t - 1 != next)
{
table[next][i] = 0;
}
t--;
}
}
}
}
}
void moveLeft()
{
for (int i = 0; i < Row; i++)
{
int t = 0;
for (int next = 1; next < Col; next++)
{
if (table[i][next] != 0)
{
if (table[i][t] == 0)
{
table[i][t] = table[i][next];
table[i][next] = 0;
}
else if (table[i][next] == table[i][t])
{
table[i][t] *= 2;
score += table[i][t];
table[i][next] = 0;
t++;
}
else
{
table[i][t + 1] = table[i][next];
if (t + 1 != next)
{
table[i][next] = 0;
}
t++;
}
}
}
}
}
void moveRight()
{
for (int i = Row - 1; i >= 0; i--)
{
int t = Row - 1;
for (int next = Col - 2; next >= 0; next--)
{
if (table[i][next] != 0)
{
if (table[i][t] == 0)
{
table[i][t] = table[i][next];
table[i][next] = 0;
}
else if (table[i][next] == table[i][t])
{
table[i][t] *= 2;
score += table[i][t];
table[i][next] = 0;
t--;
}
else
{
table[i][t - 1] = table[i][next];
if (t - 1 != next)
{
table[i][next] = 0;
}
t--;
}
}
}
}
}
void move()
{
char key = _getch();
switch (key)
{
case 'N':
case 'n':
{
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
table[i][j] = 0;
}
}
init(2); // 初始化
score = 0;
break;
}
case 'Z':
case 'z':
{
gameOver = true;
return;
}
case 'w':
case 'W':
case 72:
{
moveUp();
init(1);
break;
}
case 's':
case 'S':
case 80:
{
moveDown();
init(1);
break;
}
case 'a':
case 'A':
case 75:
{
moveLeft();
init(1);
break;
}
case 'd':
case 'D':
case 77:
{
moveRight();
init(1);
break;
}
default:
{
break;
}
}
}
void record() // 记录最高分
{
ofstream ofs("BestScore.text", ios::trunc);
Best = score;
ofs << Best << endl;
ofs.close();
}
void update() // 更新最高分
{
ifstream ifs("BestScore.text", ios::in|ios::binary);
if (!ifs.is_open()) // 判断文件是否存在
{
ofstream ofs("BestScore.text", ios::out);
ofs << 0 << endl;
ofs.close();
Best = 0;
return;
}
char bestchar[8];
ifs.getline(bestchar, 8);
string t = bestchar;
Best = stoi(t);
if (Best < score)
{
ifs.close();
record();
}
}
到了这里,关于【C++/C】2048小游戏实验报告及心得的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!