Optaplanner:如何从JSON读取游戏装甲数据并根据重量&#x2B找到最佳装甲集。统计数据?

发布于 2025-02-03 12:17:55 字数 5714 浏览 4 评论 0 原文

elden ring 是一个热门游戏,背后有一些有趣的理论。

有数百件装甲,武器和咒语。根据玩家&项目统计是一个有趣的实用问题。

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


目标:

  • 给出了游戏中所有装甲的清单,以
  • 找到的装甲集(头部,胸部,腿部,手臂):
    • 最高的镇定
    • 对于最低的重量

这是回购:

到目前为止

更新

设法在下面的建议之后解决了该

问题

@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)
]

Elden Ring is a hit game that has some interesting theorycrafting behind it.

There are hundreds of armor pieces, weapons, and spells. Finding optimal combinations of them based on player & item stats is an interesting practical problem.

I've always wanted to learn how to use Constraint Solvers and it seems a good usecase exists!


Goal:

  • Given a list of all armor in the game in JSON format
  • Find the set of armor (head, chest, legs, arms) that has:
    • The highest POISE and PHYSICAL_DEFENSE
    • For the lowest WEIGHT

Here is the repo:

My attempt so far:

Update

I managed to solve it after advice below

The trick was to change to use 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));
    }
}

Then the scorer:

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
                }
        );
    }
}

Results


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)
]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

静赏你的温柔 2025-02-10 12:17:55

这有点奇怪,因为您只需要一个计划实体实例。只有一个 Armorset 对象 - 求解器将在越来越接近最佳组合时分配不同的装甲件。

因此,您的简单得分计算器永远不需要进行任何循环。它只需采用单个 Armorset 的重量并保持平衡,并从中创建得分。

但是,即使我认为这种用例可能是通往约束求解器的学习路径,但某种蛮力算法也可以工作 - 您的数据集并不大。更重要的是,使用详尽的算法(例如蛮力),您最终可以确保达到最佳解决方案。

(也就是说,如果您想将这些装甲套件与特定特征匹配的问题增加问题,那么它可能足够复杂,以使蛮力变得不足

。为我。 :-)我更喜欢引导您更多的游戏。

This is a bit of a strange problem, because you only need one planning entity instance. There only ever needs to be one ArmorSet object - and the solver will be assigning different armor pieces as it comes closer and closer to an optimal combination.

Therefore your easy score calculator doesn't ever need to do any looping. It simply takes the single ArmorSet's weight and poise and creates a score out of it.

However, even though I think that this use case may be useful as a learning path towards constraint solvers, some sort of a brute force algorithm could work as well - your data set isn't too large. More importantly, with an exhaustive algorithm such as brute force, you're eventually guaranteed to reach the optimal solution.

(That said, if you want to enhance the problem with matching these armor sets to particular character traits, then it perhaps may be complex enough for brute force to become inadequate.)

On a personal note, I attempted Elden Ring and found it too hardcore for me. :-) I prefer games that guide you a bit more.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文