一、游戏简介与算法核心
Tron(光循环)是一款经典的街机对战游戏。两名玩家各自控制一辆”光轮”在矩形网格中移动,光轮经过的轨迹会留下不可穿越的光墙。玩家只能通过向上、下、左、右四个方向移动,一旦撞到自己或对手的光墙、或者 grid 边界,即判负。游戏的核心算法挑战在于:在有限的局部信息下,做出能最大化自身生存空间的决策。
本文将用 Java 实现一个完整的 Tron 对战引擎,重点讲解以下算法模块:
– A* 寻路:计算到达某个目标点的最短安全路径
– 空间占据评估:基于 Flood Fill 计算每个玩家可到达的格子数
– 区域划分策略:通过 Voronoi 图思想划分势力范围,评估局势优劣
二、核心数据结构
import java.util.*;
/**
* Tron 光循环游戏核心引擎
* 包含棋盘状态、玩家位置、光墙记录与决策算法
*/
public class TronEngine {
// 棋盘尺寸
public static final int WIDTH = 16;
public static final int HEIGHT = 16;
// 四个移动方向:上、右、下、左
public static final int[][] DIRS = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
public static final String[] DIR_NAMES = {"UP", "RIGHT", "DOWN", "LEFT"};
// 棋盘状态:0=空地,1=玩家A的光墙,2=玩家B的光墙
private int[][] board;
// 两名玩家的当前位置 [row, col]
private int[] playerA;
private int[] playerB;
// 当前回合编号
private int turn;
public TronEngine() {
board = new int[HEIGHT][WIDTH];
// 初始位置:对角线放置
playerA = new int[]{HEIGHT / 2, WIDTH / 4};
playerB = new int[]{HEIGHT / 2, 3 * WIDTH / 4};
board[playerA[0]][playerA[1]] = 1;
board[playerB[0]][playerB[1]] = 2;
turn = 0;
}
/**
* 检查某个位置是否在棋盘内且为空地
*/
public boolean isValid(int r, int c) {
return r >= 0 && r < HEIGHT && c >= 0 && c < WIDTH && board[r][c] == 0;
}
/**
* 尝试移动指定玩家,返回是否成功
* @param playerId 1=玩家A, 2=玩家B
* @param dirIndex 方向索引 0-3
*/
public boolean movePlayer(int playerId, int dirIndex) {
int[] player = (playerId == 1) ? playerA : playerB;
int nr = player[0] + DIRS[dirIndex][0];
int nc = player[1] + DIRS[dirIndex][1];
if (!isValid(nr, nc)) {
return false; // 撞墙或越界,移动失败
}
// 更新位置并留下光墙
player[0] = nr;
player[1] = nc;
board[nr][nc] = playerId;
turn++;
return true;
}
// Getters
public int[][] getBoard() { return board; }
public int[] getPlayerA() { return playerA; }
public int[] getPlayerB() { return playerB; }
public int getTurn() { return turn; }
}
三、A* 寻路与安全路径评估
A 算法用于评估当前玩家到达目标区域的最短安全路径。在 Tron 中,我们常用 A 来估算”如果 straight-line 前进,能活多久”。
/**
* A* 寻路:计算从起点到终点的最短路径长度
* 使用曼哈顿距离作为启发函数(网格中无对角移动,曼哈顿距离可采纳)
* @return 最短路径长度,不可达返回 -1
*/
public int aStarDistance(int[] start, int[] target) {
// 优先队列:按 f = g + h 排序
PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
boolean[][] visited = new boolean[HEIGHT][WIDTH];
pq.offer(new Node(start[0], start[1], 0,
manhattan(start[0], start[1], target[0], target[1]), null));
while (!pq.isEmpty()) {
Node cur = pq.poll();
if (visited[cur.r][cur.c]) continue;
visited[cur.r][cur.c] = true;
// 到达目标
if (cur.r == target[0] && cur.c == target[1]) {
return cur.g;
}
// 扩展四个方向
for (int[] d : DIRS) {
int nr = cur.r + d[0];
int nc = cur.c + d[1];
if (isValid(nr, nc) && !visited[nr][nc]) {
int g = cur.g + 1;
int h = manhattan(nr, nc, target[0], target[1]);
pq.offer(new Node(nr, nc, g, g + h, cur));
}
}
}
return -1; // 不可达
}
private int manhattan(int r1, int c1, int r2, int c2) {
return Math.abs(r1 - r2) + Math.abs(c1 - c2);
}
/**
* A* 节点内部类
*/
private static class Node {
int r, c, g, f;
Node parent;
Node(int r, int c, int g, int f, Node parent) {
this.r = r; this.c = c; this.g = g; this.f = f; this.parent = parent;
}
}
四、空间占据评估:Flood Fill 与 Voronoi 区域
在 Tron 中,最核心的评估指标是”我能到达的格子数 vs 对手能到达的格子数”。我们使用 BFS/Flood Fill 从双方当前位置同时扩散,计算各自的 Voronoi 区域。
/**
* 计算双方的空间占据评分(Voronoi-like 区域划分)
* @return 正数表示玩家A优势,负数表示玩家B优势
*/
public int voronoiEvaluation() {
int[][] distA = floodFillDistance(playerA);
int[][] distB = floodFillDistance(playerB);
int scoreA = 0, scoreB = 0;
for (int r = 0; r < HEIGHT; r++) {
for (int c = 0; c < WIDTH; c++) {
if (board[r][c] != 0) continue; // 已被占据
int da = distA[r][c];
int db = distB[r][c];
if (da == -1 && db == -1) continue; // 都不可达
if (da == -1) scoreB++;
else if (db == -1) scoreA++;
else if (da < db) scoreA++;
else if (db < da) scoreB++;
// da == db 时中立,不计分
}
}
return scoreA - scoreB;
}
/**
* 多源 BFS:计算从指定起点到所有可达空地的最短距离
*/
private int[][] floodFillDistance(int[] start) {
int[][] dist = new int[HEIGHT][WIDTH];
for (int[] row : dist) Arrays.fill(row, -1);
ArrayDeque<int[]> q = new ArrayDeque<>();
q.offer(start);
dist[start[0]][start[1]] = 0;
while (!q.isEmpty()) {
int[] cur = q.poll();
for (int[] d : DIRS) {
int nr = cur[0] + d[0];
int nc = cur[1] + d[1];
if (isValid(nr, nc) && dist[nr][nc] == -1) {
dist[nr][nc] = dist[cur[0]][cur[1]] + 1;
q.offer(new int[]{nr, nc});
}
}
}
return dist;
}
五、博弈决策:贪心 + Minimax 混合策略
Tron 的决策空间巨大(4^T),我们采用以下分层策略:
1. 启发式评估:优先选择能扩大 Voronoi 区域的走法
2. 终点模拟:对每种走法模拟若干步,用 Minimax 评估终局
3. ** Articulation Point 检测**:避免走入”死胡同”(割点)
/**
* 为指定玩家选择最优移动方向
* @param playerId 1=玩家A, 2=玩家B
* @return 最优方向索引 0-3
*/
public int bestMove(int playerId) {
int[] me = (playerId == 1) ? playerA : playerB;
int bestDir = -1;
int bestScore = Integer.MIN_VALUE;
for (int dir = 0; dir < 4; dir++) {
int nr = me[0] + DIRS[dir][0];
int nc = me[1] + DIRS[dir][1];
if (!isValid(nr, nc)) continue;
// 模拟走这一步
int oldR = me[0], oldC = me[1];
me[0] = nr; me[1] = nc;
int oldBoard = board[nr][nc];
board[nr][nc] = playerId;
// 综合评分
int score = evaluateMove(playerId);
// 回滚
me[0] = oldR; me[1] = oldC;
board[nr][nc] = oldBoard;
if (score > bestScore) {
bestScore = score;
bestDir = dir;
}
}
return bestDir == -1 ? 0 : bestDir; // 默认向上
}
/**
* 综合评估函数:Voronoi 差值 + 可行动空间 + 割点惩罚
*/
private int evaluateMove(int playerId) {
int base = voronoiEvaluation();
// 如果评估玩家B,需要取反
if (playerId == 2) base = -base;
int[] me = (playerId == 1) ? playerA : playerB;
int[][] myDist = floodFillDistance(me);
int reachable = 0;
for (int[] row : myDist) {
for (int d : row) if (d >= 0) reachable++;
}
// 割点惩罚:如果当前位置是 Articulation Point,大幅减少分数
int articulationPenalty = isArticulation(me[0], me[1]) ? -50 : 0;
return base * 10 + reachable + articulationPenalty;
}
/**
* 简化版割点检测:检查移除当前格后是否分裂可达区域
*/
private boolean isArticulation(int r, int c) {
// 临时标记为障碍物
int tmp = board[r][c];
board[r][c] = 3; // 3=临时障碍
// 从邻居开始 BFS,看能否连通所有邻居
List<int[]> neighbors = new ArrayList<>();
for (int[] d : DIRS) {
int nr = r + d[0], nc = c + d[1];
if (isValid(nr, nc)) neighbors.add(new int[]{nr, nc});
}
boolean isArt = false;
if (neighbors.size() >= 2) {
int[][] dist = new int[HEIGHT][WIDTH];
for (int[] row : dist) Arrays.fill(row, -1);
ArrayDeque<int[]> q = new ArrayDeque<>();
q.offer(neighbors.get(0));
dist[neighbors.get(0)[0]][neighbors.get(0)[1]] = 0;
while (!q.isEmpty()) {
int[] cur = q.poll();
for (int[] d : DIRS) {
int nr = cur[0] + d[0], nc = cur[1] + d[1];
if (isValid(nr, nc) && dist[nr][nc] == -1) {
dist[nr][nc] = dist[cur[0]][cur[1]] + 1;
q.offer(new int[]{nr, nc});
}
}
}
// 如果有邻居不可达,则是割点
for (int i = 1; i < neighbors.size(); i++) {
if (dist[neighbors.get(i)[0]][neighbors.get(i)[1]] == -1) {
isArt = true;
break;
}
}
}
board[r][c] = tmp; // 恢复
return isArt;
}
六、主程序与演示
/**
* 主程序:运行一场完整的 Tron 对战
*/
public static void main(String[] args) {
TronEngine engine = new TronEngine();
Random rand = new Random(42);
System.out.println("=== Tron 光循环对战开始 ===");
engine.printBoard();
int currentPlayer = 1;
while (true) {
int dir;
if (currentPlayer == 1) {
dir = engine.bestMove(1); // AI 决策
} else {
dir = engine.bestMove(2); // AI 决策
}
boolean success = engine.movePlayer(currentPlayer, dir);
if (!success) {
System.out.println("玩家 " + currentPlayer + " 撞墙!游戏结束!");
int winner = (currentPlayer == 1) ? 2 : 1;
System.out.println("获胜者:玩家 " + winner);
break;
}
System.out.println("
玩家 " + currentPlayer + " 移动: " + DIR_NAMES[dir]);
engine.printBoard();
currentPlayer = (currentPlayer == 1) ? 2 : 1;
// 安全上限
if (engine.getTurn() > 200) {
System.out.println("达到回合上限,平局!");
break;
}
}
}
/**
* 打印当前棋盘状态
*/
public void printBoard() {
for (int r = 0; r < HEIGHT; r++) {
for (int c = 0; c < WIDTH; c++) {
if (r == playerA[0] && c == playerA[1]) System.out.print("A ");
else if (r == playerB[0] && c == playerB[1]) System.out.print("B ");
else if (board[r][c] == 1) System.out.print("a ");
else if (board[r][c] == 2) System.out.print("b ");
else System.out.print(". ");
}
System.out.println();
}
}
七、复杂度分析
| 模块 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| A* 寻路 | O(WIDTH × HEIGHT) | O(WIDTH × HEIGHT) |
| Flood Fill (Voronoi) | O(WIDTH × HEIGHT) | O(WIDTH × HEIGHT) |
| 割点检测 | O(WIDTH × HEIGHT) | O(WIDTH × HEIGHT) |
| 每步决策 | O(4 × WIDTH × HEIGHT) | O(WIDTH × HEIGHT) |
在 16×16 的小棋盘上,每步决策可在毫秒级完成。对于更大棋盘,可通过增量式 Voronoi 更新和区域缓存进一步优化。
八、扩展思路
- Minimax + Alpha-Beta:对双方各模拟 3-4 步,构建博弈树剪枝搜索
- 神经网络评估:用 CNN 评估棋盘局面,替代手工 Voronoi 评分
- 多层防御策略:早期扩张、中期围堵、晚期收缩的安全策略切换
Tron 光循环将几何空间占据与博弈论完美结合,是理解”领土控制”类 AI 的绝佳入门案例。