找回密码
 立即注册
查看: 159|回复: 0

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

[复制链接]
发表于 2022-12-29 13:47 | 显示全部楼层 |阅读模式


试玩演示

对象分析



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


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



主函数流程图

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


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


算法实现

图形界面

图形界面的代码在主函数里:
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[i-1].x;
        snake.body.y = snake.body[i-1].y;
    }
    // 更新蛇头
    switch (snake.dir) {
    case RIGHT:
        snake.body[0].x++;
        break;
    case LEFT:
        snake.body[0].x--;
        break;
    case DOWN:
        snake.body[0].y++;
        break;
    case UP:
        snake.body[0].y--;
        break;
    }
}
蛇吃食物

结合上面的示意图理解蛇吃食物的算法实现。
void eatFood()
{
    int x = snake.body[0].x;
    int y = snake.body[0].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[snake.num].x = snake.body[snake.num-1].x;
        snake.body[snake.num].y = snake.body[snake.num-1].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[N*M];
    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[2].x = 1;
    snake.body[2].y = 1;
    snake.body[1].x = 2;
    snake.body[1].y = 1;
    snake.body[0].x = 3;
    snake.body[0].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[i-1].x;
        snake.body.y = snake.body[i-1].y;
    }
    // 更新蛇头
    switch (snake.dir) {
    case RIGHT:
        snake.body[0].x++;
        break;
    case LEFT:
        snake.body[0].x--;
        break;
    case DOWN:
        snake.body[0].y++;
        break;
    case UP:
        snake.body[0].y--;
        break;
    }
}

void eatFood()
{
    int x = snake.body[0].x;
    int y = snake.body[0].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[snake.num].x = snake.body[snake.num-1].x;
        snake.body[snake.num].y = snake.body[snake.num-1].y;
        snake.num++;
    }
}

bool snakeDie()
{
    int x = snake.body[0].x;
    int y = snake.body[0].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;
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 13:07 , Processed in 0.090946 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表