canvas简易人机五子棋

中学时看过一本关于围棋的漫画《棋魂》,奈何天赋有限,围棋至今也不会……好吧,退而求其次,五子棋相对简单一点。对着网上的教程实现了一个简单的五子棋:


其实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这个三维数组,有点难理解,举个例子:假如五子棋只有一种赢法:


那么count的值为1,但五子棋可不能一次就落五个子(这还怎么玩?)所以,每一种赢法要包含五个落子的坐标,也就是说,这五个落子的位置,无论落子先后,只要达成五子连珠,此种赢法就实现了。
        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种开局分别是:
寒星 溪月 残月 雨月 金星 丘月 新月 
山月 游星 长星 峡月 恒星 水月 流星
浦月 岚月 银月 明星 名月 彗星 花月 
松月 疏星 斜月 瑞星 云月 

其中公认黑必胜的开局有:花月,浦月
黑必败开局有:彗星,游星
以上之适用于五子棋专业规则
在民间规则里,几乎全部是黑先手必胜。

专业的五子棋比赛还有禁手、三手交换、五手两打等限制,以后有机会再研究吧……


猜你喜欢

转载自blog.csdn.net/u010815486/article/details/52383083