『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓

目录

    • 前言
    • 先画一个棋盘
    • 放点棋子在上面呗
    • 所以,怎么能少了规则呢?
      • 车逻辑
      • 马逻辑
      • 象逻辑
      • 士逻辑
      • 将逻辑
      • 炮逻辑
      • 卒逻辑
    • 来到了大家会感兴趣的AI部分了
    • 实现了AI后,不能走也不行啊
      • 判断点击的位置
      • 得出选中棋子的可走路径(可落子点)
    • 小花絮
      • 爷爷口中的那个游戏
    • 项目地址
    • 预览地址
    • 特别感谢

『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓_第1张图片

前言

小时候一直和爷爷下象棋,可是除了耍赖,就从来没有赢过(lll¬ω¬)

有一次做梦…我写出了一个能赢过爷爷的象棋AI,但却没有机会和他下了

就觉得有必要在还有机会的时候自己写一个智能体出来,也算是经我之手正式地赢了爷爷(●ˇ∀ˇ●)

先画一个棋盘

  • 以下代码来自欲泪成雪的原创分享,加上我适当的修改
DOCTYPE html>
    
    <html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>中国象棋title>
        <style>
        style>
    head>

    <body>
        <div class="layout">
            <canvas id="canvas1" style="background-color:burlywood" width="1005" height="1105">不支持Canvascanvas>
        div>
        <script type="text/javascript">
            var object = {
                //初始化
                init: function () {
                    //棋盘外框
                    var canvas1 = document.getElementById("canvas1");
                    this.ctx = canvas1.getContext("2d");
                    this.ctx.lineWidth = 5;
                    this.ctx.clearRect(0, 0, 1005, 1105);
                    this.ctx.strokeStyle = "brown";
                    this.ctx.strokeRect(100, 100, 800, 900);

                    this.row();
                    this.cols();
                    this.drawFont();
                    //注意:现在的原点中心为(100,100),所以下面的所有坐标在原来的基础上加(x+100,y+100);
                    //中心点一(1000,200)
                    this.center(200, 300);
                    //中心点二(700,200)
                    this.center(800, 300);
                    //中心点三(5,300)
                    this.center(100, 400);
                    //中心点四(200,300)
                    this.center(300, 400);
                    //中心点五(400,300)
                    this.center(500, 400);
                    //中心点六(600,300)
                    this.center(700, 400);
                    //中心点七(800,300)
                    this.center(900, 400);
                    //中心点八(800,600)
                    this.center(900, 700);
                    //中心点九(600,600)
                    this.center(700, 700);
                    //中心点十(400,600)
                    this.center(500, 700);
                    //中心点十一(200,600)
                    this.center(300, 700);
                    //中心点十二(5,600)
                    this.center(100, 700);
                    //中心点十三(700,700)
                    this.center(800, 800);
                    //中心点十四(100,700)
                    this.center(200, 800);
                },
                //此方法用来画棋盘线
                LineDrawing: function (mx, my, lx, ly) {
                    this.ctx.beginPath();
                    this.ctx.moveTo(mx, my);
                    this.ctx.lineTo(lx, ly);
                    this.ctx.stroke();
                },
                //棋盘行
                row: function () {
                    for (var i = 200; i <= 900; i += 100) {
                        this.ctx.beginPath();
                        this.ctx.moveTo(100, i);
                        this.ctx.lineTo(900, i);
                        this.ctx.stroke();
                    }
                },
                //棋盘列
                cols: function () {
                    for (var i = 200; i <= 800; i += 100) {
                        this.ctx.beginPath();
                        this.ctx.moveTo(i, 100);
                        this.ctx.lineTo(i, 1000);
                        this.ctx.stroke();
                    }
                    //清除指定的矩形区域
                    //this.ctx.clearRect(5, 402,795, 95);
                    this.ctx.clearRect(103, 502, 795, 95);
                    //斜线
                    this.LineDrawing(400, 105, 600, 300);
                    this.LineDrawing(400, 805, 600, 1000);
                    //反斜线
                    this.LineDrawing(600, 105, 400, 300);
                    this.LineDrawing(600, 805, 400, 1000);
                },
                //坐标的中心点
                center: function (x, y) {
                    this.ctx.lineWidth = 5;
                    //中心点一(100,200)
                    //左上
                    this.LineDrawing(x - 10, y - 30, x - 10, y - 10);
                    this.LineDrawing(x - 10, y - 10, x - 30, y - 10);
                    //右上
                    this.LineDrawing(x + 10, y - 30, x + 10, y - 10);
                    this.LineDrawing(x + 10, y - 10, x + 30, y - 10);
                    //左下
                    this.LineDrawing(x - 10, y + 30, x - 10, y + 10);
                    this.LineDrawing(x - 10, y + 10, x - 30, y + 10);
                    //右下
                    this.LineDrawing(x + 10, y + 30, x + 10, y + 10);
                    this.LineDrawing(x + 10, y + 10, x + 30, y + 10);

                    this.ctx.stroke();
                },
                drawFont: function () {
                    this.ctx.lineWidth = 1;
                    //绘制文字
                    this.ctx.font = "60px microsoft yahei";
                    // this.ctx.fillStyle = "black";
                    this.ctx.save(); //保存点
                    //将坐标中心作为起启点
                    this.ctx.translate(canvas1.width / 2, canvas1.height / 2);
                    var radian = Math.PI / 2; // 弧度制 Math.PI=π
                    this.ctx.rotate(radian); // 旋转画布绘制刻度
                    //填充
                    this.ctx.fillText("汉", -30, -270);
                    this.ctx.fillText("界", -30, -150);
                    this.ctx.restore(); //恢复到保存点
                    this.ctx.save();
                    //将坐标中心作为起点
                    this.ctx.translate(canvas1.width / 2, canvas1.height / 2);
                    var radian = Math.PI / -2;
                    this.ctx.rotate(radian);
                    this.ctx.fillText("楚", -30, -270);
                    this.ctx.fillText("河", -30, -150);
                    this.ctx.restore();
                },
                drawRed(chessPieceName, _x, _y) {
                    this.drawChessPieces(chessPieceName, _x, _y, {
                        color: 'red',
                        fillColor: 'black'
                    })
                },
                drawBlack(chessPieceName, _x, _y) {
                    this.drawChessPieces(chessPieceName, _x, _y, {
                        color: 'white',
                        fillColor: 'black'
                    })
                },
                showRoad(chessPieceName, _x, _y, x, y) {
                    this.drawChessPieces(chessPieceName, _x, _y, {
                        color: 'blue',
                        fillColor: '',
                        r: 50
                    })
                },
                drawChoose(chessMap, roads, chessPieceName, _x, _y, x, y) {
                    // this.drawChessPieces(chessPieceName, _x, _y, {
                    //     color: 'blue',
                    //     fillColor: '',
                    //     r: 50
                    // })
                    roads.push({
                        _x, _y, x, y
                    })
                },
                drawChessPieces: function (chessPieceName, _x, _y, config) {
                    let x = (_x) * 100 + 100;
                    let y = (_y) * 100 + 100;
                    this.ctx.strokeStyle = config.color;
                    this.ctx.lineWidth = 5;
                    this.ctx.beginPath();
                    this.ctx.arc(x, y, config.r || 40, 0, 2 * Math.PI, true);
                    if (config.fillColor) {
                        this.ctx.fillStyle = config.fillColor;
                        this.ctx.fill();
                    }
                    this.ctx.stroke();
                    this.ctx.font = "60px microsoft yahei";
                    this.ctx.fillStyle = config.color;
                    this.ctx.fillText(chessPieceName, x - 30, y + 20);
                }
            };
            object.init();
        script>
    body>
