每日算法 — 使用java实现魔塔:BFS地图探索与动态规划最优战斗策略

引言

魔塔是一款经典的角色扮演益智游戏,玩家控制勇者在多层塔楼中探索,击败怪物、收集钥匙、打开门锁,最终救出公主。游戏看似简单,但隐藏着丰富的策略深度:如何规划探索路线以最小化生命值消耗?何时战斗、何时回避?这些决策本质上是一系列图搜索与动态规划问题。本文将用Java实现一个简化版魔塔的核心算法,讲解BFS地图探索与动态规划最优战斗策略。

算法原理

BFS地图探索

魔塔的每一层可以抽象为一个二维网格图,其中包含墙壁、空地、怪物、钥匙、门和楼梯。BFS(广度优先搜索)适合求解从起点到任意可达位置的最短路径,同时记录经过的钥匙和门的状态。

动态规划最优战斗策略

每个怪物有攻击力和防御力,玩家也有攻击力和防御力。战斗结果的计算遵循固定公式:当玩家攻击力大于怪物防御力时,每次攻击造成 playerAtk - monsterDef 伤害;反之玩家无法造成伤害。战斗是回合制的,直到一方生命值归零。

对于连续遭遇多个怪物的场景,可以使用动态规划求解最优战斗顺序:设 dp[mask] 表示击败状态为 mask 的怪物集合后剩余的最大生命值,通过枚举下一个击败的怪物进行状态转移。

Java实现

import java.util.*;

/**
 * 魔塔核心算法实现:BFS地图探索 + 动态规划最优战斗策略
 */
public class MagicTower {

    // 地图元素类型
    static final int WALL = 0, EMPTY = 1, MONSTER = 2, KEY = 3, DOOR = 4, STAIRS = 5;
    // 方向数组
    static final int[] DX = {-1, 1, 0, 0};
    static final int[] DY = {0, 0, -1, 1};

    static class Monster {
        int hp, atk, def;
        Monster(int hp, int atk, int def) {
            this.hp = hp; this.atk = atk; this.def = def;
        }
    }

    static class State {
        int x, y, keys;
        State(int x, int y, int keys) {
            this.x = x; this.y = y; this.keys = keys;
        }
    }

    /**
     * BFS探索地图,返回从起点到达每个位置的最短步数及钥匙收集情况
     */
    public static int[][] bfsExplore(int[][] map, int startX, int startY) {
        int rows = map.length, cols = map[0].length;
        int[][] dist = new int[rows][cols];
        for (int[] row : dist) Arrays.fill(row, -1);
        Queue<State> q = new LinkedList<>();
        q.offer(new State(startX, startY, 0));
        dist[startX][startY] = 0;

        while (!q.isEmpty()) {
            State cur = q.poll();
            for (int i = 0; i < 4; i++) {
                int nx = cur.x + DX[i], ny = cur.y + DY[i];
                if (nx < 0 || nx >= rows || ny < 0 || ny >= cols) continue;
                if (map[nx][ny] == WALL || dist[nx][ny] != -1) continue;
                // 门需要钥匙
                if (map[nx][ny] == DOOR && cur.keys <= 0) continue;
                int newKeys = cur.keys + (map[nx][ny] == KEY ? 1 : 0) - (map[nx][ny] == DOOR ? 1 : 0);
                dist[nx][ny] = dist[cur.x][cur.y] + 1;
                q.offer(new State(nx, ny, newKeys));
            }
        }
        return dist;
    }

    /**
     * 计算击败怪物后玩家剩余生命值
     * @param playerHp 玩家生命
     * @param playerAtk 玩家攻击
     * @param playerDef 玩家防御
     * @param m 怪物属性
     * @return 剩余生命,若无法击败返回-1
     */
    public static int battle(int playerHp, int playerAtk, int playerDef, Monster m) {
        int playerDamage = Math.max(0, playerAtk - m.def);
        int monsterDamage = Math.max(0, m.atk - playerDef);
        if (playerDamage <= 0) return -1; // 无法造成伤害
        int rounds = (m.hp + playerDamage - 1) / playerDamage; // 向上取整
        int totalDamage = monsterDamage * (rounds - 1); // 怪物最后回合不会反击
        return playerHp - totalDamage;
    }

    /**
     * 动态规划求解击败一组怪物的最优顺序,返回最大剩余生命值
     * @param playerHp 初始生命
     * @param playerAtk 玩家攻击
     * @param playerDef 玩家防御
     * @param monsters 怪物列表
     * @return 最大剩余生命值
     */
    public static int optimalBattleOrder(int playerHp, int playerAtk, int playerDef, List<Monster> monsters) {
        int n = monsters.size();
        int[] dp = new int[1 << n];
        Arrays.fill(dp, -1);
        dp[0] = playerHp;

        for (int mask = 0; mask < (1 << n); mask++) {
            if (dp[mask] < 0) continue;
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) != 0) continue;
                int remain = battle(dp[mask], playerAtk, playerDef, monsters.get(i));
                if (remain >= 0) {
                    int newMask = mask | (1 << i);
                    dp[newMask] = Math.max(dp[newMask], remain);
                }
            }
        }
        return dp[(1 << n) - 1];
    }

    public static void main(String[] args) {
        // 示例地图:1=空地, 0=墙, 2=怪物, 3=钥匙, 4=门, 5=楼梯
        int[][] map = {
            {0, 0, 0, 0, 0},
            {0, 1, 3, 1, 0},
            {0, 1, 0, 4, 0},
            {0, 1, 2, 5, 0},
            {0, 0, 0, 0, 0}
        };
        int[][] dist = bfsExplore(map, 1, 1);
        System.out.println("到达楼梯的最短步数: " + dist[3][3]);

        // 示例怪物列表
        List<Monster> monsters = Arrays.asList(
            new Monster(10, 5, 1),
            new Monster(20, 8, 2),
            new Monster(15, 6, 0)
        );
        int maxHp = optimalBattleOrder(100, 10, 3, monsters);
        System.out.println("击败所有怪物后的最大剩余生命值: " + maxHp);
    }
}

复杂度分析

  • BFS地图探索:时间复杂度 $O(R \times C)$,空间复杂度 $O(R \times C)$,其中 $R$ 和 $C$ 分别为地图行列数。
  • 动态规划最优战斗顺序:时间复杂度 $O(2^N \times N)$,空间复杂度 $O(2^N)$,$N$ 为怪物数量。当 $N \leq 20$ 时完全可行。

总结

本文通过Java实现了魔塔游戏的核心算法:BFS负责地图探索与最短路径规划,动态规划负责求解最优战斗顺序。实际游戏中还可以进一步引入A*启发式搜索优化多层导航,或使用状态压缩记录钥匙种类,使策略更加丰富。