HarmonyOS 小游戏《对战五子棋》开发第17篇 - AI的两种评估策略

单点评估 vs 全盘评估------AI如何看待棋盘

设计截图如下:

两种评估函数的对比

本项目实现了两种评估策略:

维度 evaluatePosition evaluateLines
用途 普通模式、候选排序 困难模式Minimax叶节点
范围 评估单个位置的四方向 评估全盘所有线
复杂度 O(1) O(n)
精度 局部精确 全局视角

evaluatePosition:单点评估

typescript 复制代码
private evaluatePosition(board: number[][], row: number, col: number, player: number): number {
  const directions: number[][] = [[0, 1], [1, 0], [1, 1], [1, -1]];
  let totalScore: number = 0;
  for (const dir of directions) {
    totalScore += this.evaluateDirection(board, row, col, dir[0], dir[1], player);
  }
  return totalScore;
}

核心思路 :假设在(row, col)落子,计算该位置在四个方向上的棋型分数之和。

evaluateDirection:单方向扫描

typescript 复制代码
private evaluateDirection(board: number[][], row: number, col: number,
  dr: number, dc: number, player: number): number {
  let count: number = 1;
  let leftOpen: boolean = false;
  let rightOpen: boolean = false;

  // 正方向:统计连续同色棋子
  let r = row + dr;
  let c = col + dc;
  while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === player) {
    count++;
    r += dr;
    c += dc;
  }
  // 检查正方向是否开放
  if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === EMPTY) {
    rightOpen = true;
  }

  // 反方向:统计连续同色棋子
  r = row - dr;
  c = col - dc;
  while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === player) {
    count++;
    r -= dr;
    c -= dc;
  }
  // 检查反方向是否开放
  if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === EMPTY) {
    leftOpen = true;
  }

  const openCount = (leftOpen ? 1 : 0) + (rightOpen ? 1 : 0);
  return this.scoreForCount(count, openCount);
}

评估流程:

  1. 以落子点为中心
  2. 正方向延伸:统计连续同色棋子,检查端点是否开放
  3. 反方向延伸:同上
  4. 用count和openCount查表得分

evaluateLines:全盘评估

typescript 复制代码
private evaluateLines(board: number[][], player: number): number {
  let total: number = 0;
  const directions: number[][] = [[0, 1], [1, 0], [1, 1], [1, -1]];

  for (const dir of directions) {
    if (dir[0] === 0 && dir[1] === 1) {
      // 水平:每行从(0,0)开始
      for (let row = 0; row < BOARD_SIZE; row++) {
        total += this.evaluateLine(board, row, 0, dir[0], dir[1], player);
      }
    } else if (dir[0] === 1 && dir[1] === 0) {
      // 垂直:每列从(0,col)开始
      for (let col = 0; col < BOARD_SIZE; col++) {
        total += this.evaluateLine(board, 0, col, dir[0], dir[1], player);
      }
    } else if (dir[0] === 1 && dir[1] === 1) {
      // 主对角线:从左边和上边出发
      for (let row = 0; row < BOARD_SIZE; row++) {
        total += this.evaluateLine(board, row, 0, dir[0], dir[1], player);
      }
      for (let col = 1; col < BOARD_SIZE; col++) {
        total += this.evaluateLine(board, 0, col, dir[0], dir[1], player);
      }
    } else {
      // 副对角线:从左边和上边出发
      for (let row = 0; row < BOARD_SIZE; row++) {
        total += this.evaluateLine(board, row, 0, dir[0], dir[1], player);
      }
      for (let col = 1; col < BOARD_SIZE; col++) {
        total += this.evaluateLine(board, 0, col, dir[0], dir[1], player);
      }
    }
  }
  return total;
}

扫描线的起点

不同方向的扫描起点不同:

复制代码
水平方向(0,1):从每行的第0列开始 → 15条线
垂直方向(1,0):从第0行的每列开始 → 15条线
主对角(1,1):从左边+上边出发 → 29条线
副对角(1,-1):从左边+上边出发 → 29条线
总计:88条扫描线

evaluateLine:逐线扫描

