当前位置:   article > 正文

【c语言】简单贪吃蛇的实现

【c语言】简单贪吃蛇的实现

目录

一、游戏说明

​编辑

二、地图坐标​

​编辑

三、头文件

四、蛇身和食物​

五、数据结构设计​

蛇节点结构如下:

封装一个Snake的结构来维护整条贪吃蛇:​

蛇的方向,可以一一列举,使用枚举:

游戏状态,可以一一列举,使用枚举:

六、Snake.c

5.1、游戏开始函数

定位控制台的光标位置

欢迎来到贪吃蛇游戏

创建一个地图

初始化创建蛇身的节点

创建第一个食物​

5.2、游戏运行函数

检测按键状态,我们封装了一个宏

打印帮助信息

暂停函数

下一个是否是食物

下一步要走的位置处就是食物,就吃掉食物

如果下一步不是食物

检测是否撞墙

检测是否撞自己

蛇移动的函数

七、游戏结束的资源释放

八、Test.c


一、游戏说明

  • 贪吃蛇地图绘制
  • 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作)​
  • 蛇撞墙死亡
  • 蛇撞自身死亡
  • 计算得分
  • 蛇身加速、减速
  • 暂停游戏

二、地图坐标​

我们假设实现一个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙,如下:

三、头文件

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<windows.h>
  4. #include<stdbool.h>
  5. #include<locale.h>
  6. #define Case break;case
  7. #define WALL L'□'
  8. #define BODY L'●'
  9. #define FOOD L'☆'
  10. //默认的起始坐标
  11. #define POS_X 24
  12. #define POS_Y 5
  13. #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
  14. //贪吃蛇,蛇身节点的定义
  15. typedef struct SnakeNode
  16. {
  17. int x;
  18. int y;
  19. struct SnakeNode* next;
  20. }SnakeNode, * pSnakeNode;
  21. //typedef struct SnakeNode* pSnakeNode;
  22. enum GAME_STATUS
  23. //游戏状态
  24. {
  25. OK = 1,//正常运行
  26. ESC,//按了ESC键退出,正常退出
  27. KILL_BY_WALL,//撞墙
  28. KILL_BY_SELF//撞到自身
  29. };
  30. //行走的方向
  31. enum DIRECTION
  32. //方向
  33. {
  34. UP = 1,
  35. DOWN,
  36. LEFT,
  37. RIGHT
  38. };
  39. //贪吃蛇
  40. typedef struct Snake
  41. {
  42. pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头
  43. pSnakeNode pFood;//指向食物的指针
  44. int Score;//当前累积的分数
  45. int FoodWeight;//一个食物的分数,默认每个食物10
  46. int SleepTime;//每走一步休眠时间?
  47. //蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢
  48. enum GAME_STATUS status;//游戏当前的状态
  49. enum DIRECTION dir;//蛇当前走的方向,蛇头的方向默认是向右
  50. //...
  51. }Snake,* pSnake;
  52. //typedef struct Snake* pSnake;
  53. //定位控制台的光标位置
  54. void SetPos(int x, int y);
  55. //游戏开始的准备
  56. void GameStart(pSnake ps);
  57. //打印欢迎界面
  58. void welcomeToGame();
  59. //绘制地图
  60. void CreateMap();
  61. //初始化蛇
  62. void InitSnake(pSnake ps);
  63. //创建食物
  64. void CreateFood(pSnake ps);
  65. //游戏运行的整个逻辑
  66. void GameRun(pSnake ps);
  67. //打印帮助信息
  68. void PrintHelpInfo();
  69. //蛇移动的函数- 每次走一步
  70. void SnakeMove(pSnake ps);
  71. //判断蛇头的下一步要走的位置处是否是食物
  72. int NextIsFood(pSnake ps, pSnakeNode pNext);
  73. //下一步要走的位置处就是食物,就吃掉食物
  74. void EatFood(pSnake ps, pSnakeNode pNext);
  75. //下一步要走的位置处不是食物,不吃食物
  76. void NotEatFood(pSnake ps, pSnakeNode pNext);
  77. //检测是否撞墙
  78. void KillByWall(pSnake ps);
  79. //检测是否撞自己
  80. void KillBySelf(pSnake ps);
  81. //游戏结束的资源释放
  82. void GameEnd(pSnake ps);

四、蛇身和食物​

初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。

