c++实现贪吃蛇(附详细讲解)
更新
1. 更新了不会刷屏的贪吃蛇版本, 链接为:。
2. 修复星星出现在墙上的bug。
需要⽤到的知识点
1. 条件判断
2. 循环
3. 函数
4. 数组
5. 多cpp⽂件调⽤(不然你也可以直接写在⼀个⽂件⾥)
6. 指针
7. 结构体
8. 链表(采⽤头插法)
整个程序的实现流程
1. 画图
这⼀步是最简单的,没错,我的习惯就是从最简单的开始。
我们的⽬标是画⼀个框框,然后这个框框⾥有条蛇, 还有个星星,根据这个⽬标,最后的代码就会书写成这样⼦:
void draw(int **map, int *star, int height, int weight){
// 这⾥有⼀个需要关注的地⽅,那就是边界是墙,那么到时候在判定的时候,
// 我们要将墙体的⼤⼩算进到整个地图⼤⼩中,也就是height是包含了墙体了的
/* 清空屏幕 */
system("cls");
for(int i = 0; i < height; i++){
for(int j = 0; j < weight; j++){
if(map[i][j] == 1){
cout << "■"; // 蛇⾝和墙体
}else if(map[i][j] == 2){
cout << "●"; // 蛇⾸
}else if(i == star[0] && j == star[1]){
cout << "★"; // 星星
}else{
cout << "□";  // 可移动区域
}
}
cout << endl;
}
return;
}
这⾥,多介绍⼀些各个参数是什么, map是地图数组,star是星星的所在位置,height是地图的⾼,weight是地图的宽。
有⼀个细节我想你也注意到了,没错就是清空屏幕这个操作,这是为了什么要这么做呢,其实⼀开始是没有这个操作的,每次移动都会多画⼀个图在原来的图下⾯,这就会导致我们的地图位置不固定,影响我们的发挥,所以,我们要把这个地图固定住,这⾥明确⼀点,我们只会使⽤cmd这个窗⼝作为我们的游戏界⾯。
清空屏幕之后,就会开始画图,这样就会保证每次画的图都是新的。不过由于⽤了清屏的操作,所以会⼀闪⼀闪的,即便如此,我也玩得很嗨,没办法,原始⼈的快乐就是这么朴实。
不过上⾯的代码是完整版的代码,实际上, 如果你是从零开始搭建,开始的画图代码应该是只有地图,也就是⼀堵墙和地砖, 然后加上了蛇之后,画上蛇,然后有了⼩星星后, 画上⼩星星。
有个⽐较需要注意的细节, 由于蛇是⽤链表实现的,出于减少计算量的强迫症,我会先将蛇融合到地图中去,如下:
void drawSnake(int **map, struct snake snake){
struct snake *p = ;
map[p->i][p->j] = 2; // 蛇头
p = p->next;
while(p){
map[p->i][p->j] = 1;
p = p->next;
}
return;
}
然后绘图时,就还是⼀次画完就可以了, 不⽤不断遍历蛇是否在某个点上,没错,强迫症真的很可怕。
以上代码你可以简单理解为 map = map + snake(当然,这个加可不是真的数值运算哈,我再偷偷提醒你⼀下吧。)。
也许你会问我, 那为什么不把星星也直接加到map⾥呢?问得好,这是因为蛇是变长的, 蛇最长能达到map的⼤⼩,假如我们不把蛇先⽤以上⽅式加到map中的话,在绘图时进⾏检查,最糟糕情况会是O(N*N),N=height*weight的计算复杂度,这我能忍吗?这我不能忍,直接融⼊地图吧, 你这个蛇⼉。那星星呢?星星只是⼀个点,他永远都是⼀个点⽽已,按照时间复杂度的计算法来看, 都没有改变O(N),所以我就把它保留了,免得再写个函数。
没想到第⼀步就要知道这么多东西吧。(看到这⾥的都是热爱学习的好宝宝呀)。
2. 地图刷新
这个地图刷新其实上⾯已经讲过了, 并且也说了为什么要那么做,我们就不再细讲,我们可以在绘制好地图和蛇之后做这个地图刷新,⾄于星星,如果你现在就想画可以直接跳到后⾯,不过因为星星的特殊功能,我们现在可以先不管他。
3. 运动
我想,动起来,是整个游戏最最最重要的事情,不会动, 有什么⽤呢?没错,没有任何⽤处。
我们先来明确运动部分的代码我们需要做的事情。
1. 当按下⽅向键后, 蛇会按照按下的⽅向键运动(注意最终的⽬标不是马上运动,不过我们是开发者,可以先写个⽴刻执⾏的)。
2. 当没有输⼊任何命令时,蛇按照上⼀次的运动⽅向继续运动。(这个是第⼆阶段的⽬标,我们先来看看怎么实现第⼀阶段的⽬标吧。
确定⽅向键:w:上,s:下,a:左,d:右。
确定ascii值:w:119, s:115,a:97, d:100。
ok,确定之后就可以直接撸代码了。
int move(struct snake *s, int *star){
int sign = 0;
switch(ch){
case 119:
/* 向上⾛ */
sign = _move(s, star, -1, 0);
break;
case 115:
/* 向下⾛ */
sign = _move(s, star, 1, 0);
break;
case 97:
/* 向左⾛ */
sign = _move(s, star, 0, -1);
break;
case 100:
/* 向右⾛ */
sign = _move(s, star, 0, 1);
break;
}
if(sign == 2){ // 吃到了星星
return 2;
}
/* 死亡检查 */
return check_dead(s);
}
看到这个如果你不去深究_move这个函数到底怎么做的话,我相信你还是相当清晰到底是怎么回事的,那么我就接着将_move这个函数吧。
int _move(struct snake *s, int* star, int x, int y){
/* 运动检查 */
if(!check_action(s, x, y)){
//  return 0;
x = direct_x;
y = direct_y;
}
/* 修改⽬前运动⽅向 */
direct_x = x;
direct_y = y;
/* 增加新节点 */
struct snake *top = s->next;
struct snake *p = s->next;
struct snake *Ntop;
Ntop = (struct snake*)malloc(sizeof(struct snake));
Ntop->i = top->i + x;
Ntop->j = top->j + y;
s->next = Ntop;
Ntop->next = top;
/* 检查是否吃到星星 */
if(check_star(s, star) == 1){
return 2;
}
/* 删除旧节点 */
while(p->next->next != NULL){
p = p->next;
}
p->next = NULL;
return 0;
}
简单讲⼀下我没有提出来的代码(我也不准备后⾯贴出来),运动检查是看看这个输⼊的运动⽅向是否合理,⽐如不能倒退。还有更上⾯的死亡检查,就是看看是否这么⾛之后,蛇会不会撞死,我们知道它不能撞到⾃⼰,也不能撞到墙。
ok,这部分讲完我们开始讲上⾯贴出来的代码,如果⽅向错误的话,我们会选择使⽤原前进⽅向,也就说,如果你要倒退⾛的话,我们就直接照着之前的⽅向往前⾛,那么运动到底是怎么做到的,其实这⾥就需要你有个更加⼤局的看法了,我们之前把蛇融⼊到地图中去了,那么蛇的每个节点中的元素i,j其实代表的就是map中i,j坐标有蛇,且值是1或者2(当时蛇头时),所以,我们只需要把蛇前进⽅向的格⼦的坐标变成蛇头,变成蛇的新节点,然后删除尾节点就可以了。(这是没有吃到星星的情况下),如果吃到了星星,尾节点是不删除的,这样就可以实现长度+1。
好了,第⼀部分的运动我们说完了, 那么怎么做⼀个⾃动运动的蛇呢?其实这也很简单,只需要定时运动⼀下就好了, 前⾯我们知道我们有⼀个获取运动⽅向的函数,如下:
void getAction(){
int sign = 0; // 是否已输⼊按键
double duration; // 已检测时长
clock_t start, finish;
start = clock();
while(1){
if(_kbhit() && sign == 0){
int _ch = _getch();
if(_ch == 119 || _ch == 115 || _ch == 97 || _ch == 100){
ch = _ch;
sign = 1;
}
}else if(_kbhit() && sign == 1){
_getch();
}
finish = clock();
duration = (double)(finish - start);
if(duration > speed)
break;
}
return;
}
我们监视⼀定时长的键盘输⼊,并且将第⼀个有效输⼊保留到全局变量中的⽅向键ch中去,然后⼀直等到时间到了, 如果没有有效输⼊,就不做任何修改,我们会使⽤上次的⽅向键ch。这样⼦我们就得到了⼀个会执⾏⼀定时长的获取⽅向的模块了, 接下来就是调⽤这个模块的事情了,(没错,循环调⽤就好了, 这样就会没过⼀定时间获取⼀次,然后获取后运动,也就可以实现⼀段时间运动⼀次的⽬的了)。
⼀开始是想⽤多线程的, 后来发现定时的⽅式更加稳定且简单(毕竟多线程是⼀个很⿇烦的东西,弱鸡伤不起)。
####4. ⽣成星星
⽣成星星的⽅式可以采⽤随机⽣成坐标的⽅式,然后检查是否会不会撞墙,也可以采⽤我代码中的⽅式,时间复杂度是相同的。
/* ⽣成星星 */
void generateStar(int **map, int *star, int height, int weight){
int sum = 0;
int index = 0;
// 计算有多少空格
for(int i = 0; i < height; i++)
for(int j = 0; j < weight; j++)
if(map[i][j] == 0)
sum += 1;
// 随机⽣成空格下标
index = random(sum);
// 选中index个空格作为星星的⽣成位置
for(int i = 0; i < height; i++)
for(int j = 0; j < weight; j++){
if(map[i][j] == 0)
index -= 1;
if(index == 0){
star[0] = i;
star[1] = j;
cout << i << " " << j << endl;
return;
}
}
return;
贪吃蛇的编程代码}
5. 增加蛇长
增加蛇长上⾯已经有讲到了,就是吃到星星后, 头部增加,尾部不删,就可以实现增加蛇长。
6. 特殊情况检查
其实这个上⾯也已经有提到了。
1. 死亡检查:是否撞到墙和撞到⾃⼰
2. 运动检查:是否有导着⾛这种错误指令
3. 是否吃到星星:看到这个你可能觉得有点奇怪,这不是基本的吗?其实我就是在再次提醒你,如果从零开始搭建,建议先不做吃到星
星的操作,等到写好了第⼀部分的运动之后开始写也不迟。
4. 是否超时:这个是写第⼆部分运动–⾃动运动的时候的定时功能。
5. 输⼊合理性检查:如果不做这个的话,会导致输⼊wsad之外的键导致整个游戏暂停,我看⼀些博客将这个当做暂停⽅式,我想这个更
应该算是⼀个bug,所以我选择避免这种情况。
代码链接

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。