OptaPlanner:如何从 JSON 读取游戏装甲数据并根据重量 + 统计数据找到最佳装甲集?

OptaPlanner: How to read game armor data from JSON and find optimal set of armor based on weight + stats?

Elden Ring 是一款热门游戏,背后有一些有趣的理论。

有数百件盔甲、武器和法术。根据玩家和物品统计数据找到它们的最佳组合是一个有趣的实际问题。

我一直想学习如何使用约束求解器,而且似乎存在一个很好的用例!


目标:


这是回购协议:

我目前的尝试:

更新

根据下面的建议我设法解决了

诀窍是改为使用 PlanningEntityProperty:

@PlanningSolution
public class ArmorSetComboPlanningSolution {

    public List<ArmorPiece> armorPieces;
    public Map<Integer, List<ArmorPiece>> armorByType;

    @ValueRangeProvider(id = "headRange")
    @ProblemFactCollectionProperty
    public List<ArmorPiece> headList;

    @ValueRangeProvider(id = "chestRange")
    @ProblemFactCollectionProperty
    public List<ArmorPiece> chestList;

    @ValueRangeProvider(id = "armsRange")
    @ProblemFactCollectionProperty
    public List<ArmorPiece> armsList;

    @ValueRangeProvider(id = "legsRange")
    @ProblemFactCollectionProperty
    public List<ArmorPiece> legsList;

    @PlanningEntityProperty
    public ArmorSet armorSet;

    @PlanningScore(bendableHardLevelsSize = 1, bendableSoftLevelsSize = 5)
    BendableLongScore score;

    ArmorSetComboPlanningSolution() {
    }

    ArmorSetComboPlanningSolution(List<ArmorPiece> armorPieces) {
        this.armorPieces = armorPieces;
        this.armorByType = armorPieces.stream().collect(groupingBy(ArmorPiece::armorCategoryID));
        this.headList = armorByType.get(0);
        this.chestList = armorByType.get(1);
        this.armsList = armorByType.get(2);
        this.legsList = armorByType.get(3);
        // Need to initialize a starting value
        this.armorSet = new ArmorSet(0L, this.headList.get(0), this.chestList.get(0), this.armsList.get(0), this.legsList.get(0));
    }
}

那么得分手:

public class ArmorSetEasyOptimizer implements EasyScoreCalculator<ArmorSetComboPlanningSolution, BendableLongScore> {

    private final int TARGE_POISE = 61;
    private final double MAX_WEIGHT = 60.64;

    public ArmorSetEasyOptimizer() {
    }

    @Override
    public BendableLongScore calculateScore(ArmorSetComboPlanningSolution solution) {
        long hardScore = 0L;
        ArmorSet armorSet = solution.armorSet;

        if (armorSet.getTotalPoise() < TARGE_POISE) {
            hardScore--;
        }

        if (armorSet.getTotalWeight() > MAX_WEIGHT) {
            hardScore--;
        }

        long poiseRatio = (long) (armorSet.getTotalPoise() / (double) armorSet.getTotalWeight() * 100);

        long physicalDefenseScaled = (long) (armorSet.getTotalPhysicalDefense() * 100);
        long physicalDefenseToWeightRatio = (long) (physicalDefenseScaled / armorSet.getTotalWeight());

        long magicDefenseScaled = (long) (armorSet.getTotalMagicDefense() * 100);
        long magicDefenseToWeightRatio = (long) (magicDefenseScaled / armorSet.getTotalWeight());

        return BendableLongScore.of(
                new long[]{
                        hardScore
                },
                new long[]{
                        poiseRatio,
                        physicalDefenseScaled,
                        physicalDefenseToWeightRatio,
                        magicDefenseScaled,
                        magicDefenseToWeightRatio
                }
        );
    }
}

结果


19:02:12.707 [main] INFO org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase - Local Search phase (1) ended: time spent (10000), best score ([0]hard/[179/3540/97/2750/75]soft), score calculation speed (987500/sec), step total (4046).

19:02:12.709 [main] INFO org.optaplanner.core.impl.solver.DefaultSolver - Solving ended: time spent (10000), best score ([0]hard/[179/3540/97/2750/75]soft), score calculation speed (985624/sec), phase total (2), environment mode (REPRODUCIBLE), move thread count (NONE).

[0]hard/[179/3540/97/2750/75]soft

ArmorSet (Weight: 36.3, Poise: 65, Physical: 35.4, Phys/Weight: 0.97, Magic: 27.5, Magic/Weight: 0.75 ) [
    head: Radahn Soldier Helm (Weight: 4.0, Poise: 5),
    chest: Veteran's Armor (Weight: 18.9, Poise: 37),
    arms: Godskin Noble Bracelets (Weight: 1.7, Poise: 1),
    legs: Veteran's Greaves (Weight: 11.7, Poise: 22)
]

这个问题有点奇怪,因为你只需要一个规划实体实例。只需要一个 ArmorSet 对象 - 解算器将分配不同的装甲件,因为它越来越接近最佳组合。

因此,您的简易分数计算器永远不需要进行任何循环。它只是利用单个 ArmorSet 的重量和平衡并从中创建一个分数。

但是,尽管我认为这个用例作为约束求解器的学习路径可能很有用,但某种蛮力算法也可以工作 - 您的数据集不是太大。更重要的是,借助穷举算法(例如蛮力),您最终一定会找到最优解。

(也就是说,如果您想通过将这些盔甲套装与特定角色特征相匹配来增强问题,那么它可能会足够复杂,以至于蛮力变得不够用。)

就个人而言,我尝试了《Elden Ring》,但发现它对我来说太硬核了。 :-) 我更喜欢能给你更多指导的游戏。