五子棋AI算法-迭代加深

  前面讲到了算杀,其实在算杀之前应该讲一下迭代加深。因为这些文章是我边做边写的一些笔记,所以顺序上可能不是那么严谨。

  按照前面的所有算法实现之后(当然不包括算杀),会发现一个比较严重的问题,就是电脑在自己已经胜券在握的情况下(有双三之类的棋可以走),竟然会走一些冲四之类的棋来调戏玩家。这种走法出现的本质就是因为现在的AI只比较最终结果,并没有考虑到路径长短。所以很容易出现在6层搜索到一个双三,其实在4层的时候也有一个双三,因为分数一样,AI会随机选择一个走法。就导致了明明可以两步赢的棋,AI非要走3步,对玩家来说,感觉就是在调戏人。

  所以这里我们定义一个最优解的概念:分数最高的走法中路径最短的那一个走法。那么现在问题就是我们如何找到最优解。

迭代加深

  我们通过AB搜索能够找到所有的最高分走法,一个直观的想法就是我们把所有走法都找到,然后比较他们的长度,选择长度最短的走法。

  这确实是一个方法,但是我们可以有效率更高的做法,就是通过迭代加深 来优先找到最短路径。

  所谓迭代加深,就是从2层开始,逐步增加搜索深度,直到找到胜利走法或者达到深度限制为止。比如我们搜索6层深度,那么我们先尝试2层,如果没有找到能赢的走法,再尝试4层,最后尝试6层。我们只尝试偶数层。因为奇数层其实是电脑比玩家多走了一步,忽视了玩家的防守,并不会额外找到更好的解法。

  所以实现这个算法是非常简单的:

 1 var deeping = function(board, deep) {
 2   deep = deep === undefined ? config.searchDeep : deep;
 3   //迭代加深
 4   //注意这里不要比较分数的大小,因为深度越低算出来的分数越不靠谱,所以不能比较大小,而是是最高层的搜索分数为准
 5   var result;
 6   for(var i=2;i<=deep; i+=2) {
 7     result = maxmin(board, i);
 8     if(math.greatOrEqualThan(result.score, SCORE.FOUR)) return result;
 9   }
10   return result;
11 }

  这里为了减少一层搜索,我们只搜索到活四就认为是达到最高分了。

迭代加深的优势

  迭代加深可以在找到最优解的同时,只增加非常小的额外时间开销,很多时候甚至可以减少开销。假设我们平均一步 50种选择,那么可以证明,4层搜索只需要6层搜索 1/2500 分之一的时间,所以我们额外进行的浅层搜索即使全部没有找到结果,也额外增加了可以忽略不计的时间。另外很可能浅层搜索就能找到最优解,此时可以极大提升效率。

  相比之下,如果是搜索到全部最高分解再比较路径长短,时间复杂度会成倍增加。

集成算杀

  有了迭代加深之后,我们就可以集成算杀了,很明显第一步应该现将算杀算法也改造成迭代加深,以保证每次算杀找到的都是最优解:

 1 var c = function(board, role, deep) {
 2   deep = deep || config.checkmateDeep;
 3   if(deep <= 0) return false;
 4   var start = new Date();
 5   debugNodeCount = 0;
 6   //迭代加深
 7   for(var i=1;i<=deep;i++) {
 8     var result = max(board, role, i);
 9     if(result) break; //找到一个就行
10   }
11   var time = Math.round(new Date() - start);
12   if(result) console.log("算杀成功("+time+"毫秒, "+ debugNodeCount + "个节点):" + JSON.stringify(result));
13   else {
14     //console.log("算杀失败("+time+"毫秒)");
15   }
16   return result;
17 }
18 然后,我们就可以在搜索的叶节点进行算杀。因为叶节点有很多,所以叶节点加入算杀之后会导致搜索时间变长:
19
20 var negamax = function(board, deep, alpha, beta, role) {
21   var v = evaluate(board);
22   count ++;
23   if(deep <= 0 || win(board)) {
24     return v;
25   }
26
27   var best = MIN;
28   var points = gen(board, deep);
29
30   for(var i=0;i<points.length;i++) {
31     var p = points[i];
32     board[p[0]][p[1]] = role;
33
34     //pvs
35
36     /*var pv = - negamax(board, deep-1, -alpha-1, -alpha, R.reverse(role));
37     if(math.littleThan(pv, alpha) {
38       PVcut ++;
39       board[p[0]][p[1]] = R.empty;
40       //console.log(pv, alpha);
41       return pv;
42     }*/
43
44     alpha = Math.max(best, alpha);
45     var v = - negamax(board, deep-1, -beta, -alpha, R.reverse(role));
46     board[p[0]][p[1]] = R.empty;
47     if(math.greatThan(v, best)) {
48       best = v;
49     }
50     if(math.greatOrEqualThan(v, beta)) { //AB 剪枝
51       ABcut ++;
52       return v;
53     }
54
55      //算杀
56     if( (deep <= 2 ) && role == R.com && math.littleThan(best, SCORE.FOUR) && math.greatThan(best, SCORE.FOUR * -1)) {
57       var mate = checkmate(board, R.com);
58       if(mate) {
59         return SCORE.FIVE * Math.pow(.8, mate.length);
60       }
61     }
62   }
63
64   return best;
65 }

  上述代码中我们只对AI自己进行了算杀,没有对玩家进行算杀,主要是因为现在的算杀效率比较低,如果对玩家也进行算杀会导致时间增加一倍。

  另外上述的极大极小值搜索被改成了负极大值搜索,不过原理上并没有区别,只是代码变得更加简洁统一了。

  以现在的算杀和搜索效率,可以做到 4+7,即4层负极大值搜索加上7层算杀,棋力会比之前有明显的提升。

