✨✨所属专栏:C语言✨✨
✨✨作者主页:嶔某✨✨
游戏源代码链接:function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)


- void set_pos(short x, short y);//定位光标位置
-
- void Game_Start(pSnake ps);//初始化
-
- void WelcomeToGame(void);//打印欢迎界面
-
- void CreateMap(void);//创建地图
-
- void InitSnake(pSnake ps);//初始化蛇身
-
- void CreateFood(pSnake ps);//创建食物
-
- void Game_Run(pSnake ps);//游戏运行逻辑
-
- void SnakeMove(pSnake ps);//蛇的移动
-
- bool 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 Game_End(pSnake ps);//游戏善后
• 贪吃蛇地图绘制
• 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏
运用到的知识:C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等
这里分为下面几个函数对游戏进行实现:
- system("cls");
- //创建贪吃蛇
- pSnakeNode pSnake = NULL;
-
- Snake snake = { 0 };
-
- //初始化游戏
- Game_Start(&snake);
-
- //运行游戏
- Game_Run(&snake);
-
- 结束游戏
- Game_End(&snake);
- void Game_Start(pSnake ps)//初始化
- {
- //0.设置窗口大小/名字
- system("mode con cols=100 lines=30");
- system("title 贪吃蛇");
-
- //1.隐藏光标
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_CURSOR_INFO CursorInfo;
- GetConsoleCursorInfo(houtput,&CursorInfo);
- CursorInfo.bVisible = false;
- SetConsoleCursorInfo(houtput,&CursorInfo);
-
- //2.打印欢迎界面,介绍功能
- WelcomeToGame();
-
- //3.绘制地图
- CreateMap();
-
- //4.创建蛇
- InitSnake(ps);
-
- //5.创建食物
- CreateFood(ps);
- }
这里用到的函数有:
(1)system("mode con cols=100 lines=30");
将窗口设置为100列,30行
(2)system("title 贪吃蛇");
将title设置为贪吃蛇
(3)system("pause");
暂停程序,按下任意键继续
(4)system("cls");
清理屏幕
- /*******************************************/
- //0.设置窗口大小/名字
- system("mode con cols=100 lines=30");
- system("title 贪吃蛇");
- /*******************************************/
- void set_pos(short x, short y)
- {
- //获得标准输出设备的句柄
- HANDLE houtput = NULL;
- houtput = GetStdHandle(STD_OUTPUT_HANDLE);
-
- //定位光标的位置
- COORD pos = { x,y };
- SetConsoleCursorPosition(houtput,pos);
- }
- /*******************************************/
- void WelcomeToGame()
- {
- set_pos(40, 12);
- wprintf(L"欢迎来到贪吃蛇小游戏");
- set_pos(42,18);
- system("pause");
- system("cls");
- set_pos(30, 12);
- wprintf(L"用上下左右控制蛇的移动,按 {[ 加速, ]} 减速\n");
- set_pos(38, 13);
- wprintf(L"加速可以得到更高的分数\n");
- set_pos(42, 18);
- system("pause");
- system("cls");
- }
- /********************************************/
分别打印上下左右的墙,将墙宏定义为WALL,字符为’□‘
- void CreateMap()
- {
- //上
- int i = 0;
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //下
- set_pos(0,25);
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //左
- for (i = 1; i < 25; i++)
- {
- set_pos(0, i);
- wprintf(L"%lc", WALL);
- }
- //右
- for (i = 1; i < 25; i++)
- {
- set_pos(56, i);
- wprintf(L"%lc", WALL);
- }
- }
蛇头指针:指向链表头节点的指针,方便对蛇身进行维护
食物指针:从开发角度来说,其实食物也是蛇的一个节点,当蛇头的下一个位置为食物时,将食物的节点头插到蛇身上面。
方向:对蛇的方向进行枚举
游戏状态:方便判断蛇的状态:(1)正常(2)撞墙(3)撞到自己(4)正常退出每一次while循环后判断游戏状态
食物权重:每次加速食物权重+2,减速-2。
总成绩:每吃掉一个食物,蛇身长度+1,分数+=食物权重。
每走一步的缓冲时间:缓冲时间越短,蛇走得越快;反之越慢。
- #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;
-
- enum DRECCTION//方向
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
-
- //蛇的状态
- enum GAME_STATUS
- {
- OK,//正常
- KILL_BY_WALL,//撞墙
- KILL_BY_SELF,//撞自己
- END_NORMAL//正常退出
- };
-
- typedef struct Snake
- {
- pSnakeNode _pSnake;//指向蛇头的指针
- pSnakeNode _pFood;//指向食物节点的指针
- enum DRECCTION _dir;//蛇的方向
- enum GAME_STAYUS _status;//游戏状态
- int _food_weight;//一个食物都分数
- int _score;//总成绩
- int _sleep_time;//休息时间
-
- }Snake,*pSnake;
- /************************************/
- 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;
- while (cur)
- {
- set_pos(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;//单位为ms
- ps->_status = OK;
- }
生成随机数,赋给x,y。
这里x和y都有范围,不能超出地图边界,并且不能与蛇身重合。
- void CreateFood(pSnake ps)//创建食物
- {
- int x = 0;
- int y = 0;
- again:
- do
- {
- x = rand() % 52 + 2;//2~54
- y = rand() % 24 + 1;//1~25
- } while (x % 2 != 0);//x为2的倍数
- //不能和蛇身的坐标相同
- 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;
- set_pos(x, y);
- wprintf(L"%lc", FOOD);
- ps->_pFood = pFood;
- }
-
- void Game_Run(pSnake ps)//游戏运行逻辑
- {
- //打印帮助信息
- PrintHelpInfo();
- do
- {
- //打印分数,食物权重
- set_pos(64, 8);
- printf("总分数:%d\n",ps->_score);
- set_pos(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))
- {
- //正常退出
- ps->_status = END_NORMAL;
- }
- else if (KEY_PRESS(VK_OEM_6))
- {
- //加速
- if (ps->_sleep_time > 80)
- {
- ps->_sleep_time -= 30;
- ps->_food_weight += 2;
- }
- }
- else if (KEY_PRESS(VK_OEM_4))
- {
- //减速
- if (ps->_food_weight > 2)
- {
- ps->_sleep_time += 30;
- ps->_food_weight -= 2;
- }
- }
-
- SnakeMove(ps);//蛇走一步的过程
- Sleep(ps->_sleep_time);
-
- } while (ps->_status == OK);
- }
每次循环后让系统暂停一段时间(初始为200ms)
蛇每走一步,分数都有可能变化,每次循环都打印一次。
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
(1)用虚拟键值检测是否按下上下左右键,按下相应键并且蛇的当前方向不能与之相反。
(2)检测是否按下空格,按下就进函数:
- void Pause()
- {
- while (1)
- {
- Sleep(200);
- if (KEY_PRESS(VK_SPACE))
- break;
- }
- }
再次按下空格退出函数。
(3)检测加速减速,按下加速键就将缓冲时间变短,食物权重增加;反之变长,食物权重减少。(这里也是有范围的,食物权重不能为负数,也不能过大)
进入函数,创建蛇头的下一个位置所在的节点,并根据方向算出所在位置。
- bool 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)
- {
- set_pos(cur->x, cur->y);
- wprintf(L"%lc",BODY);
- cur = cur->next;
- }
- ps->_score += ps->_food_weight;
- //重新创建食物
- CreateFood(ps);
- }
创建下一位置的节点,也是头插,但是在打印蛇身之后,将蛇尾位置打印两个空格(不打印空格蛇身就不会清除一直留在屏幕上:拖尾),将蛇尾的节点释放掉。(cur->next一定要置空,不能让它为野指针)
- void NoFood(pSnakeNode pn, pSnake ps)//下一个位置不是食物
- {
- //头插
- pn->next = ps->_pSnake;
- ps->_pSnake = pn;
-
- pSnakeNode cur = ps->_pSnake;
- while (cur->next->next)
- {
- set_pos(cur->x,cur->y);
- wprintf(L"%lc",BODY);
-
- cur = cur->next;
- }
- //把最后一个节点打印空格
- set_pos(cur->next->x,cur->next->y);
- printf(" ");
- //将最后一个节点释放
- free(cur->next);
- //将倒数第二个节点置为空
- cur->next = NULL;
- }
判断蛇头坐标位置是否超出范围,若超出范围,将蛇的状态改为KILL_BY_WALL
- 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;
- }
- }
遍历蛇身链表,若坐标重合,将蛇的状态改为KILL_BY_SELF,并且跳出循环。
- void KillBySelf(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 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 Game_End(pSnake ps)//游戏善后
- {
- set_pos(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);
- }
- }
1.穿墙
2.食物分类
3.多个食物
4.双人游戏
……
本期博客到这里就结束了,如果有什么错误,欢迎指出,如果对你有帮助,请点个赞,谢谢!