游戏主要功能如下:
- 蛇的移动和食物的产生,分数增加,蛇身变长。
- 分数越高,移动速度越快,最高分数为50分。
- 游戏能够暂停,能够保存游戏、读取存档。有存档时,将直接读取存档继续游戏。
- 游戏过程中蛇头撞墙和撞身体时都会结束游戏。
游戏使用到的C语言相关知识
- 循环语句:if、for、while、swtich等语句;
- 自定义函数:本游戏使用了多个自定义函数,如游戏区域、蛇的移动、保存游戏数据、读取存档等;
- 宏定义和全局变量;
- 指针;
- 结构体:蛇的数据、食物的数据;
- 链表:用链表将蛇的身体数据串接;
- 保存文件、读取文件;
整条蛇身属于一个链表,蛇尾为链表头,蛇头为链表尾,每一个蛇身都是链表的一个节点。移动时,从链表头开始,将后一个节点的内容赋给前一个节点,重复这个过程即可完成蛇身的移动。
身体变长原理(更新链表头)
吃食物前保存node1的坐标,蛇身继续向前运动。
吃到食物后,将保存的node1的坐标赋值给new节点
new节点成为新的链表头,蛇身继续向前运动。
保存游戏时,将文件标识符(可无)、版本号(可无)、蛇身长度、运动方向、蛇身体数据即链表信息、食物信息、移动速度、分数等重要信息保存到特定文件里。
读取数据时,按照保存时的顺序,依次将信息读取到游戏中去。注:一定要按照保存数据时的顺序读取。
确定蛇身和食物需要的信息,放入结构体中。
typedef enum {UP = -1,DOWN = 1,LEFT = 2,RIGHT = 4} DIR; //前进方向共四种
typedef struct s_snake_note //此结构体保存蛇身坐标(x,y),前进方向dir;
{
int x;
int y;
DIR dir;
}Snake_data;
typedef struct link_snake //此结构体便于形成蛇身数据链表
{
Snake_data data;
struct link_snake *next;
}ls,*psnake;
typedef struct s_food //此结构体保存食物坐标(x,y)
{
int x;
int y;
}Food,*pfood;
游戏区域的确定
#define MOVETO(x,y) printf("\033[%d;%dH",(y),(x)) //将光标移动到某一位置
void display() //游戏区域
{
int i;
MOVETO(10,5);
printf("╔");
MOVETO(11,5);
for (i = 0; i < AREA_WIDHT-2; i++)
printf("═");
MOVETO(10+AREA_WIDHT-1,5);
printf("╗");
for (i = 1; i <= AREA_HIGHT-2; i++)
{
MOVETO(10,5+i);
printf("║");
MOVETO(10+AREA_WIDHT-1,5+i);
printf("║");
}
MOVETO(10,AREA_HIGHT+4);
printf("╚");
MOVETO(11,AREA_HIGHT+4);
for (i = 0; i < AREA_WIDHT-2; i++)
printf("═");
MOVETO(10+AREA_WIDHT-1,AREA_HIGHT+4);
printf("╝");
MOVETO(1,25);
}
游戏开始时蛇身长度为3,即一个蛇头两个蛇身,均使用结构体s_snake_note保存数据,使用结构体link_snake形成链表
void init_snake() //初始化蛇身
{
int i;
psnake p;
p = (psnake)malloc(sizeof(Snake_data));
if (p == NULL)
{
printf("malloc fail (init_snake-if.1)\n");
return;
}
p->data.x = 25; //先确定链表头link_head坐标
p->data.y = 13;
p->data.dir = cur_dir;
link_head = p;
tail = p;
for (i = 1; i <= snake_length - 1; i++)
{
p = (psnake)malloc(sizeof(Snake_data));
if (p == NULL)
{
printf("malloc fail (init_snake-if.2)\n");
return;
}
p->data.x = 25 + i;
p->data.y = 13;
tail->next = p;
tail = p;
}
tail->next = NULL; //链表尾部,"tail" 即为蛇头
snake_head = tail;
}
食物的产生采用系统随机数,在游戏区域内随机生成食物坐标。
void init_food() //初始化食物
{
srand(time(NULL));
food = (pfood)malloc(sizeof(pfood));
food->x = 11 + rand()%(AREA_WIDHT - 2);
food->y = 6 + rand()%(AREA_HIGHT - 2);
}
使用特定的符号代替蛇头、蛇身、食物。可以从符号大全选择喜欢的符号。
void draw_snake_food() //画蛇,食物
{
psnake ps = link_head;
MOVETO(snake_head->data.x,snake_head->data.y); //用◆代替蛇头
printf("◆");
printf("\n");
while (ps->next != NULL) //用≈代表蛇身
{
MOVETO(ps->data.x,ps->data.y);
printf("≈");
printf("\n");
ps = ps->next;
}
MOVETO(food->x,food->y);
printf("✹");
MOVETO(1,25);
printf("\n");
}
蛇身移动:通过将后一个节点的数据赋值给前一个节点来完成蛇身移动。
p = link_head; //link_head为由蛇身组成链表的链表头
while (p != NULL && p->next != NULL) //蛇身移动
{
p->data = p->next->data;
p = p->next;
}
方向控制:方向有UP=-1、DOWN=1、LEFT=2、RIGHT=4四种
if (cur_dir == RIGHT || cur_dir == LEFT) //方向控制、p此时为链表尾,即蛇头
p->data.x += cur_dir -3; //左右变向改变x的值(+1、-1)
else
p->data.y += cur_dir; //上下变向改变y的值(+1、-1)
撞墙和撞身体。撞墙:通过限定x和y的范围实现。撞身体:通过检测蛇头的坐标是否与所有的身体坐标重合。
if (p->data.x == 10 || p->data.x == 49 || p->data.y == 5 || p->data.y == 24) //判断是否撞墙
{
MOVETO(23,13);
printf("\033[0;31;24m"); //改变打印信息颜色为红色,可省略
printf("You hit the wall!\n");
printf("\033[0m"); //还原打印信息颜色
MOVETO(1,25);
return 0;
}
撞身体:遍历整个链表(链尾除外)与链尾的坐标是否相等。
while (b != NULL && b->next != NULL) //b = link_head, 判断是否撞身体
{
if (p->data.x == b->data.x && p->data.y == b->data.y)
{
MOVETO(23,13);
printf("\033[0;31;24m"); //改变打印信息颜色为红色,可省略
printf("You hit yourself!\n");
printf("\033[0m"); //还原打印信息颜色
MOVETO(1,25);
return 0;
}
b = b->next;
}
吃食物:判断蛇头(即链尾)与食物坐标是否重合。吃到食物后,判断新产生的食物是否与身体重合。
if (p->data.x == food->x && p->data.y == food->y) //吃到食物
{
score++;
snake_length++;
while(flag == 0) //判断产生的食物是否与身体重合
{
food->x = 11 + rand()%(AREA_WIDHT - 2);
food->y = 6 + rand()%(AREA_HIGHT - 2);
while (f != NULL) //f = link_head
{
if (food->x == f->data.x && food->y == f->data.y)
{
flag = 0;
break;
}
f = f->next;
}
flag = 1;
}
}
蛇身长度加1:保留蛇吃到食物前的蛇尾(即链表头)的坐标,当吃到食物后,将保留的坐标添加到吃到食物后的链表头部,从而实现身体变长。
old_link_head = (psnake)malloc(sizeof(psnake)); //为结构体变量申请动态存储空间
if (NULL == old_link_head)
{
printf("malloc fail at snake_move(if-1)\n");
return 1;
}
old_link_head->data.x = x; //将吃食物前保留的坐标赋值给即将成为新的链表头的变量
old_link_head->data.y = y;
old_link_head->next = link_head; //进行链表头部的更新
link_head = old_link_head; //蛇身加 1
void PrepareConsole(void)
{
tcgetattr(STDIN_FILENO, &oldTermios);
struct termios newTermios = oldTermios;
newTermios.c_lflag &= ~(ECHO | ICANON);
oldFcntl = fcntl(STDIN_FILENO, F_GETFL);
int newFcntl = oldFcntl | O_NONBLOCK;
if (tcsetattr(STDIN_FILENO, TCSANOW, &newTermios) == -1 || fcntl(STDIN_FILENO, F_SETFL, newFcntl) == -1)
{
RestoreConsole();
exit(-1);
}
}
void RestoreConsole(void)
{
tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios);
fcntl(STDIN_FILENO, F_SETFL, oldFcntl);
}
保存游戏数据
void save_game()
{
FILE *fp;
if ((fp = fopen("information.dat","w")) == NULL)
{
printf("open file fail,at read_information-if-1.\n");
return;
}
fwrite("SNAKE",5,1,fp);
fwrite(&ver,4,1,fp);
fwrite(&snake_length,4,1,fp);
fwrite (&cur_dir,4,1,fp);
psnake p;
p = link_head;
while (p != NULL)
{
fwrite (&p->data,sizeof(Snake_data),1,fp);
p = p->next;
}
fwrite (food,sizeof(pfood),1,fp);
fwrite(&score,4,1,fp);
fwrite(&speed,4,1,fp);
fclose(fp);
fp=NULL;
}
读取游戏数据
void read_game()
{
int n,t = 1;
signed int read_dir;
char buf[6];
FILE *fp;
if ((fp = fopen("information.dat","r")) == NULL)
{
init_food();
init_snake();
printf("open file fail,at read_information-if-1.\n");
return;
}
if(fp) fclose(fp);
fp = fopen("information.dat","r");
fread(buf,5,1,fp);
if (strncmp(buf,"SNAKE",5) != 0)
{
init_food();
init_snake();
}
else
{
fread (&ver,4,1,fp);
if (ver == 1)
{
int i;
fread (&snake_length,4,1,fp);
fread (&read_dir,4,1,fp);
psnake p,ptail;
for (i = 0; i < snake_length; i++)
{
p = (psnake)malloc(sizeof(psnake));
if (p ==NULL)
{
printf("malloc fail,at read_information\n");
return;
}
fread(&p->data,sizeof(Snake_data),1,fp);
if (t == 1)
{
link_head = p;
ptail = p;
t = 0;
}
else
{
ptail->next = p;
ptail = p;
}
}
ptail->next = NULL;
snake_head = ptail;
cur_dir = read_dir;
food = (pfood)malloc(sizeof(pfood));
fread(food,sizeof(pfood),1,fp);
fread (&score,4,1,fp);
fread (&speed,4,1,fp);
}
else if (n == 1)
{
init_food();
init_snake();
}
}
fclose(fp);
fp = NULL;
}
暂停游戏。游戏过程中可暂停、可继续,暂停时也可保存并退出游戏。
int stop_game() //游戏暂停(可保存退出)
{
char ch;
MOVETO(11,3);
printf("Game stoping,Press space to continu");
while (1)
{
if ((ch = getchar()) == 'q')
{
MOVETO(1,25);
save_game();
RestoreConsole();
return 0;
}
else if ((ch = getchar()) == ' ')
return 1;
}
}
int main()
{
int t;
read_game();
PrepareConsole();
while(1)
{
t = Speed(speed);
int c = EOF, ch = EOF;
while ((c = getchar()) != EOF)
{
ch = c;
}
switch(ch)
{
case 'w': cur_dir = UP; break;
case 's': cur_dir = DOWN; break;
case 'a': cur_dir = LEFT; break;
case 'd': cur_dir = RIGHT; break;
case 'q': save_game(); RestoreConsole(); return 0;
case ' ': if (stop_game())
break;
else
return 0;
}
display();
system("clear");
MOVETO(42,4);
printf("speed: %d",8-t);
MOVETO(10,4);
printf("score: %d",score);
if (snake_move() == 0)
break;
draw_snake_food();
usleep(100000*t);
}
RestoreConsole();
return 0;
}
最后附上游戏文件:http://gitee.com/zk151515/snake.git