贪吃蛇的寻路问题是最简单的一类路径搜索问题, 几乎所有的路径搜索算法都能帮助我们解决这一问题,接下来,我将基于广度优先搜索算法,在C++平台上实现多条蛇同时进行竞争运动。
注:本文偏新手向,如有错漏请多指教
Key | Value |
---|---|
系统 | Windows 10 |
语言 | C++ 11 |
IDE | Qt 5.6.1 |
编译器 | Clang |
- Qt 的Pro配置文件如下所示,注意这里要加上c++11的配置选项,因为接下来要用到新的feature
QT += core
QT -= gui
CONFIG += c++11
TARGET = Snakes_m
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
node.cpp \
blockmap.cpp \
snake.cpp
HEADERS += \
node.h \
blockmap.h \
snake.h
为了叙述简便,以下章节中将广度优先搜索简称为BFS。
我们对节点类型做如下规定:
//Node :: Type
/*
* 0: Target
* 1: Body
* 2: HeadUp
* 3: HeadRight
* 4: HeadDown
* 5: HeadLeft
* 6: Free Block
* 7: Banned Block
*/
贪吃蛇问题说白了就是图论中的最短路问题,而图论问题的两个主要变量就是点和边,在本问题中,边可以被抽象为权重为1的边,因此可以被忽略,我们只需要定义一个表示点的Node类就可以了。
#include
#include
class Node
{
public:
int x;
int y;
int type; //用于区分节点的类型
std::shared_ptr prenode; //父节点
std::shared_ptr nextnode; //子节点
public:
Node();
//~Node();
Node(int x,int y);
Node(int x,int y, int type);
Node(int x,int y, std::shared_ptr prenode, std::shared_ptr nextnode);
void setType(int type);
friend std::ostream& operator<< (std::ostream &os,std::shared_ptr nd)
{
os<<"X: "<x<<" "<<"Y: "<y<<"\n";
return os;
}
};
在本系统中,我们要实现的是多条蛇同时进行搜索,因此可以抽象为一个Snake类,每个Snake管理自己的头结点,并且自主进行路径搜索,搜索完成后将状态更新至BlockMap(接下来会讲到)。
#include
#include
#include //用时间实现伪随即
class BlockMap;
class Snake
{
public:
BlockMap * blockmap;
int length = 1;
std::shared_ptr head = NULL;
std::shared_ptr path = NULL; // 路径的头结点
public:
Snake();
Snake(int x,int y,int type);
void setBlockMap(BlockMap * bm); //用于解决循环引用
bool nextStep();
std::shared_ptr search();
void snakeReflush();
void generateTarget();
bool checkNodeAvailability(int, int);
int getDirection(std::shared_ptr n1, std::shared_ptr n2);
void reSearchPath();
bool checkSnake(std::shared_ptr n);
};
null->snakeBody2->snakeBody1->head->path->pathNode1->pathNode2-> … ->target
这么做的好处是可以在搜索出一条路径后,系统不需要在每一次循环中重新搜索一次,减少了计算量,path 只有在以下情况下才会重新计算:1. 某一条蛇吃到了食物,也就是target消失;2. 当前path的下一节点与其他蛇冲突
接下来,我对Snake类的函数进行功能解析
Snake::Snake(int x, int y, int type) : head(std::shared_ptr(new Node(x,y,type))){}
bool Snake::nextStep()
{
//1. 判断是否还有target,没有则给定新的target
if(!this->blockmap->target){
this->generateTarget();
}
//2. 计算path头结点
if(!this->path){
this->path = this->search();
this->visitReflush();
}
//3. 沿着path前进一格
std::shared_ptr<Node> nextNode = this->path;
if(!nextNode){ // 当蛇被block时,保持不动
return true;
}
//4. 如果下一结点正好是别的蛇的一部分,则重新计算路线
if(!this->checkSnake(this->path)){
if(nextNode->x == this->blockmap->target->x && nextNode->y == this->blockmap->target->y){
//蛇长度+1
this->length++;
//处理头部方向问题
int direction = this->getDirection(nextNode,this->head);
this->head->setType(1);
nextNode->setType(direction);
this->head = nextNode;
//重新指定target,发送notice给所有snake,重新计算路线
this->blockmap->target = nullptr;
this->generateTarget();
this->blockmap->noticeAllSnakesForNewPath();
return true;
}
this->blockmap->noticeAllSnakesForNewPath();
return true;
}
this->path = this->path->prenode;
//如果下一节点是target
if(nextNode->x == this->blockmap->target->x && nextNode->y == this->blockmap->target->y){
//蛇长度+1
this->length++;
//处理头部方向问题
int direction = this->getDirection(nextNode,this->head);
this->head->setType(1);
nextNode->setType(direction);
this->head = nextNode;
//重新指定target,发送notice给所有snake,重新计算路线
this->blockmap->target = nullptr;
this->generateTarget();
this->blockmap->noticeAllSnakesForNewPath();
return true;
}
else{
int direction = this->getDirection(nextNode,this->head);
this->head->setType(1);
nextNode->nextnode = this->head;
this->head->prenode = nextNode;
this->head = nextNode;
this->head->setType(direction);
//1.删除蛇的尾结点
std::shared_ptr<Node> tailNode = this->head;
int length = 1;
while(tailNode){
if(!tailNode->nextnode){
tailNode->prenode->nextnode = nullptr;
this->blockmap->blockMap[tailNode->x][tailNode->y] = 6;
break;
}
tailNode = tailNode->nextnode;
length++;
}
std::cout<<"length :"<length<length != length-1){
std::shared_ptr fixTailNode = this->head;
for(int i=0;ilength;i++){
if(i == this->length-1){
this->blockmap->blockMap [fixTailNode->nextnode->x][fixTailNode->nextnode->y] = 6;
fixTailNode->nextnode = nullptr;
break;
}
fixTailNode = fixTailNode->nextnode;
}
}
}
//将当前snake的最新位置信息更新到blockmap中
this->snakeReflush();
}
std::shared_ptr<Node> Snake::search()
{
std::queueNode>> q;
q.push(this->head);
while(!q.empty()){
std::shared_ptr<Node> node = q.front();
q.pop();
//如果当期结点是target,则回溯并返回pathHead指向的下一结点
if(node->x==this->blockmap->target->x && node->y==this->blockmap->target->y){
//对node链表的pre和next进行重置
node->nextnode=node->prenode;
node->prenode = nullptr;
std::shared_ptr<Node> temp_node = node;
if(node->nextnode == this->head){
return node;
}else{
node = node->nextnode;
}
while(node){
node->nextnode = node->prenode;
if(node->nextnode == this->head){
node->prenode = temp_node;
return node;
}else{
node->prenode = temp_node;
temp_node = node;
node = node->nextnode;
}
}
}
else{
if(checkNodeAvailability(node->x-1,node->y) && this->blockmap->visit[node->x-1][node->y]==0){
q.push(std::shared_ptr<Node>(new Node(node->x-1,node->y,node,nullptr)));
this->blockmap->visit[node->x-1][node->y]=1;
};
if(checkNodeAvailability(node->x+1,node->y) && this->blockmap->visit[node->x+1][node->y]==0){
q.push(std::shared_ptr<Node>(new Node(node->x+1,node->y,node,nullptr)));
this->blockmap->visit[node->x+1][node->y]=1;
};
if(checkNodeAvailability(node->x,node->y+1) && this->blockmap->visit[node->x][node->y+1]==0){
q.push(std::shared_ptr<Node>(new Node(node->x,node->y+1,node,nullptr)));
this->blockmap->visit[node->x][node->y+1]=1;
};
if(checkNodeAvailability(node->x,node->y-1) && this->blockmap->visit[node->x][node->y-1]==0){
q.push(std::shared_ptr<Node>(new Node(node->x,node->y-1,node,nullptr)));
this->blockmap->visit[node->x][node->y-1]=1;
};
}
}
return nullptr;
}
上述的链表重置是为了将path链表的父节点和子节点进行完善:
//原path链表
head <- path <- pathnode2 <- ... <-target
↑ ↑ ↑
prenode prenode prenode
//处理后path链表
head <- path <- pathnode2 <- ... <-target
↑ ↑ ↑
nextnode nextnode nextnode
head -> path -> pathnode2 -> ... ->target
↑ ↑ ↑
prenode prenode prenode
这样处理后,在进行蛇的头结点移动时就方便的多了:)
void Snake::visitReflush()
{
this->blockmap->visitReflush();
}
void Snake::snakeReflush()
{
std::shared_ptr<Node> cn = this->head; //cn=CurrentNode
this->blockmap->blockMap[cn->x][cn->y]=cn->type;
while(cn=cn->nextnode){
this->blockmap->blockMap[cn->x][cn->y]=1;
}
}
void Snake::generateTarget()
{
int a = this->blockmap->width;
int b = this->blockmap->height;
srand((unsigned)time(NULL)); //初始化随机数种子
int rw,rh;
for(int i=0;i<20;i++){
rw = (rand() % (a));
rh = (rand() % (b));
if(this->blockmap->blockMap[rh][rw]==6){
break;
}
}
this->blockmap->target = std::shared_ptr(new Node(rh,rw,0));
this->blockmap->blockMap[rh][rw]=0;
}
bool Snake::checkNodeAvailability(int x, int y)
{
if(this->blockmap->blockMap[x][y]==6 || this->blockmap->blockMap[x][y]==0){
return true;
}
else{
return false;
}
}
int Snake::getDirection(std::shared_ptr<Node> n1, std::shared_ptr<Node> n2)
{
if(n1->x == n2->x && (n1->y -n2->y) == 1){
return 3; // 右
}
if(n1->x == n2->x && (n1->y - n2->y) == -1){
return 5; // 左
}
if(n1->y == n2->y && (n1->x - n2->x) == 1){
return 4; // 右
}
if(n1->y == n2->y && (n1->x - n2->x) == -1){
return 2; // 左
}
}
bool Snake::checkSnake(std::shared_ptr n)
{
int x = n->x;
int y = n->y;
if(this->blockmap->blockMap[x][y] == 6 || this->blockmap->blockMap[x][y] == 0){
return true;
}
else{
return false;
}
}
void Snake::reSearchPath()
{
this->path = this->search();
this->visitReflush();
}
#include
#include
#include
#include //rand函数和srand函数
#include //用时间实现伪随即
#include //队列,用于实现BFS
#include //用于使用std::shared_ptr
class Snake;
class BlockMap
{
public:
Snake ** sa;
int **blockMap;
int **visit; //记录搜索过的点,减少广度搜索个数
int width;
int height;
int snakeNum;
std::shared_ptr target=NULL;
public:
BlockMap();
BlockMap(Snake ** sa,int ptrNum, int width, int height);
bool autoReflush();
void mapReflush();
void snakeReflush();
void visitReflush();
void noticeAllSnakesForNewPath();
};
相比于Snake类来说,BlockMap类的功能较为简单,这里我就贴上代码
BlockMap::BlockMap(Snake **sa, int ptrNum, int width, int height) : sa(sa),snakeNum(ptrNum),width(width),height(height)
{
setlocale(LC_CTYPE, ""); // 配合wprintf使用,输出特殊符号
int **newBlockMap = new int*[height];
for(int i=0;inew int[width];
}
int **newVisit = new int*[height];
for(int i=0;inew int[width];
}
//初始化地图
/*
* 0: Target
* 1: Body
* 2: HeadUp
* 3: HeadRight
* 4: HeadDown
* 5: HeadLeft
* 6: Free Block
* 7: Banned Block
*/
for(int i=0;ifor(int j=0;jif(i==0||j==0||i==height-1||j==width-1){
newBlockMap[i][j] = 7;
}
else{
newBlockMap[i][j] = 6;
}
newVisit[i][j] = 0;
}
}
for(int i=0;ihead->x][sa[i]->head->y] = 4;sa[i]->head->type;
newVisit[sa[i]->head->x][sa[i]->head->y] = 1;
}
this->blockMap = newBlockMap;
this->visit = newVisit;
}
bool BlockMap::autoReflush()
{
this->snakeReflush();
this->mapReflush();
return true;
}
void BlockMap::mapReflush()
{
if(this->blockMap){
int width = this->width;
int height = this->height;
for(int i=0;ifor(int j=0;jswitch (this->blockMap[i][j]) {
case 0:
wprintf(L"⊙");
break;
case 1:
wprintf(L"■");
break;
case 2:
wprintf(L"↑");
break;
case 3:
wprintf(L"→");
break;
case 4:
wprintf(L"↓");
break;
case 5:
wprintf(L"←");
break;
case 6:
wprintf(L"□");
break;
case 7:
wprintf(L"■");
break;
default:
break;
}
}
std::cout<<"\n";
}
}
}
void BlockMap::visitReflush()
{
int height = this->height;
int width = this->width;
for(int i=0;ifor(int j=0;jthis->visit[i][j] = 0;
}
}
}
void BlockMap::noticeAllSnakesForNewPath()
{
for(int i=0;i <this->snakeNum ; i++)
{
this->sa[i]->reSearchPath();
}
}
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
/* Node的type值
* 0: Target
* 1: Body
* 2: HeadUp
* 3: HeadRight
* 4: HeadDown
* 5: HeadLeft
* 6: Free Block
* 7: Banned Block
*/
Snake * s1 = new Snake(1,1,4);
Snake * s2 = new Snake(5,5,4);
Snake * s3 = new Snake(10,10,4);
Snake * s4 = new Snake(15,15,4);
Snake **sa = new Snake*[4];
sa[0] = s1;
sa[1] = s2;
sa[2] = s3;
sa[3] = s4;
int width = 30;
int height = 30;
int numOfSnake = 4;
BlockMap * bm = new BlockMap(sa,numOfSnake,width,height);
sa[0]->setBlockMap(bm);
sa[1]->setBlockMap(bm);
sa[2]->setBlockMap(bm);
sa[3]->setBlockMap(bm);
while(1){
if(bm->autoReflush()){
for(int i=0; istd::cout<<"Snake "<1<<" ";
sa[i]->nextStep();
if(i==numOfSnake-1)std::cout<<"Target:"<target;
}
Sleep(50);
}
system("cls");
}
return a.exec();
}