1. 五子棋AI的核心思路评分表算法五子棋AI开发中最让人头疼的问题就是电脑怎么知道该下在哪里这里我要分享一个实战中特别管用的方法——评分表算法。这个算法的核心思想特别简单给棋盘上每个空位打分选分数最高的位置下棋。我第一次实现这个算法时发现它比想象中强大得多。举个例子当棋盘中间出现一个活三棋型即两头都没有被堵住的三连子AI会毫不犹豫地在关键位置落子防守或进攻。这背后的秘密就在于我们预先定义好的评分规则。1.1 棋型识别与评分标准五子棋的胜负取决于棋型的组合我们需要先定义基础棋型及其威胁程度连五五子相连直接获胜评分100000分活四四子相连且两端无阻挡评分10000分眠四四子相连但一端被堵评分5000分活三三子相连且两端无阻挡评分1000分眠三三子相连但一端被堵评分500分活二两子相连且两端无阻挡评分100分实际编码时我习惯用二维数组存储这些棋型模式。比如活四可以表示为[1,1,1,0]1代表黑子0代表空位这样程序就能通过模式匹配来识别棋型。// 典型棋型示例 int[][] patterns { {1,1,1,1}, // 连五 {1,1,1,0}, // 活四 {0,1,1,1}, // 另一种活四 {1,1,1,2}, // 眠四2代表白子 {1,1,0,0}, // 活三 {0,1,1,0} // 另一种活三 };1.2 评分策略的实战技巧在真实对战中我发现几个优化点攻防平衡不仅要计算自己形成棋型的得分还要计算对方在此位置形成棋型的威胁。我通常会给防守分数加上70%的权重。位置权重棋盘中央的位置天然更有价值。我的做法是在基础分上乘以位置系数比如中心点系数为1.2边缘为0.8。多方向扫描一个位置可能在水平、垂直、对角线等方向同时形成多个棋型需要累加所有方向的得分。2. Java实现棋盘建模2.1 基础数据结构设计我们先创建棋盘表示和位置类。这里我推荐使用简单的二维数组表示棋盘配合一个Position类存储坐标public class Position { private int x; // 横坐标 private int y; // 纵坐标 public Position(int x, int y) { this.x x; this.y y; } // getter和setter省略... } public class Gobang { private int size; // 棋盘大小 private int[][] board; // 棋盘状态 public Gobang(int size) { this.size size; this.board new int[size][size]; // 0空,1黑,2白 } }2.2 棋盘状态扫描方法扫描棋盘是性能关键点。我的经验是增量扫描不必每次全盘扫描只需在上一步落子位置周围3格范围内检查八方向检测从落子点向8个方向上、下、左、右、4个对角线延伸检测public ListPosition getEmptyPositions() { ListPosition empties new ArrayList(); for (int i 0; i size; i) { for (int j 0; j size; j) { if (board[i][j] 0) { empties.add(new Position(i, j)); } } } return empties; }3. 评分算法的完整实现3.1 单点评分函数这是整个AI最核心的部分。我们需要评估在某个位置落子后的价值private int evaluatePosition(int x, int y, int player) { int score 0; // 四个主要方向水平、垂直、主对角线、副对角线 score evaluateLine(x, y, 1, 0, player); // 水平 score evaluateLine(x, y, 0, 1, player); // 垂直 score evaluateLine(x, y, 1, 1, player); // 主对角线 score evaluateLine(x, y, 1, -1, player); // 副对角线 return score; } private int evaluateLine(int x, int y, int dx, int dy, int player) { int score 0; // 正向检测 int count 0; // 连续子数 int block 0; // 阻挡数 for (int i 1; i 4; i) { int nx x i*dx; int ny y i*dy; if (nx 0 || nx size || ny 0 || ny size) { block; break; } if (board[nx][ny] player) { count; } else if (board[nx][ny] 0) { break; } else { block; break; } } // 反向检测代码类似省略 // ... // 根据count和block计算分数 return getPatternScore(count, block); }3.2 最佳落子选择算法有了单点评分后选择最佳落子位置就简单了public Position findBestMove(int player) { ListPosition empties getEmptyPositions(); Position bestPos null; int maxScore -1; for (Position pos : empties) { // 计算在此处落子的得分 int score evaluatePosition(pos.getX(), pos.getY(), player); // 同时考虑对手在此落子的威胁 int enemyScore evaluatePosition(pos.getX(), pos.getY(), 3 - player); int totalScore score (int)(enemyScore * 0.7); if (totalScore maxScore) { maxScore totalScore; bestPos pos; } } return bestPos; }4. 工程化优化与实战技巧4.1 性能优化方案在15×15的棋盘上全盘扫描的性能瓶颈很明显。我通过以下优化将计算时间从秒级降到毫秒级局部扫描只在上一步落子周围5×5范围内计算预计算将常见棋型得分缓存起来多线程对不同的候选位置并行计算得分// 并行计算示例 empties.parallelStream().forEach(pos - { int score evaluatePosition(pos.getX(), pos.getY(), player); synchronized(this) { if (score maxScore) { maxScore score; bestPos pos; } } });4.2 常见问题排查在开发过程中我遇到过几个典型问题AI不防守检查对手棋型的评分权重是否足够反应迟钝添加日志输出评分计算耗时无效落子确保棋盘边界检查正确一个实用的调试技巧是输出评分热力图public void printHeatMap(int player) { for (int i 0; i size; i) { for (int j 0; j size; j) { int score board[i][j] ! 0 ? -1 : evaluatePosition(i, j, player); System.out.printf(%6d, score); } System.out.println(); } }5. 完整代码架构最后给出一个可运行的完整示例结构// 主类 public class GobangAI { public static void main(String[] args) { Gobang game new Gobang(15); game.startGame(1); // 1表示AI先手 while (!game.isGameOver()) { if (game.getCurrentPlayer() 1) { Position move game.findBestMove(1); game.makeMove(move.getX(), move.getY(), 1); } else { // 玩家落子逻辑 } } } } // 棋盘类 class Gobang { // 包含前面介绍的所有方法 // ... }这个实现虽然不如AlphaGo复杂但在我的笔记本上测试响应时间能控制在200ms以内业余玩家很难战胜它。如果想进一步提升可以考虑加入博弈树搜索和更精细的评分规则。