每日算法 — 使用java实现捣蛋猪:免疫算法与车辆结构进化优化

引言

捣蛋猪(Bad Piggies)是一款由Rovio开发的物理建造游戏:玩家利用木板、轮子、发动机、气球等零件组装载具,帮助小猪穿越地形障碍并收集星星。设计一辆稳定且高效的载具本质上是一个复杂的组合优化问题——零件类型、连接位置和角度共同决定了载具的通过能力。本文将用Java实现基于免疫算法(Immune Algorithm, IA)的车辆结构自动进化系统,讲解抗体编码、亲和力计算与克隆选择机制,通过模拟生物免疫系统的学习过程自动搜索最优车辆设计。

核心算法设计

1. 抗体编码(车辆结构表示)

将载具设计编码为抗体(Antibody),采用基于邻接表的图结构表示:
– 每个节点代表一个零件(轮子/发动机/木板/气球/火箭等)
– 节点属性:零件类型、坐标(x,y)、旋转角度θ
– 边表示零件之间的物理连接关系
– 抗体长度 = 零件数量(5~15个零件为合理范围)

为简化实现,采用固定最大零件数的实数向量编码:
X = [type₁, x₁, y₁, θ₁, type₂, x₂, y₂, θ₂, …, typeₙ, xₙ, yₙ, θₙ]

2. 亲和力函数(适应度评估)

亲和力衡量抗体(车辆)解决问题的优劣:
Affinity(X) = w₁×Distance(X) + w₂×Stability(X) + w₃×Stars(X) – w₄×PartsCost(X)

  • Distance:载具从起点前进的水平距离(越远越好)
  • Stability:模拟运行中倾覆次数的倒数(越稳越好)
  • Stars:收集到的星星数量
  • PartsCost:使用零件的总成本(越少越好)

3. 免疫算法核心机制

克隆选择:对高亲和力抗体进行克隆扩增,克隆数量与亲和力成正比。

超变异:克隆体经历高频变异(变异率与亲和力成反比),低亲和力抗体允许更大探索步长。

免疫网络抑制:若两个抗体相似度过高(欧氏距离小于阈值),低亲和力者被抑制(淘汰),维持种群多样性。

新生成:每代随机生成少量全新抗体,补充种群多样性。

Java实现

import java.util.*;

/**
 * 捣蛋猪车辆结构进化系统
 * 核心算法:免疫算法(Immune Algorithm)
 */
public class BadPiggiesOptimizer {

    // ==================== 零件类型与车辆模型 ====================

    enum PartType {
        WOOD_BOX(1.0, 0.5),    // 木箱:基础结构,中等重量
        WHEEL(0.3, 0.8),       // 轮子:低重量,提供移动能力
        ENGINE(2.0, 0.0),      // 发动机:重,提供动力
        BALLOON(0.1, -0.5),    // 气球:极轻,提供升力
        ROCKET(1.5, 0.0),      // 火箭:一次性推进
        SPRING(0.5, 0.3),      // 弹簧:减震,提升稳定性
        TNT(0.8, 0.0);         // TNT:爆炸推进

        final double weight;   // 重量
        final double lift;     // 升力(负值表示下压力)

        PartType(double weight, double lift) {
            this.weight = weight;
            this.lift = lift;
        }
    }

    static final PartType[] PART_TYPES = PartType.values();
    static final int MAX_PARTS = 10;     // 最大零件数
    static final int GENE_SIZE = 4;      // 每个零件的编码维度:type, x, y, angle

    /** 车辆抗体:实数向量编码 */
    static class Antibody {
        double[] genes; // [type1, x1, y1, ang1, type2, x2, y2, ang2, ...]
        double affinity;
        int cloneCount;

        Antibody() {
            this.genes = new double[MAX_PARTS * GENE_SIZE];
        }

        /** 将基因解码为零件列表 */
        List<Part> decode() {
            List<Part> parts = new ArrayList<>();
            for (int i = 0; i < MAX_PARTS; i++) {
                int idx = i * GENE_SIZE;
                int typeIdx = (int) Math.abs(genes[idx]) % PART_TYPES.length;
                double x = genes[idx + 1];
                double y = genes[idx + 2];
                double angle = genes[idx + 3];
                // 只保留在合理范围内的零件
                if (x >= 0 && x <= 20 && y >= 0 && y <= 10) {
                    parts.add(new Part(PART_TYPES[typeIdx], x, y, angle));
                }
            }
            return parts;
        }