html>

出来的效果

『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓_第2张图片

放点棋子在上面呗

  • 参考了网上的多种实现方式后,我写了最适合我自己的生成棋子的方法
// 重置棋盘
function resetMap() {
    chessMap = [
        ['车红一', '马红一', '相红一', '仕红一', '帅红一', '仕红二', '相红二', '马红二', '车红二'],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['   ', '砲红一', '   ', '   ', '   ', '   ', '   ', '砲红二', '   '],
        ['兵红一', '   ', '兵红二', '   ', '兵红三', '   ', '兵红四', '   ', '兵红五'],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['卒黑一', '   ', '卒黑二', '   ', '卒黑三', '   ', '卒黑四', '   ', '卒黑五'],
        ['   ', '炮黑一', '   ', '   ', '   ', '   ', '   ', '炮黑二', '   '],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['车黑一', '马黑一', '象黑一', '士黑一', '将黑一', '士黑二', '象黑二', '马黑二', '车黑二']
    ]
    side = '红';
    flushChessCanvas();
}
resetMap();
// 画出棋子
function flushChessCanvas() {
    object.init();
    for (let y = 0; y < chessMap.length; y++) {
        for (let x = 0; x < chessMap[y].length; x++) {
            let chess = chessMap[y][x];
            if (chess[1] == '红') {
                object.drawRed(chess[0], x, y)
            }
            if (chess[1] == '黑') {
                object.drawBlack(chess[0], x, y)
            }
        }
    }
}

