简易贪吃蛇-基于C++和OpenCV的实现

2022/8/13 1:25:37

本文主要是介绍简易贪吃蛇-基于C++和OpenCV的实现,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

简易贪吃蛇-基于C++和OpenCV的实现

2022-08-12 11:20:01

1. 目的

做一些 application 方面 demo 的尝试。
使用 OpenCV 而不是 EasyX 或 SDL 的原因是: 对 OpenCV 比较熟悉觉得比较简单, 能够跨平台, 对于验证想法的小demo还是够用的。
代码大约200行。

主要思路是状态转移,即:当前帧和下一帧, 根据用户输入的方向键改变或维持方向, 根据方向情况更新 snake 的每个 body 部分(block)的位置, 清理屏幕和绘制更新位置后的 snake; 随机位置生成食物, 吃到食物后 body 需要增加一个元素。

使用到了 std::deque 双端队列数据结构, 解决了 snake 改变方向后计算新位置的问题。

2. 代码

#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>

class Point
{
public:
    int x;
    int y;
};

// Divide the whole image(canvas) into blocks
// block is the basic plot unit
class Block
{
public:
    int width;
    int height;
public:
    Block(int _height, int _width) : height(_height), width(_width) {}
};

// A randomly generated block
class Food
{
public:
    Point pos;
    cv::Scalar color;
    bool exist;
};

// User use arrow keys for snake movement
// Esc key corresponds to EXIT, means exit the game
enum Direction
{
    LEFT,
    RIGHT,
    TOP,
    BOTTOM,
    EXIT
};

class Snake
{
public:
    Snake(const Point& pos, Direction dir, cv::Scalar color);

public:
    std::vector<Point> points;
    std::deque<Direction> directions;
    cv::Scalar color;
};

Snake::Snake(const Point& init_point, Direction dir, cv::Scalar _color)
{
    points.push_back(init_point);
    directions.push_back(dir);
    color = _color;
}

Point get_center_point(int w, int h)
{
    Point pt;
    pt.x = w / 2;
    pt.y = h / 2;
    return pt;
}

void draw_block(cv::Mat& image, int block_x_idx, int block_y_idx, int block_width, int block_height, cv::Scalar color)
{
    for (int i = 1; i < block_height-1; i++)
    {
        int y = block_y_idx * block_height + i;
        for (int j = 1; j < block_width-1; j++)
        {
            int x = block_x_idx * block_width + j;
            image.ptr(y, x)[0] = color[0];
            image.ptr(y, x)[1] = color[1];
            image.ptr(y, x)[2] = color[2];
        }
    }
}

int get_random_int(int a, int b)
{
    return (rand() * 1.0 / RAND_MAX) * (b - a) + a;
}

bool is_same_block(const Point& p1, const Point& p2, const Block& block)
{
    int p1_block_idx_x = p1.x / block.width;
    int p1_block_idx_y = p1.y / block.height;

    int p2_block_idx_x = p2.x / block.width;
    int p2_block_idx_y = p2.y / block.height;

    return p1_block_idx_x == p2_block_idx_x && p1_block_idx_y == p2_block_idx_y;
}

void draw_image(cv::Mat& image, Snake& snake, Direction new_direction, const Block& block, Food& food)
{
    image = cv::Scalar(0); // clean canvas

    const int w = image.cols;
    const int h = image.rows;

    // TODO: 检查 block 大小是否符合窗口整除倍数关系

    int num_bodies = snake.points.size();
    printf("num bodies is %d\n", num_bodies);

    snake.directions.pop_back();
    snake.directions.push_front(new_direction);

    for (int i = 0; i < num_bodies; i++)
    {
        Point& point = snake.points[i];

        if (snake.directions[i] == RIGHT)
        {
            point.x = (point.x + block.width) % w;
        }
        else if (snake.directions[i] == LEFT)
        {
            point.x = (point.x - block.width + w) % w;
        }
        else if (snake.directions[i] == TOP)
        {
            point.y = (point.y - block.height + h) % h;
        }
        else if (snake.directions[i] == BOTTOM)
        {
            point.y = (point.y + block.height) % h;
        }
    }

    for (int i = 0; i < num_bodies; i++)
    {
        Point& point = snake.points[i];

        int block_x_idx = point.x / block.width;
        int block_y_idx = point.y / block.height;
        draw_block(image, block_x_idx, block_y_idx, block.width, block.height, snake.color);
    }

    // 随机生成食物的绘制
    Point& pos = food.pos;
    if (!food.exist)
    {
        pos.x = get_random_int(0, w);
        pos.y = get_random_int(0, h);
        food.exist = true;
    }

    if (is_same_block(snake.points[0], food.pos, block))
    {
        food.exist = false;
        Point new_point;
        Point& last_point = snake.points[num_bodies - 1];
        Direction last_direction = snake.directions[num_bodies - 1];

        int x_shift = 0;
        int y_shift = 0;
        if (last_direction == RIGHT)
        {
            x_shift = -1;
        }
        else if (last_direction == LEFT)
        {
            x_shift = 1;
        }
        else if (last_direction == TOP)
        {
            y_shift = 1;
        }
        else if (last_direction == BOTTOM)
        {
            y_shift = -1;
        }
        new_point.x = (last_point.x + x_shift * block.width) % w;
        new_point.y = (last_point.y + y_shift * block.height) % h;

        snake.points.push_back(new_point);
        snake.directions.push_back(last_direction);
    }
    else
    {
        int block_x_idx = pos.x / block.width;
        int block_y_idx = pos.y / block.height;
        draw_block(image, block_x_idx, block_y_idx, block.width, block.height, food.color);
    }
}

int main()
{
    const int w = 640;
    const int h = 480;
    cv::Size size;
    size.width = w;
    size.height = h;
    cv::Mat image(size, CV_8UC3);
    image = cv::Scalar(0);

    Block block(40, 40);
    Point center = get_center_point(w, h);
    Direction direction = RIGHT;
    Snake snake(center, direction, cv::Scalar(255, 255, 255));

    for (int i = 1; i < 4; i++)
    {
        Point pt;
        pt.y = center.y;
        pt.x = center.x - block.width * i;
        snake.points.push_back(pt);
        snake.directions.push_back(direction);
    }

    int i = 0;
    Food food;
    food.color = cv::Scalar(255, 0, 0);
    food.exist = false;
    const char* title = "snake";
    cv::namedWindow(title);
    while (true)
    {
        draw_image(image, snake, direction, block, food);
        i++;
        std::string image_path = std::to_string(i) + ".png";
        cv::imwrite(image_path, image);

        cv::imshow(title, image);
        int key_ret = cv::waitKey(500); // 官方文档说用 waitKeyEx; 下面的数值是我在 Linux KDE 下试出来的。
        printf("key_ret is %d\n", key_ret);
        switch (key_ret)
        {
        case 81:
            direction = LEFT;
            break;
        case 83:
            direction = RIGHT;
            break;
        case 82:
            direction = TOP;
            break;
        case 84:
            direction = BOTTOM;
            break;
        case 27:
            direction = EXIT;
            break;
        }
        if (direction == EXIT)
        {
            printf("Bye~\n");
            break;
        }
    }

    return 0;
}


这篇关于简易贪吃蛇-基于C++和OpenCV的实现的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程