【开发火星地平线辅助】智商不够,编程来凑

游戏介绍

《火星地平线》(Mars Horizon )是一款太空探索 + 管理 + 策略模拟游戏 ,由英国独立工作室 Auroch Digital 开发,并由 The Irregular Corporation 发行。该作得到 欧洲航天局(ESA)和英国航天局的支持与咨询 ,目的在于提供真实感强、策略深度高的航天探索体验

规则介绍

在《火星地平线》中,整个太空探索过程以回合制任务规划 的方式进行。

每一个回合,玩家需要在有限的行动次数内,合理选择并执行不同的指令,通过消耗已有资源来换取新的关键资源,从而逐步推进航天任务的完成。

开发辅助

1.Launcher.java

由于每次游戏命令列表是随机生成的,所以我们不能生搬硬套,必须写一个暴力求解程序。首先我们需要写一个接收输入变量的执行入口,使用的是Scanner。输入6~12条命令,这次任务是9条命令,再加上最后固定的充电命令。

java 复制代码
import java.util.*;

public class Launcher {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        MarsSolver solver = new MarsSolver();
        State init = new State();
        List<Command> userCommands = new ArrayList<>();

        // =====================================================
        // 1. 输入命令
        // =====================================================
        while (true) {
            String name;
            while (true) {
                System.out.print("\n请输入命令名称(或输入 0 结束命令输入): ");
                name = scanner.nextLine().trim();

                if ("0".equals(name)) break;

                if (name.isEmpty()) {
                    System.out.println("❌ 命令名称不能为空");
                    continue;
                }
                break;
            }
            if ("0".equals(name)) break;


            Map<String, Integer> consume = new HashMap<>();
            Map<String, Integer> produce = new HashMap<>();

            System.out.println(
                    "请输入【消耗】资源(格式:类型 数量)\n" +
                            "1-电力 2-磁力 3-数据 4-信号 5-动力 6-倾斜 7-温度 8-宇航员\n" +
                            "输入 0 结束:"
            );
            while (true) {
                System.out.print("消耗> ");
                String line = scanner.nextLine().trim();
                if ("0".equals(line)) break;
                try {
                    String[] p = line.split("\s+");
                    String key = typeToName(Integer.parseInt(p[0]));
                    int v = Integer.parseInt(p[1]);
                    consume.put(key, consume.getOrDefault(key, 0) + v);
                } catch (Exception e) {
                    System.out.println("❌ 格式错误");
                }
            }

            System.out.println(
                    "请输入【获得】资源(格式:类型 数量)\n" +
                            "1-电力 2-磁力 3-数据 4-信号 5-动力 6-倾斜 7-温度\n" +
                            "输入 0 结束:"
            );
            while (true) {
                System.out.print("获得> ");
                String line = scanner.nextLine().trim();
                if ("0".equals(line)) break;
                try {
                    String[] p = line.split("\s+");
                    String key = typeToName(Integer.parseInt(p[0]));
                    int v = Integer.parseInt(p[1]);
                    produce.put(key, produce.getOrDefault(key, 0) + v);
                } catch (Exception e) {
                    System.out.println("❌ 格式错误");
                }
            }

            userCommands.add(new Command(name, 1, consume, produce));
            System.out.println("✅ 已添加命令:" + name);
        }

        // 固定充电命令
        Map<String, Integer> charge = new HashMap<>();
        charge.put("power", 1);
        userCommands.add(new Command("Charge", 0, new HashMap<>(), charge));
        MarsSolver.ALL = userCommands;

        // =====================
        // 命令校对 & 编辑阶段
        // =====================
        editCommands(scanner, userCommands);
        MarsSolver.ALL = userCommands;
        System.out.println("\n校对完成的命令列表:");
        for (Command c : MarsSolver.ALL) {
            System.out.println(c.getName() + " | 消耗: " + c.getConsume() + " | 获得: " + c.getProduce());
        }