目前就大致实现了文章开头的棋盘的效果,至此,我们可以看到一副配备完整的象棋+棋盘了(可以拿来卖了( ̄▽ ̄)")

所以,怎么能少了规则呢?

  • 这里直接上代码了,我按超逸_流的方法,自己实现了一边棋盘的主逻辑,主要来判断每一颗棋子可以行走的路径

车逻辑

// 车逻辑
function getJuRoad(map, roads, x, y) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 上
    for (let _y = y - 1, _x = x; _y > -1; _y--) {
        if (chessMap[_y][_x] === '   ') {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
        }
        // 同色子
        else if (chessMap[_y][_x][1] === chess[1]) {
            break;
        }
        // 异色子
        else if (chessMap[_y][_x][1] !== chess[1]) {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            break;
        }
    }
    // 右
    for (let _y = y, _x = x + 1; _x < 9; _x++) {
        if (chessMap[_y][_x] === '   ') {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
        }
        // 同色子
        else if (chessMap[_y][_x][1] === chess[1]) {
            break;
        }
        // 异色子
        else if (chessMap[_y][_x][1] !== chess[1]) {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            break;
        }
    }
    // 下
    for (let _y = y + 1, _x = x; _y < 10; _y++) {
        if (chessMap[_y][_x] === '   ') {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
        }
        // 同色子
        else if (chessMap[_y][_x][1] === chess[1]) {
            break;
        }
        // 异色子
        else if (chessMap[_y][_x][1] !== chess[1]) {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            break;
        }
    }
    // 左
    for (let _y = y, _x = x - 1; _x > -1; _x--) {
        if (chessMap[_y][_x] === '   ') {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
        }
        // 同色子
        else if (chessMap[_y][_x][1] === chess[1]) {
            break;
        }
        // 异色子
        else if (chessMap[_y][_x][1] !== chess[1]) {
            object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            break;
        }
    }
}
// getJuRoad(0,0)

马逻辑

// 马逻辑
function getMaRoad(map, roads, x, y) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 1点钟方向
    if (x + 1 < 9 && y - 2 > -1 && chessMap[y - 1][x] === '   ' && chessMap[y - 2][x + 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x + 1, y - 2, x, y)
    }
    // 2点钟方向
    if (x + 2 < 9 && y - 1 > -1 && chessMap[y][x + 1] === '   ' && chessMap[y - 1][x + 2][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x + 2, y - 1, x, y)
    }
    // 4点钟方向
    if (x + 2 < 9 && y + 1 < 10 && chessMap[y][x + 1] === '   ' && chessMap[y + 1][x + 2][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x + 2, y + 1, x, y)
    }
    // 5点钟方向
    if (x + 1 < 9 && y + 2 < 10 && chessMap[y + 1][x] === '   ' && chessMap[y + 2][x + 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x + 1, y + 2, x, y)
    }
    // 7点钟方向
    if (x - 1 > -1 && y + 2 < 10 && chessMap[y + 1][x] === '   ' && chessMap[y + 2][x - 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x - 1, y + 2, x, y)
    }
    // 8点钟方向
    if (x - 2 > -1 && y + 1 < 10 && chessMap[y][x - 1] === '   ' && chessMap[y + 1][x - 2][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x - 2, y + 1, x, y)
    }
    // 10点钟方向
    if (x - 2 > -1 && y - 1 > -1 && chessMap[y][x - 1] === '   ' && chessMap[y - 1][x - 2][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x - 2, y - 1, x, y)
    }
    // 11点钟方向
    if (x - 1 > -1 && y - 2 > -1 && chessMap[y - 1][x] === '   ' && chessMap[y - 2][x - 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x - 1, y - 2, x, y)
    }
}
// getMaRoad(1,0)

象逻辑

// 象逻辑
function getXiangRoad(map, roads, x, y, side) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 1点半方向
    if (y - 2 > -1 && x + 2 < 9 && chessMap[y - 1][x + 1] === '   ' && chessMap[y - 2][x + 2][1] != chess[1]) {
        if (side == '黑' && (y - 2) > 4) {
            object.drawChoose(chessMap, roads, chess[0], x + 2, y - 2, x, y)
        }
        if (side == '红' && (y - 2) < 5) {
            object.drawChoose(chessMap, roads, chess[0], x + 2, y - 2, x, y)
        }
    }
    // 4点半方向
    if (y + 2 < 10 && x + 2 < 9 && chessMap[y + 1][x + 1] === '   ' && chessMap[y + 2][x + 2][1] != chess[1]) {
        if (side == '黑' && (y + 2) > 4) {
            object.drawChoose(chessMap, roads, chess[0], x + 2, y + 2, x, y)
        }
        if (side == '红' && (y + 2) < 5) {
            object.drawChoose(chessMap, roads, chess[0], x + 2, y + 2, x, y)
        }
    }
    // 7点半方向
    if (y + 2 < 10 && x - 2 > -1 && chessMap[y + 1][x - 1] === '   ' && chessMap[y + 2][x - 2][1] != chess[1]) {
        if (side == '黑' && (y + 2) > 4) {
            object.drawChoose(chessMap, roads, chess[0], x - 2, y + 2, x, y)
        }
        if (side == '红' && (y + 2) < 5) {
            object.drawChoose(chessMap, roads, chess[0], x - 2, y + 2, x, y)
        }
    }
    // 10点半方向
    if (y - 2 > -1 && x - 2 > -1 && chessMap[y - 1][x - 1] === '   ' && chessMap[y - 2][x - 2][1] != chess[1]) {
        if (side == '黑' && (y - 2) > 4) {
            object.drawChoose(chessMap, roads, chess[0], x - 2, y - 2, x, y)
        }
        if (side == '红' && (y - 2) < 5) {
            object.drawChoose(chessMap, roads, chess[0], x - 2, y - 2, x, y)
        }
    }
}
// getXiangRoad(2,0,'红')

