//头文件需要用的函数
#include <stdio.h>//标准输入输出的库函数
#include <time.h>//要用随机数所以用time函数
#include <Windows.h>//需要,控制DOS界面,要在DOS界面中设置文字颜色,获取坐标位置
#include <stdlib.h>//宏定义和通用工具函数
#include <conio.h>//接收键盘输入输出
//宏定义用来定义上下左右方向,把方向定义成整型的数字,方便在后面代码中进行逻辑运算
#define U 1
#define D 2
#define L 3
#define R 4
//全局变量
typedef struct Snake //蛇身的一个节点
{
int x; //用来存放节点X坐标
int y; //用来存放节点Y坐标
struct Snake *next;//蛇身的下一个节点
}SNAKE;
int score = 0, add = 10;//总得分,每次吃食物得分
int HighScore = 0;//游戏最高分
int sleeptime = 200;//sleeptime每次运行的时间间隔
int status; //方向
SNAKE *head, *food;//蛇头指针和食物指针
SNAKE *q;//便利蛇身
int endgamestatus = 0;//表示游戏状态结束的情况,因为有3种情况,咬到自己,撞到墙,主动退出
HANDLE hOut;//为了使用windows函数,建立句柄,它本身是void *
/*************函数声明**************/
void gotoxy(int x, int y);//光标位置函数
int color(int c); //设置文字颜色
void printsnake(); //开始界面蛇绘制
void welcometogame(); //开始界面
void creatMap(); //绘制游戏地图
void scoreandtips(); //游戏界面右侧的得分和小提示
void initsnake(); //初始化蛇身,绘制蛇身
void createfood(); //创建并随机出现食物
int biteself(); //判断是否咬到了自己
void cantcrosswall(); //设置蛇撞墙情况
void speedup(); //加速
void speeddown(); //减速
void snakemove(); //控制蛇前进方向
void keyboardControl(); //控制键盘按键
void lostdraw(); //游戏结束界面
void endgame(); //游戏结束
void choose(); //游戏失败后的选择
void File_out(); //存储最高分进文件
void File_in(); //在文件种读取最高分
void explation(); //游戏说明
//设置颜色函数
int color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);//设置颜色
return 0;
}
//控制台移动函数
void gotoxy(int x, int y)
{
COORD c;//通过修改pos.X和pos.Y的值就可以实现光标的位置控制。
c.X = x;
c.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);//控制光标位置
}
//在界面中绘制蛇
void printsnake()
{
//利用建立好的移动函数和颜色函数绘制图形
color(6);//用颜色函数给字体上色
gotoxy(35, 1);//移动光标到合适位置,打印出图案
printf("/^\\/^\");//注意哦C语言中要答应出\需要多加一个\
gotoxy(34, 2);//移动光标到第二行,打印出图案
printf("|_| o|");
gotoxy(33, 2);
color(2);
printf("_");
gotoxy(25, 3);
color(12);
printf("\\/"); //蛇信
gotoxy(31, 3);
color(2);
printf("/");
gotoxy(37, 3);
color(6);
printf(" \\_/"); //蛇眼睛
gotoxy(41, 3);
color(10);
printf(" \");
gotoxy(26, 4);
color(12);
printf("\\____"); //舌头
gotoxy(32, 4);
printf("_________/");
gotoxy(31, 4);
color(2);
printf("|");
gotoxy(43, 4);
color(10);
printf("\");
gotoxy(32, 5);
color(2);
printf("\\_______"); //蛇嘴
gotoxy(44, 5);
color(10);
printf("\");
gotoxy(39, 6);
printf("| | \"); //下面都是画蛇身
gotoxy(38, 7);
printf("/ / \");
gotoxy(37, 8);
printf("/ / \\ \");
gotoxy(35, 9);
printf("/ / \\ \");
gotoxy(34, 10);
printf("/ / \\ \");
gotoxy(33, 11);
printf("/ / _----_ \\ \");
gotoxy(32, 12);
printf("/ / _-~ ~-_ | |");
gotoxy(31, 13);
printf("( ( _-~ _--_ ~-_ _/ |");
gotoxy(32, 14);
printf("\\ ~-____-~ _-~ ~-_ ~-_-~ /");
gotoxy(33, 15);
printf("~-_ _-~ ~-_ _-~");
gotoxy(35, 16);
printf("~--______-~ ~-___-~");
}
//欢迎界面
void welcometogame()
{
int n;
int i, j = 1;
color(11);//设置"贪吃蛇大作战"字体颜色
//gotoxy(43, 18);//调整光标位置
//printf("贪 吃 蛇 大 作 战");//打印
color(14);//设置边框颜色
for (i = 20; i <= 26; i++)//移动纵横坐标光标,打印边框
{
for (j = 27; j < 74; j++)
{
gotoxy(j, i);//移动光标 x不停移动j就是x
if (i == 20 || i == 26)//横向定位打印范围 打印横边框
{
printf("-");
}
else if (j == 27 || j == 73)//横向定位2条边的位置才打印 打印竖边框
{
printf("|");
}
}
}
//打印选择文字
color(12);
gotoxy(35, 22);
printf("1.开始游戏");
gotoxy(55, 22);
printf("2.游戏说明");
gotoxy(35, 24);
printf("3.退出游戏");
//打印下方小文字并根据输入进入函数
color(3);
gotoxy(29, 27);
printf("请选择[1 2 3]:[ ]\b\b");// \b就是光标退格
color(14);//输入选择的文字
scanf("%d", &n);//输入选择
switch (n)
{
case 1:
system("cls");//发出DOS命令,清屏
creatMap();
initsnake();
createfood();
break;
case 2:
break;
case 3: exit(0);
break;
default:
printf("乱输入我就退出\n");
break;
}
}
//创建地图
void creatMap()
{
int i, j;
//打印上下边框(外围)
for (i = 0; i < 58; i += 2)//因为小方括占用2个字符,所以是i+=2
{
color(5);//边框颜色
gotoxy(i, 0);//上坐标
printf("●");
gotoxy(i, 26);//下坐标
printf("●");
}
//打印左右竖边框(外围)
for (i = 1; i < 26; i++)
{
gotoxy(0, i);//打印左边竖边框
printf("●");
gotoxy(56, i);//打印右边竖边框
printf("●");
}
//打印棋盘内部
for (i = 2; i < 56; i += 2)
{
for (j = 1; j < 26; j++)
{
gotoxy(i, j);
color(3);
printf("●\n\n");
}
}
}
//创建地图右侧信息
void scoreandtips()//此函数应该在键盘控制的函数中调用,因为每次按键,如果蛇吃到食物,得分都会变化
{
//File_out();//读取游戏最高分函数
//打印最高记录和当前得分信息
color(11);
gotoxy(64, 4);
printf("最高记录:%d", HighScore);
color(11);
gotoxy(64, 8);
printf("得分:%d", score);
//打印小提示
color(13);//设置颜色
gotoxy(73, 11);//移动光标
printf("小提示");//打印
color(6);//设置颜色
gotoxy(60, 13);//移动光标
printf("------------------------------------");//打印
gotoxy(60, 25);//移动光标
printf("------------------------------------");//打印
color(3);
gotoxy(64, 14);
printf("每个食物得分:%d分", add);
gotoxy(64, 16);
printf("不能撞墙,不能咬到自己");
gotoxy(64, 18);
printf("用↑ ↓ ← →分别控制蛇的移动");
gotoxy(64, 20);
printf("F1 为加速,F2 为减速");
gotoxy(64, 22);
printf("SPACE:暂停游戏");
gotoxy(64, 24);
printf("ESC :退出游戏");
}
//读取文本中最高分
void File_in()
{
FILE *fp;
fp = fopen("save.txt", "a+");//a+以附加的方式,打开文件,文件不存在创建文件
fscanf(fp, "%d", &HighScore);//把读取文件中数据给全局变量
fclose(fp);
}
//绘制蛇身,其实就是定位蛇的初始位置,调整光标位置,打印结构体
void initsnake()
{
SNAKE *tail;//蛇身
int i;
tail = (SNAKE *)malloc(sizeof(SNAKE));//创建蛇尾
tail->x = 24;//蛇尾坐标定义
tail->y = 5;
tail->next = NULL;
for (i = 1; i <= 4; i++)//分配蛇的光标位置
{
head = (SNAKE *)malloc(sizeof(SNAKE));//创建蛇头
head->next = tail;//指向蛇身
head->x = 24 + 2 * i;//2代表一个格子
head->y = 5;//纵向坐标y,y不动和尾巴在同一排
tail = head;//类似头插法
}
//打印蛇身
while (tail != NULL)//因为tail = head,所以tail在for结束后是头指针
{
gotoxy(tail->x, tail->y);//根据每一个结构体坐标调整光标位置,并打印出来
color(14);
printf("★");
tail = tail->next;
}
}
//创建并随机出现食物
void createfood()
{
SNAKE *food_1=NULL;//创建食物指针
srand((unsigned)time(NULL));//随机种子
food_1 = (SNAKE*)malloc(sizeof(SNAKE));//给食物内存
food_1->next = NULL;
while ((food_1->x % 2) != 0)//限制食物只会出现在棋盘中的坐标,准备把坐标给到移动光标,绘制食物 %2这里余2是要求食物在棋盘格中间
{
food_1->x = rand() % 25 + 2;//棋盘格子宽度是52 让食物出现在X轴最小限制2-52之间 2因为是2个字符为一个格子
}
food_1->y = 24 + 1;//棋盘格子长度是24 让食物出现在Y轴最小限制1-24之间 1因为是纵向1个字符为1列
q = head;//取得蛇头头指针
//int num = 20;//递归返回的时候可以记录进入递归之前的值,但是如果是指针,无法记录之前的
char ch = 'n';
while ( q != NULL)//蛇任何一个部分都不能和食物重叠
{
//num -= 1;
if (q->x == food_1->x && q->y == food_1->y)//如果随机出现的食物坐标和蛇初始化坐标重叠,删除这个食物内容,重新创建
{
free(food_1);//释放当前食物内容
/*
food_1 = (SNAKE*)malloc(sizeof(SNAKE));
while ((food_1->x % 2) != 0)
{
food_1->x = rand() % 52 + 2;
food_1->y = 24 + 1;
q = head;
}
*/
ch = 'y';
createfood();//递归重新创建食物坐标,但是这里注意递归进去后,循环q到NULL运行完毕,函数返回来位置在if(ch=='y'),q坐标已经被移动到NULL了,指针无法记录之前的
}
if (ch == 'y')
{
food_1 = food;
return;
}
q = q->next;//蛇身上任何坐标不能和食物重合
if (q == NULL)
{
break;
}
}
gotoxy(food_1->x, food_1->y);//把随机好的食物坐标给到移动光标里,并绘制出来
free(food);
food = food_1;//把食物指针给出去全局指针
color(12);//食物颜色
printf("●");//绘制食物
}
//判断蛇咬到自己
int biteself()
{
SNAKE *self;
q = head;
self = head;
while (q ->next!= NULL)//任何一个点碰到身体算失败
{
q = q->next;
if (self->x == q->x && self->y == q->y)
{
return 1;//返回1表示蛇咬到了自己
}
}
return 0;
}
//判断蛇是否撞墙
void cantcrosswall()
{
if (head->x == 0 || head->x == 56 || head->y == 0 || head->y == 26)
{
endgamestatus = 1;//撞墙情况的失败界面,给全局
printf("撞墙失败");
exit(1);
return;
}
}
//蛇加速前进
void speedup()
{
if (sleeptime >= 50)//限制50的刷新率,
{
sleeptime = sleeptime - 10; //时间间隔 - 10 刷新更快了
add = add + 2;//同时每吃一个食物的得分+2,可叠加再吃到14再吃到16 add全局基础得分
}
}
//蛇减速减速
void speeddown()
{
if (sleeptime < 350)//限制时间间隔小于350不能太慢了哈
{
sleeptime = sleeptime + 30;//加大刷新间隔,速度就慢了
add = add - 2;//减速了游戏就很容易,所以降低基础分
if (sleeptime == 350)//当游戏最慢的时候,基础分为1
{
add = 1;
}
}
}
//不按键时蛇前进,蛇的动力都在这里,这个程序就类似,1帧一个执行,1帧一个执行,可以这样想象,用程序执行间隔Sleep控制蛇的运行速度
void snakemove()
{
//这个动力其实就是把新的结构体节点,根据当前蛇头位置移动一个位置,然后不断捕捉键盘方向,来循环移动
SNAKE *nexthead;//表示蛇头下一个位置,新的头指针,代表新的位置,类似移动了一步
cantcrosswall();//判断蛇是否撞墙
nexthead = (SNAKE*)malloc(sizeof(SNAKE));//为下一步开辟一个空间
if (status == U)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
{
//不断向上前进
nexthead->x = head->x; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
nexthead->y = head->y - 1; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
head = nexthead;
q = head;//给一个全局傀儡方便控制
if (nexthead->x == food->x && nexthead->y == food->y)
{
while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
score = score + add;//食物吃到后加10分
speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
createfood();//重新生成食物
}
else//如果没有吃到食物就把痕迹删除
{
while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
color(3);
printf("■");
free(q->next); //释放尾巴结构体
q->next = NULL; //当前结构体,指向空
}
}
if (status == D)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
{
//不断向上前进
nexthead->x = head->x; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
nexthead->y = head->y+1; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
head = nexthead;
q = head;//给一个全局傀儡方便控制
if (nexthead->x == food->x && nexthead->y == food->y)
{
while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
score = score + add;//食物吃到后加10分
speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
createfood();//重新生成食物
}
else//如果没有吃到食物就把痕迹删除
{
while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
color(3);
printf("■");
free(q->next); //释放尾巴结构体
q->next = NULL; //当前结构体,指向空
}
}
if (status == L)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
{
//不断向上前进
nexthead->x = head->x-2; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
nexthead->y = head->y; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
head = nexthead;
q = head;//给一个全局傀儡方便控制
if (nexthead->x == food->x && nexthead->y == food->y)
{
while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
score = score + add;//食物吃到后加10分
speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
createfood();//重新生成食物
}
else//如果没有吃到食物就把痕迹删除
{
while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
color(3);
printf("■");
free(q->next); //释放尾巴结构体
q->next = NULL; //当前结构体,指向空
}
}
if (status == R)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
{
//不断向上前进
nexthead->x = head->x + 2; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
nexthead->y = head->y; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
head = nexthead;
q = head;//给一个全局傀儡方便控制
if (nexthead->x == food->x && nexthead->y == food->y)
{
while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
score = score + add;//食物吃到后加10分
speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
createfood();//重新生成食物
}
else//如果没有吃到食物就把痕迹删除
{
while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
{
gotoxy(q->x, q->y);
color(14);
printf("★");
q = q->next;
}
gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
color(3);
printf("■");
free(q->next); //释放尾巴结构体
q->next = NULL; //当前结构体,指向空
}
}
if (biteself() == 1)//判断是否吃到自己 为什么先判断撞墙呢,因为要判断当前蛇头位置是否和最开始的边沿重合
//而吃到自己是要在自己移动后判断是否蛇头和身体重合
{
endgamestatus = 2;
}
}
//控制键盘按键
void keyboardControl()
{
status = R;//设置初始移动方向为右
while (1)//可以一直按键所以设置循环
{
scoreandtips();//游戏右侧得分小提示
if (GetAsyncKeyState(VK_UP) && status != D)//运行方向不是向下,就可以记录上
{
status = U;
}
else if (GetAsyncKeyState(VK_DOWN) && status != U)
{
status = D;
}
else if (GetAsyncKeyState(VK_LEFT) && status != R)
{
status = L;
}
else if (GetAsyncKeyState(VK_RIGHT) && status != L)
{
status = R;
}
if (GetAsyncKeyState(VK_SPACE))//如果按下空格键,暂停游戏300毫秒
{
Sleep(300);
if (GetAsyncKeyState(VK_SPACE))
{
break;
}
}
else if (GetAsyncKeyState(VK_ESCAPE))//如果按了ESC键就退出游戏
{
endgamestatus = 3;
break;
}
else if (GetAsyncKeyState(VK_F1))//如果按了F1键就加速
{
speedup();
}
else if (GetAsyncKeyState(VK_F2))//如果按了F1键就加速
{
speeddown();
}
Sleep(sleeptime);//控制刷新率
snakemove();
}
}
int main(void)//每一个函数解决一个单一问题,进行组合,在以后编程中也要这样
{
system("mode con cols=100 lines=30");//让C语言发一个DOS命令,设置窗口宽度高度
printsnake();//输出绘制图案
welcometogame();//输出边框和文
//File_in();//把文件中最高分读取进来
keyboardControl();
return 0;
}