triplea-game / triplea

TripleA is a turn based strategy game and board game engine, similar to Axis & Allies or Risk.
https://triplea-game.org/
GNU General Public License v3.0
1.3k stars 387 forks source link

2.6.14752: ProSortMoveOptionsUtils#sortUnitNeededOptionsThenAttack:118 - java.lang.IllegalArgumentException #12693

Closed tripleabuilderbot closed 2 weeks ago

tripleabuilderbot commented 2 weeks ago

Map

a_song_of_ice_and_fire / A Song of Ice and Fire

Log Message

Comparison method violates its general contract!

TripleA Version

2.6.14752

Java Version

11.0.19

Operating System

Windows 11

Heap Size

8138M

Stack Trace

Exception: java.lang.IllegalArgumentException Comparison method violates its general contract!
java.lang.Exception
    at java.base/java.util.TimSort.mergeHi(TimSort.java:903)
    at java.base/java.util.TimSort.mergeAt(TimSort.java:520)
    at java.base/java.util.TimSort.mergeCollapse(TimSort.java:448)
    at java.base/java.util.TimSort.sort(TimSort.java:245)
    at java.base/java.util.Arrays.sort(Arrays.java:1515)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1750)
    at games.strategy.triplea.ai.pro.util.ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(ProSortMoveOptionsUtils.java:118)
    at games.strategy.triplea.ai.pro.ProCombatMoveAi.tryToAttackTerritories(ProCombatMoveAi.java:1344)
    at games.strategy.triplea.ai.pro.ProCombatMoveAi.determineTerritoriesToAttack(ProCombatMoveAi.java:336)
    at games.strategy.triplea.ai.pro.ProCombatMoveAi.doCombatMove(ProCombatMoveAi.java:97)
    at games.strategy.triplea.ai.pro.AbstractProAi.move(AbstractProAi.java:134)
    at games.strategy.triplea.ai.AbstractAi.start(AbstractAi.java:516)
    at games.strategy.engine.framework.ServerGame.waitForPlayerToFinishStep(ServerGame.java:603)
    at games.strategy.engine.framework.ServerGame.runStep(ServerGame.java:452)
    at games.strategy.engine.framework.ServerGame.runNextStep(ServerGame.java:347)
    at games.strategy.engine.framework.ServerGame.startGame(ServerGame.java:314)
    at games.strategy.engine.framework.startup.launcher.LocalLauncher.launchInternal(LocalLauncher.java:92)
    at games.strategy.engine.framework.startup.launcher.LocalLauncher.lambda$launch$0(LocalLauncher.java:60)
    at java.base/java.lang.Thread.run(Thread.java:829)
asvitkine commented 2 weeks ago

So this indicates the comparator is not transitive.

I think the error might be this line:

          if (territories1.isEmpty()) {
            return 0;
          }

Since we're returning 0 in this case, we should do the same when territories2 is empty.

EDIT: Actually, that's not it, because at that point, both lists would have the same size...

asvitkine commented 2 weeks ago

Other thoughts:

If calculateAttackEfficiency() can return NaN, then return (attackEfficiency1 < attackEfficiency2) ? 1 : -1; may not be transitive.

Other things I thought of is if some of the calculations called wouldn't always be consistent, but it's not clear to me that's the case...

asvitkine commented 2 weeks ago

Would be very helpful to have a repro.