士逻辑

// 士逻辑
function getShiRoad(map, roads, x, y, side) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 1点半方向
    if (y - 1 > -1 && x + 1 < 9 && chessMap[y - 1][x + 1][1] != chess[1]) {
        if (side == '黑' && (y - 1) > 6 && (x + 1) > 2 && (x + 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x + 1, y - 1, x, y)
        }
        if (side == '红' && (y - 1) < 3 && (x + 1) > 2 && (x + 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x + 1, y - 1, x, y)
        }
    }
    // 4点半方向
    if (y + 1 < 10 && x + 1 < 9 && chessMap[y + 1][x + 1][1] != chess[1]) {
        if (side == '黑' && (y + 1) > 6 && (x + 1) > 2 && (x + 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x + 1, y + 1, x, y)
        }
        if (side == '红' && (y + 1) < 3 && (x + 1) > 2 && (x + 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x + 1, y + 1, x, y)
        }
    }
    // 7点半方向
    if (y + 1 < 10 && x - 1 > -1 && chessMap[y + 1][x - 1][1] != chess[1]) {
        if (side == '黑' && (y + 1) > 6 && (x - 1) > 2 && (x - 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x - 1, y + 1, x, y)
        }
        if (side == '红' && (y + 1) < 3 && (x - 1) > 2 && (x - 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x - 1, y + 1, x, y)
        }
    }
    // 10点半方向
    if (y - 1 > -1 && x - 1 > -1 && chessMap[y - 1][x - 1][1] != chess[1]) {
        if (side == '黑' && (y - 1) > 6 && (x - 1) > 2 && (x - 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x - 1, y - 1, x, y)
        }
        if (side == '红' && (y - 1) < 3 && (x - 1) > 2 && (x - 1) < 6) {
            object.drawChoose(chessMap, roads, chess[0], x - 1, y - 1, x, y)
        }
    }
}
// getShiRoad(3,0,'红')

将逻辑

// 将逻辑
function getJiangRoad(map, roads, x, y, side) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 对阵
    if (side == '黑') {
        for (let _y = y-1;_y > -1; _y--) {
            if (chessMap[_y][x][0] === '帅') {
                object.drawChoose(chessMap, roads, chess[0], x, _y, x, y)
            }
            if (chessMap[_y][x] !== '   ') {
                break;
            }
        }
    }
    if (side == '红') {
        for (let _y = y+1;_y < 10; _y++) {
            if (chessMap[_y][x][0] === '将') {
                object.drawChoose(chessMap, roads, chess[0], x, _y, x, y)
            }
            if (chessMap[_y][x] !== '   ') {
                break;
            }
        }
    }
    // 上
    if (y - 1 > -1 && chessMap[y - 1][x][1] != chess[1]) {
        if (side == '黑' && (y - 1) > 6) {
            object.drawChoose(chessMap, roads, chess[0], x, y - 1, x, y)
        }
        if (side == '红') {
            object.drawChoose(chessMap, roads, chess[0], x, y - 1, x, y)
        }
    }
    // 右
    if (x + 1 < 6 && chessMap[y][x + 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x + 1, y, x, y)
    }
    // 下
    if (y + 1 < 10 && chessMap[y + 1][x][1] != chess[1]) {
        if (side == '黑') {
            object.drawChoose(chessMap, roads, chess[0], x, y + 1, x, y)
        }
        if (side == '红' && (y + 1) < 3) {
            object.drawChoose(chessMap, roads, chess[0], x, y + 1, x, y)
        }
    }
    // 左
    if (x - 1 > 2 && chessMap[y][x - 1][1] != chess[1]) {
        object.drawChoose(chessMap, roads, chess[0], x - 1, y, x, y)
    }
}
// getJiangRoad(4,0,'红')

炮逻辑

