1.前言
记得读大学时,有段时间特别喜欢和室友们下五子棋,由于脑子不是特别灵光,再加上室友确实经验丰富,自己自然是屡屡战败。时光荏苒,一眨眼好多年过去了,很是怀念那时惬意的时光!大学毕业后,室友们都从事了不同行业的工作,我也是如愿选择了做“程序员”。AI近些年来一直很火热,其他行业的小伙伴总是误认为我们很厉害的样子,殊不知“如鱼饮水,冷暖自知”。不过“专业思想”还确实是有的,所以有感写了这篇博文,和大家就五子棋探讨一下“计算机”思维和“人类”思维的区别和联系。话不多说,先上一张“战败”效果图(我承认我还是下不过自己的智能算法):点击查看源代码:
2.前期工作
2.1画棋盘
五子棋的棋盘很简单,不过是一些横竖交叉的线条而已。懂canvas的同学自然知道是怎么回事:设计好坐标之后,在画布上画15条横竖交叉的线就可以了,也是横x,竖y,两个for循环而已。上代码:
context.strokeStyle = "#BFBFBF"; //设置线条颜色
function drawChessBoard() { //绘制棋盘
for(var i=0; i<15; i++) {
//横线条
context.moveTo(20 + i*40, 20);
context.lineTo(20 + i*40, 580);
context.stroke();
//竖线条
context.moveTo(20, 20 + i*40);
context.lineTo(580, 20 + i*40);
context.stroke();
}
}
2.2画黑白棋子
“棋子”是圆的,有黑、白两种颜色。只要我们找好圆心点,设置好半径,颜色就可以了。为了使棋子看起来更加逼真,可以给棋子中心添加渐变光泽,也不过是调用一个函数,封装成函数代码如下:
function drawChessMan(i, j) { //绘制棋子
context.beginPath();
context.arc(20 + i*40, 20 + j*40, 18, 0, 2*Math.PI);
context.closePath();
var gradient = context.createRadialGradient(23 + i*40, 17 + j*40, 18, 23 + i*40, 17 + j*40, 0);
if(curColor === 'white') {
gradient.addColorStop(0, "#D1D1D1");
gradient.addColorStop(1, "#F9F9F9");
}
if(curColor === 'black') {
gradient.addColorStop(0, "#0A0A0A");
gradient.addColorStop(1, "#636766");
}
context.fillStyle = gradient;
context.fill();
curColor = (curColor === 'white') ? 'black' : 'white';
}
2.3 鼠标点击交替画黑白棋子
增加鼠标点击事件,在棋盘上寻找最近的落子点绘制棋子。设置一个当前棋子颜色变量,黑白交替绘制,实现这个功能在棋子绘制函数中就已经存在:curColor = (curColor === 'white') ? 'black' : 'white';下边是鼠标事件监听函数
//鼠标落下,画棋子
chessboard.onclick = function(e) {
......
var x = e.offsetX;
var y = e.offsetY;
var i = Math.floor(x / 40);
var j = Math.floor(y / 40);
if(chessManStatus[i][j] === 0) {
drawChessMan(i, j);
......
}
}
3.核心算法——计算机是怎样思考的?
3.1 计算所有可能会赢的数据
我们不得不承认“计算机”的计算速度比人类要快的多,它能够每秒进行亿万次计算,而且按照既定的流程走,它永远不会累,也不会失误。如果我是“计算机”,我就把所有对手可能会赢的情况全都算出来,然后根据下棋的过程,根据每一次的数据情况,计算出一个坐标,这个坐标棋子能够阻止对手赢比赛,也能让自己更快的赢比赛。事实上,计算机也是这样做的,提前计算好所有可能会赢的情况对聪明的人类来说只需要几秒,但是对于计算机来说,做完这件事情并存储所有的数据,只是毫秒间的工作而已。下面是程序的实现(和我们想象的一样:将横的、竖的、倾斜的连着五个相同棋子的情况都考虑进去,就是所有会赢的情况。而且计算机还将棋子的状态,自己和人的数据做分类处理。)
var wins = [], personWin = [], computerWin = [],chessManStatus = [], winCount = 0; //所有获胜的数量和数量统计
for(var i=0;i<15;i++) {
wins[i] = []; chessManStatus[i] = [];
for(var j=0;j<15;j++) {
wins[i][j] = []; chessManStatus[i][j] = 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][winCount] = true;
}
winCount++;
}
}
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][winCount] = true;
}
winCount++;
}
}
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][winCount] = true;
}
winCount++;
}
}
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][winCount] = true;
}
winCount++;
}
}
for(var i=0; i<winCount; i++) {
personWin[i] = 0; computerWin[i] = 0;
}
经过通过棋盘上所有可能胜利的情况不过572种而已!
3.2 计算最合适的落棋目标
如果我是计算机,接下来我要做的就是当“聪明的人类”下好棋之后,我怎样下好自己的棋。这个棋的目标:不让人类赢,让自己赢!作为人类的我会大概看一下棋盘上的所有棋子,然后选好一个落棋点,我很羡慕计算机的计算能力,如果我是它我就可以计算一下棋盘上的每一个坐标点,对它们的情况进行评估打分,然后选得分最高的点下棋。当然计算机就是这么做的,每一次下棋它都做了128700次计算来挑选出最合适的落子点。而进行这么多次计算也不过是毫秒之间的事情,所以计算机下棋特别的快,快到“人类”看不出它的反应,几乎和自己同时下棋的,或许你的棋子刚落下,它就赢了,感觉特别沮丧!
function computerAI() { //电脑智能下棋
var personScore = [],computerScore = [],maxScore = 0,curX = 0, curY = 0;
for(var i=0; i<15; i++) {
personScore[i] = [];computerScore[i] = [];
for(var j=0; j<15; j++) {
personScore[i][j] = 0;computerScore[i][j] = 0;
}
}
for(var i=0; i<15; i++) {
for(var j=0; j<15; j++) {
if(chessManStatus[i][j] == 0) {
for(var k=0; k<winCount; k++) {
if(wins[i][j][k]) {
if(personWin[k] == 1 || personWin[k] == 2 || personWin[k] == 3 || personWin[k] == 4) {
personScore[i][j] += personWin[k]*personWin[k] * 200;
}
if(computerWin[k] == 1 || computerWin[k] == 2 || computerWin[k] == 3 || computerWin[k] == 4) {
computerScore[i][j] += (computerWin[k]*computerWin[k] - 1) * 200 + 399;
}
}
}
if(personScore[i][j] > maxScore) {
maxScore = personScore[i][j];
curX = i;
curY = j;
}else if(personScore[i][j] == maxScore) {
if(computerScore[i][j] > computerScore[curX][curY]) {
curX = i;
curY = j;
}
}
if(computerScore[i][j] > maxScore) {
maxScore = computerScore[i][j];
curX = i;
curY = j;
}else if(computerScore[i][j] == maxScore) {
if(personScore[i][j] > personScore[curX][curY]) {
curX = i;
curY = j;
}
}
}
}
}
drawChessMan(curX, curY);
......
}
这个打分算法是整个算法的核心。计算机将连在一起的棋子做打分,1个棋子分数很低,只有200;两个棋子就会翻到800;三个棋子会更高,幂次运算。有了评分标准以后,计算机开始评分,先给对手评分,给对手评完分数后再给自己评分,计算机自己的评分规则比对手评分规则高,但是原则上是2个棋子连在一起的评分不会高于对手三个棋子连在一起的:计算机在自己赢之前是不会让对手有赢的机会的。
3.4 游戏状态判断
那么游戏什么事件结束呢?当然是有五个棋子连在一起就结束了。这些工作就交给计算机来做吧:计算机记录每一个赢的情况下连珠棋子的个数,当有棋子达到5时游戏就结束了,当然黑白棋子是互斥的,当白棋在一个位置有连珠时,黑棋就永远没有机会了,我们在程序上给它设置为6,不管它怎样加连珠,都不可能是5了。初始化一个gameOver变量为false,标识游戏是否结束,当游戏结束时,取反就行:gameOver = !gameOver了。
for(var k=0; k<winCount; k++) {
if(wins[curX][curY][k]) {
computerWin[k]++;
personWin[k] = 6;
if(computerWin[k] == 5) {
alert('电脑赢了!');
gameOver = !gameOver;
}
}
}
4.更多细节与感悟
我承认自己代码写的很烂,考虑不到很多优化的情况。但是我把能想到的,能做到的还都是尽力做到了。比如我在一个已经有棋子的地方点击,是不会让系统重新绘制棋子的;我在游戏结束之后,不会让接下来的工作继续进行的;没有重新开始,没有善后工作,这些就留想要做一个完美作品的人来做吧。写这个智能算法给我的感悟就是我更能够像计算机一样的去思考问题了,人类的思考能力会受到情感的左右,而计算机不会,一个越成熟的人,越会像计算机一样,像程序一样,做事情井然有序,受情感因素的干扰会很小。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。