0%

C++使用OpenCV实现简易贪吃蛇

项目树状结构

├── CMakeLists.txt ├── include │ ├── snake.h │ └── ui.h └── src ├── main.cpp └── snake.cpp

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
cmake_minimum_required(VERSION 3.0)
project(SNAKE)

set(CMAKE_CXX_STANDARD 14)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../../bin)

find_package(OpenCV REQUIRED)

include_directories(./include)
aux_source_directory(./src SRC_FILES)

add_executable(7 ${SRC_FILES})
target_link_libraries(7 ${OpenCV_LIBS})

include

ui.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#ifndef UI_H
#define UI_H

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

#define BOX_WIDTH 10

class UI {
public:
static void LoadMap() { Get().loadMap(); }
static inline void SetFood() { Get().setFood(); }
static inline void SetFood(const cv::Point &pt) { Get().setFood(pt); }
static inline void SetBody(const cv::Point &pt, bool head) { Get().setBody(pt, head); }
static inline void DelBody(const cv::Point &pt) { Get().delBody(pt); }
static inline bool OuttaRange(const cv::Point &pt) { return Get().outtaRange(pt); }
static inline bool Occupied(const cv::Point &pt) { return Get().occupied(pt); }
static inline void UpdateFrame(bool end) { Get().updateFrame(end); }
static inline cv::Point GetFoodPos() { return Get().getFoodPos(); }
static inline cv::Point RandomPos() { return Get().randomPos(); }

static UI &Get() {
static UI map;
return map;
}
UI(const UI &) = delete;
UI &operator=(const UI &) = delete;

private:
UI() = default;
void loadMap() {
this->gameWindow_ = cv::Mat(cv::Size(1000, 800), CV_8UC3, cv::Scalar(240, 250, 255));
this->mapRange_ = cv::Mat(cv::Size(800, 600), CV_8UC3, cv::Scalar(255, 255, 240));
this->mapBox_ = cv::Mat(cv::Size(BOX_WIDTH, BOX_WIDTH), CV_8UC3, cv::Scalar(255, 255, 240));
this->foodBox_ = cv::Mat(cv::Size(BOX_WIDTH, BOX_WIDTH), CV_8UC3, cv::Scalar(93, 66, 255));
this->snakeHeadBox_ = cv::Mat(cv::Size(BOX_WIDTH, BOX_WIDTH), CV_8UC3, cv::Scalar(128, 0, 0));
this->snakeBodyBox_ = cv::Mat(cv::Size(BOX_WIDTH, BOX_WIDTH), CV_8UC3, cv::Scalar(211, 85, 186));
this->maxPoint_ = cv::Point(mapRange_.cols + 100 - BOX_WIDTH, mapRange_.rows + 100 - BOX_WIDTH);
this->minPoint_ = cv::Point(100, 100);
this->mapRange_.copyTo(this->gameWindow_(cv::Rect(100, 100, 800, 600)));

cv::putText(this->gameWindow_, "Simple Snake", cv::Point(410, 65), 3, 0.8, cv::Scalar(255, 66, 93), 2);
cv::putText(this->gameWindow_, "Press Esc to suicide.", cv::Point(100, 75), 1, 1, cv::Scalar(205, 0, 0), 1);
cv::line(this->gameWindow_, cv::Point(100, 100), cv::Point(900, 100), cv::Scalar(230, 240, 230), 1);
cv::line(this->gameWindow_, cv::Point(900, 100), cv::Point(900, 700), cv::Scalar(230, 240, 230), 1);
cv::line(this->gameWindow_, cv::Point(900, 700), cv::Point(100, 700), cv::Scalar(230, 240, 230), 1);
cv::line(this->gameWindow_, cv::Point(100, 700), cv::Point(100, 100), cv::Scalar(230, 240, 230), 1);

srand((unsigned int)time(nullptr));
}

inline void setFood() {
this->foodPos_ = randomPos();
copyBox(this->foodBox_, this->gameWindow_, foodPos_);
this->occupation_[this->foodPos_.x / 10][this->foodPos_.y / 10] = true;
}

inline void setFood(const cv::Point &pt) {
this->foodPos_ = pt;
copyBox(this->foodBox_, this->gameWindow_, foodPos_);
this->occupation_[this->foodPos_.x / 10][this->foodPos_.y / 10] = true;
}

inline void setBody(const cv::Point &pt, bool head) {
head ? copyBox(this->snakeHeadBox_, this->gameWindow_, pt) : copyBox(this->snakeBodyBox_, this->gameWindow_, pt);
this->occupation_[pt.x / 10][pt.y / 10] = true;
}

inline void delBody(const cv::Point &pt) {
copyBox(this->mapBox_, this->gameWindow_, pt);
this->occupation_[pt.x / 10][pt.y / 10] = false;
}

inline void updateFrame(bool end) {
if (!end) {
cv::imshow("Snake", this->gameWindow_);
} else {
cv::putText(this->gameWindow_, ">.< GameOver!", cv::Point(375, 375), 4, 1, cv::Scalar(93, 66, 255), 2);
cv::imshow("Snake", this->gameWindow_);
}
}

inline cv::Point randomPos() {
cv::Point pos;
pos.x = (rand() % ((this->maxPoint_.x + BOX_WIDTH - this->minPoint_.x) / 10) + this->minPoint_.x / 10) * 10;
pos.y = (rand() % ((this->maxPoint_.y + BOX_WIDTH - this->minPoint_.y) / 10) + this->minPoint_.y / 10) * 10;
return pos;
}

inline cv::Point getFoodPos() { return this->foodPos_; }
inline bool occupied(const cv::Point &pt) { return this->occupation_[pt.x / 10][pt.y / 10]; }

inline bool outtaRange(const cv::Point &pt) { return pt.x >= this->minPoint_.x && pt.x <= this->maxPoint_.x &&
pt.y >= this->minPoint_.y && pt.y <= this->maxPoint_.y; }

static inline void copyBox(const cv::Mat &src, cv::Mat &dst, const cv::Point &pt) {
src.copyTo(dst(cv::Rect(pt.x, pt.y, BOX_WIDTH, BOX_WIDTH)));
}

private:
cv::Mat gameWindow_;
cv::Mat mapRange_;
cv::Mat mapBox_;
cv::Mat foodBox_;
cv::Mat snakeHeadBox_;
cv::Mat snakeBodyBox_;

private:
cv::Point minPoint_;
cv::Point maxPoint_;
cv::Point foodPos_;

private:
bool occupation_[100][80] = {false};
};