// 炮逻辑
function getPaoRoad(map, roads, x, y) {
    let chessMap = map;
    let chess = chessMap[y][x];
    let count = 0;
    // 上
    for (let _y = y - 1, _x = x; _y > -1; _y--) {
        if (count == 0) {
            if (chessMap[_y][_x] === '   ') {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            } else {
                count++;
            }
        } else {
            // 空白
            if (chessMap[_y][_x] === '   ') {
                continue;
            }
            // 同色子
            else if (chessMap[_y][_x][1] === chess[1]) {
                break;
            }
            // 异色子
            else if (chessMap[_y][_x][1] !== chess[1]) {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
                break;
            }
        }
    }
    count = 0;
    // 右
    for (let _y = y, _x = x + 1; _x < 9; _x++) {
        if (count == 0) {
            if (chessMap[_y][_x] === '   ') {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            } else {
                count++;
            }
        } else {
            // 空白
            if (chessMap[_y][_x] === '   ') {
                continue;
            }
            // 同色子
            else if (chessMap[_y][_x][1] === chess[1]) {
                break;
            }
            // 异色子
            else if (chessMap[_y][_x][1] !== chess[1]) {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
                break;
            }
        }
    }
    count = 0;
    // 下
    for (let _y = y + 1, _x = x; _y < 10; _y++) {
        if (count == 0) {
            if (chessMap[_y][_x] === '   ') {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            } else {
                count++;
            }
        } else {
            // 空白
            if (chessMap[_y][_x] === '   ') {
                continue;
            }
            // 同色子
            else if (chessMap[_y][_x][1] === chess[1]) {
                break;
            }
            // 异色子
            else if (chessMap[_y][_x][1] !== chess[1]) {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
                break;
            }
        }
    }
    count = 0;
    // 左
    for (let _y = y, _x = x - 1; _x > -1; _x--) {
        if (count == 0) {
            if (chessMap[_y][_x] === '   ') {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
            } else {
                count++;
            }
        } else {
            // 空白
            if (chessMap[_y][_x] === '   ') {
                continue;
            }
            // 同色子
            else if (chessMap[_y][_x][1] === chess[1]) {
                break;
            }
            // 异色子
            else if (chessMap[_y][_x][1] !== chess[1]) {
                object.drawChoose(chessMap, roads, chess[0], _x, _y, x, y)
                break;
            }
        }
    }
}
// getPaoRoad(1, 2)

卒逻辑

// 卒逻辑
function getZuRoad(map, roads, x, y, side) {
    let chessMap = map;
    let chess = chessMap[y][x];
    // 上
    if (side == '黑') {
        if (y-1>-1 && chessMap[y-1][x][1] != '黑') {
            object.drawChoose(chessMap, roads, chess[0], x, y-1, x, y);
        }
    }
    // 右
    if (side == '黑' && y < 5) {
        if (x+1<9 && chessMap[y][x+1][1] != '黑') {
            object.drawChoose(chessMap, roads, chess[0], x+1, y, x, y);
        }
    }
    if (side == '红' && y > 4) {
        if (x+1<9 && chessMap[y][x+1][1] != '红') {
            object.drawChoose(chessMap, roads, chess[0], x+1, y, x, y);
        }
    }
    // 下
    if (side == '红') {
        if (y+1<10 && chessMap[y+1][x][1] != '红') {
            object.drawChoose(chessMap, roads, chess[0], x, y+1, x, y);
        }
    }
    // 左
    if (side == '黑' && y < 5) {
        if (x-1>-1 && chessMap[y][x-1][1] != '黑') {
            object.drawChoose(chessMap, roads, chess[0], x-1, y, x, y);
        }
    }
    if (side == '红' && y > 4) {
        if (x-1>-1 && chessMap[y][x-1][1] != '红') {
            object.drawChoose(chessMap, roads, chess[0], x-1, y, x, y);
        }
    }
}
// getZuRoad(0,3,'红')

所以,到了这儿,我们手中的一颗颗棋子?,就知道他们各自能走到哪里了~

来到了大家会感兴趣的AI部分了

  • 同样,参考了超逸_流象棋AI算法算法

说实话,我是没怎么看懂,什么最小-最大搜索啦,什么负值最大函数,什么Alpha-Beta搜索通通是浮云哈哈哈o( ̄▽ ̄)o,我按我自己的理解先写再说~


