在我们学习完C语言 和单链表知识点后 我们开始写个贪吃蛇的代码
目标:使用C语言在Windows环境的控制台模拟实现经典小游戏贪吃蛇
贪吃蛇代码实现的基本功能:
地图的绘制
蛇、食物的创建
蛇的状态(正常 撞墙 撞到自己 正常退出)
蛇移动的方向(上 下 左 右)
蛇的加速 减速
游戏暂停
.......
该代码会运用到函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等
我们会在学习前 会引出一些必要的知识点
目录
1.游戏运⾏期间,右侧打印帮助信息(PrintHelpInfo)
2.根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
3.如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。

给坐标赋值:
COORD pos = { 2, 10 };
HANDLE GetStdHandle(DWORD nStdHandle);
在我们贪吃蛇代码中 函数的参数是STD_OUTPUT_HANDLE 标准输出
实例:
- HANDLE houtput = NULL;
- //获取标准输出的句柄(⽤来标识不同设备的数值)
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);
实例:
- //获得标准输出设备的句柄
- HANDLE houtput = NULL;
- houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕
-
- //定义一个光标信息的结构体
- CONSOLE_CURSOR_INFO cursor_info = { 0 };
-
- //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
- GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
- //获得标准输出设备的句柄
- HANDLE houtput = NULL;
- houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕
-
- //定义一个光标信息的结构体
- CONSOLE_CURSOR_INFO cursor_info = { 0 };
-
- //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
- GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
-
- //修改光标占比
- cursor_info.dwSize = 50;
- cursor_info.bVisible = false;
- //设置和houtput句柄相关的控制台上的光标信息
- SetConsoleCursorInfo(houtput, &cursor_info);
- //获得标准输出设备的句柄
- HANDLE houtput = NULL;
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);//句柄houput获取标准输出的句柄 使得可以操作屏幕
-
- //定位光标的位置
- COORD pos = { 10,20 };
- SetConsoleCursorPosition(houtput, pos);
SetPos:封装一个设置光标位置的函数 ----贪吃蛇地图 蛇 食物的绘制都需要移动光标
- void SetPos(short x, short y)//封装一个函数SetPos 光标位置
- {
- HANDLE houtput = NULL;
- //获取标准输出的句柄(⽤来标识不同设备的数值)
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- //定位光标位置
- COORD pos = { x, y };
- SetConsoleCursorPosition(houtput, pos);
- }
- #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
- //这里的0x1也就是 1
Win32 API我们就讲到这里
除了Win32 API外 我们还有其他知识需要学习
我这里是用VS2022写的代码 这里还需要对运行窗口做些改变


char* setlocale (int category, const char* locale);
- typedef struct Snakenode
- {
- //坐标
- int x;
- int y;
- //指向下一个节点的指针
- struct Snakenode* next;
- }Snakenode,*pSnakeNode;//结构体指针pSnakeNode
- typedef struct Snake
- {
- pSnakeNode _pSnake;//指向蛇头的指针
- pSnakeNode _pFood;//指向食物节点的指针
- enum DIRECTION _dir;//蛇的方向
- enum GAME_STATUS _status;//游戏的状态
- int _food_weight;//食物分数
- int _score;//总分数
- int _sleep_time;//休息时间 时间越短 速度越快 时间越长 速度越慢
- }Snake,*pSnake;
- enum DIRECTION
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
- //蛇的状态
- //正常 撞墙 撞到自己 正常退出
- enum GAME_STATUS
- {
- OK,
- KILL_BY_WALL,
- KILL_BY_SELF,
- END_NORMAL
- };

