制作字符游戏贪吃蛇的学习记录
c语言简洁强大,我们可以利用它写出许多程序,甚至制作一些小游戏,比如贪吃蛇。笔者在第一次用c语言制作贪吃蛇游戏时,头都大了,因为完全不懂该从何处下手开始写代码。后来借鉴了前辈的经验才明白,需要用到自顶向下、逐步求精的方法,将一个贪吃蛇游戏细分为几个小问题:打印地图、蛇的移动、gameover的判定、食物的产生。然后再慢慢化简各个小问题中的难题。在这里给大家记录下笔者的学习记录,若能给大家一点帮助则是万幸。
1.打印地图
打印地图我们首先要构造一个地图,笔者的想法是构建一个整数型的二维数组用来表示地图:0表示空地(“ ”),1表示蛇头(“H”),2表示蛇身(“X”),3表示墙壁(“*
”),4表示食物("$")。这样做的好处就是每次打印新地图时可以用同一个函数,同时也便于后续的一些判定。以下是地图的构造。
”),4表示食物("$")。这样做的好处就是每次打印新地图时可以用同一个函数,同时也便于后续的一些判定。以下是地图的构造。
int map[22][42] = { 0 };//地图全部初始化为空地,最好把map设为全局变量
for (int i = 0; i < 22; i++)
{
for (int j = 0; j < 42; j++)
if (i == 0 || i == 21 || j == 0 || j == 41)
map[i][j] = 3;
}//设置墙壁
map[1][5] = 1;//设置蛇头
map[1][1] = 2; map[1][2] = 2; map[1][3] = 2; map[1][4] = 2;//设置蛇身
接下来再写一个打印地图的函数,让蛇每次移动后打印新地图。
void new_map()
{
for (int i = 0; i < 22; i++)
{
for (int j = 0; j < 42; j++)
{
if (map[i][j] == 0)
printf(" ");//空地
if (map[i][j] == 1)
printf("H");//蛇头
if (map[i][j] == 2)
printf("X");//蛇身
if (map[i][j] == 3)
printf("*");//墙壁
if (map[i][j] == 4)
printf("$");//食物
}
printf("\n");
}
2.蛇的移动
蛇如何移动是整个游戏中最关键的一部分,若是一开始就考虑整条蛇的移动是很困难的,于是笔者先从最简单的头部的移动开始写代码:用x、y表示头的横纵坐标,读入相应的方向字符后对坐标进行相应的加减就好了,如下
while (1)//死循环,之后可加入gameover的判定
{
map[y][x] = 0;//旧蛇头归零
scanf_s("%d", &m);
switch (m)
{
case 'w': y -= 1; break;
case 's': y += 1; break;
case 'a': x -= 1; break;
case 'd': x += 1; break;
default:
break;
}//移动
map[y][x] = 1;//新蛇头
}
接着再考虑蛇身的移动,这里许多前辈使用了数据构造的办法,无奈在下学得不精,不得不放弃使用另一种方式来实现蛇的身体构造。但是一些思想还是通用的,蛇身的移动其实就是蛇头的位置变成蛇身,然后蛇的尾巴消失。这样一来就豁然开朗了,笔者只需要利用一个二维数据存储每个蛇身的坐标位置,再分别用两个指针指向蛇的脖子(头的下一节)和蛇尾,每次移动就读入旧蛇头即新蛇脖的坐标,蛇尾坐标归零后再指向下一个蛇尾即可。代码形式如下
int head = 4, tail = 1,body[801][2] = { 0 };//笔者构建的地图为20×40,考虑蛇身最大长度所以设定了801
body[1][0] = 1; body[1][1] = 1; body[2][1] = 1; body[2][0] = 2; body[3][1] = 1; body[3][0] = 3; body[4][1] = 1; body[4][0] = 4;//初始蛇身的坐标,0代表x轴,1代表y轴
while (1)
{
map[y][x] = 2;//旧蛇头替换为蛇身
head = (head + 1) % 801;//指向新蛇脖,准备读入数据,%801为了让数组循环
body[head][0] = x; body[head][1] = y;//读入新蛇脖(头的下一节)
scanf_s("%d", &m);
switch (m)
{
case 'w': y -= 1; break;
case 's': y += 1; break;
case 'a': x -= 1; break;
case 'd': x += 1; break;
default:
break;
}
map[y][x] = 1;
map[body[tail][1]][body[tail][0]] = 0;//去掉蛇尾
tail = (tail + 1) % 801;//指向新蛇尾
new_map();//打印蛇移动后的新地图
}
3.gameover的判定
这个问题其实很简单,我们只需要判断蛇头的新坐标上的数字是不是为0就好了(如果是食物只需要多加一个分支而已),写一个函数_life(),给新蛇头的位置,若该位置为0就返回1,若为蛇身或墙壁就返回0,让循环结束(只需要把返回值的结果给while循环就好了),代码如下
int _life(int a) { if (a == 4) return 2;//遇到食物,返回一个特殊值,便于后续判定。同理如果想增加游戏道具也可以用这种方法 else {
return 1;elsereturn 0;}} 需要注意的是_life()函数的判断要在新蛇头生成前,即map[y][x] = 1;的上一条。if (a == 0)
4.食物的生成
我们需要设立一个变量来监控食物是否被吃,以及一个函数来在地图中随机生成食物。看似简单,但会牵扯到上面的许多函数。食物随机生成函数
void _food()
{
int x = rand() % 40 + 1, y = rand() % 20 + 1;
while (food == 0)
{
if (map[y][x] == 0)//找一个空地放食物
{
map[y][x] = 4;
food = 1;//全局变量,监控食物是否存在
}
x = rand() % 40 + 1; y = rand() % 20 + 1;
}
}
在上面的_life()函数里吃掉食物时会返回为2,这就便于我们进行食物被吃、和蛇身变长的判定
if (life != 2){//life = _life(map[y][x])
map[body[tail][1]][body[tail][0]] = 0;
tail = (tail + 1) % 801;//没吃到食物,蛇尾消失
}
else food = 0;//吃到食物就跳过尾巴消失,则蛇自然变长,同时食物变量归零
最后只需要把上面的内容整合下,并做些调整就可以制作出最简单的贪吃蛇游戏了,下面是完整的代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include <Windows.h>
#include <conio.h>
int map[22][42] = { 0 }, food = 0;
int _life(int a);
void move();
void _food();
void new_map();
int main()
{
for (int i = 0; i < 22; i++)
{
for (int j = 0; j < 42; j++)
if (i == 0 || i == 21 || j == 0 || j == 41)
map[i][j] = 3;
}
map[1][5] = 1;
map[1][1] = 2; map[1][2] = 2; map[1][3] = 2; map[1][4] = 2;
move();
return 0;
}
int _life(int a)
{
if (a == 4)
return 2;
else
{
if (a == 0)
return 1;
else
return 0;
}
}
void move()
{
int head = 4, tail = 1, x = 5, y = 1, body[801][2] = { 0 }, life = 1;
body[1][0] = 1; body[1][1] = 1; body[2][1] = 1; body[2][0] = 2; body[3][1] = 1; body[3][0] = 3; body[4][1] = 1; body[4][0] = 4;
char m = 'd', m1 = 'd';//设定初始的移动方向
while (life)
{
_food();
map[y][x] = 2;
head = (head + 1) % 801;
body[head][0] = x; body[head][1] = y;
for (; _kbhit();) m = _getch();//用到<conio.h>库,让蛇在没有输入指令时保持原来的方向移动
switch (m)
{
case 'w':if (m1 == 's'){ m = m1; y += 1; break; }
else{ y -= 1; break; }
case 's':if (m1 == 'w'){ m = m1; y -= 1; break; }
else{ y += 1; break; }
case 'a':if (m1 == 'd'){ m = m1; x += 1; break; }
else{ x -= 1; break; }
case 'd':if (m1 == 's'){ m = m1; x -= 1; break; }
else{ x += 1; break; }
default:m = m1;
break;
}
m1 = m;//m1为了不让蛇回头
life = _life(map[y][x]);
map[y][x] = 1;
if (life != 2){
map[body[tail][1]][body[tail][0]] = 0;
tail = (tail + 1) % 801;
}
else food = 0;
Sleep(300);//用到 <Windows.h>屏幕刷新的间隔,可以控制蛇的速度
system("cls");//刷新屏幕,让游戏看起来像在一个地图里运行的
new_map();
}
}
void _food()
{
int x = rand() % 40 + 1, y = rand() % 20 + 1;
while (food == 0)
{
if (map[y][x] == 0)
{
map[y][x] = 4;
food = 1;
}
x = rand() % 40 + 1; y = rand() % 20 + 1;
}
}
void new_map()
{
for (int i = 0; i < 22; i++)
{
for (int j = 0; j < 42; j++)
{
if (map[i][j] == 0)
printf(" ");
if (map[i][j] == 1)
printf("H");
if (map[i][j] == 2)
printf("X");
if (map[i][j] == 3)
printf("*");
if (map[i][j] == 4)
printf("$");
}
printf("\n");
}
}