#C语言实现贪吃蛇
##一、需求
###通过游戏的实现,学习C语言知识的综合应用,以及解决问题的方法。
###1、通过wasd操作控制蛇的运动方向;
###2、蛇运动时不能碰到墙壁和自己的身体;
###3、食物动态产生,存在时间不同,分值不同;
###4、游戏可以暂停;
###5、游戏可以保存进度,有不同的版本;
###6、启动游戏可以从保存的进度开始游戏,也可以重新开始游戏;
##二、原理
###01、预备知识
####printf函数特殊效果
```
#define BLUE "\033[0;32;24m"
#define RED "\033[0;31;24m"
#define DEPBLUE "\033[0;34;24m"
#define NOCOLOR "\033[0m"
//清除显示内容
#define CLEAR() printf("\033[2J")
//开始反显
#define HIGHT_LIGHT() printf("\033[7m")
//反显结束
#define NHIGHT_LIGHT() printf("\033[27m")
//指定位置开始打印内容,x为命令行的字符位置,从左往右从1开始
#define MOVETO(x,y) printf("\033[%d;%dH",(y),(x))
```
####不需要回车获取字符的处理
```
void RestoreConsole(void){
tcsetattr(STDIN_FILENO,TCSANOW,&oldt);
fcntl(STDIN_FILENO,F_SETFL,oldf);
}
void perpareConsole(void){
struct termios oldt,newt;
tcgetattr(STDIN_FILENO,&oldt);
newt = oldt;
newt.c_lflag &= ~(ECHO | ICANON);
oldf = fcntl(STDIN_FILENO,F_GETFL);
int newf = oldf | O_NONBLOCK;
if(tcsetattr(STDIN_FILENO,TCSANOW,&newt) == -1 ||
fcntl(STDIN_FILENO,F_SETFL,newt ) == -1){
RestoreConsole();
exit(-1);
}
}
```
###02、蛇前进的原理
####链尾为蛇头,遍历链表实现蛇的移动
```
if(cur_dir == RIGHT || cur_dir == LEFT)
ntail->date.x += cur_dir - 3
else
ntail->date.y += cur_dir ;
void draw_snake(struct snake_node *head)
draw_snake
{
struct snake_node *snake,*nsnake,*tail;
system("clear");
snake = head;
nsnake = head;
while(snake != NULL)
{
MOVETO(snake->date.x,snake->date.y);
printf("*");
printf("\n");
tail = snake;
snake = snake->next;
}
}
```
###03、蛇是否死亡的判定
####遍历坐标是否重合
```
while(nsnake->next != NULL) //判断链尾是否撞墙撞身体
{
if(tail->date.y <= 5 || tail->date.y >= 17 || tail->date.x <= 11 || tail->date.x >= 59)
{
MOVETO(tail->date.x,tail->date.y);
printf("X");
flag = 1;
}
if(tail->date.x == nsnake->date.x && tail->date.y == nsnake->date.y)
{
flag = 1;
}
nsnake = nsnake->next;
}
```
###04、如何产生食物
####随机生成食物坐标
```
food = (struct food_node*)malloc(sizeof(struct food_node));
food->date.x = rand() % (width- 20) + 12;
food->date.y = rand() % (HEIGHT- 9) + 13;
food->date.time = rand()%90 + 60;
food->next = NULL;
foodhead = food;
```
###05、如何判断蛇吃到食物
####提前拿到下一个点坐标与食物坐标比较
```
nsnake = head;
while (nsnake != NULL && nsnake->next != NULL)
{
nsnake = nsnake->next;
ntail = nsnake;
}
if(cur_dir == RIGHT || cur_dir == LEFT)
{
x = ntail->date.x + cur_dir - 3;
y = ntail->date.y;
}
else
{
y = ntail->date.y + cur_dir ;
x = ntail->date.x;
}
head->date.dir = cur_dir;
```
###06、实现蛇身增长
####生成新节点赋值食物的坐标添加到链尾(蛇头)
```
if(x == foodhead->date.x && y == foodhead->date.y)
{
i++;
snake_l++;
node = (struct snake_node*)malloc(sizeof(struct snake_node));
node->date.x = x;
node->date.y = y;
node->date.dir = cur_dir;
sorce += foodhead->date.time;
foodhead->date.x = rand() % (width- 20) + 12; //食物初始化
foodhead->date.y = rand() % (HEIGHT- 9) + 13;
foodhead->date.time = rand()%90 + 60;
ntail->next = node;
ntail = node;
}
```
###07、游戏数据文件设计
####版本号、蛇长和数据、食物数据、分数、速度
#####实现数据保存
```
void save_date()
{
FILE *fp;
int i;
char buf[6];
struct snake_node *p;
struct food_node *f;
fp = fopen("snake.dat","w");
if(NULL == fp)
{
perror("open");
return; //退出程序块
}
fwrite("SNAKEA",6,1,fp);
fwrite(&ver,4,1,fp);
fwrite(&snake_l,4,1,fp);
p = head;
for(i = 0; i < snake_l; i++)
{
fwrite(&p->date,sizeof(snake_date),1,fp);
p = p->next;
}
fwrite(&food_l,4,1,fp);
f = foodhead;
for(i = 0; i < food_l; i++)
{
fwrite(&f->date,sizeof(food_date),1,fp);
f = f->next;
}
fwrite(&sorce,4,1,fp);
fwrite(&k,4,1,fp);
fwrite(&flag,4,1,fp);
fclose(fp);
```
#####文件读取
```
if(ver == 1)
{
//printf("1");
int i;
snake_date date;
food_date fdate;
struct snake_node *snake,*nsnake;
struct food_node *nfood;
fread(&snake_l,4,1,fp); //蛇身体
//printf("snake length:%d\n",snake_l);
//getchar();
fread(&date,sizeof(snake_date),1,fp);
head = (struct snake_node*)malloc(sizeof(struct snake_node));
head->date = date;
snake = head;
for (int i = 1; i < snake_l; ++i)
{
nsnake = (struct snake_node*)malloc(sizeof(struct snake_node));
fread(&date,sizeof(snake_date),1,fp);
nsnake->date = date;
snake->next = nsnake;
snake = snake->next;
}
snake->next = NULL;
printf("UP = -1,DOWN = 1,LEFT = 2,RIGHT = 4\n");
printf("now direct is %d\n",nsnake->date.dir);
getchar();
getchar();
fread(&food_l,4,1,fp); //food note
fread(&fdate,sizeof(food_date),1,fp);
//printf("fdate:%d\n",fdate.x);
//getchar();
foodhead = (struct food_node*)malloc(sizeof(struct food_node));
foodhead->date = fdate;
food = foodhead;
for (int i = 1; i < food_l; ++i)
{
nfood = (struct food_node*)malloc(sizeof(struct food_node));
fread(&fdate,sizeof(food_date),1,fp);
nfood->date = fdate;
food->next = nfood;
food = food->next;
}
food->next = NULL;
fread(&sorce,4,1,fp);//int类型
fread(&k,4,1,fp); //int类型
fread(&flag,4,1,fp); //int类型;
}
```
##三、设计
###01、蛇的数据结构
```
typedef struct n_snake_date
{
int x;
int y;
enum MOVE_DIR dir;
}snake_date;
struct snake_node{
snake_date date;
struct snake_node *next;
}*head;
```
###02、食物的表示
```
typedef struct n_food_date
{
int x;
int y;
int time;
}food_date;
struct food_node
{
food_date date;
struct food_node *next;
}*food,*foodhead;
```
###03、小功能实现
```
if(head->date.dir == cur_dir) //同向加速
{
k = 10;
}else
{
k = 0;
}
```
###04、界面搭建
```
void display()
{
int i;
//system("clear");
MOVETO(10,4);
printf("wasd控制方向默认方向向右");
MOVETO(10,5);
printf("┏");
for ( i = 1; i < width - 1; i++)
{
printf("—");
}
printf("┓");
for ( i = 1; i < HEIGHT; ++i)
{
MOVETO(10,5+i);
printf("|");
MOVETO(10 + width-2 ,5+i);
printf("|");
}
MOVETO(10,5+HEIGHT);
printf("┗");
for ( i = 1; i < width - 1; i++)
{
printf("—");
}
printf("┛");
}
```
###05、主函数实现
```
int main()
{
int count = 0,f = 0;
char ch;
init_game();
ch = getchar();
while(1)
{
/*
perpareConsole();
int c = EOF, ch = EOF;
while((c = getchar()) != EOF)
{
ch = c;
}*/
switch(kbhit())
{
case 'w':
case 'W':
cur_dir = UP;
break;
case 's':
case 'S':
cur_dir = DOWN;
break;
case 'a':
case 'A':
cur_dir = LEFT;
break;
case 'd':
case 'D':
cur_dir = RIGHT;
break;
case 'p':
case 'P':
MOVETO(20,1);
printf("PAUSE ENTER P TO CONTINUE");
MOVETO(10,4);
printf(" ");
MOVETO(20,2);
while((ch = getchar()) != 'p' &&(ch = getchar()) != 'P' );
MOVETO(20,1);
printf(" ");
break;
case 'v':
case 'V':
printf("write down you want to save as 1 or 2\n");
while(1)
{
ch = getchar();
if(ch == '1')
{
ver = 1;
break;
}
else if(ch == '2')
{
ver = 2;
break;
}
}
save_date();
case 'q':
case 'Q':
return 0;
}
display();
if(count == 5)
{
if (sorce > 0 && ver == 2)
{
f++;
sorce -= 5;
}
count = 0;
snake_move(head,foodhead);
}
draw_food(foodhead,head);
draw_snake(head);
usleep(1000 * 100 - (500 * k )- (10 * sorce));
if (foodhead->date.time == 0)
{
foodhead = init_food();
}
if(flag == 1 || sorce < 0)
{
if (ver == 2)
{
printf("you insist on %d\n",f);
}
printf("game over\n");
break;
}
count++;
}
//save_date();
//RestoreConsole();
return 0;
}
```