注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有一半儿出现在墙体中,另外一般在墙外的现象,坐标不好对齐。
关于食物,就是在墙体内随机生成一个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。

五、数据结构设计

在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信
息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行。

  1. #define WALL L'□'
  2. #define BODY L'●' 蛇身
  3. #define FOOD L'☆' 食物

蛇节点结构如下:

  1. //贪吃蛇,蛇身节点的定义
  2. typedef struct SnakeNode
  3. {
  4. int x;
  5. int y;
  6. struct SnakeNode* next;
  7. //是一个指向下一个 SnakeNode 类型节点的指针,用于构建链表来表示蛇的身体。
  8. }SnakeNode, * pSnakeNode;
  9. //typedef struct SnakeNode* pSnakeNode;

封装一个Snake的结构来维护整条贪吃蛇:​

pSnakeNode pSnake:这是一个指向 SnakeNode 类型的指针,代表蛇的头部。通常,贪吃蛇的实现会用一个链表来表示蛇的身体,其中每个节点(SnakeNode)代表蛇身体的一部分,而 pSnake 指向这个链表的第一个节点,即蛇头。

pSnakeNode pFood:这是一个指向 SnakeNode 类型的指针,代表食物的位置。在贪吃蛇游戏中,食物会被随机放置在游戏区域内,当蛇吃到食物时,这个食物会被移除,并且蛇的身体会增长。

enum GAME_STATUS status;:这是一个枚举类型,表示游戏当前的状态。具体的枚举值没有在代码中给出,但可能包括“游戏中”、“游戏结束”等状态。

enum DIRECTION dir;:这是一个枚举类型,表示蛇当前移动的方向。具体的枚举值也没有在代码中给出,但通常包括“向上”、“向下”、“向左”、“向右”等方向。

  1. typedef struct Snake
  2. {
  3. pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头
  4. pSnakeNode pFood;//指向食物的指针
  5. int Score;//当前累积的分数
  6. int FoodWeight;//一个食物的分数,默认每个食物10
  7. int SleepTime;//每走一步休眠时间?
  8. //蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢
  9. enum GAME_STATUS status;//游戏当前的状态
  10. enum DIRECTION dir;//蛇当前走的方向,蛇头的方向默认是向右
  11. //...
  12. }Snake,* pSnake;
  13. //typedef struct Snake* pSnake;

蛇的方向,可以一一列举,使用枚举:

  1. //行走的方向
  2. enum DIRECTION
  3. //方向
  4. {
  5. UP = 1,
  6. DOWN,
  7. LEAF,
  8. RIGHT
  9. };

游戏状态,可以一一列举,使用枚举:

  1. enum GAME_STATUS
  2. //游戏状态
  3. {
  4. OK = 1,//正常运行
  5. ESC,//按了ESC键退出,正常退出
  6. KILL_BY_WALL,//撞墙
  7. KILL_BY_SELF//撞到自身
  8. };

六、Snake.c

5.1、游戏开始函数

  1. void GameStart(pSnake ps)
  2. {
  3. //设置控制台的信息,窗口大小,窗口名
  4. system("mode con cols=120 lines=40");
  5. system("title 贪吃蛇");
  6. //隐藏光标
  7. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  8. CONSOLE_CURSOR_INFO CursorInfo;
  9. GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
  10. CursorInfo.bVisible = false;
  11. SetConsoleCursorInfo(handle, &CursorInfo);//设置光标信息
  12. //打印欢迎信息
  13. welcomeToGame();
  14. //绘制地图
  15. CreateMap();
  16. //初始化蛇
  17. InitSnake(ps);
  18. //创建食物
  19. CreateFood(ps);
  20. }

定位控制台的光标位置

  1. //定位控制台的光标位置
  2. void SetPos(int x, int y)
  3. {
  4. //获得设备句柄
  5. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  6. //根据句柄设置光标的位置
  7. COORD pos = { x,y };
  8. SetConsoleCursorPosition(handle, pos);
  9. }

欢迎来到贪吃蛇游戏

  1. void welcomeToGame()
  2. {
  3. //欢迎信息
  4. SetPos(35, 10);
  5. printf("欢迎来到贪吃蛇小游戏\n");
  6. SetPos(38, 20);
  7. system("pause");
  8. system("cls");
  9. //功能介绍信息
  10. SetPos(15, 10);
  11. printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速\n");
  12. SetPos(15, 11);
  13. printf("加速能得到更高的分数");
  14. SetPos(38, 20);
  15. system("pause");
  16. system("cls");
  17. }