#endif // UI_H

snake.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef SNAKE_H
#define SNAKE_H

#include "ui.h"

class Snake{
public:
explicit Snake(int moveSpeed);
Snake() = delete;
Snake(const Snake&) = delete;
Snake& operator= (const Snake&) = delete;
public:
void Start();
private:
void control(int key);
bool eating();
private:
std::vector<cv::Point> body_;
int moveSpeed_;
int lastestMotion_;
bool end_;
};

#endif // SNAKE_H

src

snake.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include "snake.h"

Snake::Snake(int moveSpeed) {
UI::LoadMap();
cv::Point headPos = cv::Point(500, 400);
bool flag = UI::OuttaRange(cv::Point(headPos.x + BOX_WIDTH, headPos.y));
cv::Point neckPos = flag ? cv::Point(headPos.x + BOX_WIDTH, headPos.y) : cv::Point(headPos.x - BOX_WIDTH, headPos.y);
this->moveSpeed_ = moveSpeed;
this->lastestMotion_ = flag ? 97 : 100;
this->body_.emplace_back(headPos);
this->body_.emplace_back(neckPos);
this->end_ = false;
UI::SetBody(headPos, true);
UI::SetBody(neckPos, false);
UI::SetFood();
}

void Snake::Start() {
UI::UpdateFrame(false);
while (true) {
int motion = cv::waitKey(this->moveSpeed_);
control(motion);
if (this->end_ || !UI::OuttaRange(this->body_[0])) {
UI::UpdateFrame(true);
cv::waitKey(0);
return;
}
if (this->body_.size() > 3)
for (int i = 3; i < this->body_.size(); ++i)
if (this->body_[0] == this->body_[i]) {
UI::UpdateFrame(true);
cv::waitKey(0);
return;
}
if (this->lastestMotion_ != -1) {
UI::SetBody(this->body_[0], true);
UI::SetBody(this->body_[1], false);
if (!eating()) {
UI::DelBody(this->body_.back());
UI::SetFood(UI::GetFoodPos());
this->body_.pop_back();
} else {
cv::Point pt = UI::RandomPos();
while(UI::Occupied(pt)){
pt = UI::RandomPos();
}
UI::SetFood(pt);
}
}
UI::UpdateFrame(false);
}
}

void Snake::control(int key) {
switch (key) {
case 97: // A
if (lastestMotion_ != 100) {
lastestMotion_ = 97;
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x - BOX_WIDTH, this->body_[0].y));
} else {
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x + BOX_WIDTH, this->body_[0].y));
}
break;
case 100: // D
if (lastestMotion_ != 97) {
lastestMotion_ = 100;
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x + BOX_WIDTH, this->body_[0].y));
} else {
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x - BOX_WIDTH, this->body_[0].y));
}
break;
case 119: // W
if (lastestMotion_ != 115) {
lastestMotion_ = 119;
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x, this->body_[0].y - BOX_WIDTH));
} else {
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x, this->body_[0].y + BOX_WIDTH));
}
break;
case 115: // S
if (lastestMotion_ != 119) {
lastestMotion_ = 115;
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x, this->body_[0].y + BOX_WIDTH));
} else {
this->body_.insert(this->body_.begin(), cv::Point(this->body_[0].x, this->body_[0].y - BOX_WIDTH));
}
break;
case 27:
this->end_ = true;
break;
default:
control(this->lastestMotion_);
break;
}
}

bool Snake::eating() { return bool(this->body_.front() == UI::GetFoodPos()); }

main.cpp

1
2
3
4
5
6
#include "snake.h"
int main() {
Snake snake(200); // delay(ms)
snake.Start();
return 0;
}

运行效果