typescript 复制代码
private evaluateLine(board: number[][], startRow: number, startCol: number,
  dr: number, dc: number, player: number): number {
  let score: number = 0;
  let row = startRow;
  let col = startCol;
  let consecutive: number = 0;

  while (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
    if (board[row][col] === player) {
      consecutive++;
    } else {
      if (consecutive > 0) {
        // 计算这段连续棋子的分数
        let openEnds: number = 0;
        // 检查前方是否开放
        const prevR = row - dr * (consecutive + 1);
        const prevC = col - dc * (consecutive + 1);
        if (prevR >= 0 && prevR < BOARD_SIZE && prevC >= 0 && prevC < BOARD_SIZE &&
          board[prevR][prevC] === EMPTY) {
          openEnds++;
        }
        // 检查后方是否开放
        if (board[row][col] === EMPTY) {
          openEnds++;
        }
        score += this.scoreForCount(consecutive, openEnds);
        consecutive = 0;
      }
    }
    row += dr;
    col += dc;
  }

  // 处理末尾的连续段
  if (consecutive > 0) {
    let openEnds: number = 0;
    const prevR = row - dr * (consecutive + 1);
    const prevC = col - dc * (consecutive + 1);
    if (prevR >= 0 && prevR < BOARD_SIZE && prevC >= 0 && prevC < BOARD_SIZE &&
      board[prevR][prevC] === EMPTY) {
      openEnds++;
    }
    score += this.scoreForCount(consecutive, openEnds);
  }

  return score;
}

逐线扫描逻辑:

  1. 沿着方向遍历每个格子
  2. 遇到同色棋子:consecutive++
  3. 遇到不同色或空位:结算之前的连续段
  4. 结算时检查两端是否开放
  5. 用scoreForCount查表得分

evaluateBoard:全盘综合评估

typescript 复制代码
private evaluateBoard(board: number[][]): number {
  let aiScore: number = 0;
  let humanScore: number = 0;
  aiScore += this.evaluateLines(board, this.aiColor);
  humanScore += this.evaluateLines(board, this.humanColor);
  return aiScore - humanScore * 1.1;
}

防守权重1.1:与普通模式的攻防权重一致------AI评估时对人类的棋型稍加重视。

两种评估的使用场景

evaluatePosition(单点)

  • 普通模式:评估每个候选位置的攻防价值
  • 候选排序:为困难模式排序候选

evaluateBoard(全盘)

  • Minimax叶节点:评估搜索到底时的整体局面

为什么叶节点用全盘评估? 因为Minimax的叶节点是"未来的局面",需要整体评估而非单点评估------单点评估只考虑了最后一手的影响,可能遗漏其他方向的威胁。

总结

两种评估函数代表了不同的思考方式:

  • evaluatePosition:聚焦一点,四方向扫描------快速但不全面
  • evaluateLines:扫描全盘,逐线统计------全面但较慢

普通AI用单点评估实现快速决策,困难AI用全盘评估实现深度搜索。两者的配合让AI在不同难度下都有合理的评估能力。

相关推荐
考虑考虑8 小时前
JDK26中的Map.ofLazy
java·后端·java ee
云浪9 小时前
Go 并发实战:从零实现一个高并发安全的任务调度器
javascript·后端·go
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 14 章 MCP 服务端(上):注解式实现与工具来源
java·人工智能·开源
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 15 章 MCP 服务端(下):三种协议形态、Streamable 传输与状态管理
java·人工智能·开源
alexhilton11 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 16 章 向量存储(上):`VectorStore` 接口体系、`BatchingStrategy`
java·人工智能·开源
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 12 章 工具循环的流式形态、Usage 累加与扩展体系
java·人工智能·开源
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 23 章 模型供应商实现:从 `OpenAiSetup` 到 Bedrock
java·人工智能·开源
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 06 章 内置 Advisor 全景:13 个实现逐个剖到源码级
java·人工智能·开源
鶴哥只手遮天11 小时前
Spring AI 2.0.1 深度解析-第 03 章 消息体系与 Prompt:不可变模型、`ChatOptions` 与模板渲染
java·人工智能·开源