• 简易贪吃蛇基于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语言漏洞基础(一)
    C语言函数篇(一)
    开发一种填表机器
    阿米洛varmilo键盘
    Flops
    助力高校计算机教育 —— 码云为老师推出免费高校版
    Numerical Methods LetThereBeMath
    Git Cookbook
  • 原文地址:https://www.cnblogs.com/zjutzz/p/16579204.html
Copyright © 2020-2023  润新知