        @Override
        public String toString() {
            List<Part> parts = decode();
            return "Antibody{零件数=" + parts.size() + ", 亲和力=" + String.format("%.2f", affinity) + "}";
        }
    }

    static class Part {
        PartType type;
        double x, y, angle;
        Part(PartType type, double x, double y, double angle) {
            this.type = type; this.x = x; this.y = y; this.angle = angle;
        }
    }

    // ==================== 物理模拟与适应度评估 ====================

    static class PhysicsSimulator {
        double gravity = 9.8;
        double groundY = 0.0;

        /**
         * 简化物理模拟:计算车辆在给定地形上的行驶表现
         * @param parts 车辆零件列表
         * @param terrain 地形高度数组
         * @return 模拟结果 [距离, 稳定性, 星星数]
         */
        double[] simulate(List<Part> parts, double[] terrain) {
            if (parts.isEmpty()) return new double[]{0, 0, 0};

            double totalWeight = parts.stream().mapToDouble(p -> p.type.weight).sum();
            double totalLift = parts.stream().mapToDouble(p -> p.type.lift).sum();
            double enginePower = parts.stream()
                .filter(p -> p.type == PartType.ENGINE).count() * 5.0;
            double wheelCount = parts.stream()
                .filter(p -> p.type == PartType.WHEEL).count();
            double springCount = parts.stream()
                .filter(p -> p.type == PartType.SPRING).count();
            double rocketCount = parts.stream()
                .filter(p -> p.type == PartType.ROCKET).count();
            double tntCount = parts.stream()
                .filter(p -> p.type == PartType.TNT).count();

            // 无轮子且无升力/推进 = 无法移动
            if (wheelCount == 0 && totalLift <= 0 && enginePower == 0 && rocketCount == 0) {
                return new double[]{0, 0, 0};
            }

            // 简化的运动学模拟
            double netForce = enginePower + rocketCount * 15 + tntCount * 10;
            double friction = totalWeight * 0.3;
            if (wheelCount > 0) friction *= 0.6; // 轮子降低摩擦
            double acceleration = Math.max(0, (netForce - friction) / Math.max(totalWeight, 0.1));

            // 稳定性:重心越低、弹簧越多越稳
            double avgY = parts.stream().mapToDouble(p -> p.y).average().orElse(0);
            double stability = 1.0 / (1.0 + avgY) + springCount * 0.2;
            stability = Math.min(1.0, stability);

            // 行驶距离
            double simTime = 5.0; // 模拟5秒
            double distance = 0.5 * acceleration * simTime * simTime;

            // 气球提供额外空中距离
            if (totalLift > 0) {
                distance += totalLift * 3.0;
            }

            // 星星收集(简化:行驶越远收集越多,最多3颗)
            int stars = Math.min(3, (int)(distance / 15.0));

            return new double[]{distance, stability, stars};
        }
    }

    static class FitnessEvaluator {
        PhysicsSimulator simulator = new PhysicsSimulator();
        double[] terrain;

        FitnessEvaluator(double[] terrain) {
            this.terrain = terrain;
        }

        double evaluate(Antibody ab) {
            List<Part> parts = ab.decode();
            double[] result = simulator.simulate(parts, terrain);
            double distance = result[0];
            double stability = result[1];
            double stars = result[2];
            double cost = parts.size(); // 零件成本

            // 综合适应度
            return distance * 0.5 + stability * 30 + stars * 20 - cost * 2;
        }
    }

    // ==================== 免疫算法核心 ====================

    static class ImmuneAlgorithm {
        int popSize;
        int maxGen;
        double cloneFactor;      // 克隆基数
        double mutationBase;     // 基础变异率
        double suppressionThreshold; // 相似度抑制阈值
        Random rand = new Random(123);
        FitnessEvaluator evaluator;