function doScore(chess, nowScore) {
    let _side = chess[1];
    let _chessName = chess[0];
    if (_side == '黑') {
        nowScore[_side] = nowScore[_side] ? nowScore[_side] + scoreDic[_chessName] : scoreDic[_chessName];
    }
    if (_side == '红') {
        nowScore[_side] = nowScore[_side] ? nowScore[_side] + scoreDic[_chessName] : scoreDic[_chessName];
    }
}
function getNowScore(map) {
    let nowScore = {: 0,: 0
    }
    for (let y = 0; y < chessMap.length; y++) {
        for (let x = 0; x < chessMap[y].length; x++) {
            let chess = chessMap[y][x];
            doScore(chess, nowScore);
        }
    }
    return nowScore;
}
// 会卡死,最后才出效果,目前没有效果
function addLoadingLayer() {
    document.getElementsByClassName('loading-layer')[0].style.display = 'inline-block';
}
function removeLoadingLayer() {
    document.getElementsByClassName('loading-layer')[0].style.display = 'none';
}
let startTime;
let endTime;
let ccc = 0;
function checkAllChess(map=chessMap, parentSide=side, steps=[], parentScoreSum=0) {
    let chessMap = map;
    calNum++
    let roads = [];
    if (steps.length == 0) {
        console.log('chessMap', JSON.parse(JSON.stringify(chessMap)))
        startTime = new Date().getTime();
        addLoadingLayer();
        bestSteps = [];
        allSteps = [];
        bestScore = -Infinity;
    }
    if (steps.length == 1) {
        console.log(++ccc);
    }
    // 计算哪一边
    let mainSide = parentSide;
    if (steps.length % 2 == 1 ) {
        mainSide = mainSide == '黑' ? '红' : '黑';
    }
    // round++
    let isAlive = false;
    for (let y = 0; y < chessMap.length; y++) {
        for (let x = 0; x < chessMap[y].length; x++) {
            let chess = chessMap[y][x];
            if (chess[1] == parentSide) {
                if (chess[0] == '车') {
                    getJuRoad(chessMap, roads, x, y);
                }
                if (chess[0] == '马') {
                    getMaRoad(chessMap, roads, x, y);
                }
                if (chess[0] == '象' || chess[0] == '相') {
                    getXiangRoad(chessMap, roads, x, y, chess[1]);
                }
                if (chess[0] == '士' || chess[0] == '仕') {
                    getShiRoad(chessMap, roads, x, y, chess[1]);
                }
                if (chess[0] == '炮' || chess[0] == '砲') {
                    getPaoRoad(chessMap, roads, x, y)
                }
                if (chess[0] == '卒' || chess[0] == '兵') {
                    getZuRoad(chessMap, roads, x, y, chess[1]);
                }
                if (chess[0] == '将' || chess[0] == '帅') {
                    getJiangRoad(chessMap, roads, x, y, chess[1]);
                    isAlive = true;
                }
            }
        }
    }
    if (isAlive && round < 1000) {
        let localBestScore = parentSide == mainSide ? -Infinity : Infinity;
        // let road = roads[roads.length*Math.random()|0];
        for (let i = 0; i < roads.length; i++) {
            let _side = parentSide;
            let road = roads[i];
            let _map = JSON.parse(JSON.stringify(map))
            // map 改变原地图,_map改变模拟地图
            let eatedChess = _map[road._y][road._x];
            let playChess = _map[road.y][road.x];
            let score = downChess(_map, road._x, road._y, road.x, road.y, false);
            let scoreSum = parentScoreSum;
            let _steps = JSON.parse(JSON.stringify(steps));
            if (_side == mainSide) {
                score = score * (times  - _steps.length);
            } else {
                score = -score * (times  - _steps.length);
            }
            scoreSum += score;
            _steps.push({
                score,
                _x: road._x,
                _y: road._y,
                x: road.x,
                y: road.y,
                playChess,
                eatedChess,
                id: uuid() 
            })
            if (_steps.length < times) {
                // _side = changeSide(_side);
                // checkAllChess(_map, _side, _steps, scoreSum);
                if (_side == mainSide && score >= localBestScore ) {
                    localBestScore = score;
                    if (score == localBestScore && Math.random()>.5) {
                        _side = changeSide(_side);
                        checkAllChess(_map, _side, _steps, scoreSum);
                    } else {
                        _side = changeSide(_side);
                        checkAllChess(_map, _side, _steps, scoreSum);
                    }
                }
                if (_side != mainSide && score <= localBestScore ) {
                    localBestScore = score;
                    if (score == localBestScore && Math.random()>.5) {
                        _side = changeSide(_side);
                        checkAllChess(_map, _side, _steps, scoreSum);
                    } else {
                        _side = changeSide(_side);
                        checkAllChess(_map, _side, _steps, scoreSum);
                    }
                }
            } else {
                allSteps.push(_steps);
                if (scoreSum > bestScore) {
                    bestScore = scoreSum;
                    bestSteps = _steps;
                }
            }
        }
    } else {
        let scoreSum = parentScoreSum;
        if (scoreSum > bestScore) {
            bestScore = scoreSum;
            bestSteps = steps;
        }
        // countFinal();
    }
    calNum--;
    if (calNum == 0) {
        endTime = new Date().getTime();
        removeLoadingLayer();
        let finalDic = {};
        for (let i = 0; i < allSteps.length; i++) {
            let _steps = allSteps[i];
            let accScore = 0;
            for (let j = 0; j < times; j++) {
                _steps[j] = _steps[j] || {
                    score: 0,
                    _x: -1,
                    _y: -1,
                    x: -1,
                    y: -1,
                    playChess: '   ',
                    eatedChess: '   '
                }
                accScore += _steps[j].score;
                _steps[j].accScore = accScore;
            }
            _steps[times - 1].in = 1;
            for (let j = 0; j < times; j++) {
                _steps[j].finalScore = accScore;
            }
        }

        let accCheckDic = {};
        console.log('---------------',accCheckDic['-'])
        for (let j = times - 1; j > -1; j--) {
            if (j % 2 == 0) {
                // 自己方,越大越好
                for (let i = 0; i < allSteps.length; i++) {
                    let _steps = allSteps[i];
                    let self = _steps[j];
                    if (!self.in) {
                        continue;
                    }
                    let parent = _steps[j - 1] || {};
                    let parentKey = parent.id || '-';
                    if (!accCheckDic[parentKey]) {
                        accCheckDic[parentKey] = {
                            bestId: '',
                            bestFinalScore: ((j % 2)-.5)*2*Infinity
                        }
                    }
                    if (accCheckDic[parentKey].bestFinalScore < self.finalScore) {
                        try {
                            delete accCheckDic[parentKey].parent.in;
                        } catch(err) {

                        }
                        accCheckDic[parentKey] = {
                            bestId: self.id,
                            level: j,
                            bestFinalScore: self.finalScore,
                            parent: parent,
                            src: self
                        }
                        parent.in = 1;
                    }
                }
            }
            if (j % 2 == 1) {
                // 敌对方,越小越好
                for (let i = 0; i < allSteps.length; i++) {
                    let _steps = allSteps[i];
                    let self = _steps[j];
                    if (!self.in) {
                        continue;
                    }
                    let parent = _steps[j - 1] || {};
                    let parentKey = parent.id || '-';
                    if (!accCheckDic[parentKey]) {
                        accCheckDic[parentKey] = {
                            bestId: '',
                            bestFinalScore: ((j % 2)-.5)*2*Infinity
                        }
                    }
                    if (accCheckDic[parentKey].bestFinalScore > self.finalScore) {
                        try {
                            delete accCheckDic[parentKey].parent.in;
                        } catch(err) {

                        }
                        accCheckDic[parentKey] = {
                            bestId: self.id,
                            level: j,
                            bestFinalScore: self.finalScore,
                            parent: parent,
                            src: self
                        }
                        parent.in = 1;
                    }
                }
            }
        }
        console.log(accCheckDic)
        console.log(accCheckDic['-'])
        console.log(accCheckDic[accCheckDic['-'].bestId])
        console.log(allSteps);
        console.log(map)
        downChess(map, accCheckDic['-'].src._x, accCheckDic['-'].src._y, accCheckDic['-'].src.x, accCheckDic['-'].src.y, true);
    }
}
let dic = {};
function countFinal() {
    dic['count'] = dic['count'] ? dic['count'] + 1 : 1;
    dic[round] = dic[round] ? dic[round] + 1 : 1;
    for (let y = 0; y < chessMap.length; y++) {
        for (let x = 0; x < chessMap[y].length; x++) {
            let chess = chessMap[y][x];
            if (chess !== '   ') {
                dic[chess] = dic[chess] ? dic[chess]+1 : 1;
            }
        }
    }
    chessMap = [
        ['车红一', '马红一', '相红一', '仕红一', '帅红一', '仕红二', '相红二', '马红二', '车红二'],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['   ', '砲红一', '   ', '   ', '   ', '   ', '   ', '砲红二', '   '],
        ['兵红一', '   ', '兵红二', '   ', '兵红三', '   ', '兵红四', '   ', '兵红五'],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['卒黑一', '   ', '卒黑二', '   ', '卒黑三', '   ', '卒黑四', '   ', '卒黑五'],
        ['   ', '炮黑一', '   ', '   ', '   ', '   ', '   ', '炮黑二', '   '],
        ['   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   '],
        ['车黑一', '马黑一', '象黑一', '士黑一', '将黑一', '士黑二', '象黑二', '马黑二', '车黑二']
    ]
    round = 0;
}

