Windows 平台基于 C++ 开发贪吃蛇游戏

前言

本文将介绍如何在 Windows 平台使用 C++ 开发贪吃蛇游戏。

版本说明

开发工具版本
C++ 标准 11
Windows 系统 Win 10
Visual Studio2019

游戏代码

Wall

  • Wall.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
#ifndef _WALL_HEAD
#define _WALL_HEAD

#include <iostream>
#include "windows.h"

using namespace std;

class Wall
{
public:
enum {
ROW = 26, // 行数
COL = 26 // 列数
};

// 构造函数
Wall();

// 析构函数
~Wall();

// 初始化墙
void initWall();

// 将墙画到控制台
void drawWall();

// 显示游戏结束
void showGameOver();

// 定位控制台的光标位置
void gotoXY(int x, int y);

// 获取墙的内容
char getWall(int x, int y);

// 设置墙的内容
void setWall(int x, int y, char c);

private:
// 定义墙的二维数组
char gameArray[ROW][COL];

// 定义显示器句柄
HANDLE hOut1 = GetStdHandle(STD_OUTPUT_HANDLE);
};

#endif
  • Wall.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
#include "wall.h"

#include "windows.h"


Wall::Wall() {

}

Wall::~Wall() {

}

void Wall::initWall()
{
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1) {
gameArray[i][j] = '*';
}
else {
gameArray[i][j] = ' ';
}
}
}
}

void Wall::drawWall() {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
cout << gameArray[i][j] << " ";
}
if (i == 5) {
cout << "create by clay";
}
if (i == 6) {
cout << "a: left";
}
if (i == 7) {
cout << "d: right";
}
if (i == 8) {
cout << "w: top";
}
if (i == 9) {
cout << "s: bottom";
}
cout << "\n";
}
}

void Wall::showGameOver()
{
string tip = "GameOver!";
for (int i = 0; i < tip.length(); i++) {
setWall(12, 8 + i, tip.at(i));
}
}

void Wall::gotoXY(int x, int y) {
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(hOut1, pos);
}

char Wall::getWall(int x, int y)
{
return gameArray[x][y];
}

void Wall::setWall(int x, int y, char c)
{
gameArray[x][y] = c;
// 通过定位光标修改控制台的内容
gotoXY(y * 2, x);
cout << c;
}

Food

  • Food.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
#ifndef _FOOD_HEAD
#define _FOOD_HEAD

#include <iostream>
#include "wall.h"

using namespace std;

class Food
{
public:

// 构造函数
Food(Wall & tempWall);

// 食物的X坐标
int x;

// 食物的Y坐标
int y;

// 墙
Wall& wall;

// 设置食物
void setFood();

};

#endif
  • Food.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "food.h"
#include "wall.h"

Food::Food(Wall& tempWall) :wall(tempWall)
{

}

void Food::setFood()
{
while (true) {
x = rand() % (Wall::ROW - 2) + 1;
y = rand() % (Wall::COL - 2) + 1;

// 如果随机的位置是蛇头或者蛇身,则重新生成随机数
if (wall.getWall(x, y) == ' ') {
wall.setWall(x, y, '#');
break;
}
}
}

Snake

  • 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
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
#ifndef _SNAKE_HEAD
#define _SNAKE_HEAD

#include <iostream>
#include "wall.h"
#include "food.h"

using namespace std;

class Snake
{

public:

// 构造函数
Snake(Wall& tempWall, Food& tempFood);

// 移动的方向
enum {
TOP = 'w',
BOTTOM = 's',
LEFT = 'a',
RIGHT = 'd'
};

// 蛇的初始长度
const int INIT_LENGTH = 3;

// 节点
struct Point {
int x;
int y;
Point* next;
};

// 头节点
Point* pHead;

// 墙
Wall& wall;

// 食物
Food& food;

// 死亡标识
bool isDead;

// 循环追尾
bool isRool;

// 初始化蛇
void initSnake();

// 销毁所有节点
void destroy();

// 移动蛇
bool move(char key);

// 添加节点
void addPoint(int x, int y);

// 删除尾节点
void deleteEndPoint();

// 获取游戏分数
int getScore();

// 显示游戏得分
void showScore();

// 获取蛇的长度
int getLength();

// 获取等待的时间(毫秒)
int getSleepTime();

};

#endif
  • 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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include "snake.h"

Snake::Snake(Wall& tempWall, Food& tempFood) :wall(tempWall), food(tempFood) {
pHead = NULL;
isDead = false;
isRool = false;
}

void Snake::initSnake()
{
// 销毁所有节点
this->destroy();
// 设置蛇身节点
for (int i = 0; i < INIT_LENGTH; i++) {
this->addPoint(12, 10 + i);
}
}

void Snake::destroy()
{
Point* pCur = pHead;
while (pHead != NULL) {
pCur = pHead->next;
pHead = pCur;
}
}

