前言
在实现贪吃蛇前,我们需要熟练地掌握C语言知识,对初阶数据结构中的链表有一定的掌握,并且我们还会使用到Win 32 API 的知识,下面我会对需要使用到的API接口函数进行解释。最终的代码我放在后面,有需要的可以自取。
准备阶段
首先我使用的是VS2022版本,在使用VS2022运行贪吃蛇前我们需要进行一些设置,因为我们的贪吃蛇可以设置窗口大小和窗口名字。
运行窗口如下:
如果你是win11操作系统的话:
设置好这两个:
如果你不是Win11的操作系统,可以将Windows控制台主机设计成由Windows决定。
这里的背景颜色和字体颜色都可以自己去调整。
游戏窗口设置
title
这个命令是用来设置窗口名字的
title 贪吃蛇
这里我们会使用到system函数来实现。
system("title 贪吃蛇");
这样就设置好窗口的名字
窗口大小
mode con cols=100 lines=30
这个命令是用来设置几行几列的,这里注意了,x的两个格子相当于y的一个格子的大小
system("mode con cols=100 lines=30");
如果觉得自己的窗口比例小,我们可以调节字体的大小来设置窗口的比例~~
本地化配置
由于很多特殊字符是占用两个字节的(这些字符又被称为宽字符),为了能打印出特殊字符,我们需要进行本地化设置。
setlocale(LC_ALL, "");
这个命令就是自动适配好本地化,需要的头文件是<locale.h>
光标设置
为了能在任意位置上进行输出,我们需要获取光标的位置,然后进行打印,这里我们可以封装一个函数,用来定位光标的位置,方便后续指定位置的输出。
//设置光标位置
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
这里解释一下里面的函数:
COORD这个函数是用来接收坐标的。
GetStdHandle 是用来接收不同的设备,就是你想操控那个设备,这里由三个参数,大家有兴趣可以去微软的官方文档去阅读,我们需要控制输出设备(就是屏幕),所以使用STD_OUTPUT_HANDLE这个参数
SetConsoleCursorPosition这个函数可以设置光标的位置,需要两个参数,一个就是上面的句柄和坐标。
游戏的实现
游戏准备阶段
游戏开始时,我们要初始化游戏,设置好界面大小,打印欢迎界面,打印按键功能界面,打印游戏界面(墙体,蛇,事物,右侧提示面板)等等操作,这时候我们需要对蛇这个对象进行设置,需要使用到结构体,蛇需要x、y两个坐标,我这里使用到的是单链表的结构,所以蛇的结构体如下:
//创建蛇的结构体
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode;
然后就是这个游戏需要的参数,首先就是蛇,我们需要一个蛇头的指针来找到这条蛇,然后是蛇的方向,速度,总得分,食物的分数,食物的位置,还有蛇的状态(是否死亡),于是我们对这个游戏对象进行结构体的封装:
//创建贪吃蛇对象的结构体
typedef struct SnakeInfo
{
SnakeNode* snake_head;//蛇头的位置
int total_score;//总得分
int food_score;//食物分数
int snake_speed;//蛇的速度
SnakeNode* food;//食物的位置
int snake_diretion;//蛇的方向
int snake_state;//蛇的状态
}SnakeInfo;
这里不定义食物的结构体是因为食物也是需要x、y的坐标值,所以我们可以借用上面蛇的结构体~~
由于蛇的位置和状态可以一一列举出来,我们就使用枚举来定义,这样也方便我们后面的使用和操作:
//蛇的方向
typedef enum Diretion
{
UP = 1,
DOWN,
LEFT,
RIGHT
}Dit;
//蛇的状态
typedef enum SnakeState
{
OK,
KILL_BY_ITSELF,
KILL_BY_WALL,
END_NORMAL
}State;
为了方便打印字符,我们可以宏定义一下这些字符
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
初始化游戏
//初始化游戏
void GameStart(SnakeInfo* sp)
{
//设置界面大小
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//获取句柄
HANDLE hOutput = NULL;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取光标信息
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//打印欢迎界面
Welcome();
//打印游戏界面
CreatMap(sp);
//设置好初始游戏参数
sp->food_score = 10;
sp->snake_speed = 200;
sp->total_score = 0;
sp->snake_diretion = RIGHT;//开始向右移动
sp->snake_state = OK;
}
在开始前,光标是会闪烁的,所以为了防止光标对玩家产生影响,我们需要把光标隐藏起来,就是如下代码:
//获取光标信息
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
GetConsoleCursorInfo这个函数是获取光标信息的,CursorInfo.bVisible这里设置成false就可以把光标的能见度调成0,最后使用这个函数SetConsoleCursorInfo来设置光标的能见度。
打印欢迎界面
这里就不多说了,记得设置好光标的位置,然后进行打印信息即可。
//打印欢迎界面
void Welcome()
{
SetPos(40, 12);
printf("欢迎来到贪吃蛇小游戏");
SetPos(42, 18);
system("pause");
//清理界面
system("cls");
//打印按键功能信息
SetPos(8, 12);
printf("↑ ↓ ← → 分别控制蛇的上下左右移动, F3为加速,F4为减速, Esc 退出, Space 暂停");
SetPos(35, 15);
printf("加速后的食物分数会更高!!!");
SetPos(40, 20);
system("pause");
system("cls");
}
游戏地图设置
下面是我们需要编写的函数:
void CreatMap(SnakeInfo* sp)
{
//打印墙体
CreatWall();
//打印右侧面板信息
PrintInfo();
//初始化蛇的身体
sp->snake_head = NULL;
InitSnake(&(sp->snake_head));
//打印蛇的身体
PrintSnake(sp->snake_head);
//打印食物
PrintFood(sp);
}
打印墙体和右侧面板信息
我们需要打印墙体和旁边的信息,值得注意的是窗口的坐标分布:
x的两格相当于y的一格,宽字符是占两个字符的,所以一个宽字符是两个x轴的格子加一个y轴的格子。都是从0开始的~~
//打印墙体
void CreatWall()
{
int i = 0;
//上墙体
for (i = 0; i < 30; i++)
{
wprintf(L"%lc", WALL);
}
//下墙体
SetPos(0, 24);
for (i = 0; i < 30; i++)
{
wprintf(L"%lc", WALL);
}
//左墙体
for (i = 1; i <= 23; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右墙体
for (i = 1; i <= 23; i++)
{
SetPos(58, i);
wprintf(L"%lc", WALL);
}
}
//打印右侧面板信息
void PrintInfo()
{
SetPos(64, 8);
printf("总得分:0 食物分数:10");
SetPos(64, 12);
printf("↑ ↓ ← → 分别控制蛇移动");
SetPos(65, 14);
printf("F3为加速,F4为减速");
SetPos(65, 15);
printf("Esc 退出 Space 暂停");
SetPos(65, 18);
printf("加速后的食物分数会更高!!!");
}
初始化蛇的身体
这里我设置了5个节点进行尾插操作,为了便捷,我还写了一个创建节点的函数:
SnakeNode* CreatSnakeNewNode()
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
if (newnode == NULL)
{
perror("CreatSnakeNewNode()::malloc()");
}
newnode->x = newnode->y = 0;
newnode->next = NULL;
return newnode;
}
//初始化蛇的身体
void InitSnake(SnakeNode** sp)
{
//五个节点
if (*sp == NULL)//空链表
{
*sp = CreatSnakeNewNode();
(*sp)->y = 4;
(*sp)->x = 30;
}
SnakeNode* pcur = *sp;
//尾插
for (int i = 1; i <= 4; i++)
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
newnode->y = 4;
newnode->x = 30 - 2 * i;
pcur->next = newnode;
pcur = pcur->next;
}
pcur->next = NULL;
}
打印蛇的身体与食物
//打印蛇的身体
void PrintSnake(SnakeNode* sp)
{
SnakeNode* pcur = sp;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
}
//打印食物
void PrintFood(SnakeInfo* sp)
{
//设置食物的位置
int x, y;
again:
x = 2 * (rand() % 28 + 1);
y = rand() % 23 + 1;
//不能和蛇的身体重叠
SnakeNode* pcur = sp->snake_head;
while (pcur)
{
if (pcur->x == x && pcur->y == y)
{
goto again;
}
pcur = pcur->next;
}
SnakeNode* newfood = CreatSnakeNewNode();
sp->food = newfood;
sp->food->x = x;
sp->food->y = y;
sp->food->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
这里需要注意食物的创建,为了使食物随机出现,我们可以使用随机数的知识来创建,我们只需要使用一次随机数的种子,所以我在主函数写了下面一行代码:
//设置随机数种子
srand((unsigned int)time(NULL));
然后就是随机数的判断,不能与蛇身重合,不能超过墙体!!!
游戏的运行
我们需要从玩家的键盘里获取玩家按了什么键,然后做出相关操作,这时候我们需要使用到另外一个API接口函数:
GetAsyncKeyState(VK)
GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
这时候我们可以写一个宏来便于判断什么键被按过:
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
首先我们要从键盘上进行获取哪些键被按过,然后进行修改游戏参数,由于蛇是由移动速度的,这时候我们可以通过Sleep来调整需要休眠几秒,以此来达到速度的效果。
蛇的移动也是由讲究的,当蛇正在向上移动,玩家按了向下的键是不可以让蛇进行向下移动,同理,向左向右和向下移动也是如此。
如果玩家按了加速键和减速键,我们也要对加速的上限和减速的下限进行限制,不可能一直加速,因为睡眠时间不能为负数,这里大家按自己的喜好进行调整即可。
游戏在进行的时候,就是蛇没有死亡游戏才能进行,所以这里用个da while循环,如果玩家主动退出游戏或者蛇死亡就会跳出游戏,循环的条件就是我们上面结构体中蛇的状态是不是正常~~
//游戏运行
void GameRun(SnakeInfo* sp)
{
do
{
SetPos(64, 8);
printf("总得分:%2d 食物分数:%2d", sp->total_score, sp->food_score);
if (KEY_PRESS(VK_UP) && sp->snake_diretion != DOWN)
{
//上移
sp->snake_diretion = UP;
}
else if (KEY_PRESS(VK_DOWN) && sp->snake_diretion != UP)
{
//下移
sp->snake_diretion = DOWN;
}
else if(KEY_PRESS(VK_RIGHT) && sp->snake_diretion != LEFT)
{
//右移
sp->snake_diretion = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && sp->snake_diretion != RIGHT)
{
//左移
sp->snake_diretion = LEFT;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Stop();
}
else if (KEY_PRESS(VK_ESCAPE))
{
//正常退出游戏
sp->snake_state = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
//加速
//每次加速二十
//可以加速到100ms
if (sp->snake_speed > 100)
{
sp->snake_speed -= 20;
sp->food_score += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
//每次减速二十
//可以减速到280ms
if (sp->snake_speed < 280)
{
sp->snake_speed += 20;
sp->food_score -= 2;
}
}
//睡眠时间
Sleep(sp->snake_speed);
//蛇的移动
SnakeMove(sp);
} while (sp->snake_state == OK);
//打印死亡或者结束信息
if (sp->snake_state == END_NORMAL)
{
SetPos(30, 10);
printf("您退出了游戏!!!");
}
else if (sp->snake_state == KILL_BY_WALL)
{
SetPos(30, 10);
printf("您的蛇被墙撞到了!!!");
}
else
{
SetPos(30, 10);
printf("您的蛇撞到了自己!!!");
}
}
游戏暂停
游戏暂停很简单,我们写一个死循环的睡眠,当玩家再次按下空格键,就跳出循环。
//暂停
void Stop()
{
while (1)
{
Sleep(200);
//再次按下空格跳出睡眠
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
蛇的移动
这里的蛇每次移动都是走一步,所以y的坐标每次改变1,x就是每次改变2
这里为了方便,我就直接创建一个新结点进行头插,如果没有吃到食物,最后一个节点就打印成空格(一定要打印空格,因为上一步的时候就打印好蛇的最后一个节点,所以不打印空格,就无法覆盖上一次的节点圆形,造成视觉影响)然后释放空间。如果吃到食物,就直接打印,所有节点保留,我这里设计的是每吃到一个食物蛇就会增加一个节点的长度。
在进行判断是否吃到食物的之前,我们需要对新结点的坐标进行赋值,大家要注意x,y的赋值,别搞错了~~
//蛇的移动
void SnakeMove(SnakeInfo* sp)
{
//头插新节点
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
newnode->next = sp->snake_head;
sp->snake_head = newnode;
switch (sp->snake_diretion)
{
case UP:
sp->snake_head->y = sp->snake_head->next->y - 1;
sp->snake_head->x = sp->snake_head->next->x;
break;
case DOWN:
sp->snake_head->y = sp->snake_head->next->y + 1;
sp->snake_head->x = sp->snake_head->next->x;
break;
case LEFT:
sp->snake_head->x = sp->snake_head->next->x - 2;//注意x轴
sp->snake_head->y = sp->snake_head->next->y;
break;
case RIGHT:
sp->snake_head->x = sp->snake_head->next->x + 2;
sp->snake_head->y = sp->snake_head->next->y;
break;
}
//判断新状态
if (EatFood(sp))
{
PrintSnake(sp->snake_head);
sp->total_score += sp->food_score;
//释放食物的节点
free(sp->food);
//打印新的食物
PrintFood(sp);
}
else
{
NoFood(sp);
kill_by_wall(sp);
kill_by_itself(sp);
}
}
头插和赋值完后我们就来判断是否吃到食物~~当食物的坐标和蛇头的坐标是一样的时候,我们需要释放食物的空间,然后打印新的食物出来。
//吃到食物
//蛇身体增加一节
int EatFood(SnakeInfo* sp)
{
if ((sp->food->x == sp->snake_head->x) && (sp->food->y == sp->snake_head->y))
{
//吃到返回
return 1;
}
//没有吃到返回0
return 0;
}
如果没有吃到食物,我们先打印蛇身的信息,一定要注意最后一个节点打印成空格,然后再释放掉最后一个节点,没有吃到食物,蛇身长度不增加,然后再判断是否撞墙或者撞到自己~~
//没有吃到食物
void NoFood(SnakeInfo* sp)
{
SnakeNode* pcur = sp->snake_head;
SnakeNode* prev = sp->snake_head;
while (pcur->next)
{
prev = pcur;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
SnakeNode* del = pcur;
SetPos(del->x, del->y);
printf(" ");
free(del);
del = NULL;
prev->next = NULL;
}
是否撞墙
//是否被墙撞死
void kill_by_wall(SnakeInfo* sp)
{
if (sp->snake_head->x == 0 || sp->snake_head->x == 58 || sp->snake_head->y == 0 || sp->snake_head->y == 24)
{
sp->snake_state = KILL_BY_WALL;
}
}
是否撞到自己
//是否撞到自己
void kill_by_itself(SnakeInfo* sp)
{
SnakeNode* pcur = sp->snake_head->next;
while (pcur)
{
if (sp->snake_head->x == pcur->x && sp->snake_head->y == pcur->y)
{
sp->snake_state = KILL_BY_ITSELF;
break;
}
pcur = pcur->next;
}
}
如果在GameRun这个函数走出了循环,我们需要打印一下游戏结束状态的信息。
//打印死亡或者结束信息
if (sp->snake_state == END_NORMAL)
{
SetPos(30, 10);
printf("您退出了游戏!!!");
}
else if (sp->snake_state == KILL_BY_WALL)
{
SetPos(30, 10);
printf("您的蛇被墙撞到了!!!");
}
else
{
SetPos(30, 10);
printf("您的蛇撞到了自己!!!");
}
游戏的结束善后工作
在游戏结束的时候,我们需要释放掉我们申请的空间,避免内存泄漏~~
//游戏结束
void GameEnd(SnakeInfo* sp)
{
//销毁蛇节点
SnakeNode* pcur = sp->snake_head;
while (pcur)
{
SnakeNode* next = pcur->next;
free(pcur);
pcur = next;
}
sp->snake_head = NULL;
//销毁食物节点
free(sp->food);
sp->food = NULL;
}
最后如果你觉得还想添加一个小设置,就是玩家还要不要再来一把,可以写一个do while循环~~
void test()
{
int n = 0;
do
{
system("cls");
//初始化游戏
//设置好界面大小
//打印欢迎界面
//打印按键功能界面
//打印游戏界面(墙体,蛇,事物,右侧提示面板)
SnakeInfo snake = { 0 };
GameStart(&snake);
//游戏运行
GameRun(&snake);
//游戏结束
GameEnd(&snake);
SetPos(10, 15);
printf("是否再来一局?是输入非0,不需要请输入0:");
scanf("%d", &n);
} while (n);
SetPos(0, 26);
}
如果你不想写,我们可以美化一下游戏,把程序的终止信息打印在最下面,需要设置一下光标位置,所以最后我还写了一行光标的定位代码。
就是最下面这一行的信息:
最终的代码
Sanke.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <time.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//创建蛇的结构体
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode;
//创建贪吃蛇对象的结构体
typedef struct SnakeInfo
{
SnakeNode* snake_head;//蛇头的位置
int total_score;//总得分
int food_score;//事物分数
int snake_speed;//蛇的速度
SnakeNode* food;//食物的位置
int snake_diretion;//蛇的方向
int snake_state;//蛇的状态
}SnakeInfo;
//蛇的方向
typedef enum Diretion
{
UP = 1,
DOWN,
LEFT,
RIGHT
}Dit;
//蛇的状态
typedef enum SnakeState
{
OK,
KILL_BY_ITSELF,
KILL_BY_WALL,
END_NORMAL
}State;
//初始化游戏
void GameStart(SnakeInfo* sp);
//初始化蛇的身体
void InitSnake(SnakeNode** sp);
//打印蛇的身体
void PrintSnake(SnakeNode* sp);
//打印食物
void PrintFood(SnakeInfo* sp);
//游戏运行
void GameRun(SnakeInfo* sp);
//蛇的移动
void SnakeMove(SnakeInfo* sp);
//暂停
void Stop();
//吃到食物
//吃到返回 没有吃到返回0
int EatFood(SnakeInfo* sp);
//没有吃到食物
void NoFood(SnakeInfo* sp);
//是否被墙撞死
void kill_by_wall(SnakeInfo* sp);
//是否撞到自己
void kill_by_itself(SnakeInfo* sp);
//游戏结束
void GameEnd(SnakeInfo* sp);
//设置光标位置
void SetPos(short x, short y);
Sanke.c
#include "Snake.h"
//设置光标位置
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
//打印欢迎界面
void Welcome()
{
SetPos(40, 12);
printf("欢迎来到贪吃蛇小游戏");
SetPos(42, 18);
system("pause");
//清理界面
system("cls");
//打印按键功能信息
SetPos(8, 12);
printf("↑ ↓ ← → 分别控制蛇的上下左右移动, F3为加速,F4为减速, Esc 退出, Space 暂停");
SetPos(35, 15);
printf("加速后的食物分数会更高!!!");
SetPos(40, 20);
system("pause");
system("cls");
}
//打印墙体
void CreatWall()
{
int i = 0;
//上墙体
for (i = 0; i < 30; i++)
{
wprintf(L"%lc", WALL);
}
//下墙体
SetPos(0, 24);
for (i = 0; i < 30; i++)
{
wprintf(L"%lc", WALL);
}
//左墙体
for (i = 1; i <= 23; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右墙体
for (i = 1; i <= 23; i++)
{
SetPos(58, i);
wprintf(L"%lc", WALL);
}
}
//打印右侧面板信息
void PrintInfo()
{
SetPos(64, 8);
printf("总得分:0 食物分数:10");
SetPos(64, 12);
printf("↑ ↓ ← → 分别控制蛇移动");
SetPos(65, 14);
printf("F3为加速,F4为减速");
SetPos(65, 15);
printf("Esc 退出 Space 暂停");
SetPos(65, 18);
printf("加速后的食物分数会更高!!!");
}
//创建蛇的节点
SnakeNode* CreatSnakeNewNode()
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
if (newnode == NULL)
{
perror("CreatSnakeNewNode()::malloc()");
}
newnode->x = newnode->y = 0;
newnode->next = NULL;
return newnode;
}
//初始化蛇的身体
void InitSnake(SnakeNode** sp)
{
//五个节点
if (*sp == NULL)//空链表
{
*sp = CreatSnakeNewNode();
(*sp)->y = 4;
(*sp)->x = 30;
}
SnakeNode* pcur = *sp;
//尾插
for (int i = 1; i <= 4; i++)
{
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
newnode->y = 4;
newnode->x = 30 - 2 * i;
pcur->next = newnode;
pcur = pcur->next;
}
pcur->next = NULL;
}
//打印蛇的身体
void PrintSnake(SnakeNode* sp)
{
SnakeNode* pcur = sp;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
}
//打印食物
void PrintFood(SnakeInfo* sp)
{
//设置食物的位置
int x, y;
again:
x = 2 * (rand() % 28 + 1);
y = rand() % 23 + 1;
//不能和蛇的身体重叠
SnakeNode* pcur = sp->snake_head;
while (pcur)
{
if (pcur->x == x && pcur->y == y)
{
goto again;
}
pcur = pcur->next;
}
SnakeNode* newfood = CreatSnakeNewNode();
sp->food = newfood;
sp->food->x = x;
sp->food->y = y;
sp->food->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
void CreatMap(SnakeInfo* sp)
{
//打印墙体
CreatWall();
//打印右侧面板信息
PrintInfo();
//初始化蛇的身体
sp->snake_head = NULL;
InitSnake(&(sp->snake_head));
//打印蛇的身体
PrintSnake(sp->snake_head);
//打印食物
PrintFood(sp);
}
//初始化游戏
void GameStart(SnakeInfo* sp)
{
//设置界面大小
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//获取句柄
HANDLE hOutput = NULL;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取光标信息
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//打印欢迎界面
Welcome();
//打印游戏界面
CreatMap(sp);
//设置好初始游戏参数
sp->food_score = 10;
sp->snake_speed = 200;
sp->total_score = 0;
sp->snake_diretion = RIGHT;//开始向右移动
sp->snake_state = OK;
}
//暂停
void Stop()
{
while (1)
{
Sleep(200);
//再次按下空格跳出睡眠
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//吃到食物
//蛇身体增加一节
int EatFood(SnakeInfo* sp)
{
if ((sp->food->x == sp->snake_head->x) && (sp->food->y == sp->snake_head->y))
{
//吃到返回
return 1;
}
//没有吃到返回0
return 0;
}
//没有吃到食物
void NoFood(SnakeInfo* sp)
{
SnakeNode* pcur = sp->snake_head;
SnakeNode* prev = sp->snake_head;
while (pcur->next)
{
prev = pcur;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
SnakeNode* del = pcur;
SetPos(del->x, del->y);
printf(" ");
free(del);
del = NULL;
prev->next = NULL;
}
//是否被墙撞死
void kill_by_wall(SnakeInfo* sp)
{
if (sp->snake_head->x == 0 || sp->snake_head->x == 58 || sp->snake_head->y == 0 || sp->snake_head->y == 24)
{
sp->snake_state = KILL_BY_WALL;
}
}
//是否撞到自己
void kill_by_itself(SnakeInfo* sp)
{
SnakeNode* pcur = sp->snake_head->next;
while (pcur)
{
if (sp->snake_head->x == pcur->x && sp->snake_head->y == pcur->y)
{
sp->snake_state = KILL_BY_ITSELF;
break;
}
pcur = pcur->next;
}
}
//蛇的移动
void SnakeMove(SnakeInfo* sp)
{
//头插新节点
SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));
newnode->next = sp->snake_head;
sp->snake_head = newnode;
switch (sp->snake_diretion)
{
case UP:
sp->snake_head->y = sp->snake_head->next->y - 1;
sp->snake_head->x = sp->snake_head->next->x;
break;
case DOWN:
sp->snake_head->y = sp->snake_head->next->y + 1;
sp->snake_head->x = sp->snake_head->next->x;
break;
case LEFT:
sp->snake_head->x = sp->snake_head->next->x - 2;//注意x轴
sp->snake_head->y = sp->snake_head->next->y;
break;
case RIGHT:
sp->snake_head->x = sp->snake_head->next->x + 2;
sp->snake_head->y = sp->snake_head->next->y;
break;
}
//判断新状态
if (EatFood(sp))
{
PrintSnake(sp->snake_head);
sp->total_score += sp->food_score;
//释放食物的节点
free(sp->food);
//打印新的食物
PrintFood(sp);
}
else
{
NoFood(sp);
kill_by_wall(sp);
kill_by_itself(sp);
}
}
//游戏运行
void GameRun(SnakeInfo* sp)
{
do
{
SetPos(64, 8);
printf("总得分:%2d 食物分数:%2d", sp->total_score, sp->food_score);
if (KEY_PRESS(VK_UP) && sp->snake_diretion != DOWN)
{
//上移
sp->snake_diretion = UP;
}
else if (KEY_PRESS(VK_DOWN) && sp->snake_diretion != UP)
{
//下移
sp->snake_diretion = DOWN;
}
else if(KEY_PRESS(VK_RIGHT) && sp->snake_diretion != LEFT)
{
//右移
sp->snake_diretion = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && sp->snake_diretion != RIGHT)
{
//左移
sp->snake_diretion = LEFT;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Stop();
}
else if (KEY_PRESS(VK_ESCAPE))
{
//正常退出游戏
sp->snake_state = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
//加速
//每次加速二十
//可以加速到100ms
if (sp->snake_speed > 100)
{
sp->snake_speed -= 20;
sp->food_score += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
//每次减速二十
//可以减速到300ms
if (sp->snake_speed < 280)
{
sp->snake_speed += 20;
sp->food_score -= 2;
}
}
//睡眠时间
Sleep(sp->snake_speed);
//蛇的移动
SnakeMove(sp);
} while (sp->snake_state == OK);
//打印死亡或者结束信息
if (sp->snake_state == END_NORMAL)
{
SetPos(30, 10);
printf("您退出了游戏!!!");
}
else if (sp->snake_state == KILL_BY_WALL)
{
SetPos(30, 10);
printf("您的蛇被墙撞到了!!!");
}
else
{
SetPos(30, 10);
printf("您的蛇撞到了自己!!!");
}
}
//游戏结束
void GameEnd(SnakeInfo* sp)
{
//销毁蛇节点
SnakeNode* pcur = sp->snake_head;
while (pcur)
{
SnakeNode* next = pcur->next;
free(pcur);
pcur = next;
}
sp->snake_head = NULL;
//销毁食物节点
free(sp->food);
sp->food = NULL;
}
test.c
#include <locale.h>
#include "Snake.h"
void test()
{
int n = 0;
do
{
system("cls");
//初始化游戏
//设置好界面大小
//打印欢迎界面
//打印按键功能界面
//打印游戏界面(墙体,蛇,事物,右侧提示面板)
SnakeInfo snake = { 0 };
GameStart(&snake);
//游戏运行
GameRun(&snake);
//游戏结束
GameEnd(&snake);
SetPos(10, 15);
printf("是否再来一局?是输入非0,不需要请输入0:");
scanf("%d", &n);
} while (n);
SetPos(0, 26);
}
int main()
{
//配置好本地化环境,支持宽字符输出
setlocale(LC_ALL, "");
//设置随机数种子
srand((unsigned int)time(NULL));
//游戏测试
test();
return 0;
}
C语言进阶到此完结撒花!!!
如果有机会学习C++我会回来继续更新的!!!
祝老铁们每天快乐!!!知识倍增!!!我们顶峰相见!!!