程序开始就设置程序⽀持本地模式,然后进⼊游戏的主逻辑
- //Test.c
- #include
- #include"snake.h"
- //完成的是游戏的测试逻辑
- void test()
- {
- int ch = 0;
- do
- {//创建贪吃蛇
- Snake snake = { 0 };
- //初始化游戏
- //1.打印环境页面
- //2.功能介绍
- //3.绘制地图
- //4.创建蛇
- //5.创建食物
- //6.设置游戏的相关信息
- GameStart(&snake);
- //运行游戏
- GameRun(&snake);
- //结束游戏--善后工作
-
- GameEnd(&snake);
- SetPos(20, 15);
- printf("再来一局吗?(Y/N):");
- ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n
- //getchar();//清理掉\n
- while (getchar() != '\n')
- ;
-
- } while (ch=='Y' || ch == 'y');
- SetPos(0, 26);//让代码结束的信息放在地图外
- }
- int main()
- {
- //设置适配本地环境
- setlocale(LC_ALL, "");//中文 宽字符的打印
- srand((unsigned int)time(NULL));
- test();
-
- return 0;
- }
- void GameStart(pSnake ps)
- {
- //0.先设置窗口的大小 然后把光标隐藏
- system("mode con cols=100 lines=30");//30行 100列
- system("title 贪吃蛇");
- HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
- //隐藏光标操作
- //定义一个光标信息的结构体
- CONSOLE_CURSOR_INFO cursor_info;
- //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
- GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
- cursor_info.bVisible = false;
- //设置和houtput句柄相关的控制台上的光标信息
- SetConsoleCursorInfo(houtput, &cursor_info);
-
- //1.打印欢迎页面 //2.功能介绍
- WelcomeToGame();
- //3.绘制地图
- CreatMap();
- //4.创建蛇
- InitSnake(ps);
- //5.创建食物
- CreateFood(ps);
- }
打印欢迎页面
在我们使用光标位置 之前 我们封装一个函数SetPos
- void SetPos(short x, short y)//封装一个函数SetPos 光标位置
- {
- HANDLE houtput = NULL;
- //获取标准输出的句柄(⽤来标识不同设备的数值)
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- //定位光标位置
- COORD pos = { x, y };
- SetConsoleCursorPosition(houtput, pos);
- }
- void WelcomeToGame()
- {
- SetPos(45, 14);
- wprintf(L"欢迎来到贪吃蛇小游戏\n");
- SetPos(46, 20);
- system("pause");
-
- system("cls");//清除控制台信息
- SetPos(30, 14);
- wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n");
- SetPos(34, 15);
- wprintf(L"加速能够得到更高的分数");
-
- SetPos(46, 20);
- system("pause");
-
- system("cls");//紧接着下面应该是地图绘制
- }
代码实现结果:

