中学时看过一本关于围棋的漫画《棋魂》,奈何天赋有限,围棋至今也不会……好吧,退而求其次,五子棋相对简单一点。对着网上的教程实现了一个简单的五子棋:
其实ui的实现并不难,主要记录下ai的思路吧。
// 绘制棋盘 for (var i = 0; i < 15; i++) { context.beginPath(); context.moveTo(15 + i * 30, 15); context.lineTo(15 + i * 30, 435); context.stroke(); context.beginPath(); context.moveTo(15, 15 + i * 30); context.lineTo(435, 15 + i * 30); context.stroke(); };五子棋的棋盘为15*15,落子黑先白后,落子的过程其实就是在绘制旗子。
// 绘制棋子 function oneStep(x, y, color) { // x,y为棋子在棋盘的坐标索引,color为黑棋或白棋 context.beginPath(); context.arc(15 + x * 30, 15 + y * 30, 12, 0, 2 * Math.PI); context.closePath(); var gradient = context.createRadialGradient(15 + x * 30 + 2, 15 + y * 30 - 2, 12, 15 + x * 30 + 2, 15 + y * 30 - 2, 0); if (color) { gradient.addColorStop(0, '#0a0a0a'); gradient.addColorStop(1, '#636766'); } else { gradient.addColorStop(0, '#d1d1d1'); gradient.addColorStop(1, '#f9f9f9'); }; context.fillStyle = gradient; context.fill(); }
需要一个二维数组记录当前棋盘的落子情况,每次落子需要判断胜负以及是否结束。
var me = true; var chessBoard = []; //创建一个二维数组用于记录当前棋盘的落子情况 var wins = []; //三维数组记录五子棋所有的赢法 var count = 0; //记录五子棋所有赢法的索引 var over = false;
//落子事件 chess.onclick = function(e) { var x = e.offsetX; var y = e.offsetY; var i = Math.floor(x / 30); var j = Math.floor(y / 30); if (chessBoard[i][j] == 0 && !over) { //没有落子的位置才能落子,黑子为1,白子为2 oneStep(i, j, me); if (me) { chessBoard[i][j] = 1; } else { chessBoard[i][j] = 2; }; console.log(chessBoard); winner(i, j, me); me = !me; if (!over) { computerAI(me); }; }; }先说判断胜负,其实无论哪方,五子连珠作为胜利的条件,在15*15的棋盘上胜利的所有情况是可以枚举出来的。
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);
关键在于wins这个三维数组,有点难理解,举个例子:假如五子棋只有一种赢法:
var black = []; var white = []; //分别记录五子棋黑白的赢法数组这两个数组结合wins数组来判断胜负,五子棋共有572种赢法,默认黑子与白子的赢法都为0。
for (var i = 0; i < count; i++) { //开局默认黑白所有的赢法都是0 black[i] = 0; white[i] = 0; };
还是用上面的例子,如果黑方在第一种赢法处落下一子,那么黑子的第一种赢法+1,同时白子此种赢法作废。
//判断输赢 function winner(i, j, color) { for (var k = 0; k < count; k++) { if (wins[i][j][k]) { if (color) { black[k]++; white[k] = 6; //如果某种赢法黑子已经落子,白子此种赢法就作废 } else { white[k]++; black[k] = 6; }; if (black[k] == 5) { alert('黑子获胜'); over = true; }; if (white[k] == 5) { alert('白子获胜'); over = true; }; }; }; }代码到这里,已经能实现五子棋的规则逻辑了,接下来实现ai。
var blackScore = []; var whiteScore = []; //分别记录五子棋黑白的二维得分数组这个ai其实挺简单的,实现思路就是通过遍历每一个能落子的空坐标,然后结合算法找出分数最高的一个位置落子。
//AI function computerAI(color) { var max = 0; var u = 0; var v = 0; // 保存最大的分数和相应坐标 for (var i = 0; i < 15; i++) { blackScore[i] = []; whiteScore[i] = []; for (var j = 0; j < 15; j++) { blackScore[i][j] = 0; whiteScore[i][j] = 0; }; }; //每个坐标的分数为零 //遍历每个空坐标,如果某种赢法已经落子的数量越大则该坐标加分越多 //同理拦截对方的落子 //加分的数值很重要 for (var i = 0; i < 15; i++) { for (var j = 0; j < 15; j++) { if (chessBoard[i][j] == 0) { for (var k = 0; k < count; k++) { if (wins[i][j][k]) { switch (black[k]) { case 1: blackScore[i][j] += 2; break; case 2: blackScore[i][j] += 5; break; case 3: blackScore[i][j] += 20; break; case 4: blackScore[i][j] += 50; break; } switch (white[k]) { case 1: whiteScore[i][j] += 2; break; case 2: whiteScore[i][j] += 5; break; case 3: whiteScore[i][j] += 20; break; case 4: whiteScore[i][j] += 50; break; } //找出得分最高的坐标点 if (blackScore[i][j] > max) { max = blackScore[i][j]; u = i; v = j; } else if (blackScore[i][j] == max) { if (whiteScore[i][j] > whiteScore[u][v]) { u = i; v = j; } } if (whiteScore[i][j] > max) { max = whiteScore[i][j]; u = i; v = j; } else if (whiteScore[i][j] == max) { if (blackScore[i][j] > blackScore[u][v]) { u = i; v = j; } } }; }; }; }; }; oneStep(u, v, color); color ? chessBoard[u][v] = 1 : chessBoard[u][v] = 2; winner(u, v, color); me = !color; }
五子棋的棋盘上,每一个位置都存在多种赢法,此算法的逻辑就是假如一个空坐标还未落子,那么遍历所有的赢法,如果黑方在此种赢法已经落下一子,那么这个坐标对黑方有利,加分;如果黑方落下二子,那么分数更高;白方也是同理……找出最有价值的坐标落子。
至于如何加分,可以借鉴网上的评分表:
代码只是实现了思路,没有优化;而且此类“”民间规则“”五子棋都是先手必胜,
在五子棋专业规则中规定,一共有26种开局。直指开局13种,斜指开局13种。
这26种开局分别是:
寒星 溪月 残月 雨月 金星 丘月 新月
山月 游星 长星 峡月 恒星 水月 流星
浦月 岚月 银月 明星 名月 彗星 花月
松月 疏星 斜月 瑞星 云月
其中公认黑必胜的开局有:花月,浦月
黑必败开局有:彗星,游星
以上之适用于五子棋专业规则
在民间规则里,几乎全部是黑先手必胜。
专业的五子棋比赛还有禁手、三手交换、五手两打等限制,以后有机会再研究吧……