        // =====================================================
        // 2. 搜索参数
        // =====================================================
        MarsSolver.ROUNDS = readInt(scanner, "请输入回合数 ROUNDS: ");
        MarsSolver.STEPS = readInt(scanner, "请输入每回合命令数 STEPS: ");

        // =====================================================
        // 3. 初始状态
        // =====================================================
        init.power = readInt(scanner, "请输入初始电力: ");
        init.craw = readInt(scanner, "请输入宇航员数: ");
        MarsSolver.crawPerRound = init.craw;

        // 温度
        MarsSolver.useTempLimit = readBool01(scanner, "是否启用【温度判定】?(1=是 0=否): ");
        if (MarsSolver.useTempLimit) {
            init.temp = readInt(scanner, "请输入初始温度: ");
            if (readBool01(scanner, "是否设置温度下限?1是 0否: ")) {
                MarsSolver.tempMin = readInt(scanner, "请输入温度下限: ");
            }
            if (readBool01(scanner, "是否设置温度上限?1是 0否: ")) {
                MarsSolver.tempMax = readInt(scanner, "请输入温度上限: ");
            }
            MarsSolver.tempDeltaMin = readInt(scanner, "回合温度变化最小值: ");
            MarsSolver.tempDeltaMax = readInt(scanner, "回合温度变化最大值: ");
        }

        // 倾斜
        MarsSolver.useTiltLimit = readBool01(scanner, "是否启用【倾斜判定】?(1=是 0=否): ");
        if (MarsSolver.useTiltLimit) {
            init.tilt = readInt(scanner, "请输入初始倾斜: ");
            MarsSolver.tiltMin = readInt(scanner, "倾斜下限: ");
            MarsSolver.tiltMax = readInt(scanner, "倾斜上限: ");

            MarsSolver.tiltResetEachRound =
                    readBool01(scanner, "每回合结束是否【倾斜归零】?(1=是 0=否): ");

            MarsSolver.tiltDeltaMin = readInt(scanner, "回合倾斜变化最小值: ");
            MarsSolver.tiltDeltaMax = readInt(scanner, "回合倾斜变化最大值: ");
        }


        // =====================================================
        // 4. 目标
        // =====================================================
        MarsSolver.signalGoal = readInt(scanner, "信号目标: ");
        MarsSolver.dataGoal = readInt(scanner, "数据目标: ");
        MarsSolver.magGoal = readInt(scanner, "磁力目标: ");
        MarsSolver.planeGoal = readInt(scanner, "飞机目标: ");
        MarsSolver.planeConsumePerRound = readInt(scanner, "请输入每回合消耗的飞机数量: ");

