当前位置:   article > 正文

C语言C++图形库---贪吃蛇大作战【附源码】_snakeplay和snakec++

snakeplay和snakec++

这一节中,我们来做一款经典小游戏,贪吃蛇。先看看最终效果图

 

在开始之前,我们把窗体创建好。

创建一个800 * 600的窗体。这一次我们使用默认的原点和坐标轴:原点在窗体左上角,X轴正方向向右,Y轴正方向向下。背景色设置为RGB(164, 225, 202),最后调用cleardevice函数,使用背景色清空整个窗体。

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. int main()
  4. {
  5.     initgraph(800600);
  6.     //  设置背景色
  7.     setbkcolor(RGB(164225202));
  8.     //  使用背景色清空窗体
  9.     cleardevice();
  10.     getchar();
  11.     closegraph();
  12.     return 0;
  13. }

1. 定位网格

图片

将整个800 * 600的窗体,水平分隔为20等分,垂直分隔为15等分,作为整个游戏的网格坐标系统。在上图中,蛇用5格白色的矩形表示。食物用黄色的一格矩形表示。

图片

这样,蛇的每一格身体坐标为:

  • (5, 7)

  • (4, 7)

  • (3, 7)

  • (2, 7)

  • (1, 7)

食物的坐标为:

  • (12, 7)

为了方便观察,把窗体用线段画上上述网格。网格每一格的宽度设为40像素,用符号常量NODE_WIDTH表示。

#define NODE_WIDTH 40

竖向线段

先绘制竖向的线段。

竖向线段中,起始点y坐标固定为0,终止点y坐标固定为600

图片

每条线段的起始点与终止点的x坐标一致,且随着线段不同而变化。

图片

设线段条数从0开始计数。

第0条线段: 起始点、终止点的x坐标为0
第10条线段: 起始点、终止点的x坐标为10 * NODE_WIDTH
第20条线段: 起始点、终止点的x坐标为20 * NODE_WIDTH

观察各线段起始点终止点坐标,可以总结出规律:

第n条线段:起始点(n * NODE_WIDTH, 0)、终止点(n * NODE_WIDTH, 600)。且x坐标的范围为[0, 800]

  1. //  竖线
  2. for (int x = 0; x <= 800; x += NODE_WIDTH)
  3. {
  4.     line(x, 0, x, 600);
  5. }

横向线段

再绘制横向的线段。

横向线段中,起始点x坐标固定为0,终止点x坐标固定为800

图片

每条线段的起始点与终止点的y坐标一致,且随着线段不同而变化。

图片

设线段条数从0开始计数。

第0条线段: 起始点、终止点的y坐标为0
第8条线段: 起始点、终止点的y坐标为8 * NODE_WIDTH
第15条线段: 起始点、终止点的y坐标为15 * NODE_WIDTH

观察各线段起始点终止点坐标,可以总结出规律:

第n条线段:起始点(0, n * NODE_WIDTH)、终止点(800, n * NODE_WIDTH)。且y坐标的范围为[0, 600]

  1. //  横线
  2. for (int y = 0; y <= 600; y += NODE_WIDTH)
  3. {
  4.     line(0, y, 800, y);
  5. }

网格函数paintGrid

将绘制网格的代码封装成函数paintGrid

  1. //  绘制网格
  2. //  横线(0, y), (800, y)   0 <= y <= 600
  3. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  4. void paintGrid()
  5. {
  6.     //  横线
  7.     for (int y = 0; y < 600; y += NODE_WIDTH)
  8.     {
  9.         line(0, y, 800, y);
  10.     }
  11.     //  竖线
  12.     for (int x = 0; x < 800; x += NODE_WIDTH)
  13.     {
  14.         line(x, 0, x, 600);
  15.     }
  16. }

主函数中调用paintGrid函数,给整个窗体绘制上网格。

图片

现阶段代码如下:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #define NODE_WIDTH 40
  4. //  绘制网格
  5. //  横线(0, y), (800, y)   0 <= y <= 600
  6. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  7. void paintGrid()
  8. {
  9.     //  横线
  10.     for (int y = 0; y < 600; y += NODE_WIDTH)
  11.     {
  12.         line(0, y, 800, y);
  13.     }
  14.     //  竖线
  15.     for (int x = 0; x < 800; x += NODE_WIDTH)
  16.     {
  17.         line(x, 0, x, 600);
  18.     }
  19. }
  20. int main()
  21. {
  22.     initgraph(800600);
  23.     //  设置背景色
  24.     setbkcolor(RGB(164225202));
  25.     //  使用背景色清空窗体
  26.     cleardevice();
  27.     //  绘制网格
  28.     paintGrid();
  29.     getchar();
  30.     closegraph();
  31.     return 0;
  32. }

2. 绘制蛇节点

图片

设定初始状态下,蛇有5个节点。我们把之前设置的网格规划出来的坐标称作网格坐标。每个节点的网格坐标为:

  • (5, 7)

  • (4, 7)

  • (3, 7)

  • (2, 7)

  • (1, 7)

每一个蛇节点使用一个白色正方形表示,而绘制矩形需要左上角和右下角的实际坐标。白色矩形的左上角坐标与右下角实际坐标为:

左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】

图片

右下角:【(网格x坐标 + 1) * 网格宽度, (网格y坐标 + 1) * 网格宽度

图片

每个节点需要存储x坐标、y坐标两个坐标,可以定义含有两个int成员的结构用于表示节点。

  1. //  节点
  2. typedef struct {
  3.     int x;
  4.     int y;
  5. }node;

