• EasyX图形库实现贪吃蛇游戏


    ⭐大家好,我是Dark Falme Masker,学习了动画制作及键盘交互之后,我们就可以开动利用图形库写一个简单的贪吃蛇小游戏,增加学习乐趣。

    ⭐专栏:EasyX部分小游戏实现详细讲解

    最终效果如下

    首先包含头文件

    #include
    #include
    #include
    #include

    ✅我们将会使用rand函数生成随机数,来控制食物的随机生成,设置时间戳srand(time(NULL)),所以要包含time.h

    1. int main()
    2. {
    3. initgraph(800, 600);
    4. setbkcolor(RGB(164, 225, 202));
    5. cleardevice();
    6. closegraph();
    7. return 0;
    8. }

    🏅创建窗体,更换背景颜色。

    将窗体划分为各个边长为40的正方形小格,方便后续表示蛇和食物。

    👉为了方便后续可以修改每个小格的边长,可以将其定义一下

    #define NodeWidth 40

    然后划分分界线,让后续绘制出蛇和食物位置更加清晰。

    1. void paintGrid()
    2. {
    3. for (int y = 0; y <= 600; y += NodeWidth)
    4. {
    5. line(0, y, 800, y);
    6. }
    7. for (int x = 0; x <= 800; x += NodeWidth)
    8. {
    9. line(x, 0, x, 600);
    10. }
    11. }

    运行代码观察是否存在问题

    📚接下来绘制蛇,蛇的起始长度设置为5,蛇是由五个连续的正方形小块构成,食物由一个正方形小块构成。我们可以盛情一个结构体变量,存放每个正方形小格子的左上角的坐标。

    typedef struct {
        int x;
        int y;
    }Node;

    📜创建一个结构体数组,装着蛇的坐标信息,在第七行绘制出蛇的各个节点,如果数组第一个元素作为头节点,那么其余节点的x坐标是递减NodeWidth的,直接传格数,在绘制时乘以小方块宽度即可。

    Node snake[100] = { {5,7},{4,7},{3,7},{2,7},{1,7} };

    绘制蛇的函数

    1. void paintSnake(Node* snake, int n)
    2. {
    3. int left, top, right, bottom;
    4. for (int i = 0; i < n; i++)
    5. {
    6. left = snake[i].x * NodeWidth;
    7. top = snake[i].y * NodeWidth;
    8. right = (snake[i].x + 1) * NodeWidth;
    9. bottom = (snake[i].y + 1) * NodeWidth;
    10. solidrectangle(left, top, right, bottom);
    11. }
    12. }

    💭传入结构体指针及社的节点个数,即可在画面中绘制出一个长度为5格的蛇。

    但是蛇是会移动的,所以要加上键盘交互功能,控制蛇的方向移动。

    创建枚举

    enum direction
    {
        eUp,
        eDown,
        eLeft,
        eRight
    };

    📑一共有三种状况,在蛇移动时判断键盘是否控制蛇的移位,通过控制结构体数组内x,y的值就可以实现蛇的移动。

    默认direction为右

        enum direction d = eRight;

    判断玩家按下键盘的函数,传入枚举指针,改变枚举值,从而根据改变后的值改变更新蛇的位置的x,y坐标,再次绘制时设就可以转向。

    1. void changeDirection(enum direction* pD)
    2. {
    3. if (_kbhit() != 0)
    4. {
    5. char c = _getch();
    6. switch (c)
    7. {
    8. case'w':
    9. if (*pD != eDown)
    10. *pD = eUp;
    11. break;
    12. case's':
    13. if (*pD != eUp)
    14. *pD = eDown;
    15. break;
    16. case'a':
    17. if (*pD != eRight)
    18. *pD = eLeft;
    19. break;
    20. case'd':
    21. if (*pD != eLeft)
    22. *pD = eRight;
    23. break;
    24. }
    25. }
    26. }

    🔥改变direction后就可以更改蛇节点的参数,要注意的是,蛇头不可以向正在移动的方向的相反方向移动,这里要进行判断。

    👑在移动时,后续节点覆盖前边的节点的位置,根据蛇头位置及移动方向生成新的节点位置作为蛇头,设置这个函数的返回值为NODE类型,记录蛇尾节点。

    ✨返回蛇尾节点,利用一个结构体变量接收,在判断蛇头吃掉食物结点之后就可以将返回的节点续上,并且++蛇的长度。

    1. Node snakeMove(Node* snake, int length, int direction)
    2. {
    3. Node tail = snake[length - 1];
    4. for (int i = length - 1; i > 0; i--)
    5. {
    6. snake[i] = snake[i - 1];
    7. }
    8. Node NewHead;
    9. NewHead = snake[0];
    10. if (direction == eUp)
    11. {
    12. NewHead.y--;
    13. }
    14. else if (direction == eDown)
    15. {
    16. NewHead.y++;
    17. }
    18. else if (direction == eLeft)
    19. {
    20. NewHead.x--;
    21. }
    22. else if (direction == eRight)
    23. {
    24. NewHead.x++;
    25. }
    26. snake[0] = NewHead;
    27. return tail;
    28. }

    创建食物

    🏅创建食物是随机生成坐标,不可以超出创建的窗体大小而且不可以生成到蛇的身体上,可以使用三子棋的思路,设置死循环,直到生成满足我们需要的位置然后break。

    1. Node createFood(Node* snake, int length)
    2. {
    3. Node food;
    4. while (1)
    5. {
    6. food.x = rand() % (800 / NodeWidth);
    7. food.y = rand() % (600 / NodeWidth);
    8. int i;
    9. for (i = 0; i < length; i++)
    10. {
    11. if ((food.x == snake[i].x) && (food.y == snake[i].y))
    12. {
    13. break;
    14. }
    15. }
    16. if (i < length)
    17. continue;
    18. else
    19. break;
    20. }
    21. return food;
    22. }

    生成完成后返回创建出的食物的结构体变量

    然后绘制出食物,利用不同颜色作为区分。

    1. void paintFood(Node food)
    2. {
    3. int left, right, top, bottom;
    4. left = food.x * NodeWidth;
    5. top = food.y * NodeWidth;
    6. right = (food.x + 1) * NodeWidth;
    7. bottom = (food.y + 1) * NodeWidth;
    8. setfillcolor(YELLOW);
    9. solidrectangle(left, top, right, bottom);
    10. setfillcolor(WHITE);
    11. }

    👉我们绘制出的蛇的身体是白色的,如果在这里更改了填充颜色,再次绘制蛇就变成和食物一样的颜色,所以在绘制食物后还要将填充颜色还原为白色。

    判断是否吃掉食物及节点的续接。

     Node lastTail = snakeMove(snake, length, d);
     if (snake[0].x == food.x && snake[0].y == food.y)
     {
            if (length < 100)
            {
               snake[length] = lastTail;
                length++;
             }
             food = createFood(snake, length);
     }

    在循环中不断判断,吃掉食物后就创建出新的食物。

    ☁️判断游戏结束

    如果蛇吃掉自己的身体或者蛇头越界了,就表示游戏失败

    1. bool IsGameover(Node* snake, int length)
    2. {
    3. if (snake[0].x<0 ||snake[0].x>(800 / NodeWidth))
    4. return true;
    5. if (snake[0].y<0 || snake[0].y>(600 / NodeWidth))
    6. return true;
    7. for (int i = 1; i < length; i++)//0改为1
    8. {
    9. if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
    10. return true;
    11. }
    12. return false;
    13. }

    💡如果没有碰到墙或者遍历过程中蛇头没有和某蛇节点重合,才返回false,否则返回true,表示游戏失败。

    判断失败后,重置蛇的节点,重新生成新的食物。

    讲解到这里就结束啦

    代码如下,大家可以运行看一看效果(用于使格子更加明显的线可以画也可以不画)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #define NodeWidth 40
    6. typedef struct {
    7. int x;
    8. int y;
    9. }Node;
    10. void paintGrid()
    11. {
    12. for (int y = 0; y <= 600; y += NodeWidth)
    13. {
    14. line(0, y, 800, y);
    15. }
    16. for (int x = 0; x <= 800; x += NodeWidth)
    17. {
    18. line(x, 0, x, 600);
    19. }
    20. }
    21. void paintSnake(Node* snake, int n)
    22. {
    23. int left, top, right, bottom;
    24. for (int i = 0; i < n; i++)
    25. {
    26. left = snake[i].x * NodeWidth;
    27. top = snake[i].y * NodeWidth;
    28. right = (snake[i].x + 1) * NodeWidth;
    29. bottom = (snake[i].y + 1) * NodeWidth;
    30. solidrectangle(left, top, right, bottom);
    31. }
    32. }
    33. enum direction
    34. {
    35. eUp,
    36. eDown,
    37. eLeft,
    38. eRight
    39. };
    40. Node snakeMove(Node* snake, int length, int direction)
    41. {
    42. Node tail = snake[length - 1];
    43. for (int i = length - 1; i > 0; i--)
    44. {
    45. snake[i] = snake[i - 1];
    46. }
    47. Node NewHead;
    48. NewHead = snake[0];
    49. if (direction == eUp)
    50. {
    51. NewHead.y--;
    52. }
    53. else if (direction == eDown)
    54. {
    55. NewHead.y++;
    56. }
    57. else if (direction == eLeft)
    58. {
    59. NewHead.x--;
    60. }
    61. else if (direction == eRight)
    62. {
    63. NewHead.x++;
    64. }
    65. snake[0] = NewHead;
    66. return tail;
    67. }
    68. void changeDirection(enum direction* pD)
    69. {
    70. if (_kbhit() != 0)
    71. {
    72. char c = _getch();
    73. switch (c)
    74. {
    75. case'w':
    76. if (*pD != eDown)
    77. *pD = eUp;
    78. break;
    79. case's':
    80. if (*pD != eUp)
    81. *pD = eDown;
    82. break;
    83. case'a':
    84. if (*pD != eRight)
    85. *pD = eLeft;
    86. break;
    87. case'd':
    88. if (*pD != eLeft)
    89. *pD = eRight;
    90. break;
    91. }
    92. }
    93. }
    94. Node createFood(Node* snake, int length)
    95. {
    96. Node food;
    97. while (1)
    98. {
    99. food.x = rand() % (800 / NodeWidth);
    100. food.y = rand() % (600 / NodeWidth);
    101. int i;
    102. for (i = 0; i < length; i++)
    103. {
    104. if ((food.x == snake[i].x) && (food.y == snake[i].y))
    105. {
    106. break;
    107. }
    108. }
    109. if (i < length)
    110. continue;
    111. else
    112. break;
    113. }
    114. return food;
    115. }
    116. void paintFood(Node food)
    117. {
    118. int left, right, top, bottom;
    119. left = food.x * NodeWidth;
    120. top = food.y * NodeWidth;
    121. right = (food.x + 1) * NodeWidth;
    122. bottom = (food.y + 1) * NodeWidth;
    123. setfillcolor(YELLOW);
    124. solidrectangle(left, top, right, bottom);
    125. setfillcolor(WHITE);
    126. }
    127. bool IsGameover(Node* snake, int length)
    128. {
    129. if (snake[0].x<0 ||snake[0].x>(800 / NodeWidth))
    130. return true;
    131. if (snake[0].y<0 || snake[0].y>(600 / NodeWidth))
    132. return true;
    133. for (int i = 1; i < length; i++)//0改为1
    134. {
    135. if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
    136. return true;
    137. }
    138. return false;
    139. }
    140. void reset(Node* snake, int* length, enum direction* d)
    141. {
    142. snake[0] = Node{ 5,7 };
    143. snake[1] = Node{ 4,7 };
    144. snake[2] = Node{ 3,7 };
    145. snake[3] = Node{ 2,7 };
    146. snake[4] = Node{ 1,7 };
    147. *length = 5;
    148. *d = eRight;
    149. }
    150. int main()
    151. {
    152. initgraph(800, 600);
    153. setbkcolor(RGB(164, 25, 202));
    154. cleardevice();
    155. paintGrid();
    156. Node snake[100] = { {5,7},{4,7},{3,7},{2,7},{1,7} };
    157. int length = 5;
    158. enum direction d = eRight;
    159. srand(unsigned int(time(NULL)));
    160. Node food = createFood(snake, length);
    161. while (1)
    162. {
    163. cleardevice();
    164. paintGrid();
    165. paintSnake(snake, length);
    166. paintFood(food);
    167. Sleep(500);
    168. changeDirection(&d);
    169. Node lastTail = snakeMove(snake, length, d);
    170. if (snake[0].x == food.x && snake[0].y == food.y)
    171. {
    172. if (length < 100)
    173. {
    174. snake[length] = lastTail;
    175. length++;
    176. }
    177. food = createFood(snake, length);
    178. }
    179. if (IsGameover(snake, length) == true)
    180. {
    181. reset(snake, &length, &d);
    182. food = createFood(snake, length);
    183. }
    184. }
    185. closegraph();
    186. return 0;
    187. }
  • 相关阅读:
    HTML5期末大作业:基于HTML+CSS+JavaScript茶文化中国水墨风格绿色茶叶销售(5页) 学生网页设计作业源码
    公众号点击原文下载文件怎么设置
    11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查
    Python爬取电影信息:Ajax介绍、爬取案例实战 + MongoDB存储
    极客时间Kafka - 01 Kafka术语|生产者|消费者|主题|分区|副本|ISR|OSR|AR|HW|LEO|Offset
    Apache HTTPD 多后缀解析漏洞复现
    leetcode_208 实现Trie(前缀树)
    Toshiba转换芯片-TC358775XBG:MIPI转LVDS(双路)芯片资料
    Qt day5
    SSM+广西壮族文化宣传网站 毕业设计-附源码230932
  • 原文地址:https://blog.csdn.net/qq_75270497/article/details/133816639