五指棋人机大战之ai篇
话说之前把ui篇说了,接下来就是整个游戏的核心部分了。
废话不多说,完成AI部分总共有几个难点
1. 计算机如何落子
2. 判断胜负
在阐述代码之前,先上流程图。备注一下:玩家先手(黑子) 电脑白子
总流程图:
具体步骤:
步骤一:初始化工作
1 用一个三维数组来存放五子棋的所有赢法
2 用两个数组来存放玩家的赢法总数,一个存放计算机的赢法总数
这里理解起来是比较难的,先放代码
var count = 0 ;// 赢法数组的索引 // 横线赢法 for(var i = 0;i < 15;i++){ for(var j = 0;j < 11;j++){ for(var k = 0;k < 5;k++){ wins[i][j+k][count] = true } count ++; } } // 竖线赢法 for(var i = 0;i < 15;i++){ for(var j = 0;j < 11;j++){ for(var k = 0;k < 5;k++){ wins[j+k][i][count] = true } count ++; } } // 斜线赢法 左上右下 for(var i = 0;i < 11;i++){ for(var j = 0;j < 11;j++){ for(var k = 0;k < 5;k++){ wins[i+k][j+k][count] = true } count ++; } } // 斜线赢法 左下右上 for(var i = 0;i <11 ;i++){ for(var j = 14;j > 3;j--){ for(var k = 0;k < 5;k++){ wins[i+k][j-k][count] = true } count ++; } } console.log(count) // 初始化统计数组 for(var i = 0;i < count;i++){ myWin[i] = 0 computerWin[i] = 0 }
这里赢法数组部分理解可能有问题,我用左右方向举例说明
// 横线赢法
for(var i = 0;i < 15;i++){
for(var j = 0;j < 11;j++){
for(var k = 0;k < 5;k++){
wins[i][j+k][count] = true
}
count ++;
}
}
i 表示当前行,j表示当前的行的每个棋子,为什么j的最大值是10呢,要连成5颗才能赢。枚举法说明
这样,刚好把每一行的每个棋子在这行上的横向赢的总数统计了,其他方向一样
步骤二:在棋盘上点击,画棋子的主要工作
1 . 判断游戏是否结束
2. 计算机落子
判断游戏是否结束:
画完棋子之后(当前是玩家),循环遍历赢法数组,如果在这个点的wins[i][j][k] == true,玩家myWins[k] ++ ,计算机在在该点上就没有赢的可能了,把computerWin[k]置为一个无效值,当myWin[k] == 5的时候,说明玩家赢了。
这里详细解释一下:
我们在之前统计赢法的时候,对于每一种赢法都对应五个点,而myWins[]数组又是与wins[]数组对应的,每存在一个点使得wins[][][k]=true时,让myWins[k]的值加1,当myWins[k]=5时,说明已经有五颗棋子连成一起了。
那还有问题,会不会出现黑白棋子一起连成5颗的时候就myWins[k]等于5呢?不会,因为在点击的时候就已经判断当前是黑棋还是白棋了。只有是玩家的情况才画棋子。如果之前没有进行判断则会出现这种情况。
// 画棋子 // 没有棋子才能落子 if(chessBoardArr[i][j] == 0){ oneStep(i,j,me) chessBoardArr[i][j] = 1 for(var k = 0; k < count;k++){ if(wins[i][j][k]){ myWin[k] ++; computerWin[k] = 6; if(myWin[k] == 5){ alert('你赢了!') over = true } } } if(!over){ me = !me; computerAI() } }
如果还没结束,就到计算机落子了,另一个难点又来了。
计算机落棋规则:找到棋盘上没有落子的棋格,并分别计算玩家和计算机在该点上的分值并与当前的最高分进行比较,计算机在比较之后的最高分的棋格落子。
首先:我们需要使用两个二维数组来分别存放玩家和计算机在棋盘上每一格的分数,max来存放当前所有的最高分,u、v存放当前最高分的坐标
// 得分数组 var myScore = [] var computerScore = [] var max = 0 //用来保存最高分 var u = 0,v = 0 ;//用来保存最高分的坐标 for(var i = 0;i < 15;i++){ myScore[i] = [] computerScore[i] = [] for(var j = 0;j < 15;j++){ myScore[i][j] = 0 computerScore[i][j] = 0 } }
其次:遍历棋盘上没有棋子的每一格,并对该格遍历赢法数组,判断在该格上落子是否有价值。分两种情况:分别统计玩家和计算机在该点上第k种赢法的当前连子数,连子数越高,分数也就越高。
对该棋格统计完分数之后,分别把玩家和计算机在该棋格上的分数和最高分进行比较:
A. 玩家
1. myScore[i][j]>max ,那我们认为这个点是最好的,把最高分重置为当前玩家在这个点的分数,最高分的坐标也改成这个点的坐标。
2. myScore[i][j] == max , 如果计算机在该点上的分数大于计算机在最高分这个点的分数,那我们也认为这个点比较好,更新最大分数的值和坐标
B 计算机
和玩家的情况是一样的。
不过这里需要注意一点,因为当前是计算机,还需要对玩家进行一个阻碍,所以在连子数累加分的时候可以设置比玩家连子数累加分高一些。
接下来:遍历完整个棋盘之后,画棋子,并且判断是否结束(判断逻辑跟玩家的一样)
// 遍历棋盘 for(var i = 0;i < 15;i++){ for(var j = 0;j < 15;j++){ if(chessBoardArr[i][j] == 0){ // 如果没有棋子 // 循环比遍历赢法数组,判断在这个点落子有价值 for(var k = 0;k < count;k++){ if(wins[i][j][k]){ // 分值计算 if(myWin[k] == 1){ myScore[i][j] += 200 } else if(myWin[k] == 2){ myScore[i][j] += 400 } else if(myWin[k] == 3){ myScore[i][j] += 2000 } else if(myWin[k] == 4){ myScore[i][j] += 10000 } //电脑 if(computerWin[k] == 1){ computerScore[i][j] += 220 } else if(computerWin[k] == 2){ computerScore[i][j] += 420 } else if(computerWin[k] == 3){ computerScore[i][j] += 2100 } else if(computerWin[k] == 4){ computerScore[i][j] += 20000 } } } // 遍历赢法数组 e if(myScore[i][j] > max){ max = myScore[i][j] u = i v = j } else if(myScore[i][j] == max){ if(computerScore[i][j] > computerScore[u][v]){ u = i v = j } } if(computerScore[i][j] > max){ max = computerScore[i][j] u = i v = j } else if(computerScore[i][j] == max){ if(myScore[i][j] > myScore[u][v]){ u = i v = j } } } } } // 循环遍历 棋盘 e oneStep(u,v,false) chessBoardArr[u][v] = 2 for(var k = 0; k < count;k++){ if(wins[u][v][k]){ computerWin[k] ++; myWin[k] = 6; if(computerWin[k] == 5){ alert('电脑赢了!') over = true } } } if(!over){ me = !me; }
最后:要么结束,要么继续博弈
说到这里,整个五指棋-人机大战算是完成了,其中逻辑部分个人感觉还是有点绕的,在网上看了很多五指棋逻辑的资料,分为VCF和VCT,这个版本应该算VCF吧。还待继续研究。
上面有些地方可能说的不恰当,希望大神指正,也希望有兴趣的小伙伴多多分享交流。
运行效果:http://hjjia.github.io/js_exercises/demo-chess/