这个部分我把它分成了两个函数:
1. 找出双方所有可能下子的位置(generateAllNextPossibleMove函数)
2. 在这些位置中进行挑选,选出能够产生更大优势的下子位置,减少博弈树搜索节点的次数(pointsFilter函数)
这一部分主要参考了 http://blog.csdn.net/lihongxun945/article/details/50668622 这篇博客上的写法,这里有更加详细的解释
这里相当于对将要下棋的位置进行了一个点估值,而不是全局估值,意思是这个函数将判断我方(这里指代电脑AI)在当前下子后,在当前位置上是否能够产生或者阻止对方形成有利于扭转当前局势的棋型,如:五连,活四,双三等,如果可以,我们就可以按照不同棋型所对应的评估分值的高低对 generateAllNextPossibleMove()
函数所生成的所有可能下子位置进行排序,然后将分值最高的位置作为最优解返回到MaxMin函数中。
首先,我们要重新定义一个局部评估函数(这里我们沿用前面估值函数中的部分函数和常量)
// color:1表示黑棋,0表示白棋,position:下子位置的二维坐标
pointEvaluate: function( matrix, position, color) {
if(color == 1){
return this._pointCounter(this._pointDirection(position[0], position[1], matrix), BLACK_REG);
}
else{
return this._pointCounter(this._pointDirection(position[0], position[1], matrix), WHITE_REG);
}
}
接下来就是最关键的筛选函数了
var pointsFilter = function(new_board, originalList, color){
var fives = [];
var fours = [];
var twothrees = [];
var threes = [];
var twos = [];
var neighbors = [];
for(var i = 0; i < originalList.length; i++){
new_board[originalList[i][0]][originalList[i][1]] = color;
var computorScore = ModuleEvaluate.pointEvaluate(new_board, originalList[i], color);
//console.log(computorScore);
new_board[originalList[i][0]][originalList[i][1]] = Math.abs(color-1);
var humanScore = ModuleEvaluate.pointEvaluate(new_board, originalList[i], Math.abs(color-1)); //Math.abs(color-1)表示相反颜色
//console.log(humanScore);
if(computorScore >= judge_standard.FIVE)
return [originalList[i]]; //先手直接返回
else if(humanScore >= judge_standard.FIVE)
fives.push(originalList[i]); //后手保存当前位置
else if(computorScore >= judge_standard.FOUR)
fours.unshift(originalList[i]);
else if(humanScore >= judge_standard.FOUR)
fours.push(originalList[i]);
else if(computorScore >= 2*judge_standard.THREE)
twothrees.unshift(originalList[i]);
else if(humanScore >= 2*judge_standard.THREE)
twothrees.push(originalList[i]);
else if(computorScore >= judge_standard.THREE)
threes.unshift(originalList[i]);
else if(humanScore >= judge_standard.THREE)
threes.push(originalList[i]);
else if(computorScore >= judge_standard.TWO)
twos.unshift(originalList[i]);
else if(humanScore >= judge_standard.TWO)
twos.push(originalList[i]);
else
neighbors.push(originalList[i]);
new_board[originalList[i][0]][originalList[i][1]] = 'e';
}
if(fives.length)
return fives;
if(fours.length)
return fours;
if(twothrees.length)
return twothrees;
return [...threes, ...twos, ...neighbors];
};
这样做我们就相当于将可能下子的位置按照可生成的优势棋型所占分值的高低进行了一个排序。在测试的过程中,我们可以惊喜的发现,在相同的步数和近似的局势下,在MaxMin算法中搜索的节点数从原来的几万个甚至几十万个缩减到了五千以下,算法效率提高了不少。
这个函数的调用也非常简单
将原 generateAllNextPossibleMove()
函数中最后一行的
return [...oneStepNeighbours, ...twoStepNeighbours];
替换成
//new_board是当前棋盘
var originalList = [...oneStepNeighbours, ...twoStepNeighbours];
return pointsFilter(new_board, originalList, color);
那么,generateAllNextPossibleMove()
函数可以改造成一下变成下面的样子:
var generateAllNextPossibleMove = function(wrappedBoard, color){
let oneStepNeighbours = [],
twoStepNeighbours = [];
let rowEnd = Constants.ChessBoard.ROW_NUM + 2,
colEnd = Constants.ChessBoard.COL_NUM + 2;
//这里后面考虑怎么将其优化,直接将原棋盘传进去
var new_board = new Array(15);
for(var i = 0; i < 15; i++)
new_board[i] = new Array(15);
for(let i = 2; i < rowEnd; i++){
for(let j = 2; j < colEnd; j++){
new_board[i-2][j-2] = wrappedBoard[i][j];
if(UtilMethods.isPositionEmpty(wrappedBoard, i, j)){
if(UtilMethods.hasOneStepNeighbour(wrappedBoard, i, j)){
oneStepNeighbours.push([i-2, j-2]);
}else if(UtilMethods.hasTwoStepNeighbour(wrappedBoard, i, j)){
twoStepNeighbours.push([i-2, j-2]);
}
}
}
}
var originalList = [...oneStepNeighbours, ...twoStepNeighbours];
return pointsFilter(new_board, originalList, color);
}
生成落子位置的函数就讲到这里,下一篇我将继续介绍五子棋AI中最为重要的MaxMin算法以及AlphaBeta剪枝算法。
项目地址:https://github.com/huangzhutao/Gomoku.git,大家有兴趣可以上去看看
欢迎大家对我叙述和代码中出现的不足之处进行批评指正