# C++编写的AI贪吃蛇
用C++做了个有AI功能的贪吃蛇小游戏,希望大家enjoy it.
# 总体概况
* 开发环境:VIsual Studio 2017
* 开发语言:C++ 和 少许Windows API
* 运行环境:Windows 10
# 效果图示:
AI模式演示
![](https://camo.githubusercontent.com/740278b7ebc8504470bb47c9db1983c99ae4e2d949f99bd5644881e5b708d828/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d663635343532633730656330616632622e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970)
# 整体规划+原理
![](https://camo.githubusercontent.com/7c1647a3dc1382da98f8d8e7d5e78d35c2c1e3607d4259fde32f2b5ea32f31bc/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d663865643530633866333936363863392e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)
大体上可以分为图上所示的几个类。整个程序设计的原理就是:主函数死循环,不断刷新打印贪吃蛇和食物。这样每循环一次,就类似电影里面的一帧,最终显示的效果就是蛇会动起来。
## 01 初始化工作-游戏设置
游戏设置和相关初始化放在了一个类里面,并进行了静态声明。主要设置了游戏窗口的长和款。并在GameInit()函数里面设置了窗口大小,隐藏光标,初始化随机数种子等。代码如下:
```c++
//游戏设置相关模块,把函数都放到一个类里面了。函数定义为static静态成员,不生成实体也可以直接调用
class GameSetting
{
public:
//游戏窗口的长宽
static const int window_height = 40;
static const int window_width = 80;
public:
static void GameInit()
{
//设置游戏窗口大小
char buffer[32];
sprintf_s(buffer, "mode con cols=%d lines=%d",window_width, window_height);
system(buffer);
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
//初始化随机数种子
srand((unsigned int)time(0));
}
};
```
用到了几个相关的Windows API,本文不做过多介绍,大家百度即可。
## 02 打印信息类
该类主要是用来打印一些游戏相关信息的。该类大体如下:
![](https://camo.githubusercontent.com/aba3f1944f562c3f9ccbc365df84922819faba85c954c65de1e2bdd66827b8e1/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d333165663737633364366632646132382e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)
下面挑几个重点的来讲:
### 2.1 画地图边界
这个函数主要是根据上面所给的游戏窗口长宽来打印地图边界的。其中还划分了几个区域,主要用来放不同的信息的。
```c++
//画地图边界
static void DrawMap()
{
system("cls");
int i, j;
for (i = 0; i < GameSetting::window_width; i++)
cout << "#";
cout << endl;
for (i = 0; i < GameSetting::window_height-2; i++)
{
for (j = 0; j < GameSetting::window_width; j++)
{
if (i == 13 && j >= GameSetting::window_width - 29)
{
cout << "#";
continue;
}
if (j == 0 || j == GameSetting::window_width - 29 || j == GameSetting::window_width-1)
{
cout << "#";
}
else
cout << " ";
}
cout << endl;
}
for (i = 0; i < GameSetting::window_width; i++)
cout << "#";
}
```
划分区域如下图,#就是边框了:
![](https://camo.githubusercontent.com/963bcfaeb54e65c0e55406937a88b3d2393d0a0b09468c9cf2ab2ae55052c4a3/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d343939326565336531313863313936662e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)
### 2.2 画出分数和模式
该函数主要是在右上角画出成绩和游戏模式的,在绘制之前会进行刷新处理。先清除,再重新打印。用到了一个gotoxy()函数。这个函数主要是移动光标到(x, y)坐标处的。关于(x, y)的位置,根据实际情况调整即可。
```c++
//画分数
static void DrawScore(int score)
{
gotoxy(GameSetting::window_width - 22+14, 6);
cout << " ";
gotoxy(GameSetting::window_width - 22+14, 4);
cout << " ";
gotoxy(GameSetting::window_width - 22, 6);
cout << "当前玩家分数: " << score << endl;
gotoxy(GameSetting::window_width - 22, 4);
cout << "当前游戏速度: " << 10 - speed / 25 << endl;
}
```
## 03 食物类
食物类定义了食物的坐标,随机生成规则,和画出食物等一系列操作。其中食物坐标我们用了一个结构体:
```c++
typedef struct
{
int x;
int y;
}COORDINATE;
```
该结构体两个成员,分别保存坐标的(x, y)。蛇身的坐标也会用到这个结构体。 有关食物类的大体如下:
![](https://camo.githubusercontent.com/e9d73aa57521421698a93446926af77edb080f043aa7dc288dd817451dca566b/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d373965386364363539373538353739302e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)
下面我们还是挑几个重点来讲。
### 3.1 随机生成食物
随机生成食物,原则上不允许食物出现在蛇身的位置上,如果有。我们重新生成。注意地图的范围,就是区域左边一块。实际情况根据自身的地图范围来调整食物坐标的范围,注意不要越界。用rand()函数获得随机坐标。代码如下:
```c++
void RandomXY(vector<COORDINATE> & coord)
{
m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1;
m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1;
unsigned int i;
//原则上不允许食物出现在蛇的位置上,如果有,重新生成
for (i = 0; i < coord.size(); i++)
{
//食物出现在蛇身的位置上。重新生成
if (coord[i].x == m_coordinate.x && coord[i].y == m_coordinate.y)
{
m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1;
m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1;
i = 0;
}
}
}
```
然后,在构造函数里面传入蛇身的坐标。即可生成食物。
### 3.2 画出食物
画出食物比较简单了,gotoxy到随机生成的坐标之后,cout就行。我们在这还设置了一个食物颜色为红色。代码如下:
```c++
void DrawFood()
{
setColor(12, 0);
gotoxy(m_coordinate.x, m_coordinate.y);
cout << "@";
setColor(7, 0);
}
```
## 04 贪吃蛇类
定义贪吃蛇的移动,打印,吃食物等等。这节课我们暂时不讨论AI功能,先把手动操作的贪吃蛇做了跑起来,下节课再做AI功能的介绍。该类大体如下:
![](https://camo.githubusercontent.com/993299464296c69ce0a19169929ff4e4d84ed9416b27eee441254fbd93e9c283/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f31303338363934302d313632346366363438343334353266372e6a70673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)
### 4.1 成员变量
成员变量m_direction记录每次移动的方向。m_is_alive记录贪吃蛇是否还活着。m_coordinate则是贪吃蛇身体坐标的记录。贪吃蛇是一节一节的,整条蛇必然是由许多节组成的。因此用了一个vector来存储蛇身,每节类型是