在主函数中,声明node的数组,并将前5个元素初始化为蛇的初始位置。设节点(5, 7)为蛇头,蛇头存储在数组的首元素中

  1. //  蛇节点坐标
  2. node snake[100= { {57}, {47}, {37}, {27}, {17} };

现在,我们定义一个函数paintSnake用于将所有组成蛇的矩形绘制出来。

  1. void paintSnake(node* snake, int n)
  2. {
  3.     int lefttoprightbottom;
  4.     for (int i = 0; i < n; i++)
  5.     {
  6.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  7.         left = snake[i].x * NODE_WIDTH;
  8.         top = snake[i].y * NODE_WIDTH;
  9.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  10.         right = (snake[i].x + 1* NODE_WIDTH;
  11.         bottom = (snake[i].y + 1* NODE_WIDTH;
  12.         //  通过左上角与右下角坐标绘制矩形
  13.         solidrectangle(lefttoprightbottom);
  14.     }
  15. }

paintSnake函数需要蛇节点数组首元素指针和蛇节点个数两个参数。在主函数中定义一个变量int length用于记录蛇长度,目前初始化为5。声明蛇节点数组,绘制网格及蛇节点。

 

//  蛇节点坐标
node snake[100] = { {5, 7}, {4, 7}, {3, 7}, {2, 7}, {1, 7} };
//  蛇节点长度
int length = 5;

//  绘制网格
paintGrid();
//  绘制蛇节点
paintSnake(snake, length);   

图片

现阶段代码如下:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #define NODE_WIDTH 40
  4. //  节点
  5. typedef struct {
  6.     int x;
  7.     int y;
  8. }node;
  9. //  绘制网格
  10. //  横线(0, y), (800, y)   0 <= y <= 600
  11. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  12. void paintGrid()
  13. {
  14.     //  横线
  15.     for (int y = 0; y < 600; y += NODE_WIDTH)
  16.     {
  17.         line(0, y, 800, y);
  18.     }
  19.     //  竖线
  20.     for (int x = 0; x < 800; x += NODE_WIDTH)
  21.     {
  22.         line(x, 0, x, 600);
  23.     }
  24. }
  25. void paintSnake(node* snake, int n)
  26. {
  27.     int lefttoprightbottom;
  28.     for (int i = 0; i < n; i++)
  29.     {
  30.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  31.         left = snake[i].x * NODE_WIDTH;
  32.         top = snake[i].y * NODE_WIDTH;
  33.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  34.         right = (snake[i].x + 1* NODE_WIDTH;
  35.         bottom = (snake[i].y + 1* NODE_WIDTH;
  36.         //  通过左上角与右下角坐标绘制矩形
  37.         solidrectangle(lefttoprightbottom);
  38.     }
  39. }
  40. int main()
  41. {
  42.     initgraph(800600);
  43.     //  设置背景色
  44.     setbkcolor(RGB(164225202));
  45.     //  使用背景色清空窗体
  46.     cleardevice();
  47.     //  蛇节点坐标
  48.     node snake[100= { {57}, {47}, {37}, {27}, {17} };
  49.     //  蛇节点长度
  50.     int length = 5;
  51.     //  绘制网格
  52.     paintGrid();
  53.     //  绘制蛇节点
  54.     paintSnake(snake, length);
  55.     getchar();
  56.     closegraph();
  57.     return 0;
  58. }

3.  移动蛇节点

现在,我们希望让蛇向右运动,蛇头节点为(5, 7)。以下两幅图为初始位置向右移动一步后的位置。

图片

初始位置:

  • (5, 7)

  • (4, 7)

  • (3, 7)

  • (2, 7)

  • (1, 7)

图片

向右移动一步后位置:

  • (6, 7)

  • (5, 7)

  • (4, 7)

  • (3, 7)

  • (2, 7)

可以看出移动后,蛇节点中,尾部节点(1, 7)被删除,而新增了一个头部节点(6,7)

图片

怎样对存储蛇节点的数组进行操作,可以达到删除尾部节点,而新增一个头节点的效果呢?

逐个向后移动

从蛇尾节点前一个节点开始,即从数组下标为3的元素开始。元素3设置为当前元素,将当前元素的值,移动到后一个元素当中。接着,将当前元素设置为元素2,重复该动作,直到数组元素0的值,移动到元素1为止。

图片

  • 元素3-->元素4

  • 元素2-->元素3

  • 元素1-->元素2

  • 元素0-->元素1

这样即可把蛇尾节点去掉,并留出了新蛇头节点的位置。

设置新蛇头

若蛇向右运动,那么将新蛇头设置为(6,7)即可。

图片

但是,蛇头除了可以向右运动,还可以做另外3个方向的运动,共4个方向的运动。

图片

设旧蛇头坐标为(x, y)。对于各个方向的运动,新蛇头的网格坐标相对于旧蛇头的坐标作如下运算:

上:(x, y + 1)

下:(x, y - 1)

左:(x - 1, y)

右:(x + 1, y)

为了更加明确地在程序中表明方向,我们将4个方向声明为枚举类型

  1. //  方向枚举
  2. enum direction
  3. {
  4.     eUp,
  5.     eDown,
  6.     eLeft,
  7.     eRight
  8. };

接着定义一个函数snakeMove,传入数组首元素指针、蛇节点个数和蛇前进方向。它将按照上述方法,依次移动蛇节点并根据前进方向设置蛇头。

  1. //  蛇节点移动
  2. void snakeMove(node* snake, int length, int direction)
  3. {
  4.     //  从尾结点开始,前一个节点覆盖后一个节点
  5.     //  4 3 2 1 0      4 3 2 1 0
  6.     //  E D C B A ---> D E C B A
  7.     for (int i = length - 1; i > 0; i--)
  8.     {
  9.         snake[i] = snake[i - 1];
  10.     }
  11.     //  根据方向,确定下一个头节点
  12.     node newHead;
  13.     newHead = snake[0];
  14.     if (direction == eUp)
  15.     {
  16.         newHead.y--;
  17.     }
  18.     else if (direction == eDown)
  19.     {
  20.         newHead.y++;
  21.     }
  22.     else if (direction == eLeft)
  23.     {
  24.         newHead.x--;
  25.     }
  26.     else //  right
  27.     {
  28.         newHead.x++;
  29.     }
  30.     //  更新头节点
  31.     //  D E C B A ---> D E C B N
  32.     snake[0= newHead;
  33. }

在主函数中放置一个死循环,循环体中依次执行以下步骤:

  1. 清空整个窗体

  2. 绘制网格

  3. 绘制蛇节点

  4. 休眠500ms

  5. 向右移动蛇节点

 

while (1)
{
    //  清空整个窗体
    cleardevice();
    //  绘制网格
    paintGrid();
    //  绘制蛇节点
    paintSnake(snake, length);
    //  休眠500ms
    Sleep(500);
    //  向右移动蛇节点
    snakeMove(snake, length, eRight);
}

图片

现阶段代码如下:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #define NODE_WIDTH 40
  4. //  节点
  5. typedef struct {
  6.     int x;
  7.     int y;
  8. }node;
  9. //  绘制网格
  10. //  横线(0, y), (800, y)   0 <= y <= 600
  11. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  12. void paintGrid()
  13. {
  14.     //  横线
  15.     for (int y = 0; y < 600; y += NODE_WIDTH)
  16.     {
  17.         line(0, y, 800, y);
  18.     }
  19.     //  竖线
  20.     for (int x = 0; x < 800; x += NODE_WIDTH)
  21.     {
  22.         line(x, 0, x, 600);
  23.     }
  24. }
  25. void paintSnake(node* snake, int n)
  26. {
  27.     int lefttoprightbottom;
  28.     for (int i = 0; i < n; i++)
  29.     {
  30.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  31.         left = snake[i].x * NODE_WIDTH;
  32.         top = snake[i].y * NODE_WIDTH;
  33.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  34.         right = (snake[i].x + 1* NODE_WIDTH;
  35.         bottom = (snake[i].y + 1* NODE_WIDTH;
  36.         //  通过左上角与右下角坐标绘制矩形
  37.         solidrectangle(lefttoprightbottom);
  38.     }
  39. }
  40. //  方向枚举
  41. enum direction
  42. {
  43.     eUp,
  44.     eDown,
  45.     eLeft,
  46.     eRight
  47. };
  48. //  蛇节点移动
  49. void snakeMove(node* snake, int length, int direction)
  50. {
  51.     //  从尾结点开始,前一个节点覆盖后一个节点
  52.     //  4 3 2 1 0      4 3 2 1 0
  53.     //  E D C B A ---> D E C B A
  54.     for (int i = length - 1; i > 0; i--)
  55.     {
  56.         snake[i] = snake[i - 1];
  57.     }
  58.     //  根据方向,确定下一个头节点
  59.     node newHead;
  60.     newHead = snake[0];
  61.     if (direction == eUp)
  62.     {
  63.         newHead.y--;
  64.     }
  65.     else if (direction == eDown)
  66.     {
  67.         newHead.y++;
  68.     }
  69.     else if (direction == eLeft)
  70.     {
  71.         newHead.x--;
  72.     }
  73.     else //  right
  74.     {
  75.         newHead.x++;
  76.     }
  77.     //  更新头节点
  78.     //  D E C B A ---> D E C B N
  79.     snake[0= newHead;
  80. }
  81. int main()
  82. {
  83.     initgraph(800600);
  84.     //  设置背景色
  85.     setbkcolor(RGB(164225202));
  86.     //  使用背景色清空窗体
  87.     cleardevice();
  88.     //  蛇节点坐标
  89.     node snake[100= { {57}, {47}, {37}, {27}, {17} };
  90.     //  蛇节点长度
  91.     int length = 5;
  92.     while (1)
  93.     {
  94.         //  清空整个窗体
  95.         cleardevice();
  96.         //  绘制网格
  97.         paintGrid();
  98.         //  绘制蛇节点
  99.         paintSnake(snake, length);
  100.         //  休眠500ms
  101.         Sleep(500);
  102.         //  向右移动蛇节点
  103.         snakeMove(snake, length, eRight);
  104.     }
  105.     getchar();
  106.     closegraph();
  107.     return 0;
  108. }

4. 控制移动方向

现在,程序开始后蛇会向右移动,接下来,加入用键盘来控制蛇的移动方向的功能。

键盘交互功能如下:

  • 按下w键,蛇向上移动

  • 按下s键,蛇向下移动

  • 按下a键,蛇向左移动

  • 按下d键,蛇向右移动

在主函数中声明一个枚举变量d,初始为向右移动。

  1. //  移动方向
  2. enum direction d = eRight;

d传递给snakeMove函数的第三个参数。snakeMove函数的第三个参数可以根据方向设置新蛇头的位置。若蛇需要更改移动方向,只要更改枚举变量d即可。

  1. //  枚举变量d,控制蛇的移动方向
  2. snakeMove(snake, length, d);

与之前的键盘交互一样,使用_getch_kbhit函数配合,可以获取键盘输入且不会导致程序阻塞。使用这两个函数别忘了包含头文件#include <conio.h>。获取到键盘输入后,通过传入枚举变量指针pD,修改变量枚举值。

  1. //  键盘输入改变direction
  2. void changeDirection(enum direction* pD)
  3. {
  4.     //  检查输入缓存区中是否有数据
  5.     if (_kbhit() != 0)
  6.     {
  7.         //  _getch函数获取输入缓存区中的数据
  8.         char c = _getch();
  9.         //  判断输入并转向
  10.         switch (c)
  11.         {
  12.         case 'w':
  13.             //  向上移动
  14.             *pD = eUp;
  15.             break;
  16.         case 's':
  17.             //  向下移动
  18.             *pD = eDown;
  19.             break;
  20.         case 'a':
  21.             //  向左移动
  22.             *pD = eLeft;
  23.             break;
  24.         case 'd':
  25.             //  向右移动
  26.             *pD = eRight;
  27.             break;
  28.         }
  29.     }
  30. }

这里还需要注意一个问题,蛇不能后退,如果新的方向与原方向相反,那么按键无效。

  1. //  键盘输入改变direction
  2. void changeDirection(enum direction* pD)
  3. {
  4.     //  检查输入缓存区中是否有数据
  5.     if (_kbhit() != 0)
  6.     {
  7.         //  _getch函数获取输入缓存区中的数据
  8.         char c = _getch();
  9.         //  判断输入并转向
  10.         switch (c)
  11.         {
  12.         case 'w':
  13.             //  向上移动
  14.             if (*pD != eDown)
  15.                 *pD = eUp;
  16.             break;
  17.         case 's':
  18.             //  向下移动
  19.             if (*pD != eUp)
  20.                 *pD = eDown;
  21.             break;
  22.         case 'a':
  23.             //  向左移动
  24.             if (*pD != eRight)
  25.                 *pD = eLeft;
  26.             break;
  27.         case 'd':
  28.             //  向右移动
  29.             if (*pD != eLeft)
  30.                 *pD = eRight;
  31.             break;
  32.         }
  33.     }
  34. }

主函数中,在snakeMove函数前调用changeDirection函数检查是否有键盘输入,若有输入且非回头方向的输入,则改变方向枚举变量d。新的方向枚举变量d传入snakeMove函数后,即可使用新方向设置新蛇头的位置,实现蛇改变移动方向功能。

 

//  蛇节点坐标
node snake[100] = { {5, 7}, {4, 7}, {3, 7}, {2, 7}, {1, 7} };
//  蛇节点长度
int length = 5;
enum direction d = eRight;

while (1)
{
    cleardevice();
    paintGrid();
    paintSnake(snake, length);
    Sleep(500);

    //  获取键盘输入并将方向存储到变量d
    changeDirection(&d);
    //  根据变量d的方向移动蛇节点
    snakeMove(snake, length, d);
}

图片

现阶段代码:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #include <conio.h>
  4. #define NODE_WIDTH 40
  5. //  节点
  6. typedef struct {
  7.     int x;
  8.     int y;
  9. }node;
  10. //  绘制网格
  11. //  横线(0, y), (800, y)   0 <= y <= 600
  12. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  13. void paintGrid()
  14. {
  15.     //  横线
  16.     for (int y = 0; y < 600; y += NODE_WIDTH)
  17.     {
  18.         line(0, y, 800, y);
  19.     }
  20.     //  竖线
  21.     for (int x = 0; x < 800; x += NODE_WIDTH)
  22.     {
  23.         line(x, 0, x, 600);
  24.     }
  25. }
  26. void paintSnake(node* snake, int n)
  27. {
  28.     int lefttoprightbottom;
  29.     for (int i = 0; i < n; i++)
  30.     {
  31.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  32.         left = snake[i].x * NODE_WIDTH;
  33.         top = snake[i].y * NODE_WIDTH;
  34.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  35.         right = (snake[i].x + 1* NODE_WIDTH;
  36.         bottom = (snake[i].y + 1* NODE_WIDTH;
  37.         //  通过左上角与右下角坐标绘制矩形
  38.         solidrectangle(lefttoprightbottom);
  39.     }
  40. }
  41. //  方向枚举
  42. enum direction
  43. {
  44.     eUp,
  45.     eDown,
  46.     eLeft,
  47.     eRight
  48. };
  49. //  蛇节点移动
  50. void snakeMove(node* snake, int length, int direction)
  51. {
  52.     //  从尾结点开始,前一个节点覆盖后一个节点
  53.     //  4 3 2 1 0      4 3 2 1 0
  54.     //  E D C B A ---> D E C B A
  55.     for (int i = length - 1; i > 0; i--)
  56.     {
  57.         snake[i] = snake[i - 1];
  58.     }
  59.     //  根据方向,确定下一个头节点
  60.     node newHead;
  61.     newHead = snake[0];
  62.     if (direction == eUp)
  63.     {
  64.         newHead.y--;
  65.     }
  66.     else if (direction == eDown)
  67.     {
  68.         newHead.y++;
  69.     }
  70.     else if (direction == eLeft)
  71.     {
  72.         newHead.x--;
  73.     }
  74.     else //  right
  75.     {
  76.         newHead.x++;
  77.     }
  78.     //  更新头节点
  79.     //  D E C B A ---> D E C B N
  80.     snake[0= newHead;
  81. }
  82. //  键盘输入改变direction
  83. void changeDirection(enum direction* pD)
  84. {
  85.     //  检查输入缓存区中是否有数据
  86.     if (_kbhit() != 0)
  87.     {
  88.         //  _getch函数获取输入缓存区中的数据
  89.         char c = _getch();
  90.         //  判断输入并转向
  91.         switch (c)
  92.         {
  93.         case 'w':
  94.             //  向上移动
  95.             if (*pD != eDown)
  96.                 *pD = eUp;
  97.             break;
  98.         case 's':
  99.             //  向下移动
  100.             if (*pD != eUp)
  101.                 *pD = eDown;
  102.             break;
  103.         case 'a':
  104.             //  向左移动
  105.             if (*pD != eRight)
  106.                 *pD = eLeft;
  107.             break;
  108.         case 'd':
  109.             //  向右移动
  110.             if (*pD != eLeft)
  111.                 *pD = eRight;
  112.             break;
  113.         }
  114.     }
  115. }
  116. int main()
  117. {
  118.     initgraph(800600);
  119.     //  设置背景色
  120.     setbkcolor(RGB(164225202));
  121.     //  使用背景色清空窗体
  122.     cleardevice();
  123.     //  蛇节点坐标
  124.     node snake[100= { {57}, {47}, {37}, {27}, {17} };
  125.     //  蛇节点长度
  126.     int length = 5;
  127.     enum direction d = eRight;
  128.     while (1)
  129.     {
  130.         //  清空整个窗体
  131.         cleardevice();
  132.         //  绘制网格
  133.         paintGrid();
  134.         //  绘制蛇节点
  135.         paintSnake(snake, length);
  136.         //  休眠500ms
  137.         Sleep(500);
  138.         //  获取键盘输入并将方向存储到变量d
  139.         changeDirection(&d);
  140.         //  根据变量d的方向移动蛇节点
  141.         snakeMove(snake, length, d);
  142.     }
  143.     getchar();
  144.     closegraph();
  145.     return 0;
  146. }

5. 创建食物

目前蛇的部分已经完成了,还差需要创建食物,让蛇吃到食物后长大。

主函数中声明一个node类型的变量作为食物的节点。

node food;

食物的位置是随机生成的,但是有两个要求:

  1. 不能生成在窗体外

  2. 不能生成在蛇节点上

对于网格坐标来说,宽度有800/NODE_WIDTH,即20格。高度有600 / NODE_WIDTH,即15格。限制食物的x坐标在区间[0, 19]以内,y坐标在区间[0, 14]内。

  1. food.x = rand() % (800 / NODE_WIDTH);   //  区间[019]内
  2. food.y = rand() % (600 / NODE_WIDTH);   //  区间[014]内

对于第二个条件,只能遍历所有蛇节点,检查是否食物与蛇任何一个节点重合了。如果重合,那么重新随机生成一次食物,直到食物与所有蛇节点不重合为止。

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

将生成食物封装成createFood函数,函数传入两个参数,一个参数为蛇节点数组首元素指针,另一个参数为蛇节点个数。生成完成食物坐标后,返回装有食物坐标的food结构。

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

根据createFood函数返回的食物坐标,在窗体上绘制一个黄色矩形代表食物。将绘制食物的代码封装成函数paintFood

食物的左上角坐标为:【food.x * NODE_WIDTH, food.y * NODE_WIDTH】

食物的右下角坐标为:【(food.x + 1) * NODE_WIDTH, (food.y + 1) * NODE_WIDTH】

  1. //  绘制食物
  2. void paintFood(node food)
  3. {
  4.     int lefttoprightbottom;
  5.     left = food.x * NODE_WIDTH;
  6.     top = food.y * NODE_WIDTH;
  7.     right = (food.x + 1* NODE_WIDTH;
  8.     bottom = (food.y + 1* NODE_WIDTH;
  9.     setfillcolor(YELLOW);
  10.     solidrectangle(lefttoprightbottom);
  11.     setfillcolor(WHITE);
  12. }

主函数中,第一次生成食物在循环外。循环内部每次循环绘制一次食物。使用了随机数,别忘了使用当前时间作为随机数种子。另外,time函数需要包含头文件#include <time.h>

 

//  随机生成食物
srand(unsigned int(time(NULL)));
node food = createFood(snake, length);

while (1)
{
    cleardevice();
    paintGrid();
    paintSnake(snake, length);
    //  绘制食物
    paintFood(food);
    Sleep(500);
    changeDirection(&d);
    snakeMove(snake, length, d);
}

图片

现阶段代码:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #include <conio.h>
  4. #include <time.h>
  5. #define NODE_WIDTH 40
  6. //  节点
  7. typedef struct {
  8.     int x;
  9.     int y;
  10. }node;
  11. //  绘制网格
  12. //  横线(0, y), (800, y)   0 <= y <= 600
  13. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  14. void paintGrid()
  15. {
  16.     //  横线
  17.     for (int y = 0; y < 600; y += NODE_WIDTH)
  18.     {
  19.         line(0, y, 800, y);
  20.     }
  21.     //  竖线
  22.     for (int x = 0; x < 800; x += NODE_WIDTH)
  23.     {
  24.         line(x, 0, x, 600);
  25.     }
  26. }
  27. void paintSnake(node* snake, int n)
  28. {
  29.     int lefttoprightbottom;
  30.     for (int i = 0; i < n; i++)
  31.     {
  32.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  33.         left = snake[i].x * NODE_WIDTH;
  34.         top = snake[i].y * NODE_WIDTH;
  35.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  36.         right = (snake[i].x + 1* NODE_WIDTH;
  37.         bottom = (snake[i].y + 1* NODE_WIDTH;
  38.         //  通过左上角与右下角坐标绘制矩形
  39.         solidrectangle(lefttoprightbottom);
  40.     }
  41. }
  42. //  方向枚举
  43. enum direction
  44. {
  45.     eUp,
  46.     eDown,
  47.     eLeft,
  48.     eRight
  49. };
  50. //  蛇节点移动
  51. void snakeMove(node* snake, int length, int direction)
  52. {
  53.     //  从尾结点开始,前一个节点覆盖后一个节点
  54.     //  4 3 2 1 0      4 3 2 1 0
  55.     //  E D C B A ---> D E C B A
  56.     for (int i = length - 1; i > 0; i--)
  57.     {
  58.         snake[i] = snake[i - 1];
  59.     }
  60.     //  根据方向,确定下一个头节点
  61.     node newHead;
  62.     newHead = snake[0];
  63.     if (direction == eUp)
  64.     {
  65.         newHead.y--;
  66.     }
  67.     else if (direction == eDown)
  68.     {
  69.         newHead.y++;
  70.     }
  71.     else if (direction == eLeft)
  72.     {
  73.         newHead.x--;
  74.     }
  75.     else //  right
  76.     {
  77.         newHead.x++;
  78.     }
  79.     //  更新头节点
  80.     //  D E C B A ---> D E C B N
  81.     snake[0= newHead;
  82. }
  83. //  键盘输入改变direction
  84. void changeDirection(enum direction* pD)
  85. {
  86.     //  检查输入缓存区中是否有数据
  87.     if (_kbhit() != 0)
  88.     {
  89.         //  _getch函数获取输入缓存区中的数据
  90.         char c = _getch();
  91.         //  判断输入并转向
  92.         switch (c)
  93.         {
  94.         case 'w':
  95.             //  向上移动
  96.             if (*pD != eDown)
  97.                 *pD = eUp;
  98.             break;
  99.         case 's':
  100.             //  向下移动
  101.             if (*pD != eUp)
  102.                 *pD = eDown;
  103.             break;
  104.         case 'a':
  105.             //  向左移动
  106.             if (*pD != eRight)
  107.                 *pD = eLeft;
  108.             break;
  109.         case 'd':
  110.             //  向右移动
  111.             if (*pD != eLeft)
  112.                 *pD = eRight;
  113.             break;
  114.         }
  115.     }
  116. }
  117. //  绘制食物
  118. /*
  119. (x * NODE_WIDTH, y * NODE_WIDTH)
  120. @-----------
  121. |          |
  122. |          |
  123. |          |
  124. |          |
  125. |          |
  126. -----------@ ((x + 1* NODE_WIDTH, (y + 1* NODE_WIDTH)
  127. */
  128. void paintFood(node food)
  129. {
  130.     int lefttoprightbottom;
  131.     left = food.x * NODE_WIDTH;
  132.     top = food.y * NODE_WIDTH;
  133.     right = (food.x + 1* NODE_WIDTH;
  134.     bottom = (food.y + 1* NODE_WIDTH;
  135.     setfillcolor(YELLOW);
  136.     solidrectangle(lefttoprightbottom);
  137.     setfillcolor(WHITE);
  138. }
  139. //  随机创建食物
  140. node createFood(node* snake, int length)
  141. {
  142.     node food;
  143.     while (1)
  144.     {
  145.         food.x = rand() % (800 / NODE_WIDTH);
  146.         food.y = rand() % (600 / NODE_WIDTH);
  147.         int i;
  148.         for (i = 0; i < length; i++)
  149.         {
  150.             if (snake[i].x == food.x && snake[i].y == food.y)
  151.             {
  152.                 break;
  153.             }
  154.         }
  155.         if (i < length)
  156.             continue;
  157.         else
  158.             break;
  159.     }
  160.     return food;
  161. }
  162. int main()
  163. {
  164.     initgraph(800600);
  165.     //  设置背景色
  166.     setbkcolor(RGB(164225202));
  167.     //  使用背景色清空窗体
  168.     cleardevice();
  169.     //  蛇节点坐标
  170.     node snake[100= { {57}, {47}, {37}, {27}, {17} };
  171.     //  蛇节点长度
  172.     int length = 5;
  173.     enum direction d = eRight;
  174.     //  食物
  175.     srand(unsigned int(time(NULL)));
  176.     node food = createFood(snake, length);
  177.     while (1)
  178.     {
  179.         //  清空整个窗体
  180.         cleardevice();
  181.         //  绘制网格
  182.         paintGrid();
  183.         //  绘制蛇节点
  184.         paintSnake(snake, length);
  185.         //  绘制食物
  186.         paintFood(food);
  187.         //  休眠500ms
  188.         Sleep(500);
  189.         //  获取键盘输入并将方向存储到变量d
  190.         changeDirection(&d);
  191.         //  根据变量d的方向移动蛇节点
  192.         snakeMove(snake, length, d);
  193.     }
  194.     getchar();
  195.     closegraph();
  196.     return 0;
  197. }

6. 吃掉食物并长大

观察下图,蛇头为节点(11, 7)。而蛇目前向右运动,下一步即将吃到在(12, 7)位置的食物。

图片

现在蛇头移动到了(12, 7)的位置,并吃掉了食物。原蛇尾节点(6, 7)本应该删除,但是这时蛇吃掉了食物,需要长大一节。再将蛇尾节点(6, 7)加回来。

图片

为了获得被删除的原蛇尾节点,snakeMove函数需要返回原蛇尾节点,函数返回值从void改为node。函数中需要记录原蛇尾节点,并在最后返回原蛇尾节点。

  1. //  蛇身体移动
  2. node snakeMove(node* snake, int length, int direction)
  3. {
  4.     //  记录尾节点
  5.     node tail = snake[length - 1];
  6.     for (int i = length - 1; i > 0; i--)
  7.     {
  8.         snake[i] = snake[i - 1];
  9.     }
  10.     node newHead;
  11.     newHead = snake[0];
  12.     if (direction == eUp)
  13.     {
  14.         newHead.y--;
  15.     }
  16.     else if (direction == eDown)
  17.     {
  18.         newHead.y++;
  19.     }
  20.     else if (direction == eLeft)
  21.     {
  22.         newHead.x--;
  23.     }
  24.     else
  25.     {
  26.         newHead.x++;
  27.     }
  28.     snake[0= newHead;
  29.     //  返回尾节点
  30.     return tail;
  31. }

在主函数的循环中,作新的蛇头节点是否与食物节点重合的判断。若蛇头节点与食物节点重合,那么蛇会长大一节。将snakeMove函数返回的上一次尾结点添加到蛇尾后面,蛇节点长度加1。并且,食物被吃掉以后,应当重新生成新的食物,作为新的目标。

注意蛇不能无限生长而超过snake数组的长度,当length大于等于100时,吃到食物就不再增加长度了。

 

while (1)
{
    cleardevice();
    paintGrid();
    paintSnake(snake, length);
    paintFood(food);

    Sleep(500);

    changeDirection(&d);
    node lastTail = snakeMove(snake, length, d);
    //  新的蛇头节点是否与食物节点重合
    if (snake[0].x == food.x && snake[0].y == food.y)
    {
        //  限制snake节点最大长度
        if (length < 100)
        {
            //  已经吃到食物,长度+1
            snake[length] = lastTail;
            length++;
        }
        //  重新生成新的食物
        food = createFood(snake, length);
    }
}

图片

现阶段代码:

  1. #include <easyx.h>
  2. #include <stdio.h>
  3. #include <conio.h>
  4. #include <time.h>
  5. #define NODE_WIDTH 40
  6. //  节点
  7. typedef struct {
  8.     int x;
  9.     int y;
  10. }node;
  11. //  绘制网格
  12. //  横线(0, y), (800, y)   0 <= y <= 600
  13. //  竖线(x, 0),(x, 600)    0 <= x <= 800
  14. void paintGrid()
  15. {
  16.     //  横线
  17.     for (int y = 0; y < 600; y += NODE_WIDTH)
  18.     {
  19.         line(0, y, 800, y);
  20.     }
  21.     //  竖线
  22.     for (int x = 0; x < 800; x += NODE_WIDTH)
  23.     {
  24.         line(x, 0, x, 600);
  25.     }
  26. }
  27. void paintSnake(node* snake, int n)
  28. {
  29.     int lefttoprightbottom;
  30.     for (int i = 0; i < n; i++)
  31.     {
  32.         //  左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
  33.         left = snake[i].x * NODE_WIDTH;
  34.         top = snake[i].y * NODE_WIDTH;
  35.         //  右下角:【(网格x坐标 + 1* 网格宽度, (网格y坐标 + 1* 网格宽度】
  36.         right = (snake[i].x + 1* NODE_WIDTH;
  37.         bottom = (snake[i].y + 1* NODE_WIDTH;
  38.         //  通过左上角与右下角坐标绘制矩形
  39.         solidrectangle(lefttoprightbottom);
  40.     }
  41. }
  42. //  方向枚举
  43. enum direction
  44. {
  45.     eUp,
  46.     eDown,
  47.     eLeft,
  48.     eRight
  49. };
  50. //  蛇身体移动
  51. node snakeMove(node* snake, int length, int direction)
  52. {
  53.     //for (int i = 0; i < length; i++)
  54.     //    printf("(%d, %d)\n", snake[i].x, snake[i].y);
  55.     //  记录尾节点
  56.     node tail = snake[length - 1];
  57.     //  从尾结点开始,前一个节点覆盖后一个节点
  58.     //  0 1 2 3 4      0 1 2 3 4
  59.     //  E D C B A ---> E E D C B
  60.     for (int i = length - 1; i > 0; i--)
  61.     {
  62.         snake[i] = snake[i - 1];
  63.     }
  64.     //  下一个头节点
  65.     node newHead;
  66.     newHead = snake[0];
  67.     if (direction == eUp)
  68.     {
  69.         newHead.y--;
  70.     }
  71.     else if (direction == eDown)
  72.     {
  73.         newHead.y++;
  74.     }
  75.     else if (direction == eLeft)
  76.     {
  77.         newHead.x--;
  78.     }
  79.     else //  right
  80.     {
  81.         newHead.x++;
  82.     }
  83.     //  更新头节点
  84.     //  E D C B A ---> F E D C B
  85.     snake[0= newHead;
  86.     //for (int i = 0; i < length; i++)
  87.     //    printf("(%d, %d)\n", snake[i].x, snake[i].y);
  88.     //  返回尾节点
  89.     return tail;
  90. }
  91. //  键盘输入改变direction
  92. void changeDirection(enum direction* pD)
  93. {
  94.     //  检查输入缓存区中是否有数据
  95.     if (_kbhit() != 0)
  96.     {
  97.         //  _getch函数获取输入缓存区中的数据
  98.         char c = _getch();
  99.         //  判断输入并转向
  100.         switch (c)
  101.         {
  102.         case 'w':
  103.             //  向上移动
  104.             if (*pD != eDown)
  105.                 *pD = eUp;
  106.             break;
  107.         case 's':
  108.             //  向下移动
  109.             if (*pD != eUp)
  110.                 *pD = eDown;
  111.             break;
  112.         case 'a':
  113.             //  向左移动
  114.             if (*pD != eRight)
  115.                 *pD = eLeft;
  116.             break;
  117.         case 'd':
  118.             //  向右移动
  119.             if (*pD != eLeft)
  120.                 *pD = eRight;
  121.             break;
  122.         }
  123.     }
  124. }
  125. //  绘制食物
  126. /*
  127. (x * NODE_WIDTH, y * NODE_WIDTH)
  128. @-----------
  129. |          |
  130. |          |
  131. |          |
  132. |          |
  133. |          |
  134. -----------@ ((x + 1* NODE_WIDTH, (y + 1* NODE_WIDTH)
  135. */
  136. void paintFood(node food)
  137. {
  138.     int lefttoprightbottom;
  139.     left = food.x * NODE_WIDTH;
  140.     top = food.y * NODE_WIDTH;
  141.     right = (food.x + 1* NODE_WIDTH;
  142.     bottom = (food.y + 1* NODE_WIDTH;
  143.     setfillcolor(YELLOW);
  144.     solidrectangle(lefttoprightbottom);
  145.     setfillcolor(WHITE);
  146. }
  147. //  随机创建食物
  148. node createFood(node* snake, int length)
  149. {
  150.     node food;
  151.     while (1)
  152.     {
  153.         food.x = rand() % (800 / NODE_WIDTH);
  154.         food.y = rand() % (600 / NODE_WIDTH);
  155.         int i;
  156.         for (i = 0; i < length; i++)
  157.         {
  158.             if (snake[i].x == food.x && snake[i].y == food.y)
  159.             {
  160.                 break;
  161.             }
  162.         }
  163.         if (i < length)
  164.             continue;
  165.         else
  166.             break;
  167.     }
  168.     return food;
  169. }
  170. int main()
  171. {
  172.     initgraph(800600);
  173.     //  设置背景色
  174.     setbkcolor(RGB(164225202));
  175.     //  使用背景色清空窗体
  176.     cleardevice();
  177.     //  蛇节点坐标
  178.     node snake[100= { {57}, {47}, {37}, {27}, {17} };
  179.     //  蛇节点长度
  180.     int length = 5;
  181.     enum direction d = eRight;
  182.     //  食物
  183.     srand(unsigned int(time(NULL)));
  184.     node food = createFood(snake, length);
  185.     while (1)
  186.     {
  187.         //  清空整个窗体
  188.         cleardevice();
  189.         //  绘制网格
  190.         paintGrid();
  191.         //  绘制蛇节点
  192.         paintSnake(snake, length);
  193.         //  绘制食物
  194.         paintFood(food);
  195.         //  休眠500ms
  196.         Sleep(500);
  197.         //  获取键盘输入并将方向存储到变量d
  198.         changeDirection(&d);
  199.         node lastTail = snakeMove(snake, length, d);
  200.         //  新的蛇头节点是否与食物节点重合
  201.         if (snake[0].x == food.x && snake[0].y == food.y)
  202.         {
  203.             //  限制snake节点最大长度
  204.             if (length < 100)
  205.             {
  206.                 //  已经吃到食物, 长度+1
  207.                 snake[length= lastTail;
  208.                 length++;
  209.             }
  210.             //  重新生成新的食物
  211.             food = createFood(snake, length);
  212.         }
  213.     }
  214.     getchar();
  215.     closegraph();
  216.     return 0;
  217. }

7.游戏结束

最后,我们需要判断游戏是否结束。游戏结束的条件为:

  1. 蛇头吃到墙壁

  2. 蛇头吃到蛇身

如果满足以上两个条件,则游戏结束,并复位所有设置,重新开始游戏。

游戏网格x坐标区间为[0, 800 / NODE_WIDTH),即[0, 20)
游戏网格y坐标区间为[0, 600 / NODE_WIDTH),即[0, 19)

蛇头snake[0].x小于0或大于等于20,蛇头即吃到左边或右边的墙壁。
蛇头snake[0].y小于0或大于等于15,蛇头即吃到上边或下边的墙壁。

遍历除了蛇头外的所有蛇节点坐标,若有节点坐标与蛇头坐标一致,则表明蛇头吃到了蛇身。

将上面两个结束条件封装成isGameOver函数,若游戏结束则返回true,否则返回false

  1. bool isGameOver(node *snake, int length)
  2. {
  3.     //  是否撞墙
  4.     if (snake[0].x < 0 || snake[0].x > 800 / NODE_WIDTH)
  5.         return true;
  6.     if (snake[0].y < 0 || snake[0].y > 600 / NODE_WIDTH)
  7.         return true;
  8.     //  是否吃到蛇身
  9.     for (int i = 1; i < length; i++)
  10.     {
  11.         if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
  12.             return true;
  13.     }
  14.     return false;
  15. }

游戏结束后,需要调用reset函数,复位蛇节点坐标,蛇节点长度以及前进方向。

  1. void reset(node* snake, int *pLength, enum direction *d)
  2. {
  3.     snake[0= node{57};
  4.     snake[1= node{ 47 };
  5.     snake[2= node{ 37 };
  6.     snake[3= node{ 27 };
  7.     snake[4= node{ 17 };
  8.     *pLength = 5;
  9.     *= eRight;
  10. }

在主函数的循环中,添加游戏结束的判断。若游戏结束,复位各种设置。由于,蛇身坐标被复位,有可能与之前的食物坐标重合。因此,也应当重新生成食物。

完整源码请加群【881577770】获取!里面有一些资料可以帮助大家更好的学习,在学习C语言的过程中遇到任何的问题,都可以发出来一起讨论,大家都是学习C/C++的,或是转行,或是大学生,还有工作中想提升自己能力的前端党,如果你是正在学习C/C++的小伙伴可以加入学习。

  1. while (1)
  2. {
  3.     cleardevice();
  4.     paintGrid();
  5.     paintSnake(snake, length);
  6.     paintFood(food);
  7.     Sleep(500);
  8.     changeDirection(&d); 
  9.     node lastTail = snakeMove(snake, length, d);
  10.     if (snake[0].x == food.x && snake[0].y == food.y)
  11.     {
  12.         if (length < 100)
  13.         {
  14.             snake[length= lastTail;
  15.             length++;
  16.         }
  17.         food = createFood(snake, length);
  18.     } 
  19.     //  游戏是否结束
  20.     if (isGameOver(snake, length== true)
  21.     {
  22.         //  游戏结束,复位设置,重新生成食物
  23.         reset(snake, &length&d);
  24.         food = createFood(snake, length);
  25.     }
  26. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/451975
推荐阅读
相关标签
  

闽ICP备14008679号