        // =====================================================
        // 5. DFS + 重新输入目标循环
        // =====================================================
        while (true) {

            System.out.println("\n==============================");
            System.out.println("当前目标:");
            System.out.println(" data=" + MarsSolver.dataGoal +
                    " mag=" + MarsSolver.magGoal +
                    " signal=" + MarsSolver.signalGoal +
                    " plane=" + MarsSolver.planeGoal);
            System.out.println("==============================");

            boolean found = solver.dfs(0, 0, init.copy(), new ArrayList<>());

            if (found) {
                System.out.println("\n✅ 已完成");
                break;
            }

            System.out.println("\n❌ 当前条件无解,请重新设置目标(回车保持)");

            MarsSolver.signalGoal = readIntOrKeep(scanner,
                    "信号目标(当前=" + MarsSolver.signalGoal + "): ", MarsSolver.signalGoal);
            MarsSolver.dataGoal   = readIntOrKeep(scanner,
                    "数据目标(当前=" + MarsSolver.dataGoal + "): ", MarsSolver.dataGoal);
            MarsSolver.magGoal    = readIntOrKeep(scanner,
                    "磁力目标(当前=" + MarsSolver.magGoal + "): ", MarsSolver.magGoal);
            MarsSolver.planeGoal  = readIntOrKeep(scanner,
                    "飞机目标(当前=" + MarsSolver.planeGoal + "): ", MarsSolver.planeGoal);

            MarsSolver.reset();

        }
    }

    static void editCommands(Scanner sc, List<Command> cmds) {

        while (true) {

            System.out.println("\n========== 输入完成的命令列表 ==========");
            for (int i = 0; i < cmds.size(); i++) {
                Command c = cmds.get(i);
                System.out.println(
                                c.getName() +
                                " | 消耗 " + c.getConsume() +
                                " | 获得 " + c.getProduce());
            }
            System.out.println("================================");

            System.out.println(
                    "\n操作:\n" +
                            "1 = 删除命令\n" +
                            "2 = 新增命令\n" +
                            "0 = 完成命令并继续\n");

            System.out.print("请选择: ");
            String op = sc.nextLine().trim();

            if ("0".equals(op)) {
                return;
            }
            else if ("1".equals(op)) {
                System.out.print("输入要删除的命令名称: ");
                String name = sc.nextLine().trim();
                boolean removed = cmds.removeIf(c -> c.getName().equalsIgnoreCase(name));
                if (removed) System.out.println("✅ 已删除 " + name);
                else System.out.println("❌ 未找到命令 " + name);
            }
            else if ("2".equals(op)) {
                addCommand(sc, cmds);
            }
            else {
                System.out.println("❌ 无效操作");
            }
        }
    }

    static void addCommand(Scanner scanner, List<Command> cmds) {
        String name;
        while (true) {
            System.out.print("\n请输入新命令名称: ");
            name = scanner.nextLine().trim();
            if ("0".equals(name)) break;
            if (name.isEmpty()) {
                System.out.println("❌ 命令名称不能为空");
                continue;
            }
            break;
        }

        Map<String, Integer> consume = new HashMap<>();
        Map<String, Integer> produce = new HashMap<>();

        System.out.println("输入【消耗】(类型 数量),0结束");
        while (true) {
            System.out.print("消耗> ");
            String line = scanner.nextLine().trim();
            if ("0".equals(line)) break;
            try {
                String[] p = line.split("\s+");
                String key = typeToName(Integer.parseInt(p[0]));
                int v = Integer.parseInt(p[1]);
                consume.put(key, consume.getOrDefault(key, 0) + v);
            } catch (Exception e) {
                System.out.println("❌ 格式错误");
            }
        }

        System.out.println("输入【获得】(类型 数量),0结束");
        while (true) {
            System.out.print("获得> ");
            String line = scanner.nextLine().trim();
            if ("0".equals(line)) break;
            try {
                String[] p = line.split("\s+");
                String key = typeToName(Integer.parseInt(p[0]));
                int v = Integer.parseInt(p[1]);
                produce.put(key, produce.getOrDefault(key, 0) + v);
            } catch (Exception e) {
                System.out.println("❌ 格式错误");
            }
        }

        cmds.add(new Command(name, 1, consume, produce));
        System.out.println("✅ 已新增命令: " + name);
    }

    // ================= 工具方法 =================

    static int readInt(Scanner sc, String tip) {
        while (true) {
            System.out.print(tip);
            try {
                return Integer.parseInt(sc.nextLine().trim());
            } catch (Exception e) {
                System.out.println("❌ 输入错误");
            }
        }
    }

    static boolean readBool01(Scanner sc, String tip) {
        while (true) {
            System.out.print(tip);
            String s = sc.nextLine().trim();
            if ("1".equals(s)) return true;
            if ("0".equals(s)) return false;
            System.out.println("❌ 请输入 1 或 0");
        }
    }

    static String typeToName(int type) {
        switch (type) {
            case 1:
                return "power";
            case 2:
                return "mag";
            case 3:
                return "data";
            case 4:
                return "signal";
            case 5:
                return "plane";
            case 6:
                return "tilt";
            case 7:
                return "temp";
            case 8:
                return "craw";
            default:
                throw new IllegalArgumentException("未知类型: " + type);
        }
    }

    static int readIntOrKeep(Scanner sc, String tip, int oldVal) {
        while (true) {
            System.out.print(tip);
            String line = sc.nextLine().trim();
            if (line.isEmpty()) return oldVal;
            try {
                return Integer.parseInt(line);
            } catch (Exception e) {
                System.out.println("❌ 请输入整数,或直接回车保持不变");
            }
        }
    }
}

