内容发布更新时间 : 2024/12/23 20:54:51星期一 下面是文章的全部内容请认真阅读。
单的方法避免固定方法AI必输。
第十四步:让AI自学习,不再同一个地方犯错
上面的算法还没有自学习的能力,这样AI在下棋时还可能会重蹈覆辙。因此在每盘棋结束时,如果AI输,则进行大于搜索深度的步数回退。可以把倒数为搜索深度数目的局面定为目标局面,从倒数深度加一步局面进行预测,找到不会导出必败目标局面的局面。然后记录下这个局面和前面的局面,并据此修改评分函数。这样AI就不会犯曾经犯过的错误,达到自学习的效果。 做到以上十四步,一个拥有强大AI的五子棋游戏即可诞生!
五子棋算法可简可繁,要看你对自己五子棋程序智能的要求, 人机对战的意思就是人和电脑下,也就是说电脑会思考如何下棋....其实这才是五子棋程序的核心.如果只实现人与人对战的话,是一件很简单的事情,无非就是绘制棋盘,然后绘制下棋的效果,再写个下棋合法性判断,胜负判断....大概就搞定了....所以核心其实是人机对战的电脑那部分人工智能.这东西吧,可以研究的很多,不过主要的几个设计要点就是搜索算法和估值算法,这两个是最主要的,还有提高电脑思考销率的方法就有多cpu的计算机多线程思考的设计....通过一些手段让电脑变得更像人类棋手的,例如利用一些遗传算法之类的让电脑具有学习能力,可以在失败中吸取教训,开局库,历史启发之类的一大堆......但是总而言之,这一系列算法的设计没有一个标准,只要能让你的电脑下棋下的更聪明,更快那就是好算法.国内有一个叫王晓春的写过一本叫<
五子棋的核心算法
五子棋是一种受大众广泛喜爱的游戏,其规则简单,变化多端,非常富有趣味性和消遣性。这里设计和实现了一个人机对下的五子棋程序,采用了博弈树的方法,应用了剪枝和最大最小树原理进行搜索发现最好的下子位置。介绍五子棋程序的数据结构、评分规则、胜负判断方法和搜索算法过程。
一、相关的数据结构
关于盘面情况的表示,以链表形式表示当前盘面的情况,目的是可以允许用户进行悔棋、回退等操作。 CList StepList;
其中Step结构的表示为:
struct Step {
int m; //m,n表示两个坐标值 int n;
char side; //side表示下子方 };
以数组形式保存当前盘面的情况, 目的是为了在显示当前盘面情况时使用:
char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE];
表示盘面最大的行数。FIVE_MAX_LINE其中
同时由于需要在递归搜索的过程中考虑时间和空间有效性,只找出就当前情况来说相对比较好的几个盘面,而不是对所有的可下子的位置都进行搜索,这里用变量CountList来表示当前搜索中可以选择的所有新的盘面情况对象的集合:
CList CountList;
其中类CBoardSituiton为: class CBoardSituation {
CList StepList; //每一步的列表
char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE]; struct Step machineStep; //机器所下的那一步 double value; //该种盘面状态所得到的分数 }
二、评分规则
对于下子的重要性评分,需要从六个位置来考虑当前棋局的情况,分别为:-,|,/,\\,//,\\\\
实际上需要考虑在这六个位置上某一方所形成的子的布局的情况,对于在还没有子的地方落子以后的当前局面的评分,主要是为了说明在这个地方下子的重要性程度,设定了一个简单的规则来表示当前棋面对机器方的分数。
基本的规则如下:
判断是否能成5, 如果是机器方的话给予100000分,如果是人方的话给予-100000 分; 判断是否能成活4或者是双死4或者是死4活3,如果是机器方的话给予10000分,如果是人方
的话给予-10000分;
判断是否已成双活3,如果是机器方的话给予5000分,如果是人方的话给予-5000 分; 判断是否成死3活3,如果是机器方的话给予1000分,如果是人方的话给予-1000 分; 判断是否能成死4,如果是机器方的话给予500分,如果是人方的话给予-500分; 判断是否能成单活3,如果是机器方的话给予200分,如果是人方的话给予-200分; 判断是否已成双活2,如果是机器方的话给予100分,如果是人方的话给予-100分; 判断是否能成死3,如果是机器方的话给予50分,如果是人方的话给予-50分; 判断是否能成双活2,如果是机器方的话给予10分,如果是人方的话给予-10分; 判断是否能成活2,如果是机器方的话给予5分,如果是人方的话给予-5分; 判断是否能成死2,如果是机器方的话给予3分,如果是人方的话给予-3分。
实际上对当前的局面按照上面的规则的顺序进行比较,如果满足某一条规则的话,就给该局面打分并保存,然后退出规则的匹配。注意这里的规则是根据一般的下棋规律的一个总结,在实际运行的时候,用户可以添加规则和对评分机制加以修正。
三、胜负判断
实际上,是根据当前最后一个落子的情况来判断胜负的。实际上需要从四个位置判断,以
该子为出发点的水平,竖直和两条分别为 45度角和135度角的线,目的是看在这四个方向是否最后落子的一方构成连续五个的棋子,如果是的话,就表示该盘棋局已经分出胜负。具体见下面的图示:
四、搜索算法实现描述
注意下面的核心的算法中的变量currentBoardSituation,表示当前机器最新的盘面情况, CountList表示第一层子节点可以选择的较好的盘面的集合。核心的算法如下: void MainDealFunction() {
value=-MAXINT; //对初始根节点的value赋值 CalSeveralGoodPlace(currentBoardSituation,CountList);
//该函数是根据当前的盘面情况来比较得到比较好的可以考虑的几个盘面的情况,可以根据实际的得分情况选取分数比较高的几个盘面,也就是说在第一层节点选择的时候采用贪婪算法,直接找出相对分数比较高的几个形成第一层节点,目的是为了提高搜索速度和防止堆栈溢出。 pos=CountList.GetHeadPosition(); CBoardSituation* pBoard;
for(i=0;ivalue=Search(pBoard,min,value,0); Value=Select(value,pBoard->value,max); //取value和pBoard->value中大的赋给根节点 }
for(i=0;ivalue)
//找出那一个得到最高分的盘面 {
currentBoardSituation=pBoard; PlayerMode=min; //当前下子方改为人
Break; } }
其中对于Search函数的表示如下:实际上核心的算法是一个剪枝过程,其中在这个搜索过程中相关的四个参数为:(1)当前棋局情况;(2)当前的下子方,可以是机器(max)或者是人(min);(3)父节点的值oldValue;(4)当前的搜索深度depth。
double Search(CBoardSituation& board,int mode,double oldvalue,int depth) {
CList m_DeepList;
if(deptholdvalue))== TRUE) {
if(mode==max)
value=select(value,search(successor 1),max);
+Board,min,value,depth
else
value=select(value,search(successor 1),min); + Board,max,value,depth}
return value; } else {
if ( goal(board)<>0)
表示已经可以分出胜负这里goal(board)<>0//return goal(board); else
return evlation(board); } }
是对函数是用来判断当前盘面是否可以分出胜负,而evlation(board) 注意这里的goal(board) 前的盘面从机器的角度进行打分。
即是机器还是用情况,这个函数的主要目的是根据 PlayerMode下面是 Select函数的介绍,来返回节点的应有的值。
double Select(double a,double b,int mode) {
mode==min) && mode==max)|&& | (a< b if(a>b return a; else
当户
return b; }
五、小结++实现了这个人机对战的五子棋程序。和国内许多只是操作系统下,用WindowsVC 在在智力上和时间有效性上都要采用规则或者只是采用简单递归而没有剪枝的那些程序相比,提(如象棋和围棋等)好于这些程序。同时所讨论的方法和设计过程为用户设计其他的游戏 供了一个参考。
大部分算法中,都要进行两步。第一步是优先级的计算,然后是打分。但是我在想能不能通过某种数学模式把这两种计算融合在一起。也就是说,我们能够从所打的分中间识别出优先级。这使我想到了数学十进制中的位。反正我最主要是从四个方向进行判定,所以某些优先级低的,我们在给其打分的时候,就将它的分数设为处在其优先级之上的十分之一。其实觉得这里只要大于其分数的八倍就可以因为我最后选取最优位置的方法是累加法算出数和最高的位置这样的话即使在四个方向上某较低优先级都出现了但是即使全加起也不会超过较高优先级的分数这样的话如果出现优先级较高的情况那么我们计算的果必然是优先级较高的分数最高。这样变省去了一些不必要的分类使得算法更直接,容易理解
首先我们先来看一下置棋时的优先级分布以及我为其所指定的打分方案
首先声明的是对于已经五子相连的情况我们在此部分算法中不再实现因为这个时候负已经分出,我们调checkboar类judg的方法便可以了。这只不过是在电A再judg的代码复制过去罢了 我们的目的是给棋盘上的空位进行打分进而算出分数最高的位置那么我在这里想运用探法,因为我觉得这样的话代码比较好写。所谓试探法就是,我们找出棋盘上的所有空位然后我们假设这个空位上放置的是白棋然后看看相连的情况然后打分并将分数加至储该空位分数的变量;再假设这个空位上放置的是黑棋,然后再看相连的情况,然后打分并将分数加至存储该空位分数的变量这样遍历完所有的空位的时候我们便完成了打分作,然后再比较空位的分数,选出分数最高的空位,该位置即为最优的位置
下面是一些情况的优先级排列:(假设我们是白棋)下列情况均为在该空位置棋之后
在实现的时候,我们还需要定义两个临时Checke指针变量,用来保存相连的一串棋前后的位置的状态colo用来保存棋子的颜色coun用来保存相连的棋子的个数temptrtemptr用来保存两端的棋子指针。文中的我们是电脑哦 情所分
color=1&&count=我们先赢,赢了再100 000 0000
Color=0&&count=对方要赢,如果我们不能先赢,那么一定得先阻10 000 0000
Color=1&&count=4&&!temptr1&&!temptr双方均不会赢,如果我们能成活四,那1 000 敌方必输,所以要先置0000