绘制地图就是将墙体打印出来 因为这里是宽字符打印 所有打印使用wprintf函数 打印格式串前使用L 绘制地图的关键是算好光标的坐标 才能在想要的位置打印墙体
为墙体打印的宽字符写个宏:
#define WALL L'□'
这里的易错点也就是坐标的计算
代码如下:
- void CreatMap()
- {//27行 58列的棋盘
- //上
- int i = 0;
- for (i = 0; i < 29; i++)
- {//□
- wprintf(L"%lc", WALL);
- }
- //下
- SetPos(0, 26);
- for (i = 0; i < 29; i++)
- wprintf(L"%lc", WALL);
- //左
- for (i = 1; i <= 25; i++)
- {
- SetPos(0, i);
- wprintf(L"%lc", WALL);
- }
-
- //右
- for (i = 1; i <= 25; i++)
- {
- SetPos(56, i);
- wprintf(L"%lc", WALL);
- }
- //getchar();
- }
- void InitSnake(pSnake ps)
- {
- int i = 0;
- pSnakeNode cur = NULL;
- for (i = 0; i < 5; i++)
- {
- cur = (pSnakeNode)malloc(sizeof(Snakenode));
- if (cur == NULL)
- {
- perror("InitSnake()::malloc()");
- return;
- }
- cur->next = NULL;
- cur->x =POS_X +i*2;//显示 + 未使用的表达式结果
- cur->y = POS_Y;
-
- //头插法插入链表
- if (ps->_pSnake == NULL)//空链表 //蛇头
- {
- ps->_pSnake = cur;
- }
- else//非空
- {
- cur->next = ps->_pSnake;
- ps->_pSnake = cur;
- }
- }
- cur = ps->_pSnake;
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- //设置贪吃蛇的属性
- ps->_dir = RIGHT;//默认向右
- ps->_score = 0;
- ps->_food_weight = 10;
- ps->_sleep_time = 200;//毫秒
- ps->_status = OK;
- /*getchar();*/为什么我的代码只打印一个●
- }
#define BODY L'●'
先随机生成食物的坐标(rand和srand函数)
1.x坐标必须是2的倍数
2.食物的坐标不能和蛇身每个结点的坐标重复 不能越过墙体
创建食物结点 打印食物
食物打印的宽字符:
#define FOOD L'★'
CreateFood函数:
- void CreateFood(pSnake ps)
- {
- int x = 0;
- int y = 0;
- //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数
- //y = rand()%25+1;//y:1~25---0~24
- again:
- do
- {
- x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数
- y = rand()%25+1;//y:1~25---0~24
- } 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()");
- }
- pFood->x = x;
- pFood->y = y;
- pFood->next = NULL;
-
- SetPos(x, y);
- wprintf(L"%lc", FOOD);
- ps->_pFood = pFood;
- }
- void PrintHelpInfo()
- {
- SetPos(64, 10);
- wprintf(L"%ls", L"不能穿墙,不能咬到自己");
-
- SetPos(64, 11);
- wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n");
-
- SetPos(64, 12);
- wprintf(L"%ls", L"按F3加速 F4减速");
-
- SetPos(64, 13);
- wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏");
-
- SetPos(64, 14);
- wprintf(L"%ls", L"made in LiFeiFei");
- }
- void KillByWall(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 KillBySelf(pSnake ps)
- {
- pSnakeNode cur = ps->_pSnake->next;//cur指向蛇身
- while (cur)
- {
- if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
- {
- ps->_status = KILL_BY_SELF;
- break;
- }
- cur = cur->next;
- }
- }
- //获取按键情况 如果按键最小位为1 说明已按过
- #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
蛇下一步移动方向会有几种情况:
- void NoFood(pSnakeNode pn, pSnake ps)
- {
- //头插法
- pn->next = ps->_pSnake;
- ps->_pSnake = pn;
-
- pSnakeNode cur = ps->_pSnake;
- while (cur->next->next != NULL)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
-
- //把最后一个结点打印成空格
- SetPos(cur->next->x, cur->next->y);
- printf(" ");
-
- //释放最后一个结点
- free(cur->next);
-
- //将倒数第二个结点的地址域置为NULL
- cur->next = NULL;
- }
-
- int NextIsFood(pSnakeNode pn, pSnake ps)
- {
- return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
- }
-
- void EatFood(pSnakeNode pn, pSnake ps)
- {
- //头插法
- ps->_pFood->next = ps->_pSnake;
- ps->_pSnake = ps->_pFood;
-
- //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 )
- free(pn);
- pn = NULL;
- pSnakeNode cur = ps->_pSnake;
- //打印蛇
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- ps->_score += ps->_food_weight;
-
- //重新创建食物
- CreateFood(ps);
- }
-
- void pause()//暂停
- {
- while (1)
- {
- Sleep(200);
- if (KEY_PRESS(VK_SPACE))
- {
- break;
- }
- }
- }
在NoFood中 有一个易错点:在打印下一步的蛇身时 也就是将下一个结点当作蛇头 并将之前蛇身最后一个结点打印为空格 释放掉原本蛇身的最后一个结点 在释放掉最后一个结点后 还得将指向(原先蛇身的倒数第二个结点)最后一个结点的指针改为NULL 保证蛇尾打印可以正常结束 不会越界访问
- void SnakeMove(pSnake ps)
- {
- //创建一个结点 表示蛇即将到的下一个结点
- pSnakeNode pNextNode=(pSnakeNode)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 LEFT:
- pNextNode->x = ps->_pSnake->x-2;
- pNextNode->y = ps->_pSnake->y;
- break;
- case RIGHT:
- pNextNode->x = ps->_pSnake->x+2;
- pNextNode->y = ps->_pSnake->y;
- break;
- }
-
- //检测下一个坐标处是否是食物
- if (NextIsFood(pNextNode,ps))
- {
- EatFood(pNextNode, ps);
- }
- else
- {
- NoFood(pNextNode, ps);
- }
-
- //检测蛇是否撞墙
- KillByWall(ps);
- //检测蛇是否撞到自己
- KillBySelf(ps);
- }
- void GameRun(pSnake ps)
- {
- //打印帮助信息
- PrintHelpInfo();
- do
- {
- //打印总分数 和食物的分值
- SetPos(64, 8);
- printf("总分数:%d\n", ps->_score);
- SetPos(64, 9);
- printf("当前食物的分值:%2d\n", ps->_food_weight);
-
- 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))//ESC
- {//正常退出游戏
- ps->_status = END_NORMAL;//退出游戏
- break;
- }
- else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少
- {
- if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次
- {//最小休眠时间为50
- ps->_sleep_time -= 30;
- ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分
- }
- }
- else if (KEY_PRESS(VK_F4))//F4减速
- {
- if (ps->_sleep_time < 320)//200 230 260 290 320
- { //10 8 6 4 2
- ps->_sleep_time += 30;
- ps->_food_weight -= 2;//⼀个⻝物分数最低是2分
- }
- }
-
- SnakeMove(ps);//蛇走一步的过程
- Sleep(ps->_sleep_time);
-
- } while (ps->_status == OK);
- }
游戏状态不再是OK的时候 要告知游戏结束的原因 并且释放蛇身结点
- void GameEnd(pSnake ps)
- {
- SetPos(24, 12);
- switch (ps->_status)
- {
- case END_NORMAL:
- printf("有事 不玩了\n");
- break;
- case KILL_BY_WALL:
- printf("撞个墙玩玩\n");
- break;
- case KILL_BY_SELF:
- printf("吃个自己吧\n");
- break;
- }
-
- //释放蛇身的链表
- pSnakeNode cur = ps->_pSnake;
- while (cur)
- {
- pSnakeNode del = cur;
- cur = cur->next;
- free(del);
- }
- }
- //Snake.h
- #include
- #include
- #include
- #include
- #include
-
- #define POS_X 24
- #define POS_Y 5
-
- #define WALL L'□'
- #define BODY L'●'
- #define FOOD L'★'
- //类型的声明
- //
- //蛇身的结点类型
- typedef struct Snakenode
- {
- //坐标
- int x;
- int y;
- //指向下一个节点的指针
- struct Snakenode* next;
- }Snakenode,*pSnakeNode;//结构体指针pSnakeNode
-
- //贪吃蛇
- typedef struct Snake
- {
- pSnakeNode _pSnake;//指向蛇头的指针
- pSnakeNode _pFood;//指向食物节点的指针
- enum DIRECTION _dir;//蛇的方向
- enum GAME_STATUS _status;//游戏的状态
- int _food_weight;//食物分数
- int _score;//总分数
- int _sleep_time;//休息时间 时间越短 速度越快 时间越长 速度越慢
- }Snake,*pSnake;
-
- //枚举方向
- enum DIRECTION
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
-
- //蛇的状态
- //正常 撞墙 撞到自己 正常退出
- enum GAME_STATUS
- {
- OK,
- KILL_BY_WALL,
- KILL_BY_SELF,
- END_NORMAL
- };
-
- //函数的声明
-
- //定位光标位置
- void SetPos(short x, short y);
-
- //游戏的初始化
- void GameStart(pSnake ps);
-
- //欢迎界面的打印
- void WelcomeToGame();
-
- //创建地图
- void CreatMap();
-
-
- //4.创建蛇 --初始化蛇身
- void InitSnake(pSnake ps);
-
- //创建食物
- void CreateFood(pSnake ps);
-
- //游戏运行逻辑
- void GameRun(pSnake ps);
-
- //蛇走一步的过程
- void SnakeMove(pSnake ps);
-
- //判断下一个坐标是否是食物
- int NextIsFood(pSnakeNode pn, pSnake ps);
-
- //下一个位置是食物 就吃掉食物
- void EatFood(pSnakeNode pn, pSnake ps);
-
- //下一个位置不是食物
- void NoFood(pSnakeNode pn, pSnake ps);
-
- //检测蛇是否撞墙
- void KillByWall(pSnake ps);
-
- //检测蛇是否撞到自己
- void KillBySelf(pSnake ps);
-
- //游戏善后的工作
- void GameEnd(pSnake ps);
- //snake.c
- #include"snake.h"
-
- void SetPos(short x, short y)//封装一个函数SetPos 光标位置
- {
- HANDLE houtput = NULL;
- //获取标准输出的句柄(⽤来标识不同设备的数值)
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- //定位光标位置
- COORD pos = { x, y };
- SetConsoleCursorPosition(houtput, pos);
- }
- void WelcomeToGame()
- {
- SetPos(45, 14);
- wprintf(L"欢迎来到贪吃蛇小游戏\n");
- SetPos(46, 20);
- system("pause");
-
- system("cls");//清除控制台信息
- SetPos(30, 14);
- wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n");
- SetPos(34, 15);
- wprintf(L"加速能够得到更高的分数");
-
- SetPos(46, 20);
- system("pause");
-
- system("cls");//紧接着下面应该是地图绘制
- }
-
- void CreatMap()
- {//27行 58列的棋盘
- //上
- int i = 0;
- for (i = 0; i < 29; i++)
- {//□
- wprintf(L"%lc", WALL);
- }
- //下
- SetPos(0, 26);
- for (i = 0; i < 29; i++)
- wprintf(L"%lc", WALL);
- //左
- for (i = 1; i <= 25; i++)
- {
- SetPos(0, i);
- wprintf(L"%lc", WALL);
- }
-
- //右
- for (i = 1; i <= 25; i++)
- {
- SetPos(56, i);
- wprintf(L"%lc", WALL);
- }
- //getchar();
- }
-
- //4.创建蛇 --初始化蛇身
- void InitSnake(pSnake ps)
- {
- int i = 0;
- pSnakeNode cur = NULL;
- for (i = 0; i < 5; i++)
- {
- cur = (pSnakeNode)malloc(sizeof(Snakenode));
- if (cur == NULL)
- {
- perror("InitSnake()::malloc()");
- return;
- }
- cur->next = NULL;
- cur->x =POS_X +i*2;//显示 + 未使用的表达式结果
- cur->y = POS_Y;
-
- //头插法插入链表
- if (ps->_pSnake == NULL)//空链表 //蛇头
- {
- ps->_pSnake = cur;
- }
- else//非空
- {
- cur->next = ps->_pSnake;
- ps->_pSnake = cur;
- }
- }
- cur = ps->_pSnake;
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- //设置贪吃蛇的属性
- ps->_dir = RIGHT;//默认向右
- ps->_score = 0;
- ps->_food_weight = 10;
- ps->_sleep_time = 200;//毫秒
- ps->_status = OK;
- /*getchar();*/为什么我的代码只打印一个●
- }
-
- void CreateFood(pSnake ps)
- {
- int x = 0;
- int y = 0;
- //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数
- //y = rand()%25+1;//y:1~25---0~24
- again:
- do
- {
- x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数
- y = rand()%25+1;//y:1~25---0~24
- } 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()");
- }
- pFood->x = x;
- pFood->y = y;
- pFood->next = NULL;
-
- SetPos(x, y);
- wprintf(L"%lc", FOOD);
- ps->_pFood = pFood;
- }
-
- void GameStart(pSnake ps)
- {
- //0.先设置窗口的大小 然后把光标隐藏
- system("mode con cols=100 lines=30");//30行 100列
- system("title 贪吃蛇");
- HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
- //隐藏光标操作
- //定义一个光标信息的结构体
- CONSOLE_CURSOR_INFO cursor_info;
- //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
- GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
- cursor_info.bVisible = false;
- //设置和houtput句柄相关的控制台上的光标信息
- SetConsoleCursorInfo(houtput, &cursor_info);
-
- //1.打印欢迎页面 //2.功能介绍
- WelcomeToGame();
- //3.绘制地图
- CreatMap();
- //4.创建蛇
- InitSnake(ps);
- //5.创建食物
- CreateFood(ps);
- }
-
- void PrintHelpInfo()
- {
- SetPos(64, 10);
- wprintf(L"%ls", L"不能穿墙,不能咬到自己");
-
- SetPos(64, 11);
- wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n");
-
- SetPos(64, 12);
- wprintf(L"%ls", L"按F3加速 F4减速");
-
- SetPos(64, 13);
- wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏");
-
- SetPos(64, 14);
- wprintf(L"%ls", L"made in LiFeiFei");
- }
-
- //获取按键情况 如果按键最小位为1 说明已按过
- #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
-
- void pause()//暂停
- {
- while (1)
- {
- Sleep(200);
- if (KEY_PRESS(VK_SPACE))
- {
- break;
- }
- }
- }
-
- int NextIsFood(pSnakeNode pn, pSnake ps)
- {
- return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
- }
-
- void EatFood(pSnakeNode pn, pSnake ps)
- {
- //头插法
- ps->_pFood->next = ps->_pSnake;
- ps->_pSnake = ps->_pFood;
-
- //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 )
- free(pn);
- pn = NULL;
- pSnakeNode cur = ps->_pSnake;
- //打印蛇
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
- ps->_score += ps->_food_weight;
-
- //重新创建食物
- CreateFood(ps);
- }
-
- void NoFood(pSnakeNode pn, pSnake ps)
- {
- //头插法
- pn->next = ps->_pSnake;
- ps->_pSnake = pn;
-
- pSnakeNode cur = ps->_pSnake;
- while (cur->next->next != NULL)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", BODY);
- cur = cur->next;
- }
-
- //把最后一个结点打印成空格
- SetPos(cur->next->x, cur->next->y);
- printf(" ");
-
- //释放最后一个结点
- free(cur->next);
-
- //将倒数第二个结点的地址域置为NULL
- cur->next = NULL;
- }
-
- void KillByWall(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 KillBySelf(pSnake ps)
- {
- pSnakeNode cur = ps->_pSnake->next;//cur指向蛇身
- while (cur)
- {
- if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
- {
- ps->_status = KILL_BY_SELF;
- break;
- }
- cur = cur->next;
- }
- }
-
- void SnakeMove(pSnake ps)
- {
- //创建一个结点 表示蛇即将到的下一个结点
- pSnakeNode pNextNode=(pSnakeNode)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 LEFT:
- pNextNode->x = ps->_pSnake->x-2;
- pNextNode->y = ps->_pSnake->y;
- break;
- case RIGHT:
- pNextNode->x = ps->_pSnake->x+2;
- pNextNode->y = ps->_pSnake->y;
- break;
- }
-
- //检测下一个坐标处是否是食物
- if (NextIsFood(pNextNode,ps))
- {
- EatFood(pNextNode, ps);
- }
- else
- {
- NoFood(pNextNode, ps);
- }
-
- //检测蛇是否撞墙
- KillByWall(ps);
- //检测蛇是否撞到自己
- KillBySelf(ps);
- }
-
- void GameRun(pSnake ps)
- {
- //打印帮助信息
- PrintHelpInfo();
- do
- {
- //打印总分数 和食物的分值
- SetPos(64, 8);
- printf("总分数:%d\n", ps->_score);
- SetPos(64, 9);
- printf("当前食物的分值:%2d\n", ps->_food_weight);
-
- 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))//ESC
- {//正常退出游戏
- ps->_status = END_NORMAL;//退出游戏
- break;
- }
- else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少
- {
- if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次
- {//最小休眠时间为50
- ps->_sleep_time -= 30;
- ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分
- }
- }
- else if (KEY_PRESS(VK_F4))//F4减速
- {
- if (ps->_sleep_time < 320)//200 230 260 290 320
- { //10 8 6 4 2
- ps->_sleep_time += 30;
- ps->_food_weight -= 2;//⼀个⻝物分数最低是2分
- }
- }
-
- SnakeMove(ps);//蛇走一步的过程
- Sleep(ps->_sleep_time);
-
- } while (ps->_status == OK);
- }
-
- void GameEnd(pSnake ps)
- {
- SetPos(24, 12);
- switch (ps->_status)
- {
- case END_NORMAL:
- printf("有事 不玩了\n");
- break;
- case KILL_BY_WALL:
- printf("撞个墙玩玩\n");
- break;
- case KILL_BY_SELF:
- printf("吃个自己吧\n");
- break;
- }
-
- //释放蛇身的链表
- pSnakeNode cur = ps->_pSnake;
- while (cur)
- {
- pSnakeNode del = cur;
- cur = cur->next;
- free(del);
- }
- }
- //test.c
- #include
- #include"snake.h"
- //完成的是游戏的测试逻辑
- void test()
- {
- int ch = 0;
- do
- {//创建贪吃蛇
- Snake snake = { 0 };
- //初始化游戏
- //1.打印环境页面
- //2.功能介绍
- //3.绘制地图
- //4.创建蛇
- //5.创建食物
- //6.设置游戏的相关信息
- GameStart(&snake);
- //运行游戏
- GameRun(&snake);
- //结束游戏--善后工作
-
- GameEnd(&snake);
- SetPos(20, 15);
- printf("再来一局吗?(Y/N):");
- ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n
- //getchar();//清理掉\n
- while (getchar() != '\n')
- ;
-
- } while (ch=='Y' || ch == 'y');
- SetPos(0, 26);//让代码结束的信息放在地图外
- }
- int main()
- {
- //设置适配本地环境
- setlocale(LC_ALL, "");//中文 宽字符的打印
- srand((unsigned int)time(NULL));
- test();
-
- return 0;
- }