创建一个地图

创建地图就是将墙打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使用L​
打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

  1. void CreateMap()
  2. {
  3. int i = 0;
  4. //
  5. SetPos(0, 0);
  6. for (i = 0; i <= 56; i += 2)
  7. {
  8. wprintf(L"%lc", WALL);
  9. }
  10. //
  11. SetPos(0, 25);
  12. for (i = 0; i <= 56; i += 2)
  13. {
  14. wprintf(L"%lc", WALL);
  15. }
  16. //
  17. for (i = 1; i <= 25; i++)
  18. {
  19. SetPos(0, i);
  20. wprintf(L"%lc", WALL);
  21. }
  22. //
  23. for (i = 1; i <= 25; i++)
  24. {
  25. SetPos(56, i);
  26. wprintf(L"%lc", WALL);
  27. }
  28. }

初始化创建蛇身的节点

蛇最开始长度为5节,每节对应链表的一个节点,蛇身的每一个节点都有自己的坐标。​
创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,将蛇的每一节打印在屏幕上。再设置当前游戏的状态,蛇移动的速度,默认的方向,初始成绩,蛇的状态,每个食物的分数。

结构体成员:记录它们的坐标:(x,y),和记录下一个位置的前驱结构体指针:next。

  1. void InitSnake(pSnake ps)
  2. {
  3. //创建五个蛇身的节点
  4. pSnakeNode cur = NULL;
  5. int i = 0;
  6. for (i = 0; i < 5; ++i)
  7. {
  8. cur = (pSnakeNode)malloc(sizeof(SnakeNode));
  9. if (cur == NULL)
  10. {
  11. perror("InsitSnkae():malloc()");
  12. return;
  13. }
  14. cur->x = POS_X + 2 * i;
  15. cur->y = POS_Y;
  16. cur->next = NULL;
  17. //头插法
  18. if (ps->pSnake == NULL)
  19. {
  20. ps->pSnake = cur;
  21. }
  22. else {
  23. cur->next = ps->pSnake;
  24. ps->pSnake = cur;
  25. }
  26. }
  27. //打印蛇身
  28. cur = ps->pSnake;
  29. while (cur)
  30. {
  31. SetPos(cur->x, cur->y);
  32. wprintf(L"%lc", BODY);
  33. cur = cur->next;
  34. }
  35. //贪吃蛇的其他信息
  36. ps->dir = RIGHT;
  37. ps->FoodWeight = 10;
  38. ps->pFood = NULL;
  39. ps->Score = 0;
  40. ps->SleepTime = 200;
  41. ps->status = OK;
  42. }

创建第一个食物​

  • 先随机生成食物的坐标
  • x坐标必须是2的倍数​
  • 食物的坐标不能和蛇身每个节点的坐标重复
  • 创建食物节点,打印食物
  1. void CreateFood(pSnake ps)
  2. {
  3. int x = 0;//x范围: 2~54 -> 0~52 + 2 -> rand()%53 + 2
  4. int y = 0;//y范围: 1~25 -> 0~24 + 1 -> rand()%24 + 1
  5. again:
  6. do {
  7. x = rand() % 53 + 2;
  8. y = rand() % 24 + 1;
  9. } while (x % 2 != 0);
  10. //坐标和蛇的身体的每个几点的坐标比较
  11. pSnakeNode cur = ps->pSnake;
  12. while (cur)
  13. {
  14. if (x == cur->x && y == cur->y)
  15. {
  16. goto again;
  17. }
  18. cur = cur->next;
  19. }
  20. //创建食物
  21. pSnakeNode pFood = malloc(sizeof(SnakeNode));
  22. if (pFood == NULL)
  23. {
  24. perror("CreateFood:malloc()");
  25. return;
  26. }
  27. pFood->x = x;
  28. pFood->y = y;
  29. ps->pFood = pFood;
  30. SetPos(x, y);
  31. wprintf(L"%lc", FOOD);
  32. }

5.2、游戏运行函数

