自己动手写贪吃蛇AI——附源码和程序

从第一次构想写贪吃蛇AI,到现在两个学期才落实下来。其实之前用手机C4droid控制台写过一次,不是很理想,这次把这个坑填了吧,非要问有什么意义的话,自娱自乐罢了。
再来回顾一下这张动图,非常的流畅,看着也赏心悦目,个人很喜欢这种简洁的UI,就是贪吃蛇身体间留有空隙,这样能分清蛇身经过的路线,不然全部挤在一块看起来就不是很舒服了。
在这里插入图片描述
想写贪吃蛇AI当然需要先写个贪吃蛇游戏了。语言选择C++,开发环境Visual Studio 2017,EasyX图形库。

贪吃蛇 Version 1.0 实现贪吃蛇游戏功能

让游戏动起来就是屏幕的重绘(清屏,重画)。所以我们需要存储蛇的坐标信息,然后根据每一步的操作,更新信息,在屏幕上显示出来。

  1. 游戏数据结构
    因为链表的删除和插入元素的效率比数组高,也为了实现蛇无长度限制,这里选用stl中的链表来存储蛇的坐标。(链表不能随机存取,所以后面AI算法效率较低)
  • 方向
enum Direct { RIGHT = 77, DOWN = 80, LEFT = 75, UP = 72 };
  • 坐标
// (x,y)为坐标,step记录该点到目标点的步数
struct Coor {
	int x, y, step;
};
  • 食物类
class Food {
public:
	Coor coor;			// 食物坐标
	bool EXISTFOOD;		// 存在食物
	// 产生(0,0)~(limx,limy)的坐标
	void RandCoor(int limx, int limy);
};
  • 蛇类
class Snake {	
public:
	int length;			// 蛇长度
	int speed;			// 蛇速度
	int acc;			// 加速度
	list<Coor> coor;	// 蛇身坐标
	Direct direct;		// 蛇当前方向
	void Move() ;						// 蛇移动
	void TurnDirect(char cmd = 0x00);	// 蛇转向
};

  • 游戏类
    为了方便偷个懒就不封装了成员直接public
