从零实现AI黑白棋:基于JavaScript的网页人工智能实战

1次阅读
没有评论

共计 3531 个字符,预计需要花费 9 分钟才能阅读完成。

image.webp

游戏规则与基础架构

黑白棋(又称翻转棋)的规则简单但策略丰富:

从零实现 AI 黑白棋:基于 JavaScript 的网页人工智能实战

  • 8×8 棋盘,双方使用黑白两色棋子
  • 每次落子必须夹住对手的棋子(横 / 竖 / 斜方向)
  • 被夹住的棋子会翻转为己方颜色
  • 无合法移动时跳过回合,双方都无法移动时游戏结束
  • 棋盘填满或双方无棋可下时,棋子多者胜

网页游戏的基础结构可分为三层:

  1. 表现层:Canvas 渲染棋盘和棋子
  2. 逻辑层:游戏状态管理、规则验证
  3. AI 层:决策算法和评估函数

AI 核心算法实现

Minimax 算法基础

经典的博弈树搜索算法,核心思想是:

  1. 我方回合选择收益最大的走法(max 层)
  2. 对方回合假设会选择对我最不利的走法(min 层)
  3. 递归直到终止条件(达到深度或游戏结束)
function minimax(board, depth, isMaximizing) {if (depth === 0 || gameOver(board)) {return evaluate(board);
  }

  const moves = generateMoves(board, isMaximizing);
  if (isMaximizing) {
    let maxEval = -Infinity;
    for (const move of moves) {const newBoard = makeMove(board, move);
      maxEval = Math.max(maxEval, minimax(newBoard, depth-1, false));
    }
    return maxEval;
  } else {
    let minEval = Infinity;
    for (const move of moves) {const newBoard = makeMove(board, move);
      minEval = Math.min(minEval, minimax(newBoard, depth-1, true));
    }
    return minEval;
  }
}

Alpha-Beta 剪枝优化

通过记录当前层的已知最优值,提前终止无效分支的搜索:

  • α:max 层当前最小值
  • β:min 层当前最大值
  • 当 α ≥ β 时可终止当前分支

优化后时间复杂度从 O(b^d) 降至 O(√b^d)(b 为分支因子,d 为深度)

function alphabeta(board, depth, α, β, isMaximizing) {
  // 与 minimax 类似,增加剪枝逻辑
  if (isMaximizing) {for (const move of moves) {const newBoard = makeMove(board, move);
      α = Math.max(α, alphabeta(newBoard, depth-1, α, β, false));
      if (β <= α) break; // 剪枝
    }
    return α;
  }
  // min 层同理...
}

评估函数设计

好的评估函数应考虑:

  1. 棋子数量差(终盘时最重要)
  2. 行动力(可走位置数量)
  3. 稳定子(不会被翻转的棋子)
  4. 角点和边线控制

示例加权评估:

function evaluate(board) {
  let score = 0;
  // 棋子差(权重 50%)score += (countPieces(board, AI_COLOR) - countPieces(board, PLAYER_COLOR)) * 0.5;

  // 行动力差(权重 30%)score += (generateMoves(board, true).length - generateMoves(board, false).length) * 0.3;

  // 角点控制(权重 20%)score += countCorners(board) * 0.2;

  return score;
}

完整代码实现

Canvas 渲染核心

class BoardRenderer {constructor(canvas, size=8) {
    this.cellSize = canvas.width / size;
    this.ctx = canvas.getContext('2d');
    this.drawBoard();}

  drawBoard() {
    this.ctx.fillStyle = '#2e8b57';
    this.ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 绘制网格线
    this.ctx.strokeStyle = '#000';
    for (let i = 0; i <= 8; i++) {
      const pos = i * this.cellSize;
      this.ctx.beginPath();
      this.ctx.moveTo(pos, 0);
      this.ctx.lineTo(pos, canvas.height);
      this.ctx.stroke();

      this.ctx.beginPath();
      this.ctx.moveTo(0, pos);
      this.ctx.lineTo(canvas.width, pos);
      this.ctx.stroke();}
  }

  drawPiece(x, y, color) {
    this.ctx.fillStyle = color;
    this.ctx.beginPath();
    this.ctx.arc(
      x * this.cellSize + this.cellSize/2,
      y * this.cellSize + this.cellSize/2,
      this.cellSize/2 - 2,
      0, Math.PI * 2
    );
    this.ctx.fill();}
}

游戏状态管理

class OthelloGame {constructor() {this.board = Array(8).fill().map(() => Array(8).fill(null));
    this.board[3][3] = this.board[4][4] = 'white';
    this.board[3][4] = this.board[4][3] = 'black';
    this.currentPlayer = 'black';
  }

  isValidMove(x, y) {if (this.board[x][y] !== null) return false;

    // 检查 8 个方向是否能夹住对手棋子
    for (let dx = -1; dx <= 1; dx++) {for (let dy = -1; dy <= 1; dy++) {if (dx === 0 && dy === 0) continue;
        if (this.checkDirection(x, y, dx, dy)) {return true;}
      }
    }
    return false;
  }

  checkDirection(x, y, dx, dy) {// 方向检查逻辑...}
}

性能优化实践

递归深度控制

// 根据剩余棋子数动态调整搜索深度
function getDynamicDepth(board) {const emptyCells = countEmptyCells(board);
  if (emptyCells > 40) return 3;  // 开局
  if (emptyCells > 20) return 5;  // 中局
  return 7;  // 残局
}

Web Worker 多线程

主线程:

const worker = new Worker('ai-worker.js');
worker.postMessage({board, depth: 5});
worker.onmessage = (e) => {
  const bestMove = e.data;
  // 更新 UI...
};

worker.js:

self.onmessage = (e) => {const { board, depth} = e.data;
  const move = findBestMove(board, depth);
  self.postMessage(move);
};

常见问题解决

棋盘表示误区

  • 错误:直接用二维数组存储棋子颜色
  • 正确:应包含空位、临时翻转状态等信息

避免局部最优

  1. 增加随机因素:当多个走法评分相近时随机选择
  2. 开局库:使用预定义的开局策略
  3. 多阶段评估:不同棋局阶段使用不同权重

移动端适配

  1. 触摸事件处理:

    canvas.addEventListener('touchstart', (e) => {const rect = canvas.getBoundingClientRect();
      const x = Math.floor((e.touches[0].clientX - rect.left) / cellSize);
      // 处理落点...
    });

  2. 响应式布局:

    canvas {
      max-width: 100%;
      height: auto;
      aspect-ratio: 1/1;
    }

进阶方向

  1. 蒙特卡洛树搜索(MCTS):
  2. 更适合高分支因子的棋类
  3. 通过随机模拟评估走法

  4. 机器学习:

  5. 使用卷积神经网络评估局面
  6. 通过自我对弈强化学习

  7. 并行计算:

  8. 使用 WebAssembly 加速计算
  9. 多 Worker 并行搜索不同分支

完整项目代码可参考 GitHub 仓库(示例链接),包含详细注释和在线演示。通过调整评估函数参数和搜索深度,你可以轻松创建从初学者到大师级的不同难度 AI。

正文完
 0
评论(没有评论)