效果(视频)
贪吃蛇
开始可能有点糊【上传后就糊了,不知道为什么】
全部代码放在了结尾
实现过程的解释大部分都在注释中有
实现环境
这次的贪吃蛇的实现需要借助Windows环境的控制台【一般是输出到屏幕时的黑框】
所以要先准备好环境
例:使用 VS
VS默认的标准输出的黑框是由Windows决定的。
此时就要对其进行修改,不然无法改变控制台的大小,宽字符的打印也会有问题
修改流程如下:
如果要将其再修改回去,过程如下:
鼠标右击黑框上边缘,
实现所需的一些Win32 API知识
Win32 API:
Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤
的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启
视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application), 所以便
称之为 Application Programming Interface,简称 API 函数。
WIN32 API也就是Microsoft Windows32位平台的应⽤程序编程接⼝。
改变控制台大小和标题
使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,40⾏,100列
cmd命令为:mode con cols=40 lines=100
通过cmd命令设置控制台窗⼝的名字:
cmd命令为: title 贪吃蛇
在C语言程序中使用cmd命令可以使用函数system
例
隐藏光标
为防止输入光标影响观感,可以隐藏输入光标
隐藏光标可分为以下几个流程
- 使用
GetStdHandle
函数获得控制台句柄,这样才可以改变控制台的光标属性
【GetStdHandle
是⼀个WindowsAPI函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标
准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。】
GetStdHandle
函数的参数只有三个【标准输⼊、标准输出或标准错误】,它的返回值为句柄指针【HANDLE】
任意创建一个句柄指针变量接收GetStdHandle函数的返回值,就获得了对应标准设备的句柄
控制台是标准输出设备,对应GetStdHandle
函数的参数为STD_OUTPUT_HANDLE
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- 创建包含有关控制台光标的信息的结构变量
包含有关控制台光标的信息的结构体CONSOLE_CURSOR_INFO
的定义为:
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO
-
dwSize,由光标填充的字符单元格的百分⽐。此值介于1到100之间。光标外观会变化,范围从完 全填充一个字符单元格到单元底部的⽔平线条。
-
bVisible,游标的可⻅性。如果光标可⻅,则此成员为true
- 检索(获取)控制台光标信息
使用函数GetConsoleCursorInfo
(句柄,包含有关控制台光标的信息的结构变量的地址)
- 设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。
使用函数SetConsoleCursorInfo
(句柄,包含有关控制台光标的信息的结构变量的地址)
完整代码
获得控制台句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
创建包含有关控制台光标的信息的结构变量
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);获取控制台光标信息
CursorInfo.bVisible = false; 隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);设置控制台光标状态
设置光标位置
- 利用结构体
COORD
【COORD
是WindowsAPI中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕上的坐标(X,Y)】定位坐标
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD
- 设置控制台光标坐标
利用函数SetConsoleCursorPosition
(句柄,COORD变量地址)
完整代码:
COORD a = {
10, 5};创建COORD变量并赋值
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);获取标准输出的句柄
SetConsoleCursorPosition(hOutput, pos);设置标准输出上光标的位置为a中的成员
获取按键情况
因为要通过↑,↓,←,→来操纵贪吃蛇,所以我们需要知道那些按键被按过,并以此做出响应。
通过函数GetAsyncKeyState
(虚拟键值【键盘上每一个键都有对应的虚拟键值】)的返回值来判断按键情况
GetAsyncKeyState
的返回值是short
类型,在上⼀次调⽤GetAsyncKeyState
函数后,如果返回的16位的short
数据中
最⾼位的比特位
是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;
如果最低位被置为1则说明,该按键被按过,否为0。
所以如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState
返回值的最低的比特位
的值是否为1
根据此特点我们可以构建一个宏(函数也可以,只不过小运算宏比函数要方便,运算更快
)
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 1) ? 1 : 0 )
上面的宏的运行流程为:
调用宏将虚拟键值(VK)传给宏,宏再把虚拟键值(VK)传给GetAsyncKeyState
函数,GetAsyncKeyState
函数判断按键情况后返回一个值,返回的值按位与(&)1后即可得出返回值的最低位的比特位
的值。
如果返回值的最低位的比特位
的值为1,条件运算的结果就为真,宏KEY_PRESS
的就为1
如果返回值的最低位的比特位
的值为0,条件运算的结果就为假,宏KEY_PRESS
的就为0
C语言本地化
由于过去C语⾔并不适合⾮英语国家(地区)使⽤。所以那时是C语言没有宽字符(占两个字节的字符,例如汉字就是宽字符)一说,所以宽字符不能借助字符变量打印。
后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。
⽐如:加⼊和宽字符的类型wchar_t
和宽字符的输⼊和输出函数,加⼊和<locale.h
>头⽂件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。
所以要借助字符变量打印宽字符就需要本地化
本地化可借助函数setlocale
(类项,“C”或者“”);
setlocale
函数的第一个参数是类项,本土化的类项有以下几种:
LC_COLLATE
LC_CTYPE
LC_MONETARY
LC_NUMERIC
LC_TIME
LC_ALL-针对所有类项修改
C标准给setlocale
的第⼆个参数仅定义了2种可能取值:“C”和""。
“C”表示C的国际标准,“”表示适应本地标准
所以要本土化就直接使用以下代码
setlocale(LC_ALL, " ");
那么本土化后怎么样可以借助字符变量来打印宽字符呢?
要借助字符变量来打印宽字符,就要先定义 宽字符变量
宽字符的类型为 wchar_t
,使用该类型定义变量后,在给定义的变量赋值时还要在赋值的宽字符前加L
打印宽字符时要用wprintf
函数打印
实现贪吃蛇
准备
创建文件
- 头文件Snake.h用于存放函数声明和结构体定义等
- Snake.c用于存放函数的实现
- test.c用于存放main函数等
包含头文件
- stdio.h:用于使用布尔类型
- stdio.h:标准输入输出
- stdlib.h:用于使用动态内存管理函数
- windows.h:用于使用Win32 API中的函数
- time.h:用于产生时间戳
- locale.h:用于本土化
define定义宏常量和宏
定义结构体
定义一个结构体Snake将贪吃蛇游戏的状态和蛇的状态放入其中,方便管理
- pSnake:我们使用单链表来维护蛇的身体,pSnake就指向蛇身单链表的第一个节点
- SnakeSpeed:因为程序执行得太快,所以需要停顿给玩家反应时间,停顿时间越长蛇的速度越慢,停顿时间越短
创建枚举体
在main函数中准备
随机生成食物时需要用到随机数,所以在main函数中设置变化的随机数种子
test.c全部代码
#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
void game()
{
Snake snake;
GameStart(&snake);//游戏开始前的初始化
}
int main()
{
//本土化
setlocale(LC_ALL, "");
//设置变化的随机数种子
srand((unsigned int)time(NULL));
game();
return 0;
}
打印选项界面和游戏初始化
游戏初始化
初始化蛇
打印游戏开始前的界面
为了方便设置控制台光标的位置,我们将它分装成一个函数
根据按键情况选择选项,确认选项
打印游戏开始前的界面
Welcome();
enum OPTION option = BEGIN;默认选项为开始游戏
do
{
打印选项界面
SetPos(40, 15);
printf("1.开始游戏");
SetPos(40, 16);
printf("2.排行榜");
SetPos(36, 18);
printf("请按↑,↓选择选项,按Enter键确认选项");
SetPos(36, 19);
printf("按Esc退出游戏。");
if (KEY_PRESS(VK_UP))如果按了↑就进去
{
SetPos(38, 16);按了↑,→就指向第15行
printf(" ");所以把第16行的→用空格覆盖
SetPos(38, 15);
printf("→");再在第15行打印→
option = BEGIN;更改枚举变量的值为 开始
}
else if (KEY_PRESS(VK_DOWN))如果按了↑就进去
{
SetPos(38, 15);按了↓,→就指向第16行
printf(" ");所以把第15行的→用空格覆盖
SetPos(38, 16);
printf("→");再在第16行打印→
option = CHART;更改枚举变量的值为 排行榜
}
if (option ==BEGIN)
{
SetPos(38, 16);
printf(" ");
SetPos(38, 15);
printf("→");解决第一次时 →没有指向的选项
if (KEY_PRESS(VK_RETURN) == 1)如果选项状态为 开始 并且 按了Enter键就进去
{
system("cls");清屏
SetPos(35, 15);设置控制台光标到 合适 的位置
printf("开始游戏");打印提示信息
SetPos(35, 17);
system("pause");达成 按任意键继续。。。 的效果
system("cls");
GameRun(snake);游戏进行
GameEnd(snake);游戏善后
system("cls");
}
}
if (option == CHART)
{
SetPos(38, 15);
printf(" ");
SetPos(38, 16);
printf("→");解决第一次时 →没有指向的选项
if (KEY_PRESS(VK_RETURN) == 1)如果选项状态为 排行榜 并且 按了Enter键就进去
{
system("cls");清屏
SetPos(35, 15);
printf("排行榜");
SetPos(35, 17);
system("pause");达成 按任意键继续。。。 的效果
system("cls");
ChartPrint(snake);打印排行榜
system("pause");
system("cls");
}
}
} while (KEY_PRESS(VK_ESCAPE) != 1);如果按Esc就结束循环
}
打印游戏开始后的墙和提示信息
打印游戏时的界面
void PlayInte(Snake* snake)
{
打印墙
上墙体
int i = 0;
wchar_t ch = L'■';宽字符定义并赋值
for (i = 0; i <COR; i += 2)因为一个宽字符占两个字节并且一个宽字符占两个单位的横坐标
{
所以i+=2
SetPos(i,0);
wprintf(L"%lc", ch);打印宽字符
}
下墙体
for (i = 0; i < COR; i += 2)因为一个宽字符占两个字节并且一个宽字符占两个单位的横坐标
所以i+=2
{
SetPos(i,ROW-1);
wprintf(L"%lc", ch);
}
左侧墙体
for (i = 1; i < ROW; i++)虽然一个宽字符占 2 个单位的横坐标
但是一个宽字符只占 1 个单位的纵坐标
{
SetPos(0,i);
wprintf(L"%lc", ch);
}
右侧墙体
for (i = 1; i < ROW; i++)虽然一个宽字符占 2 个单位的横坐标
但是一个宽字符只占 1 个单位的纵坐标
{
SetPos(COR-2, i);
wprintf(L"%lc", ch);
}
打印提示信息
SetPos(65, 16);
printf("F3加速");
SetPos(65, 17);
printf("F4减速");
SetPos(65, 18);
printf("Esc退出游戏");
SetPos(65, 19);
printf("空格暂停游戏");
SetPos(65, 20);
printf("使用↑,↓,→,←控制蛇的方向");
}
用头插法构建初始蛇身链表
用头插法 构建 初始 蛇身节点
for (i = 5; i >= 1; i--)
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));申请新节点
if (newnode == NULL)
{
printf("GameRun函数中malloc失败!");
return;
}
newnode->x = SNAKE_HEAD_X - i * 2;根据 初始蛇头坐标 控制链表尾节点的坐标
newnode->y = SNAKE_HEAD_Y; 因为是头插所以第一个插入的节点在插入完成后是最后一个节点
if (snake->pSnake == NULL)
{
snake->pSnake = newnode;
newnode->next = NULL;
}
else
{
newnode->next = snake->pSnake;
snake->pSnake = newnode;
}
}
打印初始蛇身
打印蛇身
void SnakePrint(Snake* snake)
{
SnakeNode* cur = snake->pSnake;获得snake中存储的蛇头
wchar_t ch1 = L'□';为了区分蛇头用 蛇头用 □
wchar_t ch2 = L'●';蛇身用 ●
int flag = 0;用于标识是否为第一次打印
while (cur)遍历链表
{
SetPos(cur->x, cur->y);设置控制台光标位置
if (flag == 0)为第一次打印就 打印蛇头 □
{
wprintf(L"%lc", ch1);
flag = 1;
}
else
{
wprintf(L"%lc", ch2);不为第一次打印就 打印蛇身 ●
}
cur = cur->next;
}
}
随机生成食物
随机生成食物
void RandomFood(Snake* snake)
{
蛇身节点与食物的类型一致,吃掉食物的时候就 只需要头插就行
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
if (newnode == NULL)
{
printf("RandomFood函数中malloc失败!");
return;
}
SnakeNode* cur = snake->pSnake;获得snake中存储的食物信息
控制随机食物的坐标生成的范围
newnode->x = (rand() % ((COR - 3 - 2)/2) + 1)*2;控制食物的横坐标与蛇一样 是偶数 方便蛇吃
newnode->y = rand() % (ROW - 1 - 1) + 1;
食物不能生成在蛇的身体中
while (cur)遍历链表
{
if (cur->x == newnode->x && cur->y == newnode->y)
{
如果在蛇身中就再随机生成一次坐标
newnode->x = (rand() % ((COR - 3 - 2) / 2) + 1) * 2;
newnode->y = rand() % (ROW - 1 - 1) + 1;
让cur回到链表开头,重新遍历,防止再次随机生成的食物又再蛇身里
cur = snake->pSnake;
}
else
{
cur = cur->next;
}
}
snake->pFood = newnode; 更改snake中存储的食物信息
SetPos(newnode->x, newnode->y);
wprintf(L"卍"); 打印食物
}
根据按键情况操纵蛇
do
{
打印会随游戏进行而变化的信息
SetPos(65, 10);
printf("当前分数:%-5d", snake->Score);
SetPos(65, 11);
printf("当前每一个食物的分数:%-2d", snake->foodWeight);
SetPos(65, 13);
printf("当前蛇的速度:%c(D为初始速度加速一次", (snake->SnakeSpeed - 200) / 40 + 'D');
SetPos(65, 14);
printf("为C,减速一次为E,最快为A)");
if (KEY_PRESS(VK_UP)&&snake->Snakedir != DOWN)按上并且 蛇在向下时不能直接向上走
{
snake->Snakedir = UP;更改蛇的方向为上
}
else if (KEY_PRESS(VK_DOWN) && snake->Snakedir != UP)下并且 蛇在向上时不能直接向下走
{
snake->Snakedir = DOWN;更改蛇的方向为下
}
else if (KEY_PRESS(VK_LEFT) && snake->Snakedir != RIGHT)左并且 蛇在向右时不能直接向左走
{
snake->Snakedir = LEFT;更改蛇的方向为左
}
else if (KEY_PRESS(VK_RIGHT) && snake->Snakedir != LEFT)右并且 蛇在向左时不能直接向右走
{
snake->Snakedir = RIGHT;更改蛇的方向为右
}
else if (KEY_PRESS(VK_SPACE))暂停
{
pause();
}
else if(KEY_PRESS(VK_ESCAPE)) 退出
{
提示信息打印
SetPos(20, 15);
printf("若退出游戏当前游戏进度无法保存!!!");
SetPos(20, 16);
printf("确定退出游戏吗?(Y/N)【输入Y/N+回车】");
char ch = 0;
do
{
ch = getchar();
if (ch == 'Y' || ch == 'y')
{
snake->status = ESC;退出游戏 更改游戏状态为退出
break;
}
if (ch == 'N' || ch == 'n')
{
继续游戏
SetPos(20, 15);
打印空格覆盖提示信息
printf(" ");
SetPos(20, 16);
printf(" ");
}
} while (ch!='Y' &&ch!='y' && ch != 'N' && ch != 'n');控制输入符号有效性
}
else if (KEY_PRESS(VK_F3))加速
{
if (snake->SnakeSpeed > 80)不能一直加速
{
snake->SnakeSpeed -= 40;加速蛇 暂停 时间减少
snake->foodWeight += 2;加速 吃掉一个食物的分数增加
}
}
else if (KEY_PRESS(VK_F4))减速
{
if (snake->SnakeSpeed < 320) 不能一直减速
{
snake->SnakeSpeed += 40; 减速蛇 暂停 时间增加
snake->foodWeight -= 2; 减速 吃掉一个食物的分数减少
}
}
休眠
Sleep(snake->SnakeSpeed);按完键后,蛇(程序)暂停一下,给玩家反应时间
走一步
SnakeMove(snake);根据按键情况走一步
} while (snake->status == OK);游戏状态要OK循环才继续
游戏进行的GameRun
全部代码
//游戏进行
void GameRun(Snake* snake)
{
//打印游戏开始后界面
PlayInte(snake);
int i = 0;
//用头插法 构建 初始 蛇身节点
for (i = 5; i >= 1; i--)
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));//申请新节点
if (newnode == NULL)
{
printf("GameRun函数中malloc失败!");
return;
}
newnode->x = SNAKE_HEAD_X - i * 2;//根据 初始蛇头坐标 控制链表尾节点的坐标
newnode->y = SNAKE_HEAD_Y; //因为是头插所以第一个插入的节点在插入完成后是最后一个节点
if (snake->pSnake == NULL)
{
snake->pSnake = newnode;
newnode->next = NULL;
}
else
{
newnode->next = snake->pSnake;
snake->pSnake = newnode;
}
}
//打印蛇身
SnakePrint(snake);
RandomFood(snake);
//蛇走
do
{
//打印会随游戏进行而变化的信息
SetPos(65, 10);
printf("当前分数:%-5d", snake->Score);
SetPos(65, 11);
printf("当前每一个食物的分数:%-2d", snake->foodWeight);
SetPos(65, 13);
printf("当前蛇的速度:%c(D为初始速度加速一次", (snake->SnakeSpeed - 200) / 40 + 'D');
SetPos(65, 14);
printf("为C,减速一次为E,最快为A)");
if (KEY_PRESS(VK_UP)&&snake->Snakedir != DOWN)//按上并且 蛇在向下时不能直接向上走
{
snake->Snakedir = UP;//更改蛇的方向为上
}
else if (KEY_PRESS(VK_DOWN) && snake->Snakedir != UP)//下并且 蛇在向上时不能直接向下走
{
snake->Snakedir = DOWN;//更改蛇的方向为下
}
else if (KEY_PRESS(VK_LEFT) && snake->Snakedir != RIGHT)//左并且 蛇在向右时不能直接向左走
{
snake->Snakedir = LEFT;//更改蛇的方向为左
}
else if (KEY_PRESS(VK_RIGHT) && snake->Snakedir != LEFT)//右并且 蛇在向左时不能直接向右走
{
snake->Snakedir = RIGHT;//更改蛇的方向为右
}
else if (KEY_PRESS(VK_SPACE))//暂停
{
pause();
}
else if(KEY_PRESS(VK_ESCAPE))//退出
{
//提示信息打印
SetPos(20, 15);
printf("若退出游戏当前游戏进度无法保存!!!");
SetPos(20, 16);
printf("确定退出游戏吗?(Y/N)【输入Y/N+回车】");
char ch = 0;
do
{
ch = getchar();
if (ch == 'Y' || ch == 'y')
{
snake->status = ESC;//退出游戏 更改游戏状态为退出
break;
}
if (ch == 'N' || ch == 'n')
{
//继续游戏
SetPos(20, 15);
//打印空格覆盖提示信息
printf(" ");
SetPos(20, 16);
printf(" ");
}
} while (ch!='Y' &&ch!='y' && ch != 'N' && ch != 'n');//控制输入符号有效性
}
else if (KEY_PRESS(VK_F3))//加速
{
if (snake->SnakeSpeed > 80)//不能一直加速
{
snake->SnakeSpeed -= 40;//加速蛇 暂停 时间减少
snake->foodWeight += 2;//加速 吃掉一个食物的分数增加
}
}
else if (KEY_PRESS(VK_F4))//减速
{
if (snake->SnakeSpeed < 320)//不能一直减速
{
snake->SnakeSpeed += 40;//减速蛇 暂停 时间增加
snake->foodWeight -= 2;//减速 吃掉一个食物的分数减少
}
}
//休眠
Sleep(snake->SnakeSpeed);//按完键后,蛇(程序)暂停一下,给玩家反应时间
//走一步
SnakeMove(snake);//根据按键情况走一步
} while (snake->status == OK);//游戏状态要OK循环才继续
}
暂停函数
根据按键情况让蛇走
根据按键情况让蛇走一步涉及的函数
吃掉食物
void EtaFoodNode(Snake* snake)
{
让snake中存储的食物节点的头插在蛇头上
snake->pFood->next = snake->pSnake;
snake->pSnake = snake->pFood;让原食物节点成为蛇头节点
打印蛇
SnakePrint(snake);
}
下一步不是食物
void NoFoodNode(Snake* snake, SnakeNode* newnode)
{
让新节点头插在蛇头上
newnode->next = snake->pSnake;
snake->pSnake = newnode;
SnakeNode* cur = snake->pSnake;
SnakeNode* prev = snake->pSnake; prev指向cur的前一个节点
wchar_t ch1 = L'□';
wchar_t ch2 = L'●';
int flag = 0;
while (cur->next) 找尾并打印
{
prev = cur;
SetPos(cur->x, cur->y);
if (flag == 0)
{
wprintf(L"%lc", ch1);
flag = 1;
}
else
{
wprintf(L"%lc", ch2);
}
cur = cur->next;
}
SetPos(cur->x, cur->y); 找到尾节点的坐标
printf(" "); 用空格覆盖尾节点之前打印的 ●
free(cur); 释放尾节点
prev->next = NULL; 让尾节点的前一个节点的指针域置空
}
判断下一个节点是不是食物
bool IsFoodNode(Snake* snake,int x,int y)
{
if (x == snake->pFood->x && y == snake->pFood->y)
return true;
else
return false;
}
判断是否撞墙
bool IsWall(SnakeNode* newnode)
{
因为墙的横坐标只能为0/墙的最大横坐标(COR)-2
墙的纵坐标只能是0/ROW-1
又因为蛇的横纵坐标不能为墙的横纵坐标
所以 已经要连接在蛇头的 下一个节点的坐标为墙的横纵坐标,蛇就撞墙了
if (newnode->x == 0 || newnode->x == COR - 2 || newnode->y == 0 || newnode->y == ROW - 1)
return true;
else
return false;
}
判断是否咬到自己
bool IsSnakeNode(Snake* snake, SnakeNode* newnode)
{
SnakeNode* cur = snake->pSnake;
while (cur) 遍历蛇身链表
{
已经要连接在蛇头的 下一个节点的坐标与蛇身节点的坐标重合
时就要到了自己
if (cur->x == newnode->x && cur->y == newnode->y)
{
return true;
}
cur = cur->next;
}
return false;
}
根据按键情况让蛇走一步
走一步
void SnakeMove(Snake* snake)
{
蛇走的方法是 制造一个新节点【新节点为蛇可能的下一个节点】,
将新节点头插连接在蛇头上,成为新的头,并根据情况删除尾节点
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode)); 申请新节点
if (newnode == NULL)
{
printf("NoFoodNode函数中malloc失败!");
return;
}
switch (snake->Snakedir) 获取蛇的方向
{
case UP:
newnode->x=snake->pSnake->x; 蛇的方向为上时下一个节点(新节点)的横坐标不变
newnode->y=snake->pSnake->y - 1; 蛇的方向为上时下一个节点(新节点)的纵坐标-1
break;
case DOWN:
newnode->x = snake->pSnake->x;
newnode->y = snake->pSnake->y+1;
break;
case LEFT:
newnode->x = snake->pSnake->x-2;
newnode->y = snake->pSnake->y;
break;
case RIGHT:
newnode->x = snake->pSnake->x+2;
newnode->y = snake->pSnake->y;
break;
}
if (IsFoodNode(snake, newnode->x, newnode->y)) 如果下一个节点(新节点)为食物
{
EtaFoodNode(snake); 吃掉食物
随机生成食物
RandomFood(snake);
增加分数
snake->Score += snake->foodWeight;
}
else
{
判断是否撞墙
if (IsWall(newnode))
{
snake->status = KILL_BY_WALL; 将游戏状态置为 因撞墙死亡
return; 结束函数
}
判断是否咬到自己
if (IsSnakeNode(snake, newnode))
{
snake->status = KILL_BY_SELF; 将游戏状态置为 因咬自己死亡
return;
}
NoFoodNode(snake, newnode);
}
}
游戏善后
游戏善后
void GameEnd(Snake* snake)
{
if (snake->status == KILL_BY_WALL)如果是因为撞墙死就进去
{
system("cls");清屏
SetPos(45, 15);
打印提示信息
printf("你撞到墙了,你失败了");
SetPos(45, 18);
printf("你的总分为:%d",snake->Score);
使用count来统计游玩的次数
int count = 0;
如果是第一次游玩,就还没有chart.txt文件,以 r 的方式打开就会失败
FILE* pf = fopen("chart.txt", "r+");
if (pf == NULL)文件打开失败 fopen函数返回NULL
{
如果是第一次游玩,就还没有chart.txt文件,就再次以w的方式打开并创建chart.txt文件
pf= fopen("chart.txt", "w");
if (pf == NULL)
{
printf("GameEnd函数中,chart.txt文件打开失败");
exit(-1);
}
把count的值和分数存入chart.txt文件
fprintf(pf, "%-5d %-10d\n",count+1, snake->Score);
fclose(pf); 关闭文件
}
else
{
如果不是第一次游玩就取出之前游玩时存入的count的值
rewind(pf); 让文件读写指针回到 文件最开头
fscanf(pf, "%d", &count); 取出之前游玩时存入的count的值
count++; 取出值后count再++,将这次的游玩也统计
fseek(pf, 0, SEEK_END); 让文件读写指针定位到文件末尾
fprintf(pf, "%-10d\n", snake->Score); 在文件最末尾记录这次的分数
rewind(pf); 再让文件读写指针回到 文件最开头
fprintf(pf, "%-5d", count); 把++后的count覆盖文件最开头的上一次的count的值
fclose(pf); 关闭文件
}
SetPos(45, 20);
system("pause");
}
if (snake->status == KILL_BY_SELF) 如果是因为咬到自己就进去
{
system("cls");
SetPos(45, 15);
printf("你咬到自己了,你失败了");
SetPos(45, 18);
printf("你的总分为:%d", snake->Score);
一样的存储流程
int count = 0;
FILE* pf = fopen("chart.txt", "r+");
if (pf == NULL)
{
pf = fopen("chart.txt", "w");
if (pf == NULL)
{
printf("GameEnd函数中,chart.txt文件打开失败");
exit(-1);
}
fprintf(pf, "%-5d\n%-10d\n", count + 1, snake->Score);
fclose(pf);
}
else
{
rewind(pf);
fscanf(pf, "%d", &count);
//确定游玩次数
count++;
fseek(pf, 0, 2);
fprintf(pf, "%-10d\n", snake->Score);
rewind(pf);
fprintf(pf, "%-5d", count);
fclose(pf);
}
SetPos(45, 20);
system("pause");
}
if (snake->status ==ESC)如果是按Esc退出
{
system("cls");
SetPos(45, 15);
printf("已退出游戏");
SetPos(0, 35);
}
销毁蛇身链表
DestSnake(snake);
初始化蛇
SnakeInit(snake); 为下一次游玩做准备
}
销毁蛇身链表
void DestSnake(Snake* snake)
{
SnakeNode* cur = snake->pSnake->next; cur指向蛇头节点的下一个节点
SnakeNode* prev = snake->pSnake; prev指向cur的前一个节点
snake->pSnake = NULL; 蛇头置空
while (cur) 遍历链表
{
free(prev); 释放prev
prev = cur; 让prev向后走一步
cur = cur->next; 让cur向后走一步
}
free(prev); 释放尾节点
}
初始化蛇
void SnakeInit(Snake* snake)
{
snake->pSnake = NULL;
snake->pFood = NULL; 游戏还未开始时没有食物
snake->Score = 0; 初始分数为0
snake->foodWeight = 10; 初始每个食物分数
snake->SnakeSpeed = 200; 初始速度
snake->status = OK; 游戏状态OK表示可以进行游戏
snake->Snakedir = RIGHT; 蛇最开始的方向默认为右
}
打印排行榜
比较函数
int compare(const void* p, const void* q)
{
return *(int*)q - *(int*)p;
}
打印排行榜
void ChartPrint(Snake* snake)
{
美化一下界面
SetPos(0, 0);
printf("---------------------------------------------");
SetPos(45, 0);
printf("__排行榜__");
SetPos(55, 0);
printf("---------------------------------------------");
打开游戏时存放分数的文件
FILE* pf = fopen("chart.txt", "r");
if (pf == NULL)
{
printf("ChartPrint函数中chart.txt文件打开失败!");
exit(-1);
}
rewind(pf); 让文件读写指针来到 最开头,读取count的值
int count = 0;
fscanf(pf, "%d", &count); 读取count的值
int i = 0;
打印提示信息
SetPos(34, 1);
printf("|名次 | |总分 |\n");
申请空间存放分数
int* tmp = (int*)malloc(sizeof(int) * count);
for (i = 0; i < count; i++)分数一共count个
{
int score = 0;
fscanf(pf, "%d", &score); 读取分数
tmp[i] = score; 将读取的分数放入tmp中
}
定义供qsort比较的 比较函数
int (*p)(const void*, const void*) = compare;
使用qsort函 降序 排序分数
qsort(tmp, count, sizeof(int), p);
for (i = 0; i < count; i++)
{
SetPos(34, i + 2);
printf("|第%2d名| |%-10d|\n", i + 1, tmp[i]);//打印分数
}
SetPos(34, i + 4); 将控制台光标放到合适的位置
fclose(pf); 关闭文件
}
全部代码
snake.h
#define _CRT_SECURE_NO_WARNINGS
#include<locale.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
//定义判断按键情况的宏
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 1) ? 1 : 0 )
//贪吃蛇墙体的行,列
#define ROW 30
#define COR 60
//初始蛇头的坐标
#define SNAKE_HEAD_X 34
#define SNAKE_HEAD_Y 15
enum GAME_STATUS//枚举游戏状态
{
OK,
ESC,
KILL_BY_WALL,//撞墙
KILL_BY_SELF//咬自己
};
enum SNAKE_DIRECTION//枚举蛇的方向
{
UP,
DOWN,
LEFT,
RIGHT
};
enum OPTION//枚举开始选项状态
{
BEGIN,//开始
CHART//排行榜
};
//蛇身节点的结构体
typedef struct SnakeNode
{
int x;//存储控制台对应横坐标
int y;//存储控制台对应纵坐标
struct SnakeNode* next;//存储下一个节点的地址
}SnakeNode;
typedef struct Snake
{
SnakeNode* pSnake;//维护构成蛇的身体的链表的指针
SnakeNode* pFood;//指向食物的指针
int Score;//当前分数
int foodWeight;//当前一个食物的分数
int SnakeSpeed;//蛇的休眠时间
enum GAME_STATUS status;//游戏当前状态
enum SNAKE_DIRECTION Snakedir;//蛇的方向
}Snake;
//游戏开始前的初始化
void GameStart(Snake*snake);
//初始化蛇
void SnakeInit(Snake*snake);
//设置光标位置
void SetPos(int x, int y);
//游戏进行
void GameRun(Snake* snake);
//打印欢迎界面
void Welcome();
//打印游戏时的界面
void PlayInte(Snake* snake);
//打印蛇身
void SnakePrint(Snake* snake);
//随机生成食物
void RandomFood(Snake* snake);
//判断下一个节点是不是食物
bool IsFoodNode(Snake* snake, int x, int y);
//下一步为食物
void EtaFoodNode(Snake* snake);
//下一步不是食物
void NoFoodNode(Snake* snake, SnakeNode* newnode);
//暂停函数
void pause();
//判断是否撞墙
bool IsWall(SnakeNode* newnode);
//判断是否咬到自己
bool IsSnakeNode(Snake* snake, SnakeNode* newnode);
//蛇走一步
void SnakeMove(Snake* snake);
//销毁蛇身链表
void DestSnake(Snake* snake);
//游戏善后
void GameEnd(Snake* snake);
//打印排行榜
void ChartPrint(Snake* snake);
snake.c
#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
void SetPos(int x, int y)
{
COORD pos = {
x,y };
//获得控制台句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
SetConsoleCursorPosition(hOutput, pos);
}
//打印欢迎界面
void Welcome()
{
//设置控制台光标到 合适 的位置
SetPos(40,15);
printf("欢迎游玩贪吃蛇");
SetPos(40, 17);//设置控制台光标到 合适 的位置,方便打印 按任意键继续...
system("pause");//暂停程序,达成 按任意键继续... 的功能
system("cls");//清除控制台之前打印的信息
SetPos(36, 15);
printf("按↑,↓,←,→即可操纵蛇的方向");
SetPos(40, 16);
printf("按F3加速,按F4减速");
SetPos(24, 17);
printf("加速时吃掉食物可以获得更高分数,减速时吃掉食物获得分数减少");
SetPos(26, 18);
printf("按Esc退出游戏,按空格可暂停游戏,再次按空格时可恢复");
SetPos(40, 20);
system("pause");
system("cls");
}
//打印游戏时的界面
void PlayInte(Snake* snake)
{
//打印墙
//上墙体
int i = 0;
wchar_t ch = L'■';//宽字符定义并赋值
for (i = 0; i <COR; i += 2)//因为一个宽字符占两个字节并且一个宽字符占两个单位的横坐标
{
//所以i+=2
SetPos(i,0);
wprintf(L"%lc", ch);//打印宽字符
}
//下墙体
for (i = 0; i < COR; i += 2)//因为一个宽字符占两个字节并且一个宽字符占两个单位的横坐标
//所以i+=2
{
SetPos(i,ROW-1);
wprintf(L"%lc", ch);
}
//左侧墙体
for (i = 1; i < ROW; i++)//虽然一个宽字符占 2 个单位的横坐标
//但是一个宽字符只占 1 个单位的纵坐标
{
SetPos(0,i);
wprintf(L"%lc", ch);
}
//右侧墙体
for (i = 1; i < ROW; i++)//虽然一个宽字符占 2 个单位的横坐标
//但是一个宽字符只占 1 个单位的纵坐标
{
SetPos(COR-2, i);
wprintf(L"%lc", ch);
}
//打印提示信息
SetPos(65, 16);
printf("F3加速");
SetPos(65, 17);
printf("F4减速");
SetPos(65, 18);
printf("Esc退出游戏");
SetPos(65, 19);
printf("空格暂停游戏");
SetPos(65, 20);
printf("使用↑,↓,→,←控制蛇的方向");
}
//打印蛇身
void SnakePrint(Snake* snake)
{
SnakeNode* cur = snake->pSnake;
wchar_t ch1 = L'□';//为了区分蛇头用 蛇头用 □
wchar_t ch2 = L'●';//蛇身用 ●
int flag = 0;//用于标识是否为第一次打印
while (cur)//遍历链表
{
SetPos(cur->x, cur->y);//设置控制台光标位置
if (flag == 0)//为第一次打印就 打印蛇头 □
{
wprintf(L"%lc", ch1);
flag = 1;
}
else
{
wprintf(L"%lc", ch2);//不为第一次打印就 打印蛇身 ●
}
cur = cur->next;
}
}
//随机生成食物
void RandomFood(Snake* snake)
{
//蛇身节点与食物的类型一致,吃掉食物的时候就 只需要头插就行
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
if (newnode == NULL)
{
printf("RandomFood函数中malloc失败!");
return;
}
SnakeNode* cur = snake->pSnake;//获得snake中存储的食物信息
//控制随机食物的坐标生成的范围
newnode->x = (rand() % ((COR - 3 - 2)/2) + 1)*2;//控制食物的横坐标与蛇一样 是偶数 方便蛇吃
newnode->y = rand() % (ROW - 1 - 1) + 1;
//食物不能生成在蛇的身体中
while (cur)//遍历链表
{
if (cur->x == newnode->x && cur->y == newnode->y)
{
//如果在蛇身中就再随机生成一次坐标
newnode->x = (rand() % ((COR - 3 - 2) / 2) + 1) * 2;
newnode->y = rand() % (ROW - 1 - 1) + 1;
//让cur回到链表开头,重新遍历,防止再次随机生成的食物又再蛇身里
cur = snake->pSnake;
}
else
{
cur = cur->next;
}
}
snake->pFood = newnode;//更改snake中存储的食物信息
SetPos(newnode->x, newnode->y);
wprintf(L"卍");//打印食物
}
//吃掉食物
void EtaFoodNode(Snake* snake)
{
//让snake中存储的食物节点的头插在蛇头上
snake->pFood->next = snake->pSnake;
snake->pSnake = snake->pFood; //让原食物节点成为蛇头节点
//打印蛇
SnakePrint(snake);
}
//下一步不是食物
void NoFoodNode(Snake* snake, SnakeNode* newnode)
{
//让新节点头插在蛇头上
newnode->next = snake->pSnake;
snake->pSnake = newnode;
SnakeNode* cur = snake->pSnake;
SnakeNode* prev = snake->pSnake; //prev指向cur的前一个节点
wchar_t ch1 = L'□';
wchar_t ch2 = L'●';
int flag = 0;
while (cur->next) // 找尾并打印
{
prev = cur;
SetPos(cur->x, cur->y);
if (flag == 0)
{
wprintf(L"%lc", ch1);
flag = 1;
}
else
{
wprintf(L"%lc", ch2);
}
cur = cur->next;
}
SetPos(cur->x, cur->y); //找到尾节点的坐标
printf(" "); //用空格覆盖尾节点之前打印的 ●
free(cur); //释放尾节点
prev->next = NULL; //让尾节点的前一个节点的指针域置空
}
//判断下一个节点是不是食物
bool IsFoodNode(Snake* snake,int x,int y)
{
if (x == snake->pFood->x && y == snake->pFood->y)
return true;
else
return false;
}
//判断是否撞墙
bool IsWall(SnakeNode* newnode)
{
//因为墙的横坐标只能为0/墙的最大横坐标(COR)-2
//墙的纵坐标只能是0/ROW-1
//又因为蛇的横纵坐标不能为墙的横纵坐标
//所以 已经要连接在蛇头的 下一个节点的坐标为墙的横纵坐标,蛇就撞墙了
if (newnode->x == 0 || newnode->x == COR - 2 || newnode->y == 0 || newnode->y == ROW - 1)
return true;
else
return false;
}
//判断是否咬到自己
bool IsSnakeNode(Snake* snake, SnakeNode* newnode)
{
SnakeNode* cur = snake->pSnake;
while (cur)//遍历蛇身链表
{
//已经要连接在蛇头的 下一个节点的坐标与蛇身节点的坐标重合
//时就要到了自己
if (cur->x == newnode->x && cur->y == newnode->y)
{
return true;
}
cur = cur->next;
}
return false;
}
//走一步
void SnakeMove(Snake* snake)
{
//蛇走的方法是 制造一个新节点【新节点为蛇可能的下一个节点】,
//将新节点头插连接在蛇头上,成为新的头,并根据情况删除尾节点
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));//申请新节点
if (newnode == NULL)
{
printf("NoFoodNode函数中malloc失败!");
return;
}
switch (snake->Snakedir)//获取蛇的方向
{
case UP:
newnode->x=snake->pSnake->x; //蛇的方向为上时下一个节点(新节点)的横坐标不变
newnode->y=snake->pSnake->y - 1;//蛇的方向为上时下一个节点(新节点)的纵坐标-1
break;
case DOWN:
newnode->x = snake->pSnake->x;
newnode->y = snake->pSnake->y+1;
break;
case LEFT:
newnode->x = snake->pSnake->x-2;
newnode->y = snake->pSnake->y;
break;
case RIGHT:
newnode->x = snake->pSnake->x+2;
newnode->y = snake->pSnake->y;
break;
}
if (IsFoodNode(snake, newnode->x, newnode->y))//如果下一个节点(新节点)为食物
{
EtaFoodNode(snake);//吃掉食物
//随机生成食物
RandomFood(snake);
//增加分数
snake->Score += snake->foodWeight;
}
else
{
//判断是否撞墙
if (IsWall(newnode))
{
snake->status = KILL_BY_WALL; //将游戏状态置为 因撞墙死亡
return;//结束函数
}
//判断是否咬到自己
if (IsSnakeNode(snake, newnode))
{
snake->status = KILL_BY_SELF;//将游戏状态置为 因咬自己死亡
return;
}
NoFoodNode(snake, newnode);
}
}
//销毁蛇身链表
void DestSnake(Snake* snake)
{
SnakeNode* cur = snake->pSnake->next;//cur指向蛇头节点的下一个节点
SnakeNode* prev = snake->pSnake;//prev指向cur的前一个节点
snake->pSnake = NULL;//蛇头置空
while (cur)//遍历链表
{
free(prev);//释放prev
prev = cur;//让prev向后走一步
cur = cur->next;//让cur向后走一步
}
free(prev);//释放尾节点
}
//游戏善后
void GameEnd(Snake* snake)
{
if (snake->status == KILL_BY_WALL)//如果是因为撞墙死就进去
{
system("cls");//清屏
SetPos(45, 15);
//打印提示信息
printf("你撞到墙了,你失败了");
SetPos(45, 18);
printf("你的总分为:%d",snake->Score);
//使用count来统计游玩的次数
int count = 0;
//如果是第一次游玩,就还没有chart.txt文件,以 r 的方式打开就会失败
FILE* pf = fopen("chart.txt", "r+");
if (pf == NULL)//文件打开失败 fopen函数返回NULL
{
//如果是第一次游玩,就还没有chart.txt文件,就再次以w的方式打开并创建chart.txt文件
pf= fopen("chart.txt", "w");
if (pf == NULL)
{
printf("GameEnd函数中,chart.txt文件打开失败");
exit(-1);
}
//把count的值和分数存入chart.txt文件
fprintf(pf, "%-5d %-10d\n",count+1, snake->Score);
fclose(pf);//关闭文件
}
else
{
//如果不是第一次游玩就取出之前游玩时存入的count的值
rewind(pf);//让文件读写指针回到 文件最开头
fscanf(pf, "%d", &count);//取出之前游玩时存入的count的值
count++;//取出值后count再++,将这次的游玩也统计
fseek(pf, 0, SEEK_END);//让文件读写指针定位到文件末尾
fprintf(pf, "%-10d\n", snake->Score);//在文件最末尾记录这次的分数
rewind(pf);//再让文件读写指针回到 文件最开头
fprintf(pf, "%-5d", count);//把++后的count覆盖文件最开头的上一次的count的值
fclose(pf);//关闭文件
}
SetPos(45, 20);
system("pause");
}
if (snake->status == KILL_BY_SELF)//如果是因为咬到自己就进去
{
system("cls");
SetPos(45, 15);
printf("你咬到自己了,你失败了");
SetPos(45, 18);
printf("你的总分为:%d", snake->Score);
int count = 0;
FILE* pf = fopen("chart.txt", "r+");
if (pf == NULL)
{
pf = fopen("chart.txt", "w");
if (pf == NULL)
{
printf("GameEnd函数中,chart.txt文件打开失败");
exit(-1);
}
fprintf(pf, "%-5d\n%-10d\n", count + 1, snake->Score);
fclose(pf);
}
else
{
rewind(pf);
fscanf(pf, "%d", &count);
//确定游玩次数
count++;
fseek(pf, 0, 2);
fprintf(pf, "%-10d\n", snake->Score);
rewind(pf);
fprintf(pf, "%-5d", count);
fclose(pf);
}
SetPos(45, 20);
system("pause");
}
if (snake->status ==ESC)//如果是按Esc退出
{
system("cls");
SetPos(45, 15);
printf("已退出游戏");
SetPos(0, 35);
}
//销毁蛇身链表
DestSnake(snake);
//初始化蛇
SnakeInit(snake);//为下一次游玩做准备
}
void pause()
{
while (1)//死循环执行暂停
{
Sleep(200);//暂停程序200毫秒
if (KEY_PRESS(VK_SPACE))//直到再次按下空格,就结束暂停
break;
}
}
//游戏进行
void GameRun(Snake* snake)
{
//打印游戏开始后界面
PlayInte(snake);
int i = 0;
//用头插法 构建 初始 蛇身节点
for (i = 5; i >= 1; i--)
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));//申请新节点
if (newnode == NULL)
{
printf("GameRun函数中malloc失败!");
return;
}
newnode->x = SNAKE_HEAD_X - i * 2;//根据 初始蛇头坐标 控制链表尾节点的坐标
newnode->y = SNAKE_HEAD_Y; //因为是头插所以第一个插入的节点在插入完成后是最后一个节点
if (snake->pSnake == NULL)
{
snake->pSnake = newnode;
newnode->next = NULL;
}
else
{
newnode->next = snake->pSnake;
snake->pSnake = newnode;
}
}
//打印蛇身
SnakePrint(snake);
RandomFood(snake);
//蛇走
do
{
//打印会随游戏进行而变化的信息
SetPos(65, 10);
printf("当前分数:%-5d", snake->Score);
SetPos(65, 11);
printf("当前每一个食物的分数:%-2d", snake->foodWeight);
SetPos(65, 13);
printf("当前蛇的速度:%c(D为初始速度加速一次", (snake->SnakeSpeed - 200) / 40 + 'D');
SetPos(65, 14);
printf("为C,减速一次为E,最快为A)");
if (KEY_PRESS(VK_UP)&&snake->Snakedir != DOWN)//按上并且 蛇在向下时不能直接向上走
{
snake->Snakedir = UP;//更改蛇的方向为上
}
else if (KEY_PRESS(VK_DOWN) && snake->Snakedir != UP)//下并且 蛇在向上时不能直接向下走
{
snake->Snakedir = DOWN;//更改蛇的方向为下
}
else if (KEY_PRESS(VK_LEFT) && snake->Snakedir != RIGHT)//左并且 蛇在向右时不能直接向左走
{
snake->Snakedir = LEFT;//更改蛇的方向为左
}
else if (KEY_PRESS(VK_RIGHT) && snake->Snakedir != LEFT)//右并且 蛇在向左时不能直接向右走
{
snake->Snakedir = RIGHT;//更改蛇的方向为右
}
else if (KEY_PRESS(VK_SPACE))//暂停
{
pause();
}
else if(KEY_PRESS(VK_ESCAPE))//退出
{
//提示信息打印
SetPos(20, 15);
printf("若退出游戏当前游戏进度无法保存!!!");
SetPos(20, 16);
printf("确定退出游戏吗?(Y/N)【输入Y/N+回车】");
char ch = 0;
do
{
ch = getchar();
if (ch == 'Y' || ch == 'y')
{
snake->status = ESC;//退出游戏 更改游戏状态为退出
break;
}
if (ch == 'N' || ch == 'n')
{
//继续游戏
SetPos(20, 15);
//打印空格覆盖提示信息
printf(" ");
SetPos(20, 16);
printf(" ");
}
} while (ch!='Y' &&ch!='y' && ch != 'N' && ch != 'n');//控制输入符号有效性
}
else if (KEY_PRESS(VK_F3))//加速
{
if (snake->SnakeSpeed > 80)//不能一直加速
{
snake->SnakeSpeed -= 40;//加速蛇 暂停 时间减少
snake->foodWeight += 2;//加速 吃掉一个食物的分数增加
}
}
else if (KEY_PRESS(VK_F4))//减速
{
if (snake->SnakeSpeed < 320)//不能一直减速
{
snake->SnakeSpeed += 40;//减速蛇 暂停 时间增加
snake->foodWeight -= 2;//减速 吃掉一个食物的分数减少
}
}
//休眠
Sleep(snake->SnakeSpeed);//按完键后,蛇(程序)暂停一下,给玩家反应时间
//走一步
SnakeMove(snake);//根据按键情况走一步
} while (snake->status == OK);//游戏状态要OK循环才继续
}
//比较函数
int compare(const void* p, const void* q)
{
return *(int*)q - *(int*)p;
}
//打印排行榜
void ChartPrint(Snake* snake)
{
//美化一下界面
SetPos(0, 0);
printf("---------------------------------------------");
SetPos(45, 0);
printf("__排行榜__");
SetPos(55, 0);
printf("---------------------------------------------");
//打开游戏时存放分数的文件
FILE* pf = fopen("chart.txt", "r");
if (pf == NULL)
{
printf("ChartPrint函数中chart.txt文件打开失败!");
exit(-1);
}
rewind(pf);//让文件读写指针来到 最开头,读取count的值
int count = 0;
fscanf(pf, "%d", &count);//读取count的值
int i = 0;
//打印提示信息
SetPos(34, 1);
printf("|名次 | |总分 |\n");
//申请空间存放分数
int* tmp = (int*)malloc(sizeof(int) * count);
for (i = 0; i < count; i++)//分数一共count个
{
int score = 0;
fscanf(pf, "%d", &score);//读取分数
tmp[i] = score;//将读取的分数放入tmp中
}
//定义供qsort比较的 比较函数
int (*p)(const void*, const void*) = compare;
//使用qsort函 降序 排序分数
qsort(tmp, count, sizeof(int), p);
for (i = 0; i < count; i++)
{
SetPos(34, i + 2);
printf("|第%2d名| |%-10d|\n", i + 1, tmp[i]);//打印分数
}
SetPos(34, i + 4);//将控制台光标放到合适的位置
fclose(pf);//关闭文件
}
//初始化蛇
void SnakeInit(Snake* snake)
{
snake->pSnake = NULL;
snake->pFood = NULL;//游戏还未开始时没有食物
snake->Score = 0;//初始分数为0
snake->foodWeight = 10;//初始每个食物分数
snake->SnakeSpeed = 200;//初始速度
snake->status = OK;//游戏状态OK表示可以进行游戏
snake->Snakedir = RIGHT;//蛇最开始的方向默认为右
}
void GameStart(Snake* snake)
{
//设置游戏窗口大小
system("mode con cols=100 lines=40");
system("title 贪吃蛇 ");
//获得控制台句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//创建保存光标信息的结构变量
CONSOLE_CURSOR_INFO CursorInfo;
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);
//隐藏光标
CursorInfo.bVisible = false;
SetConsoleCursorInfo(hOutput, &CursorInfo);
//初始化蛇
SnakeInit(snake);
//打印游戏开始前的界面
Welcome();
enum OPTION option = BEGIN;//默认选项为开始游戏
do
{
//打印选项界面
SetPos(40, 15);
printf("1.开始游戏");
SetPos(40, 16);
printf("2.排行榜");
SetPos(36, 18);
printf("请按↑,↓选择选项,按Enter键确认选项");
SetPos(36, 19);
printf("按Esc退出游戏。");
if (KEY_PRESS(VK_UP))//如果按了↑就进去
{
SetPos(38, 16);//按了↑,→就指向第15行
printf(" ");//所以把第16行的→用空格覆盖
SetPos(38, 15);
printf("→");//再在第15行打印→
option = BEGIN;//更改枚举变量的值为 开始
}
else if (KEY_PRESS(VK_DOWN))//如果按了↑就进去
{
SetPos(38, 15);//按了↓,→就指向第16行
printf(" ");//所以把第15行的→用空格覆盖
SetPos(38, 16);
printf("→");//再在第16行打印→
option = CHART;//更改枚举变量的值为 排行榜
}
if (option ==BEGIN)
{
SetPos(38, 16);
printf(" ");
SetPos(38, 15);
printf("→");//解决第一次时 →没有指向的选项
if (KEY_PRESS(VK_RETURN) == 1)//如果选项状态为 开始 并且 按了Enter键就进去
{
system("cls");//清屏
SetPos(35, 15);//设置控制台光标到 合适 的位置
printf("开始游戏");//打印提示信息
SetPos(35, 17);
system("pause");//达成 按任意键继续。。。 的效果
system("cls");
GameRun(snake);//游戏进行
GameEnd(snake);//游戏善后
system("cls");
}
}
if (option == CHART)
{
SetPos(38, 15);
printf(" ");
SetPos(38, 16);
printf("→");//解决第一次时 →没有指向的选项
if (KEY_PRESS(VK_RETURN) == 1)//如果选项状态为 排行榜 并且 按了Enter键就进去
{
system("cls");//清屏
SetPos(35, 15);
printf("排行榜");
SetPos(35, 17);
system("pause");//达成 按任意键继续。。。 的效果
system("cls");
//排行榜
ChartPrint(snake);//打印排行榜
system("pause");
system("cls");
}
}
} while (KEY_PRESS(VK_ESCAPE) != 1);//如果按Esc就结束循环
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
void game()
{
Snake snake;
GameStart(&snake);//游戏开始前的初始化
}
int main()
{
//本土化
setlocale(LC_ALL, "");
//设置变化的随机数种子
srand((unsigned int)time(NULL));
game();
return 0;
}
结语
如果觉得有意思可以给文章点个赞,支持一下!!!