        ImmuneAlgorithm(int popSize, int maxGen, double cloneFactor,
                        double mutationBase, double suppressionThreshold,
                        FitnessEvaluator evaluator) {
            this.popSize = popSize;
            this.maxGen = maxGen;
            this.cloneFactor = cloneFactor;
            this.mutationBase = mutationBase;
            this.suppressionThreshold = suppressionThreshold;
            this.evaluator = evaluator;
        }

        /** 随机初始化抗体 */
        Antibody randomAntibody() {
            Antibody ab = new Antibody();
            for (int i = 0; i < MAX_PARTS; i++) {
                int idx = i * GENE_SIZE;
                ab.genes[idx] = rand.nextInt(PART_TYPES.length);     // type
                ab.genes[idx + 1] = rand.nextDouble() * 20;          // x
                ab.genes[idx + 2] = rand.nextDouble() * 5;           // y (较低重心更稳)
                ab.genes[idx + 3] = rand.nextDouble() * 360;         // angle
            }
            ab.affinity = evaluator.evaluate(ab);
            return ab;
        }

        /** 计算两个抗体的欧氏距离 */
        double distance(Antibody a, Antibody b) {
            double sum = 0;
            for (int i = 0; i < a.genes.length; i++) {
                double diff = a.genes[i] - b.genes[i];
                sum += diff * diff;
            }
            return Math.sqrt(sum);
        }

        /** 克隆选择:高亲和力抗体产生更多克隆 */
        List<Antibody> cloneAndMutate(List<Antibody> population) {
            List<Antibody> clones = new ArrayList<>();

            // 按亲和力排序
            population.sort((a, b) -> Double.compare(b.affinity, a.affinity));

            for (int i = 0; i < population.size(); i++) {
                Antibody parent = population.get(i);
                // 克隆数量与亲和力成正比,与排名成反比
                int nClones = (int) Math.max(1, cloneFactor * parent.affinity / (i + 1));
                parent.cloneCount = nClones;

                for (int c = 0; c < nClones; c++) {
                    Antibody clone = new Antibody();
                    System.arraycopy(parent.genes, 0, clone.genes, 0, parent.genes.length);

                    // 超变异:亲和力越低(排名越后),变异率越高
                    double mutateRate = mutationBase / (1 + parent.affinity * 0.1);
                    for (int g = 0; g < clone.genes.length; g++) {
                        if (rand.nextDouble() < mutateRate) {
                            // 高斯变异
                            clone.genes[g] += rand.nextGaussian() * 2.0;
                            // type索引取整并归一化
                            if (g % GENE_SIZE == 0) {
                                clone.genes[g] = Math.abs(clone.genes[g]) % PART_TYPES.length;
                            }
                        }
                    }
                    clone.affinity = evaluator.evaluate(clone);
                    clones.add(clone);
                }
            }
            return clones;
        }

        /** 免疫网络抑制:消除相似度过高的低亲和力抗体 */
        List<Antibody> suppress(List<Antibody> population) {
            List<Antibody> survivors = new ArrayList<>();
            population.sort((a, b) -> Double.compare(b.affinity, a.affinity));

            for (Antibody ab : population) {
                boolean tooSimilar = false;
                for (Antibody kept : survivors) {
                    if (distance(ab, kept) < suppressionThreshold) {
                        // 相似度过高,保留亲和力更高的
                        tooSimilar = true;
                        break;
                    }
                }
                if (!tooSimilar) {
                    survivors.add(ab);
                }
            }
            return survivors;
        }

        /** 执行免疫优化 */
        Antibody optimize() {
            // 初始化种群
            List<Antibody> pop = new ArrayList<>();
            for (int i = 0; i < popSize; i++) {
                pop.add(randomAntibody());
            }

            Antibody best = null;

            for (int g = 0; g < maxGen; g++) {
                // 克隆与超变异
                List<Antibody> clones = cloneAndMutate(pop);

                // 合并原种群与克隆体
                List<Antibody> combined = new ArrayList<>(pop);
                combined.addAll(clones);

                // 免疫抑制
                combined = suppress(combined);

                // 选择 top popSize
                combined.sort((a, b) -> Double.compare(b.affinity, a.affinity));
                pop = combined.subList(0, Math.min(popSize, combined.size()));

                // 补充新生抗体(维持多样性)
                while (pop.size() < popSize) {
                    pop.add(randomAntibody());
                }

                // 更新最优
                Antibody genBest = pop.get(0);
                if (best == null || genBest.affinity > best.affinity) {
                    best = new Antibody();
                    System.arraycopy(genBest.genes, 0, best.genes, 0, genBest.genes.length);
                    best.affinity = genBest.affinity;
                }

                if ((g + 1) % 20 == 0) {
                    System.out.println("  第" + (g + 1) + "代, 最优亲和力=" + String.format("%.2f", best.affinity));
                }
            }
            return best;
        }
    }

