一. 头文件
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>
#define Pos_x 24
#define Pos_y 5
#define WALL L'□'#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)//判断按键是按过还是没按过
//蛇的方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};//蛇的状态
//正常,撞墙,撞到自己,正常退出
enum STATUS
{
OK,//正常,继续游戏
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞到自己
END_NORMAL//正常退出
};//蛇身的节点类型
typedef struct SnakeNode
{
//坐标
int x;
int y;
//指向下一个节点的指针
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//贪吃蛇
typedef struct Snake
{
pSnakeNode _pSnake;//指向蛇头的指针
pSnakeNode _pFood;//指向食物节点的指针
enum DIRECTION _dir;//蛇的方向
enum STATUS _status;//游戏的状态int _food_weight;//一个食物的分数
int _score;//总成绩
int _sleep_time;//休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;//函数的声明
//定位光标位置
void SetPos(short x, short y);
//游戏的初始化
void GameStart(pSnake snake);//欢迎界面的打印
void Welcome();//创建地图
void CreateMap();//初始化蛇身
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//打印帮助信息
void PrintHelpInfo();//查看节点是否是食物
int JubgeNextIsFood(pSnakeNode pn, pSnake ps);//游戏运行时的逻辑
void GameRun(pSnake ps);//游戏结束的处理
void GameEnd(pSnake ps);//检测蛇是否撞到墙
void KillWall(pSnake ps);//检测蛇是否撞到自己
void KillSelf(pSnake ps);
二. 实现蛇功能
#include"snake.h"
void SetPos(short x, short y)
{
//获得标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标的位置
COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
//设置指定控制台屏幕缓冲区中光标的位置
//参数:1. 控制台屏幕缓冲区的句柄
//2. 指定新光标的位置的 COORD结构
}
int JubgeNextIsFood(pSnakeNode pn, pSnake ps)//判断下个节点是否是食物
{
if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
return 1;
else
return 0;
}
void EatFood(pSnakeNode pn, pSnake ps)
{
//头插法
ps->_pFood->next = ps->_pSnake;//将食物的下一个变为头
ps->_pSnake = ps->_pFood;//将食物定义为头free(pn);//不需要pn记录头的位置了,就销毁
pn = NULL;//打印蛇身
pSnakeNode cur = ps->_pSnake;
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'◆');
cur = cur->next;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'●');
cur = cur->next;
}
ps->_score += ps->_food_weight;//重新创建食物
CreateFood(ps);//因为从thc.h里已经声明了,所以不用再上面声明
}
void NoFood(pSnakeNode pn, pSnake ps)
{
pn->next = ps->_pSnake;//下一步的位置之后链接上头结点
ps->_pSnake = pn;//将头变为下一步的位置pSnakeNode cur = pn->next, qrev = pn;
while (cur->next)
{
cur = cur->next;
qrev = qrev->next;
}
SetPos(cur->x, cur->y);
printf(" ");
free(cur);
cur = NULL;
qrev->next = NULL;//打印蛇身
cur = ps->_pSnake;
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'◆');
cur = cur->next;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'●');
cur = cur->next;
}
}
void Pause()//暂停函数
{
while (1)//无限暂停直到再按一次空格键
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
void KillWall(pSnake ps)
{
if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
{
ps->_status = KILL_BY_WALL;
}
}
void KillSelf(pSnake ps)
{
pSnakeNode cur = ps->_pSnake->next;
while (cur)
{
if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
{
ps->_status = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}void Welcome()
{
SetPos(40, 14);
wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 20);
system("pause");//暂停
system("cls");//清空屏幕SetPos(25, 14);
wprintf(L"用↑↓←→来操控蛇的移动方向,按F3加速,按F4减速\n");SetPos(25, 15);
wprintf(L"加速能得到更高的分数\n");SetPos(42, 20);
system("pause");
system("cls");
}//蛇的移动
void SnakeMove(pSnake ps)
{
SnakeNode* pNextNode = (SnakeNode*)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("SnakeMove()::malloc()");
return;
}
switch (ps->_dir)
{
case UP:
pNextNode->x = ps->_pSnake->x;
pNextNode->y = ps->_pSnake->y - 1;
break;
case DOWN:
pNextNode->x = ps->_pSnake->x;
pNextNode->y = ps->_pSnake->y + 1;
break;case RIGHT:
pNextNode->x = ps->_pSnake->x + 2;
pNextNode->y = ps->_pSnake->y;
break;
case LEFT:pNextNode->x = ps->_pSnake->x - 2;
pNextNode->y = ps->_pSnake->y;
break;
}
if (JubgeNextIsFood(pNextNode, ps))//检测下一个坐标是否是食物
{
EatFood(pNextNode, ps);//第一个参数记录头的位置,要在函数里销毁
}
else
{
NoFood(pNextNode, ps);
}
//撞墙死亡
KillWall(ps);
//撞到自己死亡
KillSelf(ps);
}
void CreateMap()//创造墙
{
//上
for (int i = 0; i < 29; i++)
wprintf(L"%lc", WALL);
//下
SetPos(0, 26);
for (int i = 0; i < 29; i++)
wprintf(L"%lc", WALL);
//左for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
//getchar();
}void InitSnake(pSnake ps)//初始化蛇
{
int i = 0;
pSnakeNode cur = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc()");
return;
}
cur->next = NULL;
cur->x = Pos_x + 2 * i;
cur->y = Pos_y;//头插法插入链表
if (ps->_pSnake == NULL)//空链表
{
ps->_pSnake = cur;
}
else //非空
{
cur->next = ps->_pSnake;
ps->_pSnake = cur;
}
}
cur = ps->_pSnake;
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'◆');
cur = cur->next;
int tmp = 0;while (cur)
{
tmp++;
SetPos(cur->x, cur->y);
wprintf(L"%lc", L'●');
cur = cur->next;
}
//printf("%d", tmp);
//设置贪吃蛇的属性
ps->_dir = RIGHT;//默认向右
ps->_score = 0;//总成绩初始为0
ps->_food_weight = 10;//食物的奖励分数
ps->_sleep_time = 200;//蛇多久动一次,单位是毫秒
ps->_status = OK;//蛇的状态是正常的
}void CreateFood(pSnake ps)//随机生成食物
{
int x = 0;
int y = 0;//生成x是2的倍数
//x: 2至54
//y: 1至25
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;} while (x % 2 != 0);
//x和y的坐标不能和蛇的身体坐标冲突
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;//如果食物和蛇身重叠就返回重新生成食物
}
cur = cur->next;
}
//创建食物的节点
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pfood == NULL)
{
perror("CreateFood()::malloc()");
return;
}pfood->x = x;
pfood->y = y;
pfood->next = NULL;
SetPos(x, y);//定位食物位置
wprintf(L"%lc", L'★');ps->_pFood = pfood;
}
void PrintHelpInfo()
{
SetPos(64, 10);
wprintf(L"%ls", L"不能穿墙,不能咬到自己");
SetPos(64, 11);
wprintf(L"%ls", L"用↑↓←→来控制蛇");
SetPos(64, 12);
wprintf(L"%ls", L"按F5加速,F6减速");
SetPos(64, 13);
wprintf(L"%ls", L"按ESC退出游戏,空格暂停游戏");
}
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
do
{
//打印总分数和食物的分值
SetPos(64, 10);
printf("总分数:%d\n", ps->_score);//总分数
SetPos(64, 11);
printf("当前食物的分值:%2d\n", ps->_food_weight);//食物的分值,%2d防止分数减少后面的0不去除//蛇只能向当前方向的左右转向,不能朝后转向
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//
{
ps->_dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
{
ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{
ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->_status = END_NORMAL;//游戏状态设置为退出
}
else if (KEY_PRESS(VK_F5))//加速
{
if (ps->_sleep_time > 80)
{
ps->_sleep_time -= 30;
ps->_food_weight += 2;//速度增加食物分数增加
}
}
else if (KEY_PRESS(VK_F6))//减速
{
if (ps->_sleep_time < 320)
{
ps->_sleep_time += 30;
ps->_food_weight -= 2;
}
}SnakeMove(ps);//蛇的移动
Sleep(ps->_sleep_time);
} while (ps->_status == OK);
}
void GameEnd(pSnake ps)
{
SetPos(60, 5);
switch (ps->_status)
{
case END_NORMAL:
printf("主动退出游戏\n");
break;
case KILL_BY_WALL:
printf("撞到墙啦o(╥﹏╥)o\n");
break;
case KILL_BY_SELF:
printf("撞到自己啦┭┮﹏┭┮\n");
break;
}
//释放蛇身的链表
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode tmp = cur;
cur = cur->next;
free(tmp);
tmp = NULL;
}
}void GameStart(pSnake ps)
{
//0. 设置窗口大小,隐藏光标
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标状态
CursorInfo.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
//1. 打印环境界面
Welcome();
//2. 绘制地图
CreateMap();
//3. 创建蛇
InitSnake(ps);
//4. 创建食物
CreateFood(ps);
}
三. 测试游戏文件
#include"snake.h"
//完成游戏测试逻辑
void test()
{
int f = 1;
srand((unsigned int)time(NULL));
do
{
system("cls");
//创建贪吃蛇
Snake snake = { 0 };
//初始化游戏
GameStart(&snake);
//玩游戏
GameRun(&snake);
//结束游戏
GameEnd(&snake);
system("cls");
SetPos(35, 14);
printf("是否再来一局(Y/N):");
while(1)
{
if (KEY_PRESS(78))
{
f = 0;
break;
}
if (KEY_PRESS(89))
{
break;
}
}
} while (f);
SetPos(0, 27);//结束游戏的文字打印到下方
}
int main()
{
setlocale(LC_ALL, "");
test();
return 0;
}
我好水............