You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Minimax算法的JavaScript井字棋功能故障排查

井字棋Minimax算法故障修复

问题描述

现有一个基于Minimax算法的JavaScript井字棋项目,核心功能包括:

  • generateTable函数生成可点击棋盘,落子信息存入board变量
  • 初始化board后,生成索引矩阵a(值为[[0,1,2],[3,4,5],[6,7,8]])
  • getlines函数枚举所有行、列、对角线的获胜组合

当前存在两个核心故障:

  • Minimax算法计算的每次移动得分均为-Infinity
  • AI首次落子的'O'可正常显示,后续AI落子无法在棋盘上渲染

故障原因分析及修复方案

1. AI后续落子无法显示

computermovedifficult函数中,设置AI落子到board后,未调用printBoard()函数更新DOM,导致棋盘界面没有同步更新。修复时添加printBoard()调用即可。

2. Minimax得分异常(始终为-Infinity)

  • 全局变量污染:result、lines变量未声明,成为全局变量,导致递归逻辑混乱;需在函数内部声明为局部变量
  • 获胜组合数组累积:getlines函数使用全局lines1变量,每次调用会保留之前的结果,导致获胜组合重复;需在函数内部初始化lines1
  • 空位判断逻辑错误:Minimax中判断空位用board[i] === "",但board初始值为null,应改为board[i] === null;同时平局判断逻辑错误,需先检查是否有获胜者,再判断是否平局
  • 变量命名冲突:checkWinner3和minimax中解构获胜组合时使用const [a, b, c] = lines[i],覆盖了全局的矩阵a,导致后续逻辑错误,需重命名变量

修复后完整代码

HTML部分

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>井字棋</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.2/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>

<body>
    <h1>井字棋</h1> <br>
    <table id="board"></table>
    <div id="result" class="result"></div>
</body>

CSS部分

body, html {
    height: 100%;
    margin: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}
table {
    border-collapse: collapse;
}
td {
    border: 1px solid black;
    width: 50px;
    height: 50px;
    text-align: center;
    font-size: 20px;
}
.result {
    margin-top: 50px;
    font-size: 1.5em;
}

JavaScript部分

let winner = null;
let wins = 0;
let turn = "X";
let board = initializeBoard(3);
const winMatrix = generateMatrixWin(3);

function generateTable() {
    const table = document.getElementById("board");
    table.innerHTML = "";
    for (let i = 0; i < 3; i++) {
        const row = document.createElement("tr");
        for (let j = 0; j < 3; j++) {
            const cell = document.createElement("td");
            cell.setAttribute("onclick", `cellClick(${i * 3 + j})`);
            row.appendChild(cell);
        }
        table.appendChild(row);
    }
}

generateTable();

function initializeBoard() {
    let board = [];
    for (let i = 0; i < 9; i++) {
        board.push(null);
    }
    return board;
}

function getlines(matrix) {
    let lines = [];
    const size = matrix.length;
    // 行
    for (let i = 0; i < size; i++) {
        lines.push([matrix[i][0], matrix[i][1], matrix[i][2]]);
    }
    // 列
    for (let j = 0; j < size; j++) {
        lines.push([matrix[0][j], matrix[1][j], matrix[2][j]]);
    }
    // 对角线
    lines.push([matrix[0][0], matrix[1][1], matrix[2][2]]);
    lines.push([matrix[0][2], matrix[1][1], matrix[2][0]]);
    return lines;
}

function isValidIndex(array, row, col) {
    return row >= 0 && row < array.length && col >= 0 && col < array[row].length;
}

function generateMatrixWin(matrixSize) {
    let matrix = [];
    let count = 0;
    for (let i = 0; i < matrixSize; i++) {
        let row = [];
        for (let j = 0; j < matrixSize; j++) {
            row.push(count);
            count++;
        }
        matrix.push(row);
    }
    return matrix;
}

function checkWinner3() {
    const lines = getlines(winMatrix);
    // 检查获胜组合
    for (let i = 0; i < lines.length; i++) {
        const [x, y, z] = lines[i];
        if (board[x] && board[x] === board[y] && board[x] === board[z]) {
            return board[x];
        }
    }
    // 检查是否平局
    for (let i = 0; i < 9; i++) {
        if (board[i] === null) {
            return null;
        }
    }
    return "draw";
}

