⭐大家好,我是Dark Falme Masker,学习了动画制作及键盘交互之后,我们就可以开动利用图形库写一个简单的贪吃蛇小游戏,增加学习乐趣。
⭐专栏:EasyX部分小游戏实现详细讲解
最终效果如下
首先包含头文件
#include
#include
#include
#include
✅我们将会使用rand函数生成随机数,来控制食物的随机生成,设置时间戳srand(time(NULL)),所以要包含time.h
- int main()
- {
- initgraph(800, 600);
- setbkcolor(RGB(164, 225, 202));
- cleardevice();
-
- closegraph();
- return 0;
- }
🏅创建窗体,更换背景颜色。
将窗体划分为各个边长为40的正方形小格,方便后续表示蛇和食物。
👉为了方便后续可以修改每个小格的边长,可以将其定义一下
#define NodeWidth 40
然后划分分界线,让后续绘制出蛇和食物位置更加清晰。
- void paintGrid()
- {
- for (int y = 0; y <= 600; y += NodeWidth)
- {
- line(0, y, 800, y);
- }
- for (int x = 0; x <= 800; x += NodeWidth)
- {
- line(x, 0, x, 600);
- }
- }
运行代码观察是否存在问题
📚接下来绘制蛇,蛇的起始长度设置为5,蛇是由五个连续的正方形小块构成,食物由一个正方形小块构成。我们可以盛情一个结构体变量,存放每个正方形小格子的左上角的坐标。
typedef struct {
int x;
int y;
}Node;
📜创建一个结构体数组,装着蛇的坐标信息,在第七行绘制出蛇的各个节点,如果数组第一个元素作为头节点,那么其余节点的x坐标是递减NodeWidth的,直接传格数,在绘制时乘以小方块宽度即可。
Node snake[100] = { {5,7},{4,7},{3,7},{2,7},{1,7} };
绘制蛇的函数
- void paintSnake(Node* snake, int n)
- {
- int left, top, right, bottom;
- for (int i = 0; i < n; i++)
- {
- left = snake[i].x * NodeWidth;
- top = snake[i].y * NodeWidth;
- right = (snake[i].x + 1) * NodeWidth;
- bottom = (snake[i].y + 1) * NodeWidth;
- solidrectangle(left, top, right, bottom);
- }
- }
💭传入结构体指针及社的节点个数,即可在画面中绘制出一个长度为5格的蛇。
但是蛇是会移动的,所以要加上键盘交互功能,控制蛇的方向移动。
创建枚举
enum direction
{
eUp,
eDown,
eLeft,
eRight
};
📑一共有三种状况,在蛇移动时判断键盘是否控制蛇的移位,通过控制结构体数组内x,y的值就可以实现蛇的移动。
默认direction为右
enum direction d = eRight;
判断玩家按下键盘的函数,传入枚举指针,改变枚举值,从而根据改变后的值改变更新蛇的位置的x,y坐标,再次绘制时设就可以转向。
- void changeDirection(enum direction* pD)
- {
- if (_kbhit() != 0)
- {
- char c = _getch();
- switch (c)
- {
- case'w':
- if (*pD != eDown)
- *pD = eUp;
- break;
- case's':
- if (*pD != eUp)
- *pD = eDown;
- break;
-
- case'a':
- if (*pD != eRight)
- *pD = eLeft;
- break;
-
- case'd':
- if (*pD != eLeft)
- *pD = eRight;
- break;
-
- }
- }
- }
🔥改变direction后就可以更改蛇节点的参数,要注意的是,蛇头不可以向正在移动的方向的相反方向移动,这里要进行判断。
👑在移动时,后续节点覆盖前边的节点的位置,根据蛇头位置及移动方向生成新的节点位置作为蛇头,设置这个函数的返回值为NODE类型,记录蛇尾节点。
✨返回蛇尾节点,利用一个结构体变量接收,在判断蛇头吃掉食物结点之后就可以将返回的节点续上,并且++蛇的长度。
- Node snakeMove(Node* snake, int length, int direction)
- {
- Node tail = snake[length - 1];
- for (int i = length - 1; i > 0; i--)
- {
- snake[i] = snake[i - 1];
- }
- Node NewHead;
- NewHead = snake[0];
- if (direction == eUp)
- {
- NewHead.y--;
- }
- else if (direction == eDown)
- {
- NewHead.y++;
- }
- else if (direction == eLeft)
- {
- NewHead.x--;
- }
- else if (direction == eRight)
- {
- NewHead.x++;
- }
- snake[0] = NewHead;
- return tail;
- }
创建食物
🏅创建食物是随机生成坐标,不可以超出创建的窗体大小而且不可以生成到蛇的身体上,可以使用三子棋的思路,设置死循环,直到生成满足我们需要的位置然后break。
- Node createFood(Node* snake, int length)
- {
- Node food;
-
- while (1)
- {
- food.x = rand() % (800 / NodeWidth);
- food.y = rand() % (600 / NodeWidth);
- int i;
- for (i = 0; i < length; i++)
- {
- if ((food.x == snake[i].x) && (food.y == snake[i].y))
- {
- break;
- }
- }
- if (i < length)
- continue;
- else
- break;
- }
- return food;
- }
生成完成后返回创建出的食物的结构体变量
然后绘制出食物,利用不同颜色作为区分。
- void paintFood(Node food)
- {
- int left, right, top, bottom;
- left = food.x * NodeWidth;
- top = food.y * NodeWidth;
- right = (food.x + 1) * NodeWidth;
- bottom = (food.y + 1) * NodeWidth;
- setfillcolor(YELLOW);
- solidrectangle(left, top, right, bottom);
- setfillcolor(WHITE);
- }
👉我们绘制出的蛇的身体是白色的,如果在这里更改了填充颜色,再次绘制蛇就变成和食物一样的颜色,所以在绘制食物后还要将填充颜色还原为白色。
判断是否吃掉食物及节点的续接。
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);
}
在循环中不断判断,吃掉食物后就创建出新的食物。
☁️判断游戏结束
如果蛇吃掉自己的身体或者蛇头越界了,就表示游戏失败
- bool IsGameover(Node* snake, int length)
- {
- if (snake[0].x<0 ||snake[0].x>(800 / NodeWidth))
- return true;
- if (snake[0].y<0 || snake[0].y>(600 / NodeWidth))
- return true;
- for (int i = 1; i < length; i++)//0改为1
- {
- if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
- return true;
- }
- return false;
- }
💡如果没有碰到墙或者遍历过程中蛇头没有和某蛇节点重合,才返回false,否则返回true,表示游戏失败。
判断失败后,重置蛇的节点,重新生成新的食物。
讲解到这里就结束啦
代码如下,大家可以运行看一看效果(用于使格子更加明显的线可以画也可以不画)
- #include
- #include
- #include
- #include
- #define NodeWidth 40
- typedef struct {
- int x;
- int y;
- }Node;
-
- void paintGrid()
- {
- for (int y = 0; y <= 600; y += NodeWidth)
- {
- line(0, y, 800, y);
- }
- for (int x = 0; x <= 800; x += NodeWidth)
- {
- line(x, 0, x, 600);
- }
- }
- void paintSnake(Node* snake, int n)
- {
- int left, top, right, bottom;
- for (int i = 0; i < n; i++)
- {
- left = snake[i].x * NodeWidth;
- top = snake[i].y * NodeWidth;
- right = (snake[i].x + 1) * NodeWidth;
- bottom = (snake[i].y + 1) * NodeWidth;
- solidrectangle(left, top, right, bottom);
- }
- }
- enum direction
- {
- eUp,
- eDown,
- eLeft,
- eRight
- };
- Node snakeMove(Node* snake, int length, int direction)
- {
- Node tail = snake[length - 1];
- for (int i = length - 1; i > 0; i--)
- {
- snake[i] = snake[i - 1];
- }
- Node NewHead;
- NewHead = snake[0];
- if (direction == eUp)
- {
- NewHead.y--;
- }
- else if (direction == eDown)
- {
- NewHead.y++;
- }
- else if (direction == eLeft)
- {
- NewHead.x--;
- }
- else if (direction == eRight)
- {
- NewHead.x++;
- }
- snake[0] = NewHead;
- return tail;
- }
- void changeDirection(enum direction* pD)
- {
- if (_kbhit() != 0)
- {
- char c = _getch();
- switch (c)
- {
- case'w':
- if (*pD != eDown)
- *pD = eUp;
- break;
- case's':
- if (*pD != eUp)
- *pD = eDown;
- break;
-
- case'a':
- if (*pD != eRight)
- *pD = eLeft;
- break;
-
- case'd':
- if (*pD != eLeft)
- *pD = eRight;
- break;
-
- }
-
- }
- }
-
- Node createFood(Node* snake, int length)
- {
- Node food;
-
- while (1)
- {
- food.x = rand() % (800 / NodeWidth);
- food.y = rand() % (600 / NodeWidth);
- int i;
- for (i = 0; i < length; i++)
- {
- if ((food.x == snake[i].x) && (food.y == snake[i].y))
- {
- break;
- }
- }
- if (i < length)
- continue;
- else
- break;
- }
- return food;
- }
-
- void paintFood(Node food)
- {
- int left, right, top, bottom;
- left = food.x * NodeWidth;
- top = food.y * NodeWidth;
- right = (food.x + 1) * NodeWidth;
- bottom = (food.y + 1) * NodeWidth;
- setfillcolor(YELLOW);
- solidrectangle(left, top, right, bottom);
- setfillcolor(WHITE);
- }
- bool IsGameover(Node* snake, int length)
- {
- if (snake[0].x<0 ||snake[0].x>(800 / NodeWidth))
- return true;
- if (snake[0].y<0 || snake[0].y>(600 / NodeWidth))
- return true;
- for (int i = 1; i < length; i++)//0改为1
- {
- if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
- return true;
- }
- return false;
- }
- void reset(Node* snake, int* length, enum direction* d)
- {
- snake[0] = Node{ 5,7 };
- snake[1] = Node{ 4,7 };
- snake[2] = Node{ 3,7 };
- snake[3] = Node{ 2,7 };
- snake[4] = Node{ 1,7 };
- *length = 5;
- *d = eRight;
- }
-
- int main()
- {
- initgraph(800, 600);
- setbkcolor(RGB(164, 25, 202));
- cleardevice();
- paintGrid();
-
- Node snake[100] = { {5,7},{4,7},{3,7},{2,7},{1,7} };
- int length = 5;
- enum direction d = eRight;
- srand(unsigned int(time(NULL)));
- Node food = createFood(snake, length);
-
- 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)
- {
- if (length < 100)
- {
- snake[length] = lastTail;
- length++;
- }
- food = createFood(snake, length);
- }
- if (IsGameover(snake, length) == true)
- {
- reset(snake, &length, &d);
- food = createFood(snake, length);
- }
- }
-
- closegraph();
- return 0;
- }