【canvas】JS五子棋UI与AI

注:本文思路源于幕客网JS实现人机大战五子棋

0. 效果

在这里插入图片描述

1. 代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Chess</title>
</head>

<body>
    <style>
        canvas {
            display: block;
            margin: 5px auto;
            box-shadow: -2px -2px 2px #efefef, 5px 5px 5px #b9b9b9;
        }

        .information {
            text-align: center;
        }

        button {
            background: dodgerblue;
            color: white;
            border: 0;
        }
    </style>
    <canvas id="chess" width="450px" height="450px"></canvas>
    <div class="information">
        <div class="operator">
            <button id="order" onclick="changeOrder()">先后</button>
            <button id="mode" onclick="changeMode()">模式</button>
            <button id="replay" onclick="replay()">重玩</button>
        </div>
        <div id="records">胜 0 负 0</div>
    </div>
    <script>
        /* 游戏数据配置 */
        const size = 450; // 棋盘尺寸
        const lineCounts = 15; // 棋盘行数
        const rowWidth = size / lineCounts; // 棋盘行宽
        const margin = rowWidth / 2; // 棋盘边界
        const pieceRadius = rowWidth / 2; // 棋子半径
        const offset = 2; // 渐变偏移
        const conJunctions = 5; // 五子棋,五子连珠
        var order = true; // 黑先; 白后
        var mode = false; // Player Vs Player; Player Vs Computer;

        var player = order;
        var ord = {
            true: "先手执黑",
            false: "后手执白"
        }
        var mod = {
            true: "人人对战",
            false: "人机对战"
        }
        var records = {
            win: 0,
            lose: 0
        };
        // winMatrix;
        var winMatrix = [];
        var counts = 0;
        // black and white
        var black = [];
        var white = [];
        // pieceMatrix;
        var pieceMatrix = [];
        // 棋盘,canvas绘制棋盘和棋子
        var chess = document.getElementById("chess");
        var context = chess.getContext("2d");

        /* ----- */
        document.getElementById("order").innerText = ord[order];
        document.getElementById("mode").innerText = mod[mode];
        start();
        /* ----- */

        function changeOrder() {
            order = !order;
            document.getElementById("order").innerText = ord[order];
            start();
        }

        function changeMode() {
            mode = !mode;
            // 清零记录
            records = {
                win: 0,
                lose: 0
            };
            updateRecords();
            if (mode) { // 人人对战不计分
                document.getElementById("records").style.display = "none";
            } else {
                document.getElementById("records").style.display = "block";
            }
            document.getElementById("mode").innerText = mod[mode];
            start();
        }

        function initData() {
            winMatrix = [];
            counts = 0;
            black = [];
            white = [];
            pieceMatrix = [];

            // 初始化
            for (let i = 0; i < lineCounts; i++) {
                winMatrix[i] = [];
                for (let j = 0; j < lineCounts; j++) {
                    winMatrix[i][j] = [];
                }
            }

            // 横向
            for (let i = 0; i < lineCounts; i++) {
                for (let j = 0; j <= lineCounts - conJunctions; j++) {
                    // 选择到合适的起始点,加5次即可
                    for (let k = 0; k < conJunctions; k++) {
                        winMatrix[i][j + k][counts] = true;
                    }
                    counts++;
                }
            }

            // 竖向
            for (let i = 0; i <= lineCounts - conJunctions; i++) {
                for (let j = 0; j < lineCounts; j++) {
                    // 选择到合适的起始点,加5次即可
                    for (let k = 0; k < conJunctions; k++) {
                        winMatrix[i + k][j][counts] = true;
                    }
                    counts++;
                }
            }

            // 正斜向[\]
            for (let i = 0; i <= lineCounts - conJunctions; i++) {
                for (let j = 0; j <= lineCounts - conJunctions; j++) {
                    // 选择到合适的起始点,加5次即可
                    for (let k = 0; k < conJunctions; k++) {
                        winMatrix[i + k][j + k][counts] = true;
                    }
                    counts++;
                }
            }

            // 反斜向[/]
            for (let i = 0; i <= lineCounts - conJunctions; i++) {
                for (let j = conJunctions - 1; j < lineCounts; j++) {
                    // 选择到合适的起始点,加5次即可
                    for (let k = 0; k < conJunctions; k++) {
                        winMatrix[i + k][j - k][counts] = true;
                    }
                    counts++;
                }
            }

            // 黑白棋赢法统计
            for (let i = 0; i < counts; i++) {
                black[i] = 0;
                white[i] = 0;
            }

            // 落子矩阵
            for (let i = 0; i < lineCounts; i++) {
                pieceMatrix[i] = [];
                for (let j = 0; j < lineCounts; j++) {
                    pieceMatrix[i][j] = true;
                }
            }
        }

        // 开始游戏
        function start() {
            // 初始化
            initData();
            // 绘盘
            drawChessBoard();
            // 电脑先手随机内部落子
            if (!mode && !order) { // 人机且电脑先手
                let x = Math.floor(Math.random() * conJunctions) + conJunctions;
                let y = Math.floor(Math.random() * conJunctions) + conJunctions;
                oneStep(x, y, true);
                player = false; // 白
            }
            // 人落子
            chess.addEventListener("click", clickChessBoard);
        }

        // 重玩
        function replay() {
            // 清盘
            context.clearRect(0, 0, size, size);
            start();
        }

        // 绘制棋盘
        function drawChessBoard() {
            // 背景色
            context.fillStyle = "#CDAA7D";
            context.fillRect(0, 0, size, size);
            // 棋盘线条色
            context.strokeStyle = "#bfbfbf";
            // 划线
            context.beginPath();
            for (let i = 0; i < lineCounts; i++) {
                let position = margin + i * rowWidth;
                // row lines
                context.moveTo(margin, position);
                context.lineTo(size - margin, position);
                context.stroke();
                // column lines
                context.moveTo(position, margin);
                context.lineTo(position, size - margin);
                context.stroke();
            }
            context.closePath();
        }

        // 绘制棋子
        function drawPiece(i, j, side) {
            let x = margin + i * rowWidth;
            let y = margin + j * rowWidth;
            // arc
            context.beginPath();
            context.arc(x, y, pieceRadius, 0, 2 * Math.PI);
            // gradient
            let gradient = context.createRadialGradient(
                x + offset, y - offset, pieceRadius, x + offset, y - offset, 0);
            if (side) { // 黑
                gradient.addColorStop(0, "#0a0a0a");
                gradient.addColorStop(1, "#636766");
            } else { // 白
                gradient.addColorStop(0, "#d1d1d1");
                gradient.addColorStop(1, "#f9f9f9");
            }
            context.fillStyle = gradient;
            context.fill();
            context.closePath();
        }

        // 游戏结束
        function gameOver(side) {
            let str = side ? "黑" : "白";
            if (order == side) { // 先手执黑胜 或者 后手执白胜
                records.win++;
            } else {
                records.lose++;
            }
            updateRecords();
            // 设置字体
            context.font = "4em bold 黑体";
            // 设置水平对齐方式
            context.textAlign = "center";
            // 设置垂直对齐方式
            context.textBaseline = "middle";
            // 设置颜色
            context.strokeStyle = "red";
            context.fillStyle = "green";
            // 绘制文字(参数:要写的字,x坐标,y坐标)
            context.strokeText(str + " 胜!", size / 2, size / 2);
            context.fillText(str + " 胜!", size / 2, size / 2);
            forbidClick();
        }

        // 更新记录
        function updateRecords() {
            document.getElementById("records").innerText = `胜 ${records.win}${records.lose}`;
        }

        // 禁止落子,移除事件监听
        function forbidClick() {
            chess.removeEventListener("click", clickChessBoard);
        }

        // 落子
        function oneStep(i, j, side) {
            // 落子
            drawPiece(i, j, side);
            // 更新棋子矩阵
            pieceMatrix[i][j] = false;
            // 胜负判断逻辑
            for (let k = 0; k < counts; k++) {
                if (winMatrix[i][j][k]) {
                    if (side) { // 黑
                        white[k] = 6; // 被黑棋落子的赢法,白棋永远不可能赢了
                        if (++black[k] == 5) {
                            gameOver(true);
                            return false;
                        };
                    } else { // 白
                        black[k] = 6;
                        if (++white[k] == 5) {
                            gameOver(false);
                            return false;
                        };
                    }
                }
            }
            return true;
        }

        // 点击棋盘事件
        function clickChessBoard(e) {
            let x = e.offsetX; // 相对于chess的偏移
            let y = e.offsetY;
            // 寻找点击范围
            let i = Math.floor(x / rowWidth);
            let j = Math.floor(y / rowWidth);
            // 有子不得落子
            if (pieceMatrix[i][j]) {
                // 落子未终局(没有一方胜利结束游戏)
                if (oneStep(i, j, player)) {
                    // 黑白交替走子
                    if (mode) {
                        player = !player;
                    } else {
                        computerAI(player);
                    }
                }
            }
        }
        /**
         * 计算机AI实现
         */
        // 攻防策略分数定义
        function deffence(num) {
            let pts = {
                0: 0,
                1: 200,
                2: 400,
                3: 2000,
                4: 10000
            }
            if (num > 4) {
                return 0;
            } else {
                return pts[num];
            }
        }
        function attack(num) {
            let pts = {
                0: 0,
                1: 210,
                2: 420,
                3: 2100,
                4: 20000
            }
            if (num > 4) {
                return 0;
            } else {
                return pts[num];
            }
        }

        // AI
        function computerAI(player) {
            // 判断黑白方
            let opponent = black, self = white;
            if (!player) {
                opponent = white;
                self = black;
            }
            // 得分数组
            let person = [];
            let com = [];
            // 初始化
            for (let i = 0; i < lineCounts; i++) {
                person[i] = [];
                com[i] = [];
                for (let j = 0; j < lineCounts; j++) {
                    person[i][j] = 0;
                    com[i][j] = 0;
                }
            }
            // 判断各个落点得分
            for (let i = 0; i < lineCounts; i++) {
                for (let j = 0; j < lineCounts; j++) {
                    if (pieceMatrix[i][j]) { // 可落子
                        for (let k = 0; k < counts; k++) {
                            if (winMatrix[i][j][k]) { // 可赢
                                person[i][j] += deffence(opponent[k]); // 防守
                                com[i][j] += attack(self[k]); // 进攻
                            }
                        }
                    }
                }
            }
            // 寻找最优点
            var max = 0; // 最高分
            var u = 0, v = 0; // 最优落子点
            for (let i = 0; i < lineCounts; i++) {
                for (let j = 0; j < lineCounts; j++) {
                    // 防守最高分
                    if (person[i][j] > max) {
                        max = person[i][j];
                        u = i;
                        v = j;
                    }
                    // 进攻最高分
                    if (com[i][j] > max) {
                        max = com[i][j];
                        u = i;
                        v = j;
                    }
                }
            }
            // 电脑落子
            oneStep(u, v, !player);
        }
    </script>
</body>
</html>

2. 后记

  • clearRect() 清除画布
  • fill stroke 填充和描边
  • createRadialGradient(x1,y1,x2,y2) addColorStop(0, color) 径向渐变
  • beginPath() closePath 开闭路径
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值