zt3ff3n 发表于 2022-12-29 13:47

C++贪吃蛇游戏开发实践



试玩演示

对象分析



我们首先需要确定一个像素点组成的地图(画布),要确定行数、列数和像素点大小。这个地图上将会有两个对象:蛇和食物。
蛇由头和身子组成,他们都有自己的位置,所以考虑使用位置点数组来存储。同时,还要存储蛇头方向和蛇身体节数。
而食物只要记录其位置即可。


地图上的内容就是蛇与食物,如果做图形界面,就是不断地在画布上绘制蛇与食物的过程。这里主要用到了绘制矩形与填充矩形的函数。



主函数流程图

蛇的移动
蛇的移动就是对蛇身数组的操作,可以分类讨论。如果是蛇身,我们从蛇尾开始观察,会发现每一节的位置将是前一节的位置。即前一节的位置直接赋值给后一节即可,但要注意要从蛇尾开始操作。如果是蛇头,将取决于当前蛇的方向,但要注意不能掉头。


蛇吃食物
蛇吃食物的判定是蛇头与食物位置是否重叠,如果吃到了要增长蛇身和重新投放食物。增长蛇身其实就是在蛇身数组的末尾再复制一份蛇尾。重新投放食物时要注意不能放在蛇身上。


算法实现

图形界面

图形界面的代码在主函数里:
initgraph(WIDTH, HEIGHT);
while (true) {
    ...
}
closegraph();
整个绘制对象只用到了绘制矩形

[*]setcolor(color);设置线颜色
[*]setfillcolor(color);设置填充颜色
[*]fillrect(x1, y1, x2, y2);绘制矩形需要左上角和右下角的点位置
方向枚举

enum Direction
{
    RIGHT = 39,
    LEFT = 37,
    DOWN = 40,
    UP = 38,
};
为了方便与键盘输入对应,将上下左右的键值进行枚举。
小蛇移动

结合上面的示意图理解小蛇移动的算法实现。
void snakeMove()
{
    // 更新蛇身
    for (int i = snake.num-1; i > 0; i--) {
      snake.body.x = snake.body.x;
      snake.body.y = snake.body.y;
    }
    // 更新蛇头
    switch (snake.dir) {
    case RIGHT:
      snake.body.x++;
      break;
    case LEFT:
      snake.body.x--;
      break;
    case DOWN:
      snake.body.y++;
      break;
    case UP:
      snake.body.y--;
      break;
    }
}
蛇吃食物

结合上面的示意图理解蛇吃食物的算法实现。
void eatFood()
{
    int x = snake.body.x;
    int y = snake.body.y;
    if (food.pos.x == x && food.pos.y == y) {
      // 食物重新投放
      bool flag;
      do {
            flag = false;
            food.pos.x = rand()%(M-2) + 1;
            food.pos.y = rand()%(N-2) + 1;
            for (int i = 0; i < snake.num; i++) {
                int x = snake.body.x, y = snake.body.y;
                if (x == food.pos.x && y == food.pos.y) {
                  flag = true;
                  break;
                }
            }
      } while (flag);
      // 蛇身增长
      snake.body.x = snake.body.x;
      snake.body.y = snake.body.y;
      snake.num++;
    }
}
代码实现

这里使用的是EGE库,需要提前安装。
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>
// 画布参数
const int N = 10;   // 行数
const int M = 10;   // 列数
const int L = 40;   // 像素点大小
const int T = 400;// 间隔
// 方向枚举
enum Direction
{
    RIGHT = 39,
    LEFT = 37,
    DOWN = 40,
    UP = 38,
};
// 点结构体
struct Point
{
    int x, y;
    int X() {return x*L;}
    int Y() {return y*L;}
};
// 蛇结构体
struct Snake
{
    int num;
    Point body;
    Direction dir;
} snake;
// 食物结构体
struct Food
{
    Point pos;
} food;
// 功能函数
void initSnake();   // 初始化小蛇
void initFood();    // 初始化食物
void drawBoard();   // 绘制网格
void drawSnake();   // 绘制小蛇
void drawFood();    // 绘制食物
void drawAll();   // 全部绘制
void snakeMove();   // 小蛇移动
void eatFood();   // 吃到食物
bool snakeDie();    // 小蛇撞死
void keyDown();   // 按键处理

// -------------------主函数----------------------
int main()
{
    initgraph(M*L, N*L);
    begin:
    initSnake();
    initFood();
    while (true) {
      if (kbhit()) {// 监听键盘按键
            keyDown();
      }
      snakeMove();
      if (snakeDie()) {
            MessageBox(getHWnd(),"Game Over!","rePlay",MB_OK);
            goto begin;
      }
      eatFood();
      drawAll();
      Sleep(T);
    }
    closegraph();
    return 0;
}

