码迷,mamicode.com
首页 > 编程语言 > 详细

五子棋AI算法-算杀

时间:2016-10-21 16:10:49      阅读:403      评论:0      收藏:0      [点我收藏+]

标签:原理   方式   height   find   var   length   log   五子棋   博弈树   

关于剪枝问题

  前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下。只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步。

  6层搜索的棋力其实相当弱,碰到经常玩五子棋的人基本都会输,更不要说对五子棋有研究的玩家。以目前的平均一个节点有50个子节点的搜索方式,把搜索效率提高50倍则可以增加一层搜索深度。而除了前面讲到的AlphaBeta剪枝和启发式搜索,其他的剪枝算法基本都是非安全剪枝。也就是说后序我们会使用一些非安全的剪枝算法来提升搜索效率,而这样做的话,有一定的概率会剪掉一些有益的分支。

  这里我们把启发式搜索也当做一种剪枝算法,其实它的实际作用就是剪枝。

克服水平线效应

  假设我们目前搜索深度为N层,那么AI只会考虑N层以内的利益,而对N+1层以及以后的局势全然没有任何考虑。那么造成的结果就是AI就是一个视力为N的近视眼,很容易因为N层以内的短期利益而造成大局上的劣势,这就是水平线效应。

  如果我们提高搜索深度,依然会存在水平线效应,只是这个水平线会隐藏的更深。想要克服水平线效应,那么就需要在一定情况下对N层以外的节点进行搜索。当然,我们不可能搜索整个博弈树,所以只能在某些前提下进行一些代价不高的深入搜索。

  这里我们先实现一种最简单的克服水平线效应的方式-算杀。所谓算杀就是计算出杀棋,杀棋就是指一方通过连续的活三和冲四进行进攻,一直到赢的一种走法。 
很显然,同样的深度,算杀要比前面讲的搜索效率高很多。因为算杀的情况下,每个节点只计算活三和冲四的子节点。所以同样是1秒钟的时间,搜索只能进行4层,而算杀很多时候可以进行到12层以上。 
  为了方便,我们把前面的讲到全面的极大极小值搜索简称为搜索

  而且很容易想到,算杀其实也是一种极大极小值搜索,具体的策略是这样的:

  • MAX层,只搜索己方的活三和冲四节点,只要有一个子节点的能赢即可
  • MIN 层,搜索所有的己方和对面的活三和冲四节点(进攻也是防守),只要有一个子节点能保持不败即可。

  算杀的代码实现如下:

  1 /*
  2  * 算杀
  3  * 算杀的原理和极大极小值搜索是一样的
  4  * 不过算杀只考虑冲四活三这类对方必须防守的棋
  5  * 因此算杀的复杂度虽然是 M^N ,但是底数M特别小,可以算到16步以上的杀棋。
  6  */
  7 
  8 /*
  9  * 基本思路
 10  * 电脑有活三或者冲四,认为是玩家必须防守的
 11  * 玩家防守的时候却不一定根据电脑的棋来走,而是选择走自己最好的棋,比如有可能是自己选择冲四
 12  */
 13 
 14 var R = require("./role.js");
 15 var hasNeighbor = require("./neighbor.js");
 16 var scorePoint = require("./evaluate-point.js");
 17 var S = require("./score.js");
 18 var win = require("./win.js");
 19 var config = require("./config.js");
 20 
 21 //找到所有比目标分数大的位置
 22 var find = function(board, role, score) {
 23   var result = [];
 24   for(var i=0;i<board.length;i++) {
 25     for(var j=0;j<board[i].length;j++) {
 26       if(board[i][j] == R.empty) {
 27         var p = [i, j];
 28         if(hasNeighbor(board, p, 2, 1)) { //必须是有邻居的才行
 29 
 30           if(role == R.empty) {
 31             var s1 = scorePoint(board, p, R.com);
 32             var s2 = scorePoint(board, p, R.hum);
 33             var s = s1+s2;
 34             if(s > score) {
 35               p.score = s;
 36               result.push(p);
 37             }
 38           } else {
 39             var s = scorePoint(board, p, role);
 40             if(s >= score) {
 41               p.score = s;
 42               result.push(p);
 43             }
 44           }
 45         }
 46       }
 47     }
 48   }
 49   //注意对结果进行排序
 50   result.sort(function(a, b) {
 51     return b.score - a.score;
 52   });
 53   return result;
 54 }
 55 
 56 var max = function(board, role, deep) {
 57   var w = win(board);
 58   if(w == role) return true;
 59   if(w == R.reverse(role)) return false;
 60   if(deep < 0) return false;
 61 
 62   var points = find(board, role, S.BLOCKED_FOUR);
 63   if(points.length == 0) return false;
 64   for(var i=0;i<points.length;i++) {
 65     var p = points[i];
 66     board[p[0]][p[1]] = role;
 67     var m = min(board, role, deep-1);
 68     board[p[0]][p[1]] = R.empty;
 69     if(m) {
 70       if(m.length) {
 71         m.unshift(p); //注意 unshift 方法返回的是新数组长度,而不是新数组本身
 72         return m;
 73       } else {
 74         return [p];
 75       }
 76     }
 77   }
 78   return false;
 79 }
 80 
 81 //只要有一种方式能防守住,就可以了
 82 var min = function(board, role, deep) {
 83   var w = win(board);
 84   if(w == role) return true;
 85   if(w == R.reverse(role)) return false;
 86   if(deep < 0) return false;
 87   var points = find(board, R.empty, S.FOUR);
 88   if(points.length == 0) return false;
 89 
 90   var cands = [];
 91   for(var i=0;i<points.length;i++) {
 92     var p = points[i];
 93     board[p[0]][p[1]] = R.reverse(role);
 94     var m = max(board, role, deep-1);
 95     board[p[0]][p[1]] = R.empty;
 96     if(m) {
 97       m.unshift(p);
 98       cands.push(m);
 99       continue;
100     } else {
101       return false; //只要有一种能防守住
102     }
103   }
104   return cands[Math.floor(cands.length*Math.random())];  //无法防守住
105 }

算杀算法的集成

  有了算杀模块之后,我们可以直接对当前棋局进行算杀,但是显然更好的做法是在搜索中进行算杀,通过N层搜索结合M层算杀,我们可以最多搜索到N+M层。

  至于如何组合算杀和搜索,最简单的是在每一个非连五的叶节点都进行一次算杀。为了避免算杀出现非最优解,还需要进行迭代加深,具体做法下一章再讲。

五子棋AI算法-算杀

标签:原理   方式   height   find   var   length   log   五子棋   博弈树   

原文地址:http://www.cnblogs.com/Bita/p/5984620.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!