新手立体四子棋AI教程(1)——基础扫盲

1、引言

最近身边好几个朋友开始玩立体四子棋,激起了个人好奇心。那么首先来讲什么是【立体四子棋】,规则又是如何呢?html

上图即为立体四子棋,规则相似于五子棋四子连在一块儿,可是四子棋更加多样、丰富。不只能够在平面内横竖斜四子连在一块儿,还能够在不一样平面内四子斜着连在一块儿、同一根柱子上四子连在一块儿,可谓十分有趣。算法

2、规则介绍

那么咱们用科学的方法总结一下规则:网络

状况一:xy平面内横竖斜四子连成ide

上图中的三种状况,能够推广到任意z平面函数

状况二:立体中四子斜着连成优化

以上状况一样能够在同一x轴上、同一y轴上成立spa

状况三:.net

这种状况在任意位置均成立,即z轴上四子连成翻译

3、设计思路

鉴于立体四子棋脱胎于五子棋,那么咱们能够在传统五子棋的AI算法上进行咱们的创做。首先对于棋盘来讲,五子棋可下的位置一共有15*15=225个,立体四子棋可下的位置有4*4*4=64个,而且因为空间限制,任何一步内的可下位置不会超过16个。这是由于不能违背物理限制,落子时棋子的下方必须不为空。设计

那么咱们能够简单的创建一个int[4][4][4]来表示当前棋盘状态,此处不须要过多解释。对于棋盘的各类状态,咱们能够经过一个enum chessPicesStatus{empty, black, white}来描述一个位置的状况。一样咱们创建一个棋子位置的结构体来提供更多的信息:(value的做用后续会在提到)

enum chessPicesStatus{empty, black, white}; struct PicesPos{ int x; int y; int z; chessPicesStatus type; int value; };

 

说完这些,咱们再来聊聊真正的算法部分。立体四子棋和其余棋类如五子棋、围棋、国际象棋同样,均为【零和博弈】,如下为维基百科中的定义

在零和属性(若是一方得益,另外一方必然损失)下,是指结果是零和的状况下会出现帕累托最优的现象。反过来讲,全体参加者可得益或受损的状况被称为非零和博弈。若是一个国家利用其过剩的香蕉与另外一国家剩余的苹果进行贸易,由于两方都从交易中受惠,这是一个非零和的例子。

这个概念最先是在博弈论(game theory)上发展,所以零和状况一般被称为零和游戏(zero-sum game)。

翻译一下,棋类零和游戏必须知足的几点:

  1. 整个棋盘必须是透明的,即游戏双方均可以了解到棋盘全部的信息
  2. 双方轮流出棋
  3. 在规则下一定出现一方胜利,一方失败的局面,或者产生和棋

目前在这类博弈中算法大部分已经至关成熟,基本都采用暴力搜索,模拟将来n步走子产生最优位置。可是鉴于棋类巨大的可能性,模拟出将来的每种可能性是不现实的,所以,模拟出尽量多的步数是你们努力的目标。举个例子,著名围棋Ai Alpha Go,核心的地方在于在突破传统的蒙特卡洛搜索树,采用预先训练好的【价值评估网络】和【走子网络】两个神经网络来评估,缩小了每层的搜索量,而且配以Google的TPU,实现超越人类棋手。

在咱们此次的程序中,将采用极值(极大极小)算法,以及启发式搜索,并配以alpha-beta剪枝来优化搜索量。鉴于立体四子棋每层16个位置的搜索,咱们暂时还用不到蒙特卡洛搜索树和神经网络。

 

4、基本功能

好了,既然理论知识已经扫盲完成(没明白也不要紧,后续还会再详细介绍),咱们快速构建一下以后会用到的各个功能。

首先定义chessBoard类,提供以下方法:

 

typedef std::vector<PicesPos> PicesPosList; class ChessBoard { public: int chessBoard[4][4][4]; void init(); bool insertPices(int x,int y,int type); void printBoard(); PicesPosList getAvailablePos(int board[4][4][4],chessPicesStatus side); }

 

接着咱们来依次实现:

首先是初始化,鉴于将来咱们会重复使用棋盘,这个方法仍是有必定必要性的。

 

void ChessBoard::init() { for(int z = 0;z < 4;z++) { for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { chessBoard[x][y][z] = chessPicesStatus::empty; } } } }

 

 很简单,而后咱们实现落子的方法。在此须要保证几点:

  1. 这个位置是存在的
  2. 这个位置并无落满
  3. 插入中不能出现悬空的状态
bool ChessBoard::insertPices(int x, int y, int type) { if(x < 0 || x >3 || y < 0 || y > 3) return false; //check 0-3th floor
    for(int z = 0;z < 4;z++) { if(chessBoard[x][y][z] == chessPicesStatus::empty) { chessBoard[x][y][z] = chessPicesStatus(type); return true; } } return false; }

 

一样没什么难度,接着咱们来看打印函数,这个函数只是让咱们能方便的看到棋盘状况,真正项目中还须要用更友好的UI来替代。

void ChessBoard::printBoard() { for(int z = 0;z < 4;z++) { cout<<"The "<<z<<"th floor"<<endl; for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { cout<<chessBoard[x][y][z]<<" "; } cout<<endl; } cout<<"\\\\\\\\\\\\\\"<<endl; } }

 

打印效果以下:

The 0th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 1th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 2th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 3th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\

 

最后咱们来看下稍有难度的获取当前可下位置的方法,其实也很简单:

PicesPosList ChessBoard::getAvailablePos(int board[4][4][4], chessPicesStatus side) { PicesPosList availablePos; for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { for(int z = 0; z < 4;z++) { if(board[x][y][z] == chessPicesStatus::empty) { PicesPos pos; pos.x = x; pos.y = y; pos.z = z; pos.type = chessPicesStatus::empty; pos.value = getPosValue(board,&pos,side); availablePos.push_back(pos); break; } } } } return availablePos; }

 

基本就是对于每一个x,y,从下往上找可用位置,找到后退出。其中side参数和getPosValue(board,&pos,side)是咱们以后会提到的启发式搜索所用到的价值评估函数,能够先行略过。

至此,咱们已经构建了一个基本的棋盘,配上简单的io交互就能够在这上面下棋了!

在以后的几篇文章,我会讲到胜负判断、当前局势分评估、搜索树的构建,剪枝算法优化,启发式搜索等内容,敬请期待!

 

参考博文:

http://blog.csdn.net/lihongxun945/article/details/50625267

https://www.cnblogs.com/pangxiaodong/archive/2011/05/26/2058864.html

https://www.zhihu.com/question/27221568

致谢!

相关文章
相关标签/搜索