void initSnake()
{
    snake.num = 3;
    snake.dir = RIGHT;
    snake.body.x = 1;
    snake.body.y = 1;
    snake.body.x = 2;
    snake.body.y = 1;
    snake.body.x = 3;
    snake.body.y = 1;
}

void initFood()
{
    bool flag;
    do {
      flag = false;
      food.pos.x = rand()%(M-2) + 1;
      food.pos.y = rand()%(N-2) + 1;
      // 如果食物在蛇身上则重新投放
      for (int i = 0; i < snake.num; i++) {
            int x = snake.body.x, y = snake.body.y;
            if (x == food.pos.x && y == food.pos.y) {
                flag = true;
                break;
            }
      }
   } while (flag);
}

void drawBoard()
{
    for (int i = 0; i < M; i++) {
      for (int j = 0; j < N; j++) {
            setcolor(BLUE);
            setfillcolor(BLACK);
            fillrect(i*L, j*L, i*L+L, j*L+L);
      }
    }
    for (int i = 0; i < N; i++) {
      setcolor(BLUE);
      setfillcolor(EGERGB(0, 0, 139));
      fillrect(0, i*L, L, i*L+L);
      fillrect((M-1)*L, i*L, M*L, i*L+L);
    }
    for (int i = 0; i < M; i++) {
      setcolor(BLUE);
      setfillcolor(EGERGB(0, 0, 139));
      fillrect(i*L, 0, i*L+L, L);
      fillrect(i*L, (N-1)*L, i*L+L, N*L);
    }
}

void drawSnake()
{
    for (int i = 0; i < snake.num; i++) {
      setcolor(BLUE);
      setfillcolor(EGERGB(0,100,0));
      fillrect(snake.body.X(),snake.body.Y(),snake.body.X()+L,snake.body.Y()+L);
    }
}

void drawFood()
{
    setcolor(BLUE);
    setfillcolor(RED);
    fillrect(food.pos.X(),food.pos.Y(),food.pos.X()+L,food.pos.Y()+L);
}

void drawAll()
{
    drawBoard();
    drawFood();
    drawSnake();
}

void snakeMove()
{
    // 更新蛇身
    for (int i = snake.num-1; i > 0; i--) {
      snake.body.x = snake.body.x;
      snake.body.y = snake.body.y;
    }
    // 更新蛇头
    switch (snake.dir) {
    case RIGHT:
      snake.body.x++;
      break;
    case LEFT:
      snake.body.x--;
      break;
    case DOWN:
      snake.body.y++;
      break;
    case UP:
      snake.body.y--;
      break;
    }
}

void eatFood()
{
    int x = snake.body.x;
    int y = snake.body.y;
    if (food.pos.x == x && food.pos.y == y) {
      // 食物重新投放
      bool flag;
      do {
            flag = false;
            food.pos.x = rand()%(M-2) + 1;
            food.pos.y = rand()%(N-2) + 1;
            for (int i = 0; i < snake.num; i++) {
                int x = snake.body.x, y = snake.body.y;
                if (x == food.pos.x && y == food<span class="p">.pos.y) {
                  flag = true;
                  break;
                }
            }
      } while (flag);
      // 蛇身增长
      snake.body.x = snake.body.x;
      snake.body.y = snake.body.y;
      snake.num++;
    }
}

bool snakeDie()
{
    int x = snake.body.x;
    int y = snake.body.y;
    // 超出边框
    if (x >= (M-1) || y >= (N-1) || x <= 0 || y <= 0) {
      return true;
    }
    // 迟到自身
    for (int i = 1; i < snake.num; i++) {
      if (x == snake.body.x && y == snake.body.y) {
            return true;
      }
    }
    return false;
}

void keyDown()
{
    char userKey = 0;
    userKey = getch();
    switch (userKey) {
    case LEFT:
      if (snake.dir != RIGHT) {
            snake.dir = LEFT;
      }
      break;
    case RIGHT:
      if (snake.dir != LEFT) {
            snake.dir = RIGHT;
      }
      break;
    case UP:
      if (snake.dir != DOWN) {
            snake.dir = UP;
      }
      break;
    case DOWN:
      if (snake.dir != UP) {
            snake.dir = DOWN;
      }
    }
}
页: [1]
查看完整版本: C++贪吃蛇游戏开发实践