1 内容提要
项目题目是贪吃蛇(Greedy Snake),实现了Windows控制台下的一个小游戏,这是一个广为人知的非常经典的休闲类小游戏。该游戏功能玩法简单,玩家通过键盘方向键控制蛇上下左右移动,蛇吃到食物得分并增长,直到撞墙等造成死亡游戏才会结束。整个游戏其实就是一个无穷的循环,直到退出游戏时退出循环。这个项目目前仅可进行最基本的贪吃蛇游戏,即开始游戏、选择游戏模式、退出游戏。该项目使用的编程语言为C++语言;所采用的开发环境为code blocks。
2 类设计
该项目中仅直接定义了一个类,就是Snake(蛇)类。该类的作用,即定义了一个Snake类封装贪吃蛇游戏,并声明了一些基本的变量和成员函数:游戏地图、蛇的长度、蛇头坐标、蛇尾坐标、描述蛇的移动方向、蛇的速度、显示游戏地图的函数、移动函数、生成食物的函数。通过Snake类,可以控制蛇的移动和吃到食物;除此之外,还有蛇的速度游戏地图的展开。该类中的这些设计,在整个项目中发挥着重要作用,是项目正常运行的一大核心。
3 总体设计流程
输入代码并开始运行程序,首先出现的是游戏开始界面,可选择开始游戏或者退出游戏。若选择退出游戏,则退出程序,程序关闭;若选择开始游戏,则进入游戏模式选择界面,若选择正常模式,直接开始游戏,玩家操作小蛇吃到食物得分并长大,直到小蛇撞墙等造成死亡游戏才结束并显示最终得分,最后可按enter键回到游戏开始界面;若选择无障碍模式,同样直接进入游戏,开始操作小蛇吃到食物。但小蛇撞到墙并不会死亡,如果想结束游戏。可按Esc键退出回到游戏开始界面。
模块划分:
Snake类:声明一些基本的变量和成员函数,基本的变量包括游戏地图、蛇的长度、蛇头坐标、蛇尾坐标、描述蛇的移动方向、蛇的速度等。
移动函数move():控制蛇的移动以及蛇头、蛇尾的操作。
转弯函数turn():蛇的转弯,向左、向右、向上、向下。
生产食物的函数create():食物是随机生成的,小蛇每吃掉一个食物就得出现新的食物。
游戏开始函数GameStart函数():用来集成函数,实现函数的基本功能。
4 详细设计
4.1 游戏地图
显示游戏地图采用函数print(),游戏地图map为二维数组,如果数组元素map[i][j]的值为0,代表该位置什么也没有;如果map[i][j]的值大于0,代表该位置是蛇的身体;如果-1==map[i][j],代表该位置是食物;如果map[i][j]>0,代表该位置是蛇的头部,如表4.1所示。用8,2,4,6作为方向指针来描述蛇的身体,用-8,-2,-4,-6作为方向指针来描述蛇的头部。蛇的身体任何位置沿蛇的身体指向头部,蛇的头部指向蛇的身体。
map[i][j]的值为0 | Map[i][j]的值大于0 | -1==map[i][j] | Map[i][j]>0 | |
该位置 | 空 | 蛇的身体 | 食物 | 蛇的头部 |
食物用“◇”表示,蛇头用“¤”表示,蛇身用“■”表示实现的代码为:
4.2 蛇的移动
运用移动move()函数。蛇的移动可以通过“加头去尾”实现,即把蛇尾去掉,在蛇的移动方向加上新的蛇头。这个过程可以拆分成蛇头操作和蛇尾操作。
4.2.1 蛇头操作
蛇头操作时,先现在的蛇头,在移动后将成为蛇的身体;然后根据蛇的移动方向,生成一个新的蛇头,新蛇头位置指向蛇身。设置几个返回值:0为正常移动,1为撞墙,2为咬到自己。向上移动时,如果蛇头上方是蛇的身体,则咬到自己,返回2;若当前蛇头位置指向新蛇头位置(上),新蛇头位置指向蛇身,则正常移动,返回0;如果0==head[0],则撞墙,返回1。其它向下、左、右也是同样的方法。
4.2.2 蛇尾操作
蛇尾操作时,由于蛇尾是沿蛇的身体指向蛇头的,所以将蛇尾指向的位置设为新的蛇尾。运行时,如果蛇尾指向下,则蛇尾横坐标-1;如果蛇尾指向下,则蛇尾横坐标+1;如果蛇尾指向左,则蛇尾纵坐标-1;如果蛇尾指向右,则蛇尾纵坐标+1。
4.3 蛇的转弯
蛇的转弯用转弯函数turn(),根据参数kbint来调整蛇的方向。当然,不是所有的情况下都可以改变蛇的方向,比如蛇正在往右走,就不能把方向设置成左,也没必要把方向设置成右。如果蛇左右移动,则把方向设置成上或下;如果蛇上下移动,则把方向设置成左或右。实现代码如下所示:
void turn(int const&kbinput) {
switch (kbinput) {
case 8:if (4 == direction || 6 == direction)direction = 8; break;
case 2:if (4 == direction || 6 == direction)direction = 2; break;
case 4:if (8 == direction || 2 == direction)direction = 4; break;
case 6:if (8 == direction || 2 == direction)direction = 6; break;
}
}
用WSAD来控制光标的上下左右移动。如果输入W,则调用转弯函数turn(),参数为上;如果输入S,则调用转弯函数turn(),参数为下;如果输入A,则调用转弯函数turn(),参数为左;如果输入D,则调用转弯函数turn(),参数为右。实现代码如下所示:
case'w':case'W':turn(8); break;
case's':case'S':turn(2); break;
case'a':case'A':turn(4); break;
case'd':case'D':turn(6); break;
4.4 食物
4.4.1 生成食物
物的生成用函数create()。食物是随机生成的,用rand()函数,该函数在头文件或<stdlib.h>中,rand()函数的功能是返回一个0~RAND_MAX的随机值,RAND_MAX是一个很大的值。但是,rand()函数返回的是一个假随机值,为了得到相对真的随机值,设置了随机数的种子为随机值,时间是随机的,所以用时间作为种子。设置随机数的种子采用代码srand((unsigned)time(NULL))来实现。先确定食物的坐标,食物的行坐标为随机数除最大行数的余数,可以得到0到maxRow-1的一个随机数,食物的列坐标为随机数除最大列数的余数,可得到0到maxCol-1的一个随机数,如表4.4.1。除此之外,不能在蛇的身体上生成食物;最后,按照先前规定的,在地图上把食物用-1表示。实现代码如下:
4.4.2 吃到食物
前面move()函数的实现原理——“加头去尾”。而吃掉食物之后,蛇的长度要增加,要实现这个功能,就得“加头不去尾”。在move()函数中加入条件判断语句,以向上移动为例:如果蛇头前方是食物,则食物会被蛇吃掉;调用create()新生成一个食物,蛇的长度增加。
实现的代码为:
4.5 游戏开始
有了移动、转弯、生成食物和显示的函数,游戏的核心已经完备,在Snake类中添加一个GameStart()函数,用来集成这些函数,实现基本的游戏功能。采用_getch()函数和_kbhit()函数完成基本游戏功能。这两个函数在头文件<conio.h>中,这是一个非标准库。如果move()函数的返回非零值,则游戏失败。这几个在代码中的关系如下所示:
_getch()函数,和getchar()函数类似,不过_getch()函数可以直接读取键盘输入的字符,不用在敲击回车键,适合用来做控制台游戏。如果_kbhit()函数检测到键盘输入,则调用_getch()函数读取,否则游戏进程继续。
_kbhit()函数,如果有键盘输入,则返回一个非零值,否则返回零值。这个函数是非阻塞的,它不会暂停游戏的进程。为了让蛇在不按键盘时也动,引入了这个函数。如果_kbhit()函数检测到键盘输入,则调用_getch()函数读取,否则游戏进程继续。
实现代码如下所示:
4.6 游戏暂停
在游戏过程中,按空格键可以使游戏暂停,且暂停界面将出现一个暂停符号;暂停后,在按空格键可以继续游戏。
4.7 蛇的速度
蛇的长度增加时,速度会加快,Sleep()函数可以暂停游戏的进程,暂停的时间取决于参数,例如:Sleep(1000)表示休眠1000个单位时间。一般来说,1秒有1000个单位时间,则暂停几秒就Sleep几千。在GameStart()函数中加入代码Sleep(1000/speed)来控制游戏速度。蛇的速度和蛇的长度相关,需要计算公式,但无法确定多少合适,用宏来编写:
#define _SPEED(_LENGTH) _LENGTH>6?_LENGTH:6
speed = _SPEED(length);
4.8 游戏界面
这个项目的运行也由很多界面组成,包括主界面、退出确认窗口、返回确认窗口、结算界面、模式选择窗口。
4.8.1 主界面
游戏开始的主界面,显示游戏名称贪吃蛇以及边框,并上下选择开始游戏或退出游戏。用maxRow和maxCol来实现边框,确认位置。实现代码如下所示:
int MainWindow() {
system("cls");
char cursor = 0, position = 0;
do {
unsigned short i, j;
for (i = 0; i < maxCol + 2; i++)cout << "■";
cout << endl;
for (i = 0; i < maxRow / 2 - 1; i++) {
cout << "■";
for (j = 0; j < maxCol; j++)cout << " ";
cout << "■" << endl;
}
cout << "■"; for (j = 0; j < maxCol - 8; j++)cout << ' '; cout << "贪吃蛇(GreedySnake)"; for (j = 0; j < maxCol - 8; j++)cout << ' '; cout << "■" << endl;
cout << "■"; for (j = 0; j < maxCol - 4; j++)cout << ' '; cout << "开始游戏";
if (0 == position)cout << "←"; for (j = 2 - 2 * position; j < maxCol - 4; j++)cout << ' '; cout << "■" << endl;
cout << "■"; for (j = 0; j < maxCol - 4; j++)cout << ' '; cout << "退出游戏";
if (position)cout << "←"; for (j = 2 * position; j < maxCol - 4; j++)cout << ' '; cout << "■" << endl;
for (; i < maxRow - 3; i++) {
cout << "■";
for (j = 0; j < maxCol; j++)cout << " ";
cout << "■" << endl;
}
for (i = 0; i < maxCol + 2; i++)cout << "■";
cursor = _getch();
if (('w' == cursor || 'W' == cursor) && position)position--;
else if (('s' == cursor || 'S' == cursor) && 0 == position)position++;
else if (27 == cursor)return 1;
} while (cursor != '\r');
return position;
}
4.8.2 确认窗口
当选择退出游戏时,会弹出退出确认窗口,左右选择是否要结束游戏。其边框同样由maxRow和maxCol来实现。
当游戏过程中,选择退出时,会弹出返回确认窗口,左右选择是否返回主菜单,与退出确认窗口相类似,采用的同样的方法。实现代码如下:
4.8.3 结算界面
小蛇死亡游戏结束后,会弹出结算界面,显示你的最终得分。而游戏得分与蛇的速度采用了同一方法,在代码前面加入#define _SCORE(_LENGTH) _LENGTH,最后得到最终得分,cout << ' '; cout << "你的最终得分为:" << _SCORE(length - initLength)。
4.8.3 选择窗口
在主界面选择开始游戏之后,进入模式选择窗口,左右选择正常模式或者无障碍模式。与前面的退出确认窗口和返回确认窗口一样,采用的同样的做法。
5 总结
该项目最大的亮点就是设置了正常与无障碍两种模式。正常模式与无障碍模式区别就在于无障碍模式下,小蛇撞到墙游戏不会结束。当然,这个项目也存在着不足,首先游戏界面太小可能会影响游戏体验,其次,可以植入音乐或是图形库,让游戏效果更好;除此之外,还有一个问题,就是在玩游戏的过程中,游戏界面可能会出现闪的情况,且目前还不知道出现这个问题原因,也没能找到解决办法。
参考文献
[1] 谭浩强.C++程序设计(第三版).北京:清华大学出版社,2015.
[2] 百度百科,rand(),https://baike.baidu.com/item/rand%28%29/3002042
[3] 百度百科,getch(),https://baike.baidu.com/item/getch%28%29/1099639 4
[4]百度百科,kbhit,https://baike.baidu.com/item/kbhit/3921261
[5]百度百科,Sleep函数,https://baike.baidu.com/item/Sleep%E5%87%BD%E6%95%B0/67350 27
详细软件说明书下载链接:https://download.csdn.net/download/cxx0316/89582641?spm=1001.2014.3001.5503