最后执行DFS(Depth-First Search,深度优先搜索)算法,也是我们整个程序的灵魂。

2.State

java 复制代码
class State {
    int craw;
    int power;
    int signal;
    int mag;
    int data;
    int plane;
    int temp;
    int tilt;
//    int rad;

    State copy() {
        State s = new State();
        s.craw = craw;
        s.power = power;
        s.signal = signal;
        s.mag = mag;
        s.data = data;
        s.plane = plane;
        s.temp = temp;
        s.tilt = tilt;
//        s.rad = rad;
        return s;
    }
}

状态类,定义当前的资源状态。辐射☢️暂时先不考虑,降低命令成功率,不作为任务成功的判定标准。

3.Command

java 复制代码
import java.util.Map;

class Command {
    String name;
    int costPower;
    Map<String, Integer> consume;
    Map<String, Integer> produce;

    Command(String name, int costPower,
            Map<String, Integer> consume,
            Map<String, Integer> produce) {
        this.name = name;
        this.costPower = costPower;
        this.consume = consume;
        this.produce = produce;
    }

    Command(String name) {
        this.name = name;
    }

    public int getCostPower() {
        return costPower;
    }

    public Map<String, Integer> getConsume() {
        return consume;
    }

    public Map<String, Integer> getProduce() {
        return produce;
    }

    public String getName() {
        return name;
    }
}

命令类用于记录每条命令的资源消耗和生产。

4.MarsSolver

这里就进入主菜了,由于每回合结束环境会发生变化,所以要考虑进去。每回合结束温度都不能碰那个边界,步骤中间可以短期碰,但每一回合结束一定要拉回来,否则任务直接失败。有些任务温度的警戒线不是上限,而是下限,比如去冰巨星,天王星和海王星,还有柯伊伯带边界的冥王星,每回合都要升温,无论升多高直接将为0,所以升到1是最佳策略。倾斜虽然也作为一个限制,但是只在所有回合完成的最后达成目标即可,回合中随便偏移方向。倾斜可为负,而温度最低降为0。有些任务每回合结束也会有环境随机变化,比如温度+2,倾斜-2等。所以这样就不一定能搜索出解了,运气不好就是无解。

java 复制代码
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

public class MarsSolver {

    // ========= 搜索参数 =========
    public static int ROUNDS;
    public static int STEPS;

    // ========= 搜索统计 =========
    static long visited = 0;
    static final long PRINT_INTERVAL = 1_000_000;
    static long lastPrint = 0;
    static final long START_TIME = System.currentTimeMillis();

    // ========= 目标 =========
    public static int magGoal = 0;
    public static int dataGoal = 0;
    public static int signalGoal = 0;
    public static int planeGoal = 0;

    // ========= 回合规则 =========
    public static int crawPerRound;
    public static int planeConsumePerRound;
    public static boolean tiltResetEachRound = false;

    // ========= 回合环境变化 =========
    public static int tempDeltaMin = 0;
    public static int tempDeltaMax = 0;
    public static int tiltDeltaMin = 0;
    public static int tiltDeltaMax = 0;

    // ========= 判定 =========
    public static boolean useTempLimit = false;
    public static Integer tempMin = null;
    public static Integer tempMax = null;

    public static boolean useTiltLimit = false;
    public static int tiltMin;
    public static int tiltMax;

    // ========= 命令池 =========
    public static List<Command> ALL = new ArrayList<>();

    public static void reset() {
        visited = 0;
        lastPrint = 0;
    }
    static int maxProducePerRound(String key) {
        int best = 0;
        for (Command c : ALL) {
            int v = c.produce.getOrDefault(key, 0);
            if (v > best) best = v;
        }
        return best * STEPS;
    }