function downChess(map, _x, _y, x, y, isFlush=true) {
    if (_x==x&&_y==y) {
        return false;
    }
    let chessMap = map;
    let chess = chessMap[y][x].toString();
    chessMap[y][x] = '   ';
    eatedChess = chessMap[_y][_x].toString();
    chessMap[_y][_x] = chess;
    if (isFlush) {
        flushChessCanvas();
        side = side == '黑' ? '红' : '黑';

        if (isGameMode && eatedChess[0] == '帅') {
            alert('黑方胜');
        }
        if (isGameMode && eatedChess[0] == '将') {
            alert('红方胜');
        }
    }
    
    return scoreDic[eatedChess[0]];
}
function changeSide(side) {
    if (side == '黑') {
        side = '红';
    } else {
        side = '黑';
    }
    return side;
}
flushChessCanvas()

这么大一串,我猜你们也不会去看,给你们大致讲一下吧

大概就是搜索每一步每一方可走的路径,大约有40种可选路径,然后再这40种中随机挑选1条路,一直计算下去

当然,对手也不会太笨,他也会从这些中间挑1种走法,而且是最有利于他的方法(当然也可以选不利于他的,但他如果选了,我们赢得概率一定更大了)

所以层层下去,算出当下最利于我们的那一条路(考虑到对手选最好的情况下)

实现了AI后,不能走也不行啊

  • 所以,我实现了下棋的方法

判断点击的位置

  • 如果原先未选中棋子,就进入选择落子状态
  • 如果原先选了棋子,就完成落子
  • 如果没选中棋子/或者选了错误的落子点,就不予以反应