游戏运行期间,右侧打印帮助信息,提示玩家
根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。​
如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。
确定了蛇的方向和速度,蛇就可以移动了。

  1. void GameRun(pSnake ps)
  2. {
  3. //打印帮助信息
  4. PrintHelpInfo();
  5. //检测按键
  6. do
  7. {
  8. //当前的分数情况
  9. SetPos(62, 10);
  10. printf("总分:%5d\n", ps->Score);
  11. SetPos(62, 11);
  12. printf("食物的分支:%02d\n", ps->FoodWeight);
  13. //检测按键
  14. //上、下、左、右、ESC、空格、F3、F4
  15. if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
  16. {
  17. ps->dir = UP;
  18. }
  19. else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
  20. {
  21. ps->dir = DOWN;
  22. }
  23. else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
  24. {
  25. ps->dir = LEFT;
  26. }
  27. else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
  28. {
  29. ps->dir = RIGHT;
  30. }
  31. else if (KEY_PRESS(VK_ESCAPE))
  32. {
  33. ps->status = ESC;
  34. break;
  35. }
  36. else if (KEY_PRESS(VK_SPACE))
  37. {
  38. //游戏要暂停
  39. pause();//暂停和开始
  40. }
  41. else if (KEY_PRESS(VK_F3))
  42. {
  43. if (ps->SleepTime >= 80)
  44. {
  45. ps->SleepTime -= 30;
  46. ps->FoodWeight += 2;
  47. }
  48. }
  49. else if (KEY_PRESS(VK_F4))
  50. {
  51. if (ps->FoodWeight > 2)
  52. {
  53. ps->SleepTime += 30;
  54. ps->FoodWeight -= 2;
  55. }
  56. }
  57. //走一步
  58. SnakeMove(ps);
  59. //睡眠一下
  60. Sleep(ps->SleepTime);
  61. }while(ps->status == OK);
  62. }

检测按键状态,我们封装了一个宏

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

打印帮助信息

  1. //打印帮助信息
  2. void PrintHelpInfo()
  3. {
  4. SetPos(62, 15);
  5. printf("1. 不能穿墙. 不能咬到自己");
  6. SetPos(62, 16);
  7. printf("2. 用 ↑ . ↓ . ← . → 来控制蛇的移动");
  8. SetPos(62, 17);
  9. printf("3. F3是加速,F4是减速");
  10. SetPos(62, 19);
  11. printf(" ");
  12. }

暂停函数

  1. void pause()
  2. {
  3. while (1)
  4. {
  5. Sleep(100);
  6. if (KEY_PRESS(VK_SPACE))
  7. {
  8. break;
  9. }
  10. }
  11. }

下一个是否是食物

  1. //pSnakeNode psn 是下一个节点的地址​
  2. //pSnake ps 维护蛇的指针
  3. int NextIsFood(pSnake ps, pSnakeNode pNext)
  4. {
  5. if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
  6. return 1;
  7. else
  8. return 0;
  9. }

下一步要走的位置处就是食物,就吃掉食物

  1. //下一步要走的位置处就是食物,就吃掉食物
  2. void EatFood(pSnake ps, pSnakeNode pNext)
  3. {
  4. pNext->next = ps->pSnake;
  5. ps->pSnake = pNext;
  6. pSnakeNode cur = ps->pSnake;
  7. //打印蛇身
  8. while (cur)
  9. {
  10. SetPos(cur->x, cur->y);
  11. wprintf(L"%lc", BODY);
  12. cur = cur->next;
  13. }
  14. ps->Score += ps->FoodWeight;
  15. //释放旧的食物
  16. free(ps->pFood);
  17. //新建食物
  18. CreateFood(ps);
  19. }

如果下一步不是食物

将下一个节点头插入蛇的身体,并将之前蛇身最后一个节点打印为空格,放弃掉蛇身的最后一个节点

  1. //pSnakeNode psn 是下一个节点的地址​
  2. //pSnake ps 维护蛇的指针​
  3. void NotEatFood(pSnake ps, pSnakeNode pNext)
  4. {
  5. //头插法
  6. pNext->next = ps->pSnake;
  7. ps->pSnake = pNext;
  8. //释放尾结点
  9. pSnakeNode cur = ps->pSnake;
  10. while (cur->next->next)
  11. {
  12. SetPos(cur->x, cur->y);
  13. wprintf(L"%lc", BODY);
  14. cur = cur->next;
  15. }
  16. //将尾节点的位置打印成空白字符
  17. SetPos(cur->next->x, cur->next->y);
  18. printf(" ");
  19. free(cur->next);
  20. cur->next = NULL;//易错
  21. }