class SnakeGame {
public:
	Snake snake;
	Food food;
	void GameInit() ;	// 游戏初始化
	void DrawMap() ;	// 画地图
	void EatFood();		// 处理吃食物
	void CreatFood();	// 产生食物
	bool GameOver();	// 判断游戏结束
	bool EatFullScreen();				// 是否吃满食物
	bool inBorder(Coor coor);			// 坐标在方框里
	bool onSnake_ExceptHead(Coor coor);	// 坐标在蛇上/不包括蛇头
	void ShowGameEnd();				   	// 游戏结束画面
	void ShowGameInfo();  	 			// 绘制游戏相关信息
  1. 游戏主循环放在main里了
    这里调用方法的顺序要整理好,不然达不到我们想要的结果。
SnakeGame SG;
while (true) {
	SG.EatFood();				// 处理吃食物 
	SG.CreatFood();				// 生成食物
	SG.snake.TurnDirect();		// 蛇转向
	SG.snake.Move();			// 蛇移动
	if(SG.GameOver()){			// 如果游戏结束
		SG.ShowGameEnd();		// 显示游戏结束标志
		_getch();				// 按任意键继续
		SG.GameInit();
	}
	SG.ShowGameInfo();			// 显示游戏信息
	SG.DrawMap();				// 画图
	Sleep(1000/(SG.snake.speed+SG.snake.acc));	// 挂起进程,控制游戏速度
}
  1. 步骤分析
  2. 完整代码
    为了方便就使用单文件书写了,snake.cpp内容如下:
///////////////////////////////////////////////////
// 程序名称:贪吃蛇AI
// 编译环境:Visual Studio 2017,EasyX_2019测试版
// 作  者:柯西丶不是你 <[email protected]>
//
#include <iostream>
#include <conio.h>
#include <ctime>
#include <list>
#include <queue>
#include <set>
#include <graphics.h>
using namespace std;
// 窗口参数
const int WINDOW_WIDTH = 640;	// 窗口宽度
const int WINDOW_HEIGHT = 480;	// 窗口高度

// 游戏参数
const int MAP_ROWNUM = 20;		// 地图行数
const int MAP_COLNUM = 20;		// 地图列数
const int GRIDGAP = 5;			// 格子间隙
const int GRID = WINDOW_HEIGHT / MAP_COLNUM;	// 大格子宽度
const int _GRID = GRID - 2 * GRIDGAP;			// 小格子宽度
const int SPEED = 5;			// 蛇初始速度
const int ACC = 0;				// 蛇加速度(其实是速度增量)

// 游戏数据结构
enum Direct { RIGHT = 77, DOWN = 80, LEFT = 75, UP = 72 };
struct Coor {
	int x, y, step;
	Coor() { step = 0; }
	Coor(int _x, int _y, int _step = 0) {
		x = _x;
		y = _y;
		step = _step;
	}
	bool operator==(const Coor &t)const {
		return (x == t.x) && (y == t.y);
	}
	bool operator!=(const Coor &t)const {
		return (x != t.x) || (y != t.y);
	}
	bool operator<(const Coor &t)const {
		return x == t.x ? y < t.y : x < t.x;
	}
};

// 各类实现
class Food {
public:
	Coor coor;			// 食物坐标
	bool EXISTFOOD;		// 存在食物
	Food() { EXISTFOOD = true; }
	~Food() { EXISTFOOD = false; }
	// 产生(0,0)~(limx,limy)的坐标
	void RandCoor(int limx, int limy) {
		coor.x = rand() % limx;
		coor.y = rand() % limy;
	}
};

class Snake {	
public:
	int length;			// 蛇长度
	int speed;			// 蛇速度
	int acc;			// 加速度
	list<Coor> coor;	// 蛇身坐标
	Direct direct;		// 蛇当前方向
	Snake() {}
	~Snake() {}
	// 蛇移动
	void Move() {
		Coor head = coor.front();
		switch (direct) {
		case UP:	head.y--;	break;
		case DOWN:	head.y++;	break;
		case LEFT:	head.x--;	break;
		case RIGHT:	head.x++;	break;
		}
		coor.pop_back();		// 尾巴出列
		coor.push_front(head);	// 新头入列
	}
	// 蛇转向
	void TurnDirect(char cmd = 0x00) {
		// 使用while而不是if
		if (cmd == 0x00) {
			while (_kbhit()) {
				cmd = _getch();
			}
		}
		// 两次方向相同设置加速度
		if (cmd == direct)
			acc = ACC;
		else
			acc = 0;
		switch (cmd) 
		{
		case UP:
			if (direct != DOWN)
				direct = UP;
			break;
		case DOWN:
			if (direct != UP)
				direct = DOWN;
			break;
		case LEFT:
			if (direct != RIGHT)
				direct = LEFT;
			break;
		case RIGHT:
			if (direct != LEFT)
				direct = RIGHT;
			break;
		}
	}
};

class SnakeGame {
private:
	bool isAI;
public:
	Snake snake;
	Food food;
	SnakeGame() { GameInit(); }
	~SnakeGame() {}
	// 游戏初始化
	void GameInit() {
		isAI = false;
		snake.length = 3;
		snake.speed = SPEED;
		snake.acc = 0;
		snake.direct = UP;
		while (!snake.coor.empty()) {
			snake.coor.pop_back();
		}
		Coor body(MAP_ROWNUM / 2, MAP_COLNUM / 2);
		for (int i = 0; i < snake.length;i++) {
			snake.coor.push_back(body);
			body.y++;
		}
		srand((unsigned)time(0));
		food.RandCoor(MAP_COLNUM, MAP_ROWNUM);
	}
	// 画地图
	void DrawMap() {
		setfillcolor(DARKGRAY);
		solidrectangle(0, 0, GRID*MAP_COLNUM, GRID*MAP_ROWNUM);
		setcolor(LIGHTGRAY);
		line(WINDOW_HEIGHT, 0, WINDOW_HEIGHT, WINDOW_HEIGHT);
		// 画食物
		setfillcolor(RED);
		fillrectangle(food.coor.x*GRID + GRIDGAP, food.coor.y*GRID + GRIDGAP,
			(food.coor.x+1)*GRID - GRIDGAP, (food.coor.y+1)*GRID - GRIDGAP);
		// 画蛇
		setfillcolor(WHITE);
		Coor temp = snake.coor.front();
		for (auto iter : snake.coor) {
			solidrectangle(iter.x*GRID + GRIDGAP, iter.y*GRID + GRIDGAP,
				(iter.x + 1)*GRID - GRIDGAP, (iter.y + 1)*GRID - GRIDGAP);
			// 画缝隙
			int iter_x = iter.x*GRID + GRIDGAP;
			int iter_y = iter.y*GRID + GRIDGAP;
			int temp_x = temp.x*GRID + GRIDGAP;
			int temp_y = temp.y*GRID + GRIDGAP;
			if (temp.x == iter.x) {
				if (iter.y > temp.y) {
					solidrectangle(temp_x, temp_y + _GRID, iter_x + _GRID, iter_y);
				}
				if (iter.y < temp.y) {
					solidrectangle(iter_x, iter_y + _GRID, temp_x + _GRID, temp_y);
				}
			}
			if (temp.y==iter.y) {
				if (iter.x > temp.x) {
					solidrectangle(temp_x + _GRID, temp_y, iter_x, iter_y + _GRID);
				}
				if (iter.x < temp.x) {
					solidrectangle(iter_x + _GRID, iter_y, temp_x, temp_y + _GRID);
				}
			}
			temp = iter;
		}
	}
	// 处理吃食物
	void EatFood() {
		Coor head = snake.coor.front();
		if (head==food.coor) {
			food.EXISTFOOD = false;
			snake.coor.push_back(snake.coor.back());
			snake.length++;
		}
	}
	// 产生食物
	void CreatFood() {
		if (food.EXISTFOOD == false) {
			list<Coor>::const_iterator iter;
			while (true) {
				food.RandCoor(MAP_COLNUM,MAP_ROWNUM);
				if (!onSnake(food.coor))
					break;
			}
			food.EXISTFOOD = true;
		}
	}
	// 判断游戏结束
	bool GameOver() {
		Coor head = snake.coor.front();
		if (!inBorder(head)|| onSnake_ExceptHead(head)|| EatFullScreen()) {
			return true;
		}
		return false;
	}
	// 是否吃满食物
	bool EatFullScreen() {
		return snake.length == MAP_COLNUM*MAP_ROWNUM;
	}
	// 游戏结束画面
	void ShowGameEnd() {
		setfillcolor(BLACK);
		// 在屏幕中央输出字符串
		TCHAR *str_end = _T("GAME OVER!");
		if (EatFullScreen())
			str_end = _T("YOU WIN!");
		int Tstrlen = (int)_tcslen(str_end);
		outtextxy(WINDOW_HEIGHT / 2 - Tstrlen * 20 / 4, WINDOW_HEIGHT / 2 - 20 / 4, str_end);
	}
	// 绘制游戏相关信息
	void ShowGameInfo(){
		// 填充黑色底
		setfillcolor(BLACK);
		solidrectangle(WINDOW_HEIGHT, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
		// 尺寸信息
		TCHAR str_mapsize[20];
		swprintf_s(str_mapsize, _T("MAPSIZE:  %d×%d"), MAP_COLNUM, MAP_ROWNUM);
		settextcolor(WHITE);
		outtextxy(WINDOW_HEIGHT + 20, 20, str_mapsize);
		// AI状态信息
		TCHAR *str_ai = _T("AI:  OFF");
		if (isAI == true)	str_ai = _T("AI:  ON");
		outtextxy(WINDOW_HEIGHT + 20, 50, str_ai);
		// 速度信息
		TCHAR str_speed[20];
		swprintf_s(str_speed, _T("SPEED:  %d"), snake.speed+snake.acc);
		outtextxy(WINDOW_HEIGHT + 20, 80, str_speed);
		// 加速度信息
		TCHAR str_acc[20];
		swprintf_s(str_acc, _T("ACC:  %d"), snake.acc);
		outtextxy(WINDOW_HEIGHT + 20, 110, str_acc);
		// 蛇长度信息
		TCHAR str_life[20];
		swprintf_s(str_life, _T("LIFE:  %d/%d"), snake.length,MAP_COLNUM*MAP_ROWNUM);
		outtextxy(WINDOW_HEIGHT + 20, 140, str_life);
		// 保存结点,使用引用是为了处理尾巴设置为可走结点
		const Coor &snakeHead = snake.coor.front();
		const Coor &snakeTail = snake.coor.back();
		// 食物坐标信息
		TCHAR str_food[20];
		swprintf_s(str_food, _T("食物坐标:  (%d,%d)"), food.coor.x, food.coor.y);
		outtextxy(WINDOW_HEIGHT + 20, 200, str_food);
		// 蛇头坐标信息
		TCHAR str_head[20];
		swprintf_s(str_head, _T("蛇头坐标:  (%d,%d)"), snakeHead.x, snakeHead.y);
		outtextxy(WINDOW_HEIGHT + 20, 230, str_head);
		// 蛇尾坐标信息
		TCHAR str_tail[20];
		swprintf_s(str_tail, _T("蛇尾坐标:  (%d,%d)"), snakeTail.x, snakeTail.y);
		outtextxy(WINDOW_HEIGHT + 20, 260, str_tail);
		// 方向信息
		TCHAR *str_dir;
		switch (snake.direct) {
		case RIGHT:	str_dir = _T("方向:  RIGHT");	break;
		case DOWN:	str_dir = _T("方向:  DOWN");		break;
		case LEFT:	str_dir = _T("方向:  LEFT"); 	break;
		case UP:	str_dir = _T("方向:  UP");		break;
		default:	str_dir = _T("方向:  None");
		}
		outtextxy(WINDOW_HEIGHT + 20, 290, str_dir);
		// 游戏操作说明
		outtextxy(WINDOW_HEIGHT + 20, 320, _T("操作说明:"));
		outtextxy(WINDOW_HEIGHT + 20, 340, _T("使用方向键控制"));
		outtextxy(WINDOW_HEIGHT + 20, 360, _T("长按方向键加速"));
		outtextxy(WINDOW_HEIGHT + 20, 380, _T("上  :      ↑"));
		outtextxy(WINDOW_HEIGHT + 20, 400, _T("下  :      ↓"));
		outtextxy(WINDOW_HEIGHT + 20, 420, _T("左  :      ←"));
		outtextxy(WINDOW_HEIGHT + 20, 440, _T("右  :      →"));
	}
	// 坐标在蛇上/包括蛇头蛇尾
	bool onSnake(Coor coor) {
		for (auto iter : snake.coor) {
			if (coor == iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在蛇上/不包括蛇头
	bool onSnake_ExceptHead(Coor coor){
		auto iter = snake.coor.begin();
		for (iter++; iter != snake.coor.end(); iter++) {
			if (coor == *iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在蛇上/不包括蛇尾
	bool onSnake_ExceptTail(Coor coor) {
		auto iter = snake.coor.begin();
		auto flag = --snake.coor.end();
		for (; iter != flag; iter++) {
			if (coor == *iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在方框里
	bool inBorder(Coor coor)const {
		return (coor.x >= 0 && coor.x < MAP_COLNUM && coor.y >= 0 && coor.y < MAP_ROWNUM);
	}
};

// 程序入口
int main() {
	// 加参数, SHOWCONSOLE开启控制台
	initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
	// 开启双缓冲绘图
	BeginBatchDraw();
	SnakeGame SG;
	_getch();
	while (true) {
		SG.EatFood();
		SG.CreatFood();
		SG.snake.TurnDirect(SG.getNextCmd());
		SG.snake.Move();
		SG.ShowGameInfo();
		if (SG.GameOver()) {
			
			_getch();
			SG.GameInit();
		}
		SG.DrawMap();
		FlushBatchDraw();
		Sleep(1000/(SG.snake.speed+SG.snake.acc));
	}
	EndBatchDraw();
	
	closegraph();
	return 0;
}

贪吃蛇 Version 2.0 添加AI控制
  1. 完整代码
///////////////////////////////////////////////////
// 程序名称:贪吃蛇AI
// 编译环境:Visual Studio 2017,EasyX_2019测试版
// 作  者:柯西丶不是你 <[email protected]>
// 创建时间:2019-2-23
// 最后修改:2019-3-8
// 说明:使用Debug产生卡顿可以换Release。
//
// 待改进产生食物算法,后期弃用最短距离,
// 假设食物在右下角或许可以接近想要的效果,待解决回环问题即有多个解
// 优化算法速度
//
#include <iostream>
#include <conio.h>
#include <ctime>
#include <list>
#include <queue>
#include <set>
#include <graphics.h>
using namespace std;
// 窗口参数
const int WINDOW_WIDTH = 640;	// 窗口宽度
const int WINDOW_HEIGHT = 480;	// 窗口高度

// 游戏参数
const int MAP_ROWNUM = 20;		// 地图行数
const int MAP_COLNUM = 20;		// 地图列数
const int GRIDGAP = 5;			// 格子间隙
const int GRID = WINDOW_HEIGHT / MAP_COLNUM;	// 大格子宽度
const int _GRID = GRID - 2 * GRIDGAP;			// 小格子宽度
const int SPEED = 50;			// 蛇初始速度
const int ACC = 20;				// 蛇加速度(其实是速度增量)

// 游戏数据结构
enum Direct { RIGHT = 77, DOWN = 80, LEFT = 75, UP = 72 };
struct Coor {
	int x, y, step;
	Coor() { step = 0; }
	Coor(int _x, int _y, int _step = 0) {
		x = _x;
		y = _y;
		step = _step;
	}
	bool operator==(const Coor &t)const {
		return (x == t.x) && (y == t.y);
	}
	bool operator!=(const Coor &t)const {
		return (x != t.x) || (y != t.y);
	}
	bool operator<(const Coor &t)const {
		return x == t.x ? y < t.y : x < t.x;
	}
};

// 各类实现
class Food {
public:
	Coor coor;			// 食物坐标
	bool EXISTFOOD;		// 存在食物
	Food() { EXISTFOOD = true; }
	~Food() { EXISTFOOD = false; }
	// 产生(0,0)~(limx,limy)的坐标
	void RandCoor(int limx, int limy) {
		coor.x = rand() % limx;
		coor.y = rand() % limy;
	}
};

class Snake {	
public:
	int length;			// 蛇长度
	int speed;			// 蛇速度
	int acc;			// 加速度
	list<Coor> coor;	// 蛇身坐标
	Direct direct;		// 蛇当前方向
	Snake() {}
	~Snake() {}
	// 蛇移动
	void Move() {
		Coor head = coor.front();
		switch (direct) {
		case UP:	head.y--;	break;
		case DOWN:	head.y++;	break;
		case LEFT:	head.x--;	break;
		case RIGHT:	head.x++;	break;
		}
		coor.pop_back();		// 尾巴出列
		coor.push_front(head);	// 新头入列
	}
	// 蛇转向
	void TurnDirect(char cmd = 0x00) {
		// 使用while而不是if
		if (cmd == 0x00) {
			while (_kbhit()) {
				cmd = _getch();
			}
		}
		// 两次方向相同设置加速度
		if (cmd == direct)
			acc = ACC;
		else
			acc = 0;
		switch (cmd) 
		{
		case UP:
			if (direct != DOWN)
				direct = UP;
			break;
		case DOWN:
			if (direct != UP)
				direct = DOWN;
			break;
		case LEFT:
			if (direct != RIGHT)
				direct = LEFT;
			break;
		case RIGHT:
			if (direct != LEFT)
				direct = RIGHT;
			break;
		}
	}
};

class SnakeGame {
private:
	bool isAI;
public:
	Snake snake;
	Food food;
	SnakeGame() { GameInit(); }
	~SnakeGame() {}
	// 游戏初始化
	void GameInit() {
		isAI = false;
		snake.length = 3;
		snake.speed = SPEED;
		snake.acc = 0;
		snake.direct = UP;
		while (!snake.coor.empty()) {
			snake.coor.pop_back();
		}
		Coor body(MAP_ROWNUM / 2, MAP_COLNUM / 2);
		for (int i = 0; i < snake.length;i++) {
			snake.coor.push_back(body);
			body.y++;
		}
		srand((unsigned)time(0));
		food.RandCoor(MAP_COLNUM, MAP_ROWNUM);
	}
	// 画地图
	void DrawMap() {
		setfillcolor(DARKGRAY);
		solidrectangle(0, 0, GRID*MAP_COLNUM, GRID*MAP_ROWNUM);
		setcolor(LIGHTGRAY);
		line(WINDOW_HEIGHT, 0, WINDOW_HEIGHT, WINDOW_HEIGHT);
		// 画食物
		setfillcolor(RED);
		fillrectangle(food.coor.x*GRID + GRIDGAP, food.coor.y*GRID + GRIDGAP,
			(food.coor.x+1)*GRID - GRIDGAP, (food.coor.y+1)*GRID - GRIDGAP);
		// 画蛇
		setfillcolor(WHITE);
		Coor temp = snake.coor.front();
		for (auto iter : snake.coor) {
			solidrectangle(iter.x*GRID + GRIDGAP, iter.y*GRID + GRIDGAP,
				(iter.x + 1)*GRID - GRIDGAP, (iter.y + 1)*GRID - GRIDGAP);
			// 画缝隙
			int iter_x = iter.x*GRID + GRIDGAP;
			int iter_y = iter.y*GRID + GRIDGAP;
			int temp_x = temp.x*GRID + GRIDGAP;
			int temp_y = temp.y*GRID + GRIDGAP;
			if (temp.x == iter.x) {
				if (iter.y > temp.y) {
					solidrectangle(temp_x, temp_y + _GRID, iter_x + _GRID, iter_y);
				}
				if (iter.y < temp.y) {
					solidrectangle(iter_x, iter_y + _GRID, temp_x + _GRID, temp_y);
				}
			}
			if (temp.y==iter.y) {
				if (iter.x > temp.x) {
					solidrectangle(temp_x + _GRID, temp_y, iter_x, iter_y + _GRID);
				}
				if (iter.x < temp.x) {
					solidrectangle(iter_x + _GRID, iter_y, temp_x, temp_y + _GRID);
				}
			}
			temp = iter;
		}
	}
	// 处理吃食物
	void EatFood() {
		Coor head = snake.coor.front();
		if (head==food.coor) {
			food.EXISTFOOD = false;
			snake.coor.push_back(snake.coor.back());
			snake.length++;
		}
	}
	// 产生食物
	void CreatFood() {
		if (food.EXISTFOOD == false) {
			list<Coor>::const_iterator iter;
			while (true) {
				food.RandCoor(MAP_COLNUM,MAP_ROWNUM);
				if (!onSnake(food.coor))
					break;
			}
			food.EXISTFOOD = true;
		}
	}
	// 判断游戏结束
	bool GameOver() {
		Coor head = snake.coor.front();
		if (!inBorder(head)|| onSnake_ExceptHead(head)|| EatFullScreen()) {
			return true;
		}
		return false;
	}
	// 是否吃满食物
	bool EatFullScreen() {
		return snake.length == MAP_COLNUM*MAP_ROWNUM;
	}
	// 游戏结束画面
	void ShowGameEnd() {
		setfillcolor(BLACK);
		/*fillrectangle(0, WINDOW_HEIGHT / 4, WINDOW_HEIGHT, WINDOW_HEIGHT / 4 * 3);*/
		// 在屏幕中央输出字符串
		TCHAR *str_end = _T("GAME OVER!");
		if (EatFullScreen())
			str_end = _T("YOU WIN!");
		// x64会报警告:“size_t”转换到“int”,可能丢失数据
		int Tstrlen = (int)_tcslen(str_end);
		outtextxy(WINDOW_HEIGHT / 2 - Tstrlen * 20 / 4, WINDOW_HEIGHT / 2 - 20 / 4, str_end);
	}
	// 绘制游戏相关信息
	void ShowGameInfo(){
		// 填充黑色底
		setfillcolor(BLACK);
		solidrectangle(WINDOW_HEIGHT, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
		// 尺寸信息
		TCHAR str_mapsize[20];
		swprintf_s(str_mapsize, _T("MAPSIZE:  %d×%d"), MAP_COLNUM, MAP_ROWNUM);
		settextcolor(WHITE);
		outtextxy(WINDOW_HEIGHT + 20, 20, str_mapsize);
		// AI状态信息
		TCHAR *str_ai = _T("AI:  OFF");
		if (isAI == true)	str_ai = _T("AI:  ON");
		outtextxy(WINDOW_HEIGHT + 20, 50, str_ai);
		// 速度信息
		TCHAR str_speed[20];
		swprintf_s(str_speed, _T("SPEED:  %d"), snake.speed+snake.acc);
		outtextxy(WINDOW_HEIGHT + 20, 80, str_speed);
		// 加速度信息
		TCHAR str_acc[20];
		swprintf_s(str_acc, _T("ACC:  %d"), snake.acc);
		outtextxy(WINDOW_HEIGHT + 20, 110, str_acc);
		// 蛇长度信息
		TCHAR str_life[20];
		swprintf_s(str_life, _T("LIFE:  %d/%d"), snake.length,MAP_COLNUM*MAP_ROWNUM);
		outtextxy(WINDOW_HEIGHT + 20, 140, str_life);
		// 保存结点,使用引用是为了处理尾巴设置为可走结点
		const Coor &snakeHead = snake.coor.front();
		const Coor &snakeTail = snake.coor.back();
		// 测试
		TCHAR str_test[20];
		swprintf_s(str_test, _T("找尾巴步数:  %d"), canFindPath(snakeHead, snakeTail));
		outtextxy(WINDOW_HEIGHT + 20, 170, str_test);
		// 食物坐标信息
		TCHAR str_food[20];
		swprintf_s(str_food, _T("食物坐标:  (%d,%d)"), food.coor.x, food.coor.y);
		outtextxy(WINDOW_HEIGHT + 20, 200, str_food);
		// 蛇头坐标信息
		TCHAR str_head[20];
		swprintf_s(str_head, _T("蛇头坐标:  (%d,%d)"), snakeHead.x, snakeHead.y);
		outtextxy(WINDOW_HEIGHT + 20, 230, str_head);
		// 蛇尾坐标信息
		TCHAR str_tail[20];
		swprintf_s(str_tail, _T("蛇尾坐标:  (%d,%d)"), snakeTail.x, snakeTail.y);
		outtextxy(WINDOW_HEIGHT + 20, 260, str_tail);
		// 方向信息
		TCHAR *str_dir;
		switch (snake.direct) {
		case RIGHT:	str_dir = _T("方向:  RIGHT");	break;
		case DOWN:	str_dir = _T("方向:  DOWN");		break;
		case LEFT:	str_dir = _T("方向:  LEFT"); 	break;
		case UP:	str_dir = _T("方向:  UP");		break;
		default:	str_dir = _T("方向:  None");
		}
		outtextxy(WINDOW_HEIGHT + 20, 290, str_dir);
		// 游戏操作说明
		outtextxy(WINDOW_HEIGHT + 20, 320, _T("操作说明:"));
		outtextxy(WINDOW_HEIGHT + 20, 340, _T("使用方向键控制"));
		outtextxy(WINDOW_HEIGHT + 20, 360, _T("长按方向键加速"));
		outtextxy(WINDOW_HEIGHT + 20, 380, _T("上  :      ↑"));
		outtextxy(WINDOW_HEIGHT + 20, 400, _T("下  :      ↓"));
		outtextxy(WINDOW_HEIGHT + 20, 420, _T("左  :      ←"));
		outtextxy(WINDOW_HEIGHT + 20, 440, _T("右  :      →"));
	}

	// AI相关
	void OpenAI() { isAI = true; }
	void CloseAI() { isAI = false; }
	// 得到下一步AI指令--
	char getNextCmd() {
		if (isAI == false)
			return 0x00;
		char cmd = 0x00;
		// 如果能找到食物
		if (canFindFood(snake.coor.front())) {
			// 模拟一条蛇
			SnakeGame simulate = *this;
			// 去吃食物
			while (simulate.food.EXISTFOOD) {
				simulate.snake.TurnDirect(simulate.NextCmdToFood());
				simulate.snake.Move();
				simulate.EatFood();
			}
			// 如果吃完还能找到尾巴/到尾巴的距离大于1
			if (simulate.canFindPath(simulate.snake.coor.front(),simulate.snake.coor.back())>1) {
				// 真正去吃
				cmd = NextCmdToFood();
			}
			else {
				// 吃完找不到
				cmd = NextCmdToFarAway(snake.coor.front());
			}
		}
		else {
			// 找不到食物
			cmd = NextCmdToFarAway(snake.coor.front());
		}
		return cmd;
	}
	// 存在路径返回步数,不存在返回-1
	int canFindPath(const Coor& _start,const Coor& _end) {
		if (_start == _end) {
			return 0;
		}
		const int next[4][2] = { {1,0} ,{0,1}, {-1,0}, {0,-1} }; // 右下左上
		queue<Coor> bfs_que;
		set<Coor> snake_set;
		Coor tcoor;
		bfs_que.push(_start);
		for (auto iter : snake.coor) {
			snake_set.insert(iter);
		}
		// 如果终点是尾巴就把尾巴置为可走结点 *
		// 调用时需要使用引用
		if (&_end == &snake.coor.back()) {
			snake_set.erase(_end);
		}
		while (!bfs_que.empty()) {
			for (int k = 0; k < 4; k++) {
				tcoor.x = bfs_que.front().x + next[k][0];
				tcoor.y = bfs_que.front().y + next[k][1];
				// 超出地图进入下次循环
				if (!inBorder(tcoor)) {
					continue;
				}
				// 无障碍没有走过的结点加入队列
				if (!snake_set.count(tcoor)) {
					tcoor.step= bfs_que.front().step+1;
					snake_set.insert(tcoor);
					bfs_que.push(tcoor);
				}
				// 到达目标返回步数
				if (tcoor == _end) {
					return tcoor.step;
				}
			}
			bfs_que.pop();
		}
		return -1;
	}
	// 存在到尾巴的路径
	bool canFindTail(Coor _start) {
		return canFindPath(_start, snake.coor.back()) >= 0 ? true : false;
	}
	// 存在到食物的路径
	bool canFindFood(Coor _start) {
		return canFindPath(_start, food.coor) >= 0 ? true : false;
	}
	// 最短距离吃食物
	char NextCmdToFood() {
		const int next[4][2] = { { 1,0 } ,{ 0,1 },{ -1,0 },{ 0,-1 } }; // 右下左上
		// 得到头部周围4点
		Coor aroundPoint[4];
		for (int i = 0; i < 4; i++) {
			aroundPoint[i].x = snake.coor.front().x + next[i][0];
			aroundPoint[i].y = snake.coor.front().y + next[i][1];
			if (!inBorder(aroundPoint[i]) || onSnake_ExceptTail(aroundPoint[i]))	// 特殊处理尾巴
				aroundPoint[i].step = -1;
			else
				aroundPoint[i].step = canFindPath(aroundPoint[i], food.coor);
		}
		// 选出最近的走
		int minstep_index = 0;
		for (int i = 1; i < 4; i++) {
			// 是0表示是食物直接走
			// 如果是-1不参与比较
			if (aroundPoint[minstep_index].step == -1)
				minstep_index = i;
			if (aroundPoint[i].step != -1) {
				if (aroundPoint[i].step < aroundPoint[minstep_index].step) {
					minstep_index = i;
				}
			}
		}
		char ret = 0x00;
		// 返回操作,找不到食物就返回0x00什么都不做
		if (aroundPoint[minstep_index].step != -1)
			ret = RetCmd(aroundPoint[minstep_index]);
		return ret;
	}
	// 后期吃食物用最远

	// 远离食物--待改进**
	char NextCmdToFarAway(const Coor &coor) {
		const int next[4][2] = { { 1,0 } ,{ 0,1 },{ -1,0 },{ 0,-1 } }; // 右下左上
		// 得到周围四点曼哈顿距离
		Coor aroundPoint[4];
		for (int i = 0; i < 4; i++) {
			aroundPoint[i].x = snake.coor.front().x + next[i][0];
			aroundPoint[i].y = snake.coor.front().y + next[i][1];
			// 曼哈顿距离,有障碍的点置为-1
			if(canFindTail(aroundPoint[i])&& inBorder(aroundPoint[i])&& !onSnake_ExceptTail(aroundPoint[i]))
				aroundPoint[i].step = getManhattanDistance(aroundPoint[i], coor);
			else // *这里蛇尾应设置为可行
				aroundPoint[i].step = -1;
		}
		// 能到尾巴,远离食物
		// 另一种策略是选离尾巴远的
		int maxstep_index = 0;
		for (int i = 1; i < 4; i++) {
			// 等于号很重要
			if ((aroundPoint[i].step >= aroundPoint[maxstep_index].step)) {
				maxstep_index = i;
			}
		}
		char cmd = 0x00;
		if (aroundPoint[maxstep_index].step != -1)
			cmd = RetCmd(aroundPoint[maxstep_index]);
		return cmd;
	}
	// 最远距离追尾巴
	char NextCmdToTail() {
		const int next[4][2] = { { 1,0 } ,{ 0,1 },{ -1,0 },{ 0,-1 } }; // 右下左上
		// 得到头部周围4点
		Coor aroundPoint[4];
		for (int i = 0; i < 4; i++) {
			aroundPoint[i].x = snake.coor.front().x + next[i][0];
			aroundPoint[i].y = snake.coor.front().y + next[i][1];
			if (inBorder(aroundPoint[i]) && !onSnake_ExceptTail(aroundPoint[i]))	//	*特殊处理尾巴
				aroundPoint[i].step = canFindPath(aroundPoint[i], snake.coor.back());
			else
				aroundPoint[i].step = -1;
		}
		// 选出最远的点
		int maxstep_index = 0;
		for (int i = 1; i < 4; i++) {
			// 是0表示是食物直接走
			// 如果是-1不参与比较
			if (aroundPoint[maxstep_index].step == -1)
				maxstep_index = i;
			if (aroundPoint[i].step != -1) {
				if (aroundPoint[i].step > aroundPoint[maxstep_index].step) {
					maxstep_index = i;
				}
			}
		}
		char ret = 0x00;
		// 返回操作,找不到食物就返回0x00什么都不做
		if (aroundPoint[maxstep_index].step != -1)
			ret = RetCmd(aroundPoint[maxstep_index]);
		return ret;
	}
	// 返回可用的指令,必须是周围4点
	char RetCmd(Coor nextPoint) {
		char cmd = 0x00;
		int dx = nextPoint.x - snake.coor.front().x;
		int dy = nextPoint.y - snake.coor.front().y;
		if (dx == 0 && dy < 0)
			cmd = UP;
		if (dx == 0 && dy > 0)
			cmd = DOWN;
		if (dx < 0 && dy == 0)
			cmd = LEFT;
		if (dx > 0 && dy == 0)
			cmd = RIGHT;
		return cmd;
	}
	// 两点曼哈顿距离
	int getManhattanDistance(Coor _a,Coor _b) {
		return abs(_a.x-_b.x)+abs(_a.y-_b.y);
	}
	// 坐标在蛇上/包括蛇头蛇尾
	bool onSnake(Coor coor) {
		for (auto iter : snake.coor) {
			if (coor == iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在蛇上/不包括蛇头
	bool onSnake_ExceptHead(Coor coor){
		auto iter = snake.coor.begin();
		for (iter++; iter != snake.coor.end(); iter++) {
			if (coor == *iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在蛇上/不包括蛇尾
	bool onSnake_ExceptTail(Coor coor) {
		auto iter = snake.coor.begin();
		auto flag = --snake.coor.end();
		for (; iter != flag; iter++) {
			if (coor == *iter) {
				return true;
			}
		}
		return false;
	}
	// 坐标在方框里
	bool inBorder(Coor coor)const {
		return (coor.x >= 0 && coor.x < MAP_COLNUM && coor.y >= 0 && coor.y < MAP_ROWNUM);
	}
};

// 程序入口
int main() {
	// 加参数, SHOWCONSOLE开启控制台
	initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
	// 开启双缓冲绘图
	BeginBatchDraw();
	SnakeGame SG;
	SG.OpenAI();
	_getch();
	while (true) {
		SG.EatFood();
		SG.CreatFood();
		SG.snake.TurnDirect(SG.getNextCmd());
		SG.snake.Move();
		if (SG.GameOver()) {
			SG.DrawMap();
			SG.ShowGameEnd();
			SG.ShowGameInfo();	//显示最终信息
			FlushBatchDraw();
			_getch();
			SG.GameInit();
			SG.OpenAI();
		}
		SG.ShowGameInfo();
		SG.DrawMap();
		FlushBatchDraw();
		Sleep(1000/(SG.snake.speed+SG.snake.acc));
	}
	EndBatchDraw();
	closegraph();
	return 0;
}

程序下载

只有源码是不是很枯燥无味呢?现在上传了发布版供大家尝鲜,目前算法效率较低,吃满屏幕大约需要7分钟左右,并有一定概率剩余一两个空格吃不满。
真实截图:
在这里插入图片描述
下载链接:https://download.csdn.net/download/Necrolic/12319342

另外之后会使用更方便的H5重写一遍,进行更细致的记录和分析。

发布了19 篇原创文章 · 获赞 9 · 访问量 2992

猜你喜欢

转载自blog.csdn.net/Necrolic/article/details/88378934