内部迭代加深

  上面讲的迭代加深只对负极大值搜索进行了一层封装,其实可以更深入一点,对每一次 negamax 搜索都进行迭代加深,具体效果还没有验证,有兴趣可以尝试下,后续我应该也会尝试这个方法。

时间: 2024-12-15 01:45:17

五子棋AI算法-迭代加深的相关文章

五子棋AI算法

之前说想写一些比较大型的算法,想了半天,还是觉得写五子棋的AI比较合适.一则因为自己研究过这个,有一些基础,二则尽管现在网上有很多五子棋AI算法的资料,但是确实都有些晦涩难懂.就想着借这个机会,凭自己的理解,尽量的讲通俗一些.然而,这个算法确实有些复杂,想要通俗一些需要较大的篇幅,一篇博客难以讲完,这里就分很多个章节,一点一点的深入探讨.为了让文章更加通俗一些,我会略去一部分很简单但是占用篇幅很长的代码,改为用几行注释说明. 框架的搭建 首先,我们计划是做一个五子棋AI,也就是说让玩家和这个AI

五子棋AI算法-算杀

关于剪枝问题 前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下.只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步. 6层搜索的棋力其实相当弱,碰到经常玩五子棋的人基本都会输,更不要说对五子棋有研究的玩家.以目前的平均一个节点有50个子节点的搜索方式,把搜索效率提高50倍则可以增加一层搜索深度.而除了前面讲到的AlphaBeta剪枝和启发式搜索,其他的剪枝算法基本都是非安全剪枝.也就是说后序我们会使用一些非安全的剪枝算法来提升搜

五子棋AI算法第二篇-极大极小值搜索算法

AI实现的基本思路-极大极小值搜索算法 五子棋看起来有各种各样的走法,而实际上把每一步的走法展开,就是一颗巨大的博弈树.在这个树中,从根节点为0开始,奇数层表示电脑可能的走法,偶数层表示玩家可能的走法. 假设电脑先手,那么第一层就是电脑的所有可能的走法,第二层就是玩家的所有可能走法,以此类推. 我们假设平均每一步有50种可能的走法,那么从根节点开始,往下面每一层的节点数量是上一层的 50被,假设我们进行4层思考,也就是电脑和玩家各走两步,那么这颗博弈树的最后一层的节点数为 50^4 = 625W

五子棋AI算法第三篇-Alpha Beta剪枝

剪枝是必须的 上一篇讲了极大极小值搜索,其实单纯的极大极小值搜索算法并没有实际意义. 可以做一个简单的计算,平均一步考虑 50 种可能性的话,思考到第四层,那么搜索的节点数就是 50^4 = 6250000,在我的酷睿I7的电脑上一秒钟能计算的节点不超过 5W 个,那么 625W 个节点需要的时间在 100 秒以上.电脑一步思考 100秒肯定是不能接受的,实际上最好一步能控制在 5 秒 以内. 顺便说一下层数的问题,首先思考层数必须是偶数.因为奇数节点是AI,偶数节点是玩家,如果AI下一个子不考

人机ai五子棋 ——五子棋AI算法之Java实现

人机ai五子棋 下载:chess.jar (可直接运行) 源码:https://github.com/xcr1234/chess 其实机器博弈最重要的就是打分,分数也就是权重,把棋子下到分数大的地方,我获胜的概率就更大. 而在下棋过程中,大部分的点的得分都很小,或者接近,因此无需对每一个点都打分,只需要在我方附近(进攻)或者敌方附近(防守)的几个点进行打分. 具体原理大家可以看源码中的注释,说明的很清楚. 参考 http://blog.csdn.net/pi9nc/article/details

五子棋AI算法-Zobrist

这个博客不是把五子棋算法研究透彻之后再写的,而是一边研究算法一边写代码,同时一边写博客,所以有些博文的顺序不太对,比如 Zobrist 其实应该放在算杀之前就讲的.不过这并没有大的影响,总体上的顺序是OK的. 另外,这一系列博客讲的五子棋代码其实是一个开源的项目,源码地址:https://github.com/lihongxun945/gobang 由于是边写代码边写博客,所以博客中的代码不是最新的,甚至是有bug的,所以源码请尽量参考上述开源项目中的代码.比如之前讲极大极小值搜索改为负极大值的

五子棋AI算法-重构代码

为什么需要重构 之前的代码有很多松散的模块组合在一起.在把 Zobrist 集成进去时,会发现全部需要走棋的操作其实都需要进行一次 Zobrist 异或操作.另外在逻辑上,其实很多模块都是可以合并到同一个类的,所以这次把代码进行了一次大的重构.所以如果发现博客说的一些模块找不到了也是很正常的,因为大部分模块都被移到了 Board 类中. 这次重构主要的工作就是 把AI相关的代码分成了四个模块: Board ,所有和棋子相关的操作都在这里,包括打分,判断胜负,zobrist缓存,启发函数等. ne

算法复习——迭代加深搜索(骑士精神bzoj1085)

题目: Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位.在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上. 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步数完成任务. Input 第一行有一个正整数T(T<=10),表示一共有N组数据.接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位.两组数据

uva529 迭代加深+必要剪枝

// // main.cpp // 529 // // Created by Fangpin on 15/3/14. // Copyright (c) 2015年 FangPin. All rights reserved. // #include <iostream> #include <cstring> using namespace std; int a[10005]={1,2},n; bool ok; void dfs(int limit,int d){ if(d==limi