let roads = [];
let selectedWhat = null;
canvas1.addEventListener('click', (e)=>{
    let viewWidth = window.innerWidth;
    if (viewWidth > 1000) {
        viewWidth = 1000;
    }
    let canvasWidth = 1005;
    let ctx = canvas1.getContext('2d');
    ctx.fillStyle = 'black';
    let locX = e.clientX/viewWidth*canvasWidth;
    let locY = e.clientY/viewWidth*canvasWidth;
    let r = 50;
    let x = (locX+r-100)/100|0;
    let y = (locY+r-100)/100|0;
    for (let i = 0; i < roads.length; i++) {
        let road = roads[i];
        if (road._x == x && road._y == y) {
            let chess = chessMap[road.y][road.x];
            downChess(chessMap, road._x, road._y, road.x, road.y);
            side = chess[1] == '黑' ? '红' : '黑';
            roads = [];
            selectedWhat = null;
            return;
            break;
        }
    }
    if (chessMap[y][x] != '   ') {
        // ctx.fillText(`(${x},${y})${chessMap[y][x]}`, locX, locY);
        selectedWhat = selectedWhat == chessMap[y][x] ? null : chessMap[y][x];
        if (selectedWhat) {
            ctx.fillStyle = 'blue';
            flushChessCanvas();
            roads = [];
            getChessRoads(chessMap, x, y);
        } else {
            flushChessCanvas();
            roads = [];
        }
    }
})
let autoBtn = document.querySelector('#auto-btn');
autoBtn.onclick = function() {
    checkAllChess()
}
let resetBtn = document.querySelector('#reset-btn');
resetBtn.onclick = function() {
    resetMap();
}

得出选中棋子的可走路径(可落子点)

function getChessRoads(map=chessMap, x, y) {
    let chessMap = map;
    let chess = chessMap[y][x];
    let isAlive = false;
    if (chess[0] == '车') {
        getJuRoad(chessMap, roads, x, y);
    }
    if (chess[0] == '马') {
        getMaRoad(chessMap, roads, x, y);
    }
    if (chess[0] == '象' || chess[0] == '相') {
        getXiangRoad(chessMap, roads, x, y, chess[1]);
    }
    if (chess[0] == '士' || chess[0] == '仕') {
        getShiRoad(chessMap, roads, x, y, chess[1]);
    }
    if (chess[0] == '炮' || chess[0] == '砲') {
        getPaoRoad(chessMap, roads, x, y)
    }
    if (chess[0] == '卒' || chess[0] == '兵') {
        getZuRoad(chessMap, roads, x, y, chess[1]);
    }
    if (chess[0] == '将' || chess[0] == '帅') {
        getJiangRoad(chessMap, roads, x, y, chess[1]);
        isAlive = true;
    }
    for (let i = 0; i < roads.length; i++) {
        let road = roads[i];
        object.showRoad(chess[0], road._x, road._y, road.x, road.y);
    }
}

这一处是纯自己实现的哦,此处可以有专利O(∩_∩)O

大概就是,点击事件后,通过clientXclientY计算那个位置所在的棋子是什么 xiang’qi

然后第二次点击事件后,通过clientXclientY计算目标位置是什么,不管那个位置是什么,只要在选中棋子的合规落子范围内,都把棋子走过去就OK啦

然后根据新得Map重绘Canvas

『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓_第3张图片

小花絮

戊戌年的除夕,给爷爷看了我这个初版

我:我写了个电脑,可是怎么下,他都走的不对

爷爷:慢慢来么,像以前游戏机里的那个老人、中年人、长头发的女的和小孩,你先试着把那个小孩下赢嘛

我:哦哦,对的,我先一步步来?

最后的最后,在己亥的正月初一完成了我的算是一个完整版,爷爷很认真的和我的AI下了一局

我还是完败,哈哈哈?,后来事实证明我的确写了个笨呼呼的AI呢(它也同样输给了小青和我妹妹),之后再优化吧~但感觉心里还是蛮开心的

发现CSDN发布文章>改文章>重新发布 可以刷积分耶( •̀ ω •́ )y

爷爷口中的那个游戏

『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓_第4张图片
『002』写个象棋AI来和爷爷下棋( o=^•ェ•)o ┏━┓_第5张图片

项目地址

https://gitee.com/ko-orz/chinese_chess/

预览地址

https://tekii.cn/public/chinese_chess/

特别感谢

[1] 欲泪成雪. HTML5学习总结——canvas绘制象棋(canvas绘图) 2016-11-24 18:03
[2] 超逸_流. 象棋AI算法(一) 2015-07-29 21:29:28
[3] 超逸_流. 象棋AI算法(二) 2015-07-30 14:11:37
[4] 写了这篇文章的2019年02月18日22时41分的Maxmon

你可能感兴趣的:(文章分享之路,中国象棋,AI,Canvas,家人,HTML)