    // =====================================================
    // DFS
    // =====================================================
    public boolean dfs(int round, int step, State state, List<String> path) {

        int remainRounds = ROUNDS - round;

        // ✂ 剪枝:剩余回合最多能生产的资源上限
        int maxData = state.data + remainRounds * maxProducePerRound("data");
        int maxMag  = state.mag  + remainRounds * maxProducePerRound("mag");
        int maxSig  = state.signal + remainRounds * maxProducePerRound("signal");
        int maxPlane = state.plane + remainRounds * maxProducePerRound("plane");
        if (maxData < dataGoal) return false;
        if (maxMag < magGoal) return false;
        if (maxSig < signalGoal) return false;
        if (maxPlane < planeGoal) return false;

        visited++;
        if (visited - lastPrint >= PRINT_INTERVAL) {
            lastPrint = visited;
            long cost = (System.currentTimeMillis() - START_TIME) / 1000;
            System.out.println("[PROGRESS] visited=" + visited +
                    " time=" + cost + "s R=" + round + " S=" + step);
        }

        // ===== 成功判定 =====
        if (round == ROUNDS) {
            // 最终倾斜判定
            if (useTiltLimit) {
                if (state.tilt < tiltMin || state.tilt > tiltMax) {
                    return false;
                }
            }
            if (state.mag >= magGoal &&
                    state.data >= dataGoal &&
                    state.signal >= signalGoal &&
                    state.plane >= planeGoal) {

                System.out.println("\n===== FOUND SOLUTION =====");
                printSolutionByRound(path);
                System.exit(0);
                return true;
            }
            return false;
        }

        // ===== 回合结束 =====
        if (step == STEPS) {
            // 判定
            if (useTempLimit) {
                if (tempMin != null && state.temp < tempMin) return false;
                if (tempMax != null && state.temp > tempMax) return false;
            }

            // 环境枚举
            for (int dt = tempDeltaMin; dt <= tempDeltaMax; dt++) {
                for (int dk = tiltDeltaMin; dk <= tiltDeltaMax; dk++) {

                    State next = state.copy();

                    // 环境变化
                    next.temp += dt;
                    if (MarsSolver.tiltResetEachRound) {
                        next.tilt = 0;
                    }
                    next.tilt += dk;
                    boolean planeConsumed = false;
                    boolean crawRecovered = false;

                    if (next.plane > 0) {
                        if (next.plane < MarsSolver.planeConsumePerRound) {
                            continue; // 飞机不够,剪枝
                        }
                        next.plane -= MarsSolver.planeConsumePerRound;
                        planeConsumed = true;
                    }

                    if (next.craw != crawPerRound) {
                        next.craw = crawPerRound;
                        crawRecovered = true;
                    }

                    StringBuilder log = new StringBuilder();
                    log.append("R").append(round + 1)
                            .append(" ENV CHANGE:")
                            .append(" temp").append(dt >= 0 ? "+" : "").append(dt)
                            .append(" tilt").append(dk >= 0 ? "+" : "").append(dk);
                    if (planeConsumed) {
                        log.append(" plane-").append(MarsSolver.planeConsumePerRound);
                    }
                    if (crawRecovered) log.append(" craw=").append(crawPerRound);

                    path.add(log.toString());

                    dfs(round + 1, 0, next, path);

                    path.remove(path.size() - 1);
                }
            }
            return false;
        }

        // ===== 执行命令 =====
        for (Command cmd : ALL) {
            if (!canExecute(cmd, state)) continue;

            State next = state.copy();
            apply(cmd, next);

            if (next.power < 0) continue;

            path.add("R" + (round + 1) + "S" + (step + 1) + ":" + cmd.name);

            dfs(round, step + 1, next, path);

            path.remove(path.size() - 1);
        }
        return false;
    }

