## @贪吃蛇游戏(纯属小白制作)
## 这是我的第一篇博客,所以博客格式上可能不太清晰,还请见谅 ,不喜勿喷
#
入坑C语言接近一年了,今天写完贪吃蛇,终于觉得自己可以是一个码农了(写到这里笔者眼里有点湿,,,因为是大一新生被调剂了,学了一门自己不喜欢的专业,没有计算机的老师教,所以一直很迷茫,现在自己是否入门C语言了),
好了废话不多说,上代码!
## 思路:
1、初始化
2、绘图窗口绘图
3、和用户输入无关的数据更新
4、和用户输入有关的数据更新
和用户输入无关的数据更新,
一、全局变量
struct snake//创建蛇结构体
{
short x;//每一段蛇身体的x坐标
short y;//每一段蛇身体的y坐标
snake* snake_next;//指向下一段蛇的身体(节点)
}*head_snake;//表头
struct food//食物结构体
{
short x;//食物随机出现的x坐标
short y;//食物随机出现的y坐标
short Is_eat;//判断这个食物有没有被吃(食物的状态,没有被吃为值0,被吃了为值1)
}food_[NUM];//食物数组
enum Dir{ UP, DOWN, LEFT, RIGHT }dir;//蛇头的方向
short max_length,length;//分别表示蛇的历史最长和实时长度
char str_max[5];//将最高分转化为字符串形式
FILE *fp;//存档文件指针
二、宏定义
#define W 1000//画面的宽度
#define H 500//画面的高度
#define SIZE 20//蛇的身体和食物的规格为SIZE*SIZE的小方格
#define NUM 5//每次食物出现的数量
三、函数声明
void init();//初始化
void draw_information();
snake* creat_snake();//创建一条三米长的小蛇链表
snake* creat_snake_node();//创建一节蛇的身体
void produce_food();//随机生成食物
void draw();//画蛇画食物
void rect_snake();//画红方块
void rect_food();//画蓝方块
void eat();//蛇吃饭
void move();//蛇移动
void key_down();//按键移动
void over();//游戏结束
void update_without_input();//与用户输入无关
void update_with_input();//与用户输入有关
void foo_x(snake* p, short x_postion);//将蛇身体前一个节点的x坐标赋值给下一个节点
void foo_y(snake* p, short y_poston);//将蛇身体前一个节点的y坐标赋值给下一个节点
void ctrl_s();//存档函数
void run();//运行
四、函数的封装
int main()
{
init();//初始化函数
while (1)
{
run();//运行函数
Sleep(100);
}
EndBatchDraw();
system("pause");
closegraph();
return 0;
}
void init()
{
draw_information();//开始界面
head_snake = creat_snake();//创建3米长的小蛇
produce_food();//产生食物
dir = RIGHT;//初始化方向
BeginBatchDraw();//开始批量绘图
}
void run()
{
draw();//绘图函数
update_with_input();//与玩家输入操作有关的数据更新
update_without_input();//与玩家输入无关的数据更新
ctrl_s();//判断是否进行存档函数
over();//判断是否死亡函数
}
void update_without_input()
{
move();//即使玩家不进行按键操作蛇自身也会往以前的方向动的
eat();//判断蛇是否吃到了食物,并且进行蛇身体的增长
over();//判断蛇是否死亡
}
void update_with_input()
{
key_down();//监听键盘并且进行方向改变
}
各个函数封装具体如下
⑴init函数
void init()
{
draw_information();//开始界面
head_snake = creat_snake();//创建3米长的小蛇
produce_food();//产生食物
dir = RIGHT;//初始化方向
BeginBatchDraw();//开始批量绘图
}
snake* creat_snake()//创建蛇链表
{
snake* head = (snake*)malloc(sizeof(snake));
if (head == NULL)
{
printf("申请内存失败!!!\n");
return NULL;
}
snake* tail = head;
for (short i = 3; i >0; --i)
{
tail->snake_next = creat_snake_node();
tail = tail->snake_next;
tail->x = i;
tail->y = 0;
}
return head;
}
snake* creat_snake_node()//创建单个蛇节点
{
snake* temp = (snake*)malloc(sizeof(snake));
if (temp == NULL)
{
printf("申请内存失败!!!\n");
return NULL;
}
temp->snake_next = NULL;
return temp;
}
void produce_food()//随机生成食物
{
for (short i = 0; i < NUM; ++i)
{
snake* temp = head_snake->snake_next;
food_[i].x = rand() % W / SIZE;
food_[i].y = rand() % H / SIZE;
food_[i].Is_eat = 0;
while (temp != NULL)
{
if (temp->x == food_[i].x&&temp->y == food_[i].y)
{
food_[i].x = rand() % W / SIZE;
food_[i].y = rand() % H / SIZE;
}
temp = temp->snake_next;//让temp指针重新指向head_snake->snake_next;这一步有含金量
}
}
}
void draw_information()
{
srand((unsigned)time(NULL));
initgraph(W, H);
setbkcolor(0x000000);
cleardevice();
fp = fopen("MAX_LENGTH.txt", "r");
if (fp == NULL)
{
fp = fopen("MAX_LENGTH.txt", "w+");
if (fp == NULL)
exit(0);
}
fscanf(fp, "%d", &max_length);
fclose(fp);
line(190, 100, 800, 100);
line(190, 100, 190, 400);
line(190, 400, 800, 400);
line(800, 400, 800, 100);
itoa(max_length, str_max, 10);
settextstyle(50, 40, "Adobe Caslon Pro Bold");
settextcolor(RGB(0, 255, 125));
setbkmode(1);
char str[50] = "历史最高:";
strcat(str, str_max);
outtextxy(200, 100, (str));
outtextxy(200, 250, "按任意键继续");
_getch();
}
⑵draw函数
void draw()
{
cleardevice();
rect_snake();//画蛇
rect_food();//画食物
FlushBatchDraw();
}
void rect_snake()//画蛇,蛇的身体是用红色的小方块来代表的
{
snake* temp = head_snake->snake_next;
while (temp != NULL)
{
setfillcolor(RED);
fillrectangle(temp->x*SIZE, temp->y*SIZE, temp->x*SIZE + SIZE, temp->y*SIZE + SIZE);
temp = temp->snake_next;
}
}
void rect_food()//画食物,食物是用一个个蓝色的小方块代表的
{
for (short i = 0; i < NUM; ++i)
{
if (food_[i].Is_eat == 0)
{
setfillcolor(BLUE);
fillrectangle(food_[i].x*SIZE, food_[i].y*SIZE, food_[i].x*SIZE + SIZE, food_[i].y*SIZE + SIZE);
}
}
}
⑶和玩家输入无关的数据更新(核心部分)
//贪吃蛇最核心的一点就是蛇的身体每次走的路径都是它的上一段身体走过的路径,也就是说要将它的上一段身体的坐标赋值下一段身体
//foo_x(snake* p,short x_postion)函数接受两个参数,它的作用就是将这一段身体的坐标赋值给下一段身体,这里用到递归
//p表示指向下一段身体的指针,x_postion表示这一段身体的x坐标,将这一个蛇的,这里涉及到了递归,
//你品你细品,就品出来了
//按照逻辑顺序要先进行改变坐标之后,再改变蛇头坐标,
//因为当递归完成之后所有坐标都将向下赋值完成,而蛇头处于最顶端坐标没有改变,所以最后还要根据方向,进行自增改变坐标
void update_without_input()
{
move();//移动函数,以为即使玩家不进行输入,蛇依然会沿着以前的方向运动,所以要时时刻刻改变蛇的坐标
eat();//判断蛇吃到食物了吗?如果吃到了就会增加蛇的身长
over();//判断蛇是否死亡(两种情况,1、蛇撞墙。2、蛇自己咬到自己了。)
}
void move()
{
short x, y;
snake* temp = head_snake->snake_next;
foo_x(temp->snake_next, temp->x);//将当前节点的x坐标赋值给下一个节点的x坐标
foo_y(temp->snake_next, temp->y);//将当前节点的y坐标赋值给下一个节点的y坐标,因为这两个函数涉及到了递归,有点难理解,但是这也使之成为这个程序的亮点之一
switch (dir)//改变蛇头的xy坐标,因为是向下赋值,所以蛇头的坐标需要更新
{
case UP:temp->y--;
break;
case DOWN:temp->y++;
break;
case LEFT:temp->x--;
break;
case RIGHT:temp->x++;
break;
}
}
//将蛇身节点xy的改变
void foo_x(snake* p, short x_postion)
{
if (p != NULL)
{
foo_x(p->snake_next, p->x);
p->x = x_postion;
}
}
void foo_y(snake* p, short y_postion)
{
if (p != NULL)
{
foo_y(p->snake_next, p->y);
p->y = y_postion;
}
}
void eat()//判断蛇是否吃到食物
{
snake* temp = head_snake->snake_next;
snake* new_node;
short n = 0;
for (short i = 0; i < NUM; ++i)//遍历食物数组
{
if (temp->x == food_[i].x&&temp->y == food_[i].y)//如果吃到食物,就进入if语句
{
length++;
new_node = creat_snake_node();
snake* p = temp;
food_[i].Is_eat = 1;
while (p->snake_next != NULL)//让p指向最后一个节点,并且将刚刚增加的那个节点插在尾部
{
p = p->snake_next;
}
p->snake_next = new_node;
}
if (food_[i].Is_eat == 1)//记录已经被吃掉的食物个数
{
n++;
}
}
if (n >= NUM )//如果画面中的食物都被吃没了,就再产生一次食物
produce_food();
}
void over()//判断是否死亡,
{
snake* temp = head_snake->snake_next;
snake* ptr;
\\蛇撞墙导致死亡
if (temp->x*SIZE >= W || temp->x*SIZE < 0 || temp->y*SIZE >= H || temp->y*SIZE < 0)
{
MessageBox(GetHWnd(), "实在不好意思,你挂了", "游戏结束", 0);
while (temp != NULL)//蛇死亡之后要释放所有申请来的内存空间
{
ptr = temp;
temp = temp->snake_next;
free(ptr);
}
free(head_snake);
exit(0);
}
else//蛇没有撞墙
{
snake* p = temp->snake_next;
while (p != NULL)//遍历链表
{
if (temp->x == p->x&&temp->y == p->y)//如果蛇头和蛇的身体撞在一起就死了
{
MessageBox(GetHWnd(), "实在不好意思,你挂了", "游戏结束", 0);
while (temp != NULL)//蛇死亡之后要释放所有申请来的内存空间
{
ptr = temp;
temp = temp->snake_next;
free(ptr);
}
free(head_snake);
exit(0);
}
p = p->snake_next;
}
}
}
⑷和玩家输入有关的数据更新
void update_with_input()
{
key_down();//监听键盘,获取方向改变
}
void key_down()
{
if (_kbhit())
{
switch (_getch())//注意蛇不能回头!!!
{
case'W':
case'w':if (dir != DOWN)//
dir = UP;
break;
case'S':
case's':if (dir != UP)
dir = DOWN;
break;
case'A':
case'a':if (dir != RIGHT)
dir = LEFT;
break;
case'D':
case'd':if (dir != LEFT)
dir = RIGHT;
break;
}
}
}
⑸存档函数
void ctrl_s()
{
if (length > max_length)
{
fp = fopen("MAX_LENGTH.txt", "r+");
if (fp == NULL) //如果文档不存在就创建一个
{
fp = fopen("MAX_LENGTH.txt", "W+");
}
fprintf(fp, "%d", length); //写入当前的最高成绩
fclose(fp);
}
}
其他的思路就没啥好说的了直接上完整代码注意:
1、项目属性中配置属性设置为多字节字符集
完整代码如下
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<conio.h>
#include<easyx.h>
#include<time.h>
#include<Windows.h>
#define W 1000
#define H 500
#define SIZE 20
#define NUM 5//每次食物出现的数量
struct snake//创建蛇结构体
{
short x;
short y;
snake* snake_next;
}*head_snake;
struct food
{
short x;
short y;
short Is_eat;
}food_[NUM]; //食物数组
enum Dir{ UP, DOWN, LEFT, RIGHT }dir;
short max_length, length;
char str_max[5];
FILE *fp;
void init();//初始化
void draw_information();
snake* creat_snake();//创建一条三米长的小蛇链表
snake* creat_snake_node();//创建一节蛇的身体
void produce_food();//随机生成食物
void draw();//画蛇画食物
void rect_snake();//画红方块
void rect_food();//画蓝方块
void eat();//蛇吃饭
void move();//蛇移动
void key_down();//按键移动
void over();//游戏结束
void update_without_input();//与用户输入无关
void update_with_input();//与用户输入有关
void foo_x(snake* p, short x_postion);//将蛇身体前一个节点的x坐标赋值给下一个节点
void foo_y(snake* p, short y_poston);//将蛇身体前一个节点的y坐标赋值给下一个节点
void ctrl_s();//存档函数
void run();//运行
int main()
{
init();
while (1)
{
run();
Sleep(100);
}
EndBatchDraw();
system("pause");
closegraph();
return 0;
}
void run()
{
draw();
update_with_input();
update_without_input();
ctrl_s();
}
void init()
{
draw_information();
head_snake = creat_snake();
produce_food();
dir = RIGHT;
BeginBatchDraw();
}
snake* creat_snake()
{
snake* head = (snake*)malloc(sizeof(snake));
if (head == NULL)
{
printf("申请内存失败!!!\n");
return NULL;
}
snake* tail = head;
for (short i = 3; i >0; --i)
{
tail->snake_next = creat_snake_node();
tail = tail->snake_next;
tail->x = i;
tail->y = 0;
}
return head;
}
snake* creat_snake_node()
{
snake* temp = (snake*)malloc(sizeof(snake));
if (temp == NULL)
{
printf("申请内存失败!!!\n");
return NULL;
}
temp->snake_next = NULL;
return temp;
}
void produce_food()
{
for (short i = 0; i < NUM; ++i)
{
snake* temp = head_snake->snake_next;
food_[i].x = rand() % W / SIZE;
food_[i].y = rand() % H / SIZE;
food_[i].Is_eat = 0;
while (temp != NULL)
{
if (temp->x == food_[i].x&&temp->y == food_[i].y)
{
food_[i].x = rand() % W / SIZE;
food_[i].y = rand() % H / SIZE;
}
temp = temp->snake_next;
}
}
}
void update_without_input()
{
move();
eat();
over();
}
void move()
{
short x, y;
snake* temp = head_snake->snake_next;
foo_x(temp->snake_next, temp->x);
foo_y(temp->snake_next, temp->y);
switch (dir)
{
case UP:temp->y--;
break;
case DOWN:temp->y++;
break;
case LEFT:temp->x--;
break;
case RIGHT:temp->x++;
break;
}
}
//将蛇身节点xy的改变
void foo_x(snake* p, short x_postion)
{
if (p != NULL)
{
foo_x(p->snake_next, p->x);
p->x = x_postion;
}
}
void foo_y(snake* p, short y_postion)
{
if (p != NULL)
{
foo_y(p->snake_next, p->y);
p->y = y_postion;
}
}
void eat()
{
snake* temp = head_snake->snake_next;
snake* new_node;
short n = 0;
for (short i = 0; i < NUM; ++i)
{
if (temp->x == food_[i].x&&temp->y == food_[i].y)
{
length++;
new_node = creat_snake_node();
snake* p = temp;
food_[i].Is_eat = 1;
while (p->snake_next != NULL)
{
p = p->snake_next;
}
p->snake_next = new_node;
}
if (food_[i].Is_eat == 1)
{
n++;
}
}
if (n >= NUM - 1)
produce_food();
}
void over()
{
snake* temp = head_snake->snake_next;
snake* ptr;
if (temp->x*SIZE >= W || temp->x*SIZE < 0 || temp->y*SIZE >= H || temp->y*SIZE < 0)
{
MessageBox(GetHWnd(), "不好意思,你挂了", "游戏结束", 0);
while (temp != NULL)
{
ptr = temp;
temp = temp->snake_next;
free(ptr);
}
free(head_snake);
exit(0);
}
else
{
snake* p = temp->snake_next;
while (p != NULL)
{
if (temp->x == p->x&&temp->y == p->y)
{
MessageBox(GetHWnd(), "不好意思,你挂了", "游戏结束", 0);
while (temp != NULL)
{
ptr = temp;
temp = temp->snake_next;
free(ptr);
}
free(head_snake);
exit(0);
}
p = p->snake_next;
}
}
}
void update_with_input()
{
key_down();
}
void key_down()
{
if (_kbhit())
{
switch (_getch())
{
case'W':
case'w':if (dir != DOWN)
dir = UP;
break;
case'S':
case's':if (dir != UP)
dir = DOWN;
break;
case'A':
case'a':if (dir != RIGHT)
dir = LEFT;
break;
case'D':
case'd':if (dir != LEFT)
dir = RIGHT;
break;
}
}
}
void draw()
{
cleardevice();
rect_snake();//画蛇
rect_food();//画食物
FlushBatchDraw();
}
void rect_snake()
{
snake* temp = head_snake->snake_next;
while (temp != NULL)
{
setfillcolor(RED);
fillrectangle(temp->x*SIZE, temp->y*SIZE, temp->x*SIZE + SIZE, temp->y*SIZE + SIZE);
temp = temp->snake_next;
}
}
void rect_food()
{
for (short i = 0; i < NUM; ++i)
{
if (food_[i].Is_eat == 0)
{
setfillcolor(BLUE);
fillrectangle(food_[i].x*SIZE, food_[i].y*SIZE, food_[i].x*SIZE + SIZE, food_[i].y*SIZE + SIZE);
}
}
}
void ctrl_s()
{
if (length > max_length)
{
fp = fopen("MAX_LENGTH.txt", "r+");
if (fp == NULL)
{
fp = fopen("MAX_LENGTH.txt", "W+");
}
fprintf(fp, "%d", length);
fclose(fp);
}
}
void draw_information()
{
srand((unsigned)time(NULL));
initgraph(W, H);
setbkcolor(0x000000);
cleardevice();
fp = fopen("MAX_LENGTH.txt", "r");
if (fp == NULL)
{
fp = fopen("MAX_LENGTH.txt", "w+");
if (fp == NULL)
exit(0);
}
fscanf(fp, "%d", &max_length);
fclose(fp);
line(190, 100, 800, 100);
line(190, 100, 190, 400);
line(190, 400, 800, 400);
line(800, 400, 800, 100);
itoa(max_length, str_max, 10);
settextstyle(50, 40, "Adobe Caslon Pro Bold");
settextcolor(RGB(0, 255, 125));
setbkmode(1);
char str[50] = "历史最高:";
strcat(str, str_max);
outtextxy(200, 100, (str));
outtextxy(200, 250, "按任意键继续");
_getch();
}
实现效果如下(界面有点简陋,个人认为界面只是一种装饰,里面的功能才是程序的核心,当然如果想要美化界面完全可以在run函数里面在封装一个美化界面的函数,全凭个人喜好):