ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-08-12 11:32:51  阅读:217  来源: 互联网

标签:idx point int image C++ OpenCV 贪吃蛇 snake block


简易贪吃蛇-基于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;
}

标签:idx,point,int,image,C++,OpenCV,贪吃蛇,snake,block
来源: https://www.cnblogs.com/zjutzz/p/16579204.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有