    // ==================== 主程序与测试 ====================

    public static void main(String[] args) {
        System.out.println("===== 捣蛋猪车辆结构进化系统 =====\n");
        System.out.println("核心算法:免疫算法(Immune Algorithm)");
        System.out.println("优化目标:最大化行驶距离、稳定性与星星收集\n");

        // 定义一个简单起伏地形
        double[] terrain = new double[100];
        for (int i = 0; i < 100; i++) {
            terrain[i] = Math.sin(i * 0.2) * 0.5 + Math.cos(i * 0.5) * 0.3;
        }

        FitnessEvaluator evaluator = new FitnessEvaluator(terrain);

        // 免疫算法参数
        int popSize = 40;
        int maxGen = 150;
        double cloneFactor = 3.0;
        double mutationBase = 0.3;
        double suppressionThreshold = 5.0;

        ImmuneAlgorithm ia = new ImmuneAlgorithm(
            popSize, maxGen, cloneFactor, mutationBase, suppressionThreshold, evaluator);

        long start = System.currentTimeMillis();
        Antibody best = ia.optimize();
        long elapsed = System.currentTimeMillis() - start;

        System.out.println("\n===== 最优车辆设计 =====");
        List<Part> bestParts = best.decode();
        System.out.println("最优抗体: " + best);
        System.out.println("零件清单:");
        for (int i = 0; i < bestParts.size(); i++) {
            Part p = bestParts.get(i);
            System.out.println("  [" + i + "] " + p.type +
                " 位置=(" + String.format("%.1f", p.x) + "," + String.format("%.1f", p.y) + ")" +
                " 角度=" + String.format("%.0f", p.angle) + "°");
        }

        double[] simResult = ia.evaluator.simulator.simulate(bestParts, terrain);
        System.out.println("\n模拟结果:");
        System.out.println("  行驶距离: " + String.format("%.2f", simResult[0]) + "m");
        System.out.println("  稳定性评分: " + String.format("%.2f", simResult[1]));
        System.out.println("  收集星星: " + (int)simResult[2] + "/3");
        System.out.println("  进化耗时: " + elapsed + "ms");
    }
}

复杂度分析

操作 时间复杂度 空间复杂度 说明
抗体解码 O(P) O(P) P为最大零件数
物理模拟 O(P) O(1) 简化模拟为线性计算
适应度评估 O(P) O(1) 调用模拟器
克隆操作 O(N×C×G) O(N×C×G) N种群大小,C平均克隆数,G基因长度
变异操作 O(N×C×G) O(G) 逐基因判断变异
免疫抑制 O(M²×G) O(M) M为合并后种群数,两两比较
整体优化 O(Gen×N²×P) O(N×P) Gen为迭代代数

算法扩展思路

  1. 物理引擎集成:将简化模拟替换为Box2D等真实物理引擎,在精确碰撞检测和刚体动力学中评估车辆性能。
  2. 图神经网络编码:将车辆结构表示为图,使用GNN编码结构特征,提升相似度计算的语义准确性。
  3. 多目标免疫算法:将距离、稳定性、成本作为独立的帕累托目标,使用MOIA算法求解散布均匀的解集。

总结

本文将捣蛋猪的载具设计问题建模为组合优化问题,通过免疫算法的克隆选择、超变异和免疫网络抑制机制,自动进化出高性能的车辆结构。免疫算法通过抗体间的相似度抑制维持种群多样性,避免早熟收敛,是求解结构优化和组合设计问题的有效仿生智能方法。