bool Snake::move(char key)
{
isRool = false;
int x = pHead->x;
int y = pHead->y;

// 计算移动的位置
switch (key) {
case TOP:
x--;
break;
case BOTTOM:
x++;
break;
case LEFT:
y--;
break;
case RIGHT:
y++;
break;
default:
break;
}

// 查找尾节点
Point* pPre = pHead;
Point* pEnd = pHead->next;
while (pEnd->next != NULL) {
pPre = pPre->next;
pEnd = pEnd->next;
}

// 判断如果移动下一步后碰到的是尾巴,则不应该死亡
if (pEnd->x == x && pEnd->y == y) {
isRool = true;
}
else {
// 判断移动的位置是否合法,不合法则游戏结束
if (wall.getWall(x, y) == '*' || wall.getWall(x, y) == '=') {
// 标记死亡状态
isDead = true;
// 死后多走一步
addPoint(x, y);
// 显示游戏结束
wall.showGameOver();
// 显示游戏得分
showScore();
return false;
}
}

// 判断移动成功后,是否吃到食物
if (wall.getWall(x, y) == '#') {
// 添加节点
addPoint(x, y);
// 重新设置食物的位置
food.setFood();
}
else {
// 添加节点
addPoint(x, y);
// 删除尾节点
deleteEndPoint();
// 重新显示头节点
if (isRool) {
wall.setWall(x, y, '@');
}
}

return true;
}

void Snake::addPoint(int x, int y)
{
// 新的头节点
Point* newPoint = new Point();
newPoint->x = x;
newPoint->y = y;

// 如果旧的头结点不为空,则将其改为身子节点
if (pHead != NULL) {
wall.setWall(pHead->x, pHead->y, '=');
}

// 更新头节点
newPoint->next = pHead;
pHead = newPoint;
wall.setWall(newPoint->x, newPoint->y, '@');
}

void Snake::deleteEndPoint()
{
// 蛇身有两个节点以上,才允许执行删除操作
if (pHead == NULL || pHead->next == NULL) {
return;
}

// 查找尾节点
Point* pPre = pHead;
Point* pEnd = pHead->next;
while (pEnd->next != NULL) {
pPre = pPre->next;
pEnd = pEnd->next;
}

// 删除尾节点
wall.setWall(pEnd->x, pEnd->y, ' ');
delete pEnd;
pEnd = NULL;

pPre->next = NULL;
}

int Snake::getScore()
{
int length = getLength();
int score = (length - INIT_LENGTH) * 100;
return score;
}

void Snake::showScore() {
wall.gotoXY(0, Wall::ROW);
cout << "\n游戏得分:" << getScore() << endl;
}

int Snake::getLength() {
int length = 0;
Point* pCur = pHead;
while (pCur != NULL) {
length++;
pCur = pCur->next;
}
return length;
}

int Snake::getSleepTime() {
// 根据蛇的长度,控制等待时间,以此达到控制游戏难度的目的
int length = getLength();
if (length <= 5) {
return 200;
}
else if (length <= 10) {
return 100;
}
else {
return 50;
}
}

Main

  • main.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
#include <iostream>
#include <ctime>
#include "wall.h"
#include "food.h"
#include "snake.h"
#include <conio.h>
#include <windows.h>

using namespace std;

int main()
{
// 设置随机种子
srand((unsigned int)time(NULL));

// 上一次移动的方向
char preKey = NULL;

// 初始化墙
Wall wall;
wall.initWall();
wall.drawWall();

// 初始化食物
Food food(wall);
food.setFood();

// 初始化蛇
Snake snake(wall, food);
snake.initSnake();

// 显示游戏得分
snake.showScore();

while (true) {
// 接收用户输入
char key = _getch();

// 判断蛇是否已经死亡
if (snake.isDead) {
continue;
}

// 如果第一次按了左键,则撤销蛇的移动操作
if (key == snake.LEFT && preKey == NULL) {
continue;
}

do {
// 判断移动的方向是否有效
if (key == snake.TOP || key == snake.BOTTOM || key == snake.LEFT || key == snake.RIGHT) {
// 判断本次移动的方向与上次是否冲突
if ((key == snake.LEFT && preKey == snake.RIGHT) ||
(key == snake.RIGHT && preKey == snake.LEFT) ||
(key == snake.TOP && preKey == snake.BOTTOM) ||
(key == snake.BOTTOM && preKey == snake.TOP)) {
break;
}

// 记录上一次移动的方向
preKey = key;

// 让蛇移动一步
if (snake.move(key)) {
// 显示游戏得分
snake.showScore();
// 设置等待时间(控制游戏难度)
Sleep(snake.getSleepTime());
}
else {
break;
}
}
else {
// 强制将错误按键变为上一次的移动方向
key = preKey;
break;
}
} while (!_kbhit);
}

return 0;
}

游戏效果

expressvpn-speedtest