简易贪吃蛇-基于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;
}
标签:idx,point,int,image,C++,OpenCV,贪吃蛇,snake,block 来源: https://www.cnblogs.com/zjutzz/p/16579204.html