    // =====================================================
    // apply
    // =====================================================
    static void apply(Command cmd, State s) {
        s.power -= cmd.costPower;
        for (Map.Entry<String, Integer> e : cmd.consume.entrySet()) adjust(s, e.getKey(), -e.getValue());
        for (Map.Entry<String, Integer> e : cmd.produce.entrySet()) adjust(s, e.getKey(), e.getValue());
    }

    static void adjust(State s, String k, int v) {
        if ("power".equals(k)) s.power += v;
        else if ("mag".equals(k)) s.mag += v;
        else if ("data".equals(k)) s.data += v;
        else if ("signal".equals(k)) s.signal += v;
        else if ("plane".equals(k)) s.plane += v;
        else if ("tilt".equals(k)) s.tilt += v;
        else if ("temp".equals(k)) s.temp += v;
        else if ("craw".equals(k)) s.craw += v;
    }

    // =====================================================
    // canExecute
    // =====================================================
    static boolean canExecute(Command cmd, State s) {
        if (cmd.costPower > s.power) return false;
        for (Map.Entry<String, Integer> e : cmd.consume.entrySet()) {
            int need = e.getValue();
            int cur;
            String key = e.getKey();
            switch (key) {
                case "power":
                    cur = s.power;
                    break;
                case "mag":
                    cur = s.mag;
                    break;
                case "data":
                    cur = s.data;
                    break;
                case "signal":
                    cur = s.signal;
                    break;
                case "plane":
                    cur = s.plane;
                    break;
                case "craw":
                    cur = s.craw;
                    break;
                case "temp":
                    cur = s.temp;
                    break;
                case "tilt":
                    continue;
                default:
                    continue;
            }
            if (cur < need) return false;
        }
        return true;
    }

    // =====================================================
    // 打印解
    // =====================================================
    static void printSolutionByRound(List<String> path) {
        int cur = -1;
        for (String s : path) {
            if (s.matches("R\d+S\d+:.*")) {
                int r = Integer.parseInt(s.substring(1, s.indexOf('S')));
                if (r != cur) {
                    cur = r;
                    System.out.println("\n--- Round " + r + " ---");
                }
                System.out.println(s);
            } else if (s.contains("ENV CHANGE")) {
                System.out.println(s.replaceFirst("R\d+\s*", ""));
            }
        }
    }
}

共分为两个维度,round指当前第几回合,step指当前回合第几步。所以我们设计出

java 复制代码
boolean dfs(round, step, state, path)

这样的递归结构。使用canExecute函数检测某个命令当前是否可以执行,即需要的资源够不够。apply函数去执行一条命令。通过round == ROUNDS进入所有命令执行结束的判定阶段,step == STEPS进入回合结束的环境变化。使用path集合来保存已搜索过的路径。其中的亮点是还带有剪枝功能,大大降低搜索复杂度,降低时间消耗。这里我们称它为乐观上界剪枝。意思就是就算以后每一回合都用最强命令,我最多也只能产这么多。如果还不够,那这条路必死。

相关推荐
im_AMBER9 小时前
Leetcode 100 在链表中插入最大公约数
数据结构·c++·笔记·学习·算法·leetcode·链表
Z1Jxxx9 小时前
删除字符串2
开发语言·c++·算法
踩坑记录9 小时前
leetcode hot100 15. 三数之和 medium
算法·leetcode·职场和发展
独自破碎E10 小时前
【二分法】旋转数组的最小数字
数据结构·算法·排序算法
苦藤新鸡10 小时前
9.找到字符串中所有字母异位词
数据结构·c++·算法·力扣
逑之10 小时前
C语言笔记12:C语言内存函数
c语言·笔记·算法
ltqshs10 小时前
嵌入式C语言-指针数组和数组指针
c语言·数据结构·算法
小小宫城狮10 小时前
BPE 算法原理与训练实现
算法·llm
胡萝卜不甜10 小时前
算法宗门---广度有优先搜索BFS
算法·宽度优先