检测是否撞墙

判断蛇头的坐标是否和墙的坐标冲突

  1. //检测是否撞墙
  2. void KillByWall(pSnake ps)
  3. {
  4. if (ps->pSnake->x == 0 ||
  5. ps->pSnake->x == 56 ||
  6. ps->pSnake->y == 0 ||
  7. ps->pSnake->y == 25)
  8. {
  9. ps->status = KILL_BY_WALL;
  10. }
  11. }

检测是否撞自己

判断蛇头的坐标是否和蛇身体的坐标冲突

  1. //检测是否撞自己
  2. void KillBySelf(pSnake ps)
  3. {
  4. pSnakeNode cur = ps->pSnake->next;//从第二个节点开始
  5. while (cur)
  6. {
  7. if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
  8. {
  9. ps->status = KILL_BY_SELF;
  10. return;
  11. }
  12. cur = cur->next;
  13. }
  14. }

蛇移动的函数

先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理
(EatFood),如果不是食物则做前进一步的处理(NoFood)。​
蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。

  1. void SnakeMove(pSnake ps)
  2. {
  3. pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  4. if (pNext == NULL)
  5. {
  6. perror("SnakeMove():malloc()");
  7. return;
  8. }
  9. pNext->next = NULL;
  10. switch (ps->dir)
  11. {
  12. case UP:
  13. pNext->x = ps->pSnake->x;
  14. pNext->y = ps->pSnake->y - 1;
  15. break;
  16. Case DOWN:
  17. pNext->x = ps->pSnake->x;
  18. pNext->y = ps->pSnake->y + 1;
  19. Case LEFT:
  20. pNext->x = ps->pSnake->x - 2;
  21. pNext->y = ps->pSnake->y;
  22. Case RIGHT:
  23. pNext->x = ps->pSnake->x + 2;
  24. pNext->y = ps->pSnake->y;
  25. break;
  26. }
  27. //下一个坐标是否是食物
  28. if (NextIsFood(ps, pNext))
  29. {
  30. //是食物就吃掉
  31. EatFood(ps, pNext);
  32. }
  33. else {
  34. //不是食物就正常一步
  35. NotEatFood(ps, pNext);
  36. }
  37. //检测撞墙
  38. KillByWall(ps);
  39. //检测是否撞自己
  40. KillBySelf(ps);
  41. }

七、游戏结束的资源释放

游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。​

  1. //游戏结束的资源释放
  2. void GameEnd(pSnake ps)
  3. {
  4. SetPos(20, 15);
  5. switch (ps->status)
  6. {
  7. case ESC:
  8. printf("主动退出游戏,正常退出\n");
  9. Case KILL_BY_WALL:
  10. printf("很遗憾,撞墙了,游戏结束\n");
  11. Case KILL_BY_SELF:
  12. printf("很遗憾,撞到自己了,游戏结束\n");
  13. break;
  14. }
  15. //释放贪吃蛇的链表资源
  16. pSnakeNode cur = ps->pSnake;
  17. pSnakeNode del = NULL;
  18. while (cur)
  19. {
  20. del = cur;
  21. cur = cur->next;
  22. free(del);
  23. }
  24. free(ps->pFood);
  25. ps = NULL;
  26. }

八、Test.c

  1. void test()
  2. {
  3. //创建贪食蛇
  4. Snake snake = { 0 };
  5. //GameStart(&snake);//游戏开始前的初始化
  6. //GameRun();//玩游戏的过程
  7. //GameEnd();//善后的工作
  8. int ch = 0;
  9. do
  10. {
  11. Snake snake = { 0 };
  12. GameStart(&snake);//游戏开始前的初始化
  13. GameRun(&snake);//玩游戏的过程
  14. GameEnd(&snake);//善后的工作
  15. SetPos(15, 20);
  16. printf("再来一局吗?(Y/N):");
  17. ch = getchar();
  18. getchar();// 清理\n
  19. } while (ch == 'Y' || ch == 'y');
  20. }
  21. int main()
  22. {
  23. //修改适配本地的环境
  24. setlocale(LC_ALL, "");
  25. test();//贪吃蛇游戏的测试
  26. SetPos(0, 30);
  27. return 0;
  28. }

今天就先到这了!!!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!

你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/数据科学创新者/article/detail/63070
推荐阅读
相关标签
  

闽ICP备14008679号