function printBoard() {
    const table = document.getElementById("board");
    for (let i = 0; i < 9; i++) {
        const row = Math.floor(i / 3);
        const col = i % 3;
        table.rows[row].cells[col].textContent = board[i] || "";
    }
}

function cellClick(index) {
    if (!winner && board[index] === null) {
        board[index] = turn;
        printBoard();
        winner = checkWinner3();
        if (winner) {
            const resultEl = document.getElementById("result");
            if (winner === "draw") {
                resultEl.textContent = "平局!感谢游玩。";
            } else {
                resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`;
            }
        } else {
            turn = turn === "X" ? computermovedifficult() : "X";
        }
    }
}

function computermoveeasy() {
    const table = document.getElementById("board");
    let index;
    do {
        index = Math.floor(Math.random() * board.length);
    } while (board[index] !== null);
    board[index] = "O";
    printBoard();
    turn = "X";
    const winner = checkWinner3();
    if (winner) {
        const resultEl = document.getElementById("result");
        if (winner === "draw") {
            resultEl.textContent = "平局!感谢游玩。";
        } else {
            resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`;
        }
    }
    return turn;
}

function computermovedifficult() {
    let bestScore = -Infinity;
    let bestMove = null;
    for (let index = 0; index < 9; index++) {
        if (board[index] === null) {
            board[index] = "O";
            const score = minimax(board, 0, false);
            board[index] = null; // 撤销移动
            if (score > bestScore) {
                bestScore = score;
                bestMove = index;
            }
        }
    }
    board[bestMove] = "O";
    printBoard(); // 更新棋盘显示
    winner = checkWinner3();
    if (winner) {
        const resultEl = document.getElementById("result");
        if (winner === "draw") {
            resultEl.textContent = "平局!感谢游玩。";
        } else {
            resultEl.textContent = `恭喜!${winner}获胜!感谢游玩。`;
        }
    }
    turn = "X";
    return turn;
}

function minimax(board, depth, isMaximizing) {
    let result = null;
    const lines = getlines(winMatrix);
    
    // 检查是否有获胜者
    for (let i = 0; i < lines.length; i++) {
        const [x, y, z] = lines[i];
        if (board[x] && board[x] === board[y] && board[x] === board[z]) {
            result = board[x];
            break;
        }
    }
    
    // 检查是否平局
    if (result === null) {
        let isDraw = true;
        for (let i = 0; i < 9; i++) {
            if (board[i] === null) {
                isDraw = false;
                break;
            }
        }
        if (isDraw) {
            result = "draw";
        }
    }
    
    // 终止条件
    if (result !== null) {
        if (result === "draw") {
            return 0;
        } else if (result === "O") {
            return 10 - depth;
        } else if (result === "X") {
            return depth - 10;
        }
    }
    
    if (depth >= 9) { // 最大深度为9(棋盘填满)
        return 0;
    }
    
    if (isMaximizing) {
        let bestScore = -Infinity;
        for (let i = 0; i < 9; i++) {
            if (board[i] === null) {
                board[i] = "O";
                const score = minimax(board, depth + 1, false);
                board[i] = null;
                bestScore = Math.max(score, bestScore);
            }
        }
        return bestScore;
    } else {
        let bestScore = Infinity;
        for (let i = 0; i < 9; i++) {
            if (board[i] === null) {
                board[i] = "X";
                const score = minimax(board, depth + 1, true);
                board[i] = null;
                bestScore = Math.min(score, bestScore);
            }
        }
        return bestScore;
    }
}

修复说明

  1. 解决AI落子显示问题:在computermovedifficult函数中添加printBoard()调用,同步DOM与board数据
  2. 修复Minimax得分异常:
    • 将lines1改为getlines内部的局部变量,避免获胜组合累积
    • 修复minimax中的result变量为局部变量,避免全局污染
    • 修正空位判断条件为board[i] === null,与初始化逻辑一致
    • 重命名解构变量避免覆盖全局矩阵a
    • 优化平局判断逻辑,先检查获胜者再判断是否无空位
    • 调整Minimax最大深度为9,覆盖所有可能的落子情况

内容的提问来源于stack exchange,提问作者Tayba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 21:00:54