引言
炸弹人(Bomberman)是一款经典的策略动作游戏。玩家在二维网格中放置炸弹,利用爆炸清除障碍物和敌人,同时需要保证自身安全。本文将用Java实现一个带AI的炸弹人核心逻辑,重点讲解BFS爆炸范围传播算法计算炸弹影响区域,以及A*最短路径算法帮助玩家规划安全撤离路线。
问题建模
将游戏地图抽象为二维网格,每个格子有以下几种状态:
– EMPTY(空地):可以自由移动和放置炸弹
– WALL(固定墙):不可破坏,阻挡爆炸
– BLOCK(可破坏障碍):可以被炸弹炸毁
– BOMB(炸弹):倒计时后爆炸
– PLAYER(玩家)与 ENEMY(敌人)
爆炸沿四个方向(上下左右)传播,遇到墙或边界停止,遇到可破坏障碍则炸毁该障碍并停止传播。
核心算法一:BFS爆炸范围传播
炸弹爆炸时,需要快速计算其影响范围。采用BFS从炸弹位置向四个方向扩散,直到遇到阻挡物。
import java.util.*;
public class BombermanGame {
// 地图状态常量
public static final int EMPTY = 0;
public static final int WALL = 1; // 固定墙,不可破坏
public static final int BLOCK = 2; // 可破坏障碍
public static final int BOMB = 3; // 炸弹
private int rows, cols;
private int[][] grid;
private int bombRange; // 炸弹爆炸半径
public BombermanGame(int rows, int cols, int bombRange) {
this.rows = rows;
this.cols = cols;
this.bombRange = bombRange;
this.grid = new int[rows][cols];
}
/**
* 使用BFS计算炸弹爆炸的影响范围
* @param bombRow 炸弹行坐标
* @param bombCol 炸弹列坐标
* @return 所有会被爆炸波及的位置集合
*/
public Set<Position> calculateExplosionRange(int bombRow, int bombCol) {
Set<Position> affected = new HashSet<>();
Queue<ExplosionWave> queue = new LinkedList<>();
// 四个方向向量
int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// 初始状态:从炸弹位置向四个方向发射冲击波
affected.add(new Position(bombRow, bombCol));
for (int d = 0; d < 4; d++) {
queue.offer(new ExplosionWave(bombRow, bombCol, d, 0));
}
while (!queue.isEmpty()) {
ExplosionWave wave = queue.poll();
int nextRow = wave.row + dirs[wave.dir][0];
int nextCol = wave.col + dirs[wave.dir][1];
int nextDist = wave.dist + 1;
// 边界检查
if (nextRow < 0 || nextRow >= rows || nextCol < 0 || nextCol >= cols) {
continue;
}
// 超出爆炸半径
if (nextDist > bombRange) {
continue;
}
affected.add(new Position(nextRow, nextCol));
int cell = grid[nextRow][nextCol];
if (cell == WALL) {
// 固定墙阻挡爆炸传播
continue;
} else if (cell == BLOCK) {
// 可破坏障碍被炸毁,爆炸停止
continue;
} else {
// 空地或炸弹,爆炸继续沿原方向传播
queue.offer(new ExplosionWave(nextRow, nextCol, wave.dir, nextDist));
}
}
return affected;
}
// 爆炸冲击波:记录当前位置、传播方向和已传播距离
static class ExplosionWave {
int row, col, dir, dist;
ExplosionWave(int r, int c, int d, int dist) {
this.row = r; this.col = c; this.dir = d; this.dist = dist;
}
}
static class Position {
int row, col;
Position(int r, int c) { this.row = r; this.col = c; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Position)) return false;
Position p = (Position) o;
return row == p.row && col == p.col;
}
@Override public int hashCode() { return row * 131 + col; }
}
}
BFS爆炸传播的时间复杂度为 O(4 × R),其中 R 为爆炸半径。由于只有四个方向,实际运算量很小。
核心算法二:A*最短路径逃离策略
当玩家放置炸弹后,需要在爆炸倒计时结束前移动到安全区域。A*算法结合曼哈顿距离启发式函数,可以快速找到最短逃生路径。
/**
* A*寻路:从当前位置找到离炸弹爆炸范围最远的安全位置
* @param startRow 玩家当前行
* @param startCol 玩家当前列
* @param dangerZone 炸弹爆炸影响范围
* @return 安全撤离路径,若无处可逃返回空列表
*/
public List<Position> findEscapePath(int startRow, int startCol,
Set<Position> dangerZone) {
PriorityQueue<AStarNode> open = new PriorityQueue<>(
Comparator.comparingInt(n -> n.f)
);
boolean[][] closed = new boolean[rows][cols];
Map<Position, Position> parent = new HashMap<>();
Map<Position, Integer> gScore = new HashMap<>();
Position start = new Position(startRow, startCol);
open.offer(new AStarNode(start, 0, heuristic(start, dangerZone)));
gScore.put(start, 0);
int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while (!open.isEmpty()) {
AStarNode current = open.poll();
Position currPos = current.pos;
if (closed[currPos.row][currPos.col]) continue;
closed[currPos.row][currPos.col] = true;
// 找到安全位置:不在爆炸范围内且是空地
if (!dangerZone.contains(currPos) &&
grid[currPos.row][currPos.col] == EMPTY) {
return reconstructPath(parent, currPos);
}
for (int[] d : dirs) {
int nr = currPos.row + d[0];
int nc = currPos.col + d[1];
Position next = new Position(nr, nc);
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
if (closed[nr][nc]) continue;
if (grid[nr][nc] == WALL || grid[nr][nc] == BLOCK) continue;
int tentativeG = gScore.getOrDefault(currPos, Integer.MAX_VALUE) + 1;
if (tentativeG < gScore.getOrDefault(next, Integer.MAX_VALUE)) {
parent.put(next, currPos);
gScore.put(next, tentativeG);
int h = heuristic(next, dangerZone);
open.offer(new AStarNode(next, tentativeG, tentativeG + h));
}
}
}
return Collections.emptyList(); // 无路可逃
}
/**
* 启发式函数:计算到最近危险格子的曼哈顿距离的相反数
* 越远离爆炸范围,启发值越小(优先级越高)
*/
private int heuristic(Position pos, Set<Position> dangerZone) {
int minDist = Integer.MAX_VALUE;
for (Position d : dangerZone) {
int dist = Math.abs(pos.row - d.row) + Math.abs(pos.col - d.col);
minDist = Math.min(minDist, dist);
}
return -minDist; // 负数使得远离危险区的节点优先级更高
}
private List<Position> reconstructPath(Map<Position, Position> parent,
Position goal) {
List<Position> path = new ArrayList<>();
Position curr = goal;
while (curr != null) {
path.add(curr);
curr = parent.get(curr);
}
Collections.reverse(path);
return path;
}
static class AStarNode {
Position pos;
int g, f;
AStarNode(Position p, int g, int f) {
this.pos = p; this.g = g; this.f = f;
}
}
完整演示与复杂度分析
将上述模块整合后,玩家AI的决策流程为:
1. 扫描地图寻找可破坏障碍密集区域
2. 计算放置炸弹后的BFS爆炸范围
3. 在爆炸倒计时内使用A*找到最短逃生路径
4. 若逃生路径长度 < 倒计时步数,则执行投放
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| BFS爆炸传播 | O(R) | O(R) |
| A*最短路径 | O(V log V) | O(V) |
其中 R 为炸弹半径,V 为地图可通行格子数。在标准 13×15 的炸弹人地图中,A*通常只需访问少量节点即可找到安全位置。
总结
炸弹人游戏的AI核心在于危险预判与路径规划的平衡。BFS负责精确计算爆炸的物理影响范围,A*则负责在倒计时约束下寻找最优逃生路线。两种算法的结合使AI玩家能够在复杂地图中做出既激进又安全的战术决策。