From e2c37d11e711a97e4d32b9e20b7ac4288595f574 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 14 Nov 2024 19:11:25 +0800 Subject: [PATCH] AI Attack Timeout --- .../java/forge/ai/AiAttackController.java | 288 +++++++------- .../src/main/java/forge/ai/AiController.java | 182 +++++---- .../main/java/forge/ai/AiCostDecision.java | 5 +- .../src/main/java/forge/ai/ComputerUtil.java | 24 +- .../main/java/forge/ai/ComputerUtilMana.java | 28 +- .../java/forge/ai/PlayerControllerAi.java | 12 +- .../src/main/java/forge/ai/SpecialCardAi.java | 3 +- .../main/java/forge/ai/ability/CharmAi.java | 6 +- .../java/forge/ai/ability/ChooseCardAi.java | 4 +- .../forge/ai/ability/ChooseCompanionAi.java | 4 +- .../main/java/forge/ai/ability/DiscardAi.java | 4 +- .../ai/simulation/SimulationController.java | 4 +- .../src/main/java/forge/StaticData.java | 3 +- .../src/main/java/forge/card/CardDb.java | 3 +- .../src/main/java/forge/card/CardEdition.java | 4 +- .../src/main/java/forge/deck/CardPool.java | 3 +- .../item/generation/BoosterGenerator.java | 3 +- .../main/java/forge/util/CollectionUtil.java | 65 ++++ .../java/forge/util/collect/FCollection.java | 134 ++++--- forge-game/src/main/java/forge/game/Game.java | 5 +- .../src/main/java/forge/game/GameAction.java | 6 +- .../forge/game/ability/effects/DigEffect.java | 5 +- .../ability/effects/DigMultipleEffect.java | 4 +- .../game/ability/effects/DigUntilEffect.java | 4 +- .../game/ability/effects/DraftEffect.java | 3 +- .../ability/effects/ReorderZoneEffect.java | 4 +- .../main/java/forge/game/card/CardLists.java | 3 +- .../java/forge/game/card/CardProperty.java | 7 +- .../main/java/forge/game/combat/Combat.java | 337 ++++++++++------ .../java/forge/game/cost/CostPayment.java | 4 +- .../main/java/forge/game/mana/ManaPool.java | 69 ++-- .../forge/game/mana/ManaRefundService.java | 4 +- .../main/java/forge/game/player/Player.java | 4 +- .../forge/game/spellability/SpellAbility.java | 359 +++++++++++++----- .../src/main/java/forge/game/zone/Zone.java | 4 +- .../java/forge/itemmanager/CardManager.java | 3 +- .../controllers/CProbabilities.java | 4 +- .../screens/home/quest/DialogChooseSets.java | 3 +- .../home/sanctioned/CSubmenuDraft.java | 4 +- .../main/java/forge/view/SimulateMatch.java | 4 +- .../src/main/java/forge/deck/DeckgenUtil.java | 6 +- .../java/forge/deck/NetDeckArchiveBlock.java | 3 +- .../java/forge/deck/NetDeckArchiveLegacy.java | 3 +- .../java/forge/deck/NetDeckArchiveModern.java | 3 +- .../java/forge/deck/NetDeckArchivePauper.java | 3 +- .../forge/deck/NetDeckArchivePioneer.java | 3 +- .../forge/deck/NetDeckArchiveStandard.java | 3 +- .../forge/deck/NetDeckArchiveVintage.java | 3 +- .../forge/gamemodes/limited/BoosterDraft.java | 5 +- .../limited/CardThemedDeckBuilder.java | 6 +- .../gamemodes/limited/LimitedPlayer.java | 3 +- .../gamemodes/limited/LimitedPlayerAI.java | 8 +- .../limited/SealedCardPoolGenerator.java | 3 +- .../forge/gamemodes/limited/WinstonDraft.java | 4 +- .../planarconquest/ConquestData.java | 3 +- .../forge/gamemodes/quest/BoosterUtils.java | 6 +- .../quest/MainWorldEventDuelManager.java | 4 +- .../gamemodes/quest/QuestController.java | 4 +- .../quest/QuestEventCommanderDuelManager.java | 4 +- .../gamemodes/quest/QuestEventDraft.java | 7 +- .../quest/QuestEventDuelManager.java | 4 +- .../quest/QuestEventLDADuelManager.java | 4 +- .../forge/gamemodes/quest/QuestUtilCards.java | 4 +- .../gamemodes/quest/QuestUtilUnlockSets.java | 9 +- .../setrotation/QueueRandomRotation.java | 4 +- .../tournament/system/AbstractTournament.java | 4 +- .../tournament/system/TournamentSwiss.java | 4 +- .../main/java/forge/itemmanager/GroupDef.java | 3 +- .../java/forge/player/HumanCostDecision.java | 2 +- .../forge/player/PlayerControllerHuman.java | 4 +- 70 files changed, 1128 insertions(+), 617 deletions(-) create mode 100644 forge-core/src/main/java/forge/util/CollectionUtil.java diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 88c05099c95..d4975a41bd9 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -50,6 +50,10 @@ import forge.util.collect.FCollectionView; import org.apache.commons.lang3.tuple.Pair; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; /** @@ -169,7 +173,7 @@ public class AiAttackController { } public void removeBlocker(Card blocker) { - this.oppList.remove(blocker); + this.oppList.remove(blocker); this.blockers.remove(blocker); } @@ -286,7 +290,7 @@ public class AiAttackController { } if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) { - return true; + return true; } // Damage opponent if unblocked @@ -388,7 +392,7 @@ public class AiAttackController { // reduce the search space final List opponentsAttackers = CardLists.filter(ai.getOpponents().getCreaturesInPlay(), c -> !c.hasSVar("EndOfTurnLeavePlay") && (c.toughnessAssignsDamage() || c.getNetCombatDamage() > 0 // performance shortcuts - || c.getNetCombatDamage() + ComputerUtilCombat.predictPowerBonusOfAttacker(c, null, null, true) > 0) + || c.getNetCombatDamage() + ComputerUtilCombat.predictPowerBonusOfAttacker(c, null, null, true) > 0) && ComputerUtilCombat.canAttackNextTurn(c)); // don't hold back creatures that can't block any of the human creatures @@ -884,7 +888,7 @@ public class AiAttackController { // TODO: detect Season of the Witch by presence of a card with a specific trigger final boolean seasonOfTheWitch = ai.getGame().isCardInPlay("Season of the Witch"); - List attackersLeft = new ArrayList<>(this.attackers); + final Queue attackersLeft = new ConcurrentLinkedQueue<>(this.attackers); // TODO probably use AttackConstraints instead of only GlobalAttackRestrictions? GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders()); @@ -900,63 +904,71 @@ public class AiAttackController { } // Attackers that don't really have a choice - int numForcedAttackers = 0; + final AtomicInteger numForcedAttackers = new AtomicInteger(0); // nextTurn is now only used by effect from Oracle en-Vec, which can skip check must attack, // because creatures not chosen can't attack. + List> futures = new ArrayList<>(); if (!nextTurn) { for (final Card attacker : this.attackers) { - GameEntity mustAttackDef = null; - if (attacker.getSVar("MustAttack").equals("True")) { - mustAttackDef = defender; - } else if (attacker.hasSVar("EndOfTurnLeavePlay") - && isEffectiveAttacker(ai, attacker, combat, defender)) { - mustAttackDef = defender; - } else if (seasonOfTheWitch) { - //TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack - mustAttackDef = defender; - } else { - if (combat.getAttackConstraints().getRequirements().get(attacker) == null) continue; - // check defenders in order of maximum requirements - List> reqs = combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements(); - final GameEntity def = defender; - reqs.sort((r1, r2) -> { - if (r1.getValue() == r2.getValue()) { - // try to attack the designated defender - if (r1.getKey().equals(def) && !r2.getKey().equals(def)) { - return -1; + final GameEntity finalDefender = defender; + futures.add(CompletableFuture.supplyAsync(()-> { + GameEntity mustAttackDef = null; + if (attacker.getSVar("MustAttack").equals("True")) { + mustAttackDef = finalDefender; + } else if (attacker.hasSVar("EndOfTurnLeavePlay") + && isEffectiveAttacker(ai, attacker, combat, finalDefender)) { + mustAttackDef = finalDefender; + } else if (seasonOfTheWitch) { + //TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack + mustAttackDef = finalDefender; + } else { + if (combat.getAttackConstraints().getRequirements().get(attacker) == null) return 0; + // check defenders in order of maximum requirements + List> reqs = combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements(); + final GameEntity def = finalDefender; + reqs.sort((r1, r2) -> { + if (r1.getValue() == r2.getValue()) { + // try to attack the designated defender + if (r1.getKey().equals(def) && !r2.getKey().equals(def)) { + return -1; + } + if (r2.getKey().equals(def) && !r1.getKey().equals(def)) { + return 1; + } + // otherwise PW + if (r1.getKey() instanceof Card && r2.getKey() instanceof Player) { + return -1; + } + if (r2.getKey() instanceof Card && r1.getKey() instanceof Player) { + return 1; + } + // or weakest player + if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) { + return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife(); + } } - if (r2.getKey().equals(def) && !r1.getKey().equals(def)) { - return 1; + return r2.getValue() - r1.getValue(); + }); + for (Pair e : reqs) { + if (e.getRight() == 0) continue; + GameEntity mustAttackDefMaybe = e.getLeft(); + if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) { + mustAttackDef = mustAttackDefMaybe; + break; } - // otherwise PW - if (r1.getKey() instanceof Card && r2.getKey() instanceof Player) { - return -1; - } - if (r2.getKey() instanceof Card && r1.getKey() instanceof Player) { - return 1; - } - // or weakest player - if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) { - return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife(); - } - } - return r2.getValue() - r1.getValue(); - }); - for (Pair e : reqs) { - if (e.getRight() == 0) continue; - GameEntity mustAttackDefMaybe = e.getLeft(); - if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) { - mustAttackDef = mustAttackDefMaybe; - break; } } - } - if (mustAttackDef != null) { - combat.addAttacker(attacker, mustAttackDef); - attackersLeft.remove(attacker); - numForcedAttackers++; - } + if (mustAttackDef != null) { + combat.addAttacker(attacker, mustAttackDef); + attackersLeft.remove(attacker); + numForcedAttackers.incrementAndGet(); + } + return 0; + })); } + CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[0]); + CompletableFuture.allOf(futuresArray).completeOnTimeout(null, 5, TimeUnit.SECONDS).join(); + futures.clear(); if (attackersLeft.isEmpty()) { return aiAggression; } @@ -964,18 +976,19 @@ public class AiAttackController { // Lightmine Field: make sure the AI doesn't wipe out its own creatures if (lightmineField) { - doLightmineFieldAttackLogic(attackersLeft, numForcedAttackers, playAggro); + doLightmineFieldAttackLogic(attackersLeft, numForcedAttackers.get(), playAggro); } // Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily - if (!doRevengeOfRavensAttackLogic(defender, attackersLeft, numForcedAttackers, attackMax)) { + if (!doRevengeOfRavensAttackLogic(defender, attackersLeft, numForcedAttackers.get(), attackMax)) { return aiAggression; } if (bAssault && defender == defendingOpponent) { // in case we are forced to attack someone else if (LOG_AI_ATTACKS) System.out.println("Assault"); - CardLists.sortByPowerDesc(attackersLeft); - for (Card attacker : attackersLeft) { + List left = new ArrayList<>(attackersLeft); + CardLists.sortByPowerDesc(left); + for (Card attacker : left) { // reached max, breakup if (attackMax != -1 && combat.getAttackers().size() >= attackMax) return aiAggression; @@ -1227,7 +1240,7 @@ public class AiAttackController { if (ratioDiff > 0 && doAttritionalAttack) { aiAggression = 5; // attack at all costs } else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0)) - || (playAggro && MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1)) { + || (playAggro && MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1)) { aiAggression = 4; // attack expecting to trade or damage player. } else if (MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1 && defendingOpponent != null @@ -1237,7 +1250,7 @@ public class AiAttackController { && (ComputerUtilMana.getAvailableManaEstimate(ai) > 0) || tradeIfTappedOut && (ComputerUtilMana.getAvailableManaEstimate(defendingOpponent) == 0) || MyRandom.percentTrue(extraChanceIfOppHasMana) && (!tradeIfLowerLifePressure || (ai.getLifeLostLastTurn() + ai.getLifeLostThisTurn() < - defendingOpponent.getLifeLostThisTurn() + defendingOpponent.getLifeLostThisTurn()))) { + defendingOpponent.getLifeLostThisTurn() + defendingOpponent.getLifeLostThisTurn()))) { aiAggression = 4; // random (chance-based) attack expecting to trade or damage player. } else if (ratioDiff >= 0 && this.attackers.size() > 1) { aiAggression = 3; // attack expecting to make good trades or damage player. @@ -1265,19 +1278,20 @@ public class AiAttackController { if ( LOG_AI_ATTACKS ) System.out.println("Normal attack"); - attackersLeft = notNeededAsBlockers(combat.getAttackers(), attackersLeft); - attackersLeft = sortAttackers(attackersLeft); + List left = new ArrayList<>(attackersLeft); + left = notNeededAsBlockers(combat.getAttackers(), left); + left = sortAttackers(left); if ( LOG_AI_ATTACKS ) - System.out.println("attackersLeft = " + attackersLeft); + System.out.println("attackersLeft = " + left); FCollection possibleDefenders = new FCollection<>(defendingOpponent); possibleDefenders.addAll(defendingOpponent.getPlaneswalkersInPlay()); - while (!attackersLeft.isEmpty()) { + while (!left.isEmpty()) { CardCollection attackersAssigned = new CardCollection(); - for (int i = 0; i < attackersLeft.size(); i++) { - final Card attacker = attackersLeft.get(i); + for (int i = 0; i < left.size(); i++) { + final Card attacker = left.get(i); if (aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() && ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, defendingOpponent) >= ComputerUtilCombat.getDamageToKill(attacker, false)) { @@ -1291,7 +1305,7 @@ public class AiAttackController { attackersAssigned.add(attacker); // check if attackers are enough to finish the attacked planeswalker - if (i < attackersLeft.size() - 1 && defender instanceof Card) { + if (i < left.size() - 1 && defender instanceof Card) { final int blockNum = this.blockers.size(); int attackNum = 0; int damage = 0; @@ -1312,9 +1326,9 @@ public class AiAttackController { } } - attackersLeft.removeAll(attackersAssigned); + left.removeAll(attackersAssigned); possibleDefenders.remove(defender); - if (attackersLeft.isEmpty() || possibleDefenders.isEmpty()) { + if (left.isEmpty() || possibleDefenders.isEmpty()) { break; } CardCollection pwDefending = new CardCollection(Iterables.filter(possibleDefenders, Card.class)); @@ -1496,51 +1510,51 @@ public class AiAttackController { // decide if the creature should attack based on the prevailing strategy choice in aiAggression switch (aiAggression) { - case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked - if ((saf.canKillAll && saf.isWorthLessThanAllKillers) || !saf.canBeBlocked()) { + case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked + if ((saf.canKillAll && saf.isWorthLessThanAllKillers) || !saf.canBeBlocked()) { + if (LOG_AI_ATTACKS) + System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); + return true; + } + break; + case 5: // all out attacking if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); + System.out.println(attacker.getName() + " = all out attacking"); return true; - } - break; - case 5: // all out attacking - if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = all out attacking"); - return true; - case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack - if (saf.canKillAll || (saf.dangerousBlockersPresent && saf.canKillAllDangerous && !saf.canBeKilledByOne) || !saf.canBeBlocked() - || saf.defPower == 0) { - if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); - return true; - } - break; - case 3: // expecting to at least kill a creature of equal value or not be blocked - if ((saf.canKillAll && saf.isWorthLessThanAllKillers) - || (((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne) - || !saf.canBeBlocked()) { - if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); - return true; - } - break; - case 2: // attack expecting to attract a group block or destroying a single blocker and surviving - if (!saf.canBeBlocked() || ((saf.canKillAll || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne && - ((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || !saf.canBeKilled))) { - if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); - return true; - } - break; - case 1: // unblockable creatures only - if (!saf.canBeBlocked() || (saf.numberOfPossibleBlockers == 1 && saf.canKillAll && !saf.canBeKilledByOne)) { - if (LOG_AI_ATTACKS) - System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); - return true; - } - break; - default: - break; + case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack + if (saf.canKillAll || (saf.dangerousBlockersPresent && saf.canKillAllDangerous && !saf.canBeKilledByOne) || !saf.canBeBlocked() + || saf.defPower == 0) { + if (LOG_AI_ATTACKS) + System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); + return true; + } + break; + case 3: // expecting to at least kill a creature of equal value or not be blocked + if ((saf.canKillAll && saf.isWorthLessThanAllKillers) + || (((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne) + || !saf.canBeBlocked()) { + if (LOG_AI_ATTACKS) + System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); + return true; + } + break; + case 2: // attack expecting to attract a group block or destroying a single blocker and surviving + if (!saf.canBeBlocked() || ((saf.canKillAll || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne && + ((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || !saf.canBeKilled))) { + if (LOG_AI_ATTACKS) + System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); + return true; + } + break; + case 1: // unblockable creatures only + if (!saf.canBeBlocked() || (saf.numberOfPossibleBlockers == 1 && saf.canKillAll && !saf.canBeKilledByOne)) { + if (LOG_AI_ATTACKS) + System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); + return true; + } + break; + default: + break; } return false; // don't attack } @@ -1657,31 +1671,31 @@ public class AiAttackController { } if (color != null) { switch (color) { - case "black": - if (!c.isBlack()) { - color = null; - } - break; - case "blue": - if (!c.isBlue()) { - color = null; - } - break; - case "green": - if (!c.isGreen()) { - color = null; - } - break; - case "red": - if (!c.isRed()) { - color = null; - } - break; - case "white": - if (!c.isWhite()) { - color = null; - } - break; + case "black": + if (!c.isBlack()) { + color = null; + } + break; + case "blue": + if (!c.isBlue()) { + color = null; + } + break; + case "green": + if (!c.isGreen()) { + color = null; + } + break; + case "red": + if (!c.isRed()) { + color = null; + } + break; + case "white": + if (!c.isWhite()) { + color = null; + } + break; } } if (color == null && artifact == null) { //nothing can make the attacker unblockable @@ -1697,7 +1711,7 @@ public class AiAttackController { return null; //should never get here } - private void doLightmineFieldAttackLogic(final List attackersLeft, int numForcedAttackers, boolean playAggro) { + private void doLightmineFieldAttackLogic(final Queue attackersLeft, int numForcedAttackers, boolean playAggro) { CardCollection attSorted = new CardCollection(attackersLeft); CardCollection attUnsafe = new CardCollection(); CardLists.sortByToughnessDesc(attSorted); @@ -1727,7 +1741,7 @@ public class AiAttackController { attackersLeft.removeAll(attUnsafe); } - private boolean doRevengeOfRavensAttackLogic(final GameEntity defender, final List attackersLeft, int numForcedAttackers, int maxAttack) { + private boolean doRevengeOfRavensAttackLogic(final GameEntity defender, final Queue attackersLeft, int numForcedAttackers, int maxAttack) { // TODO: detect Revenge of Ravens by the trigger instead of by name boolean revengeOfRavens = false; if (defender instanceof Player) { diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 14915df4a79..cb8e12abeaa 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -61,6 +61,7 @@ import forge.game.trigger.WrappedAbility; import forge.game.zone.ZoneType; import forge.item.PaperCard; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.ComparatorUtil; import forge.util.Expressions; import forge.util.MyRandom; @@ -68,6 +69,9 @@ import io.sentry.Breadcrumb; import io.sentry.Sentry; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; /** *

@@ -88,6 +92,7 @@ public class AiController { private SpellAbilityPicker simPicker; private int lastAttackAggression; private boolean useLivingEnd; + private List skipped; public AiController(final Player computerPlayer, final Game game0) { player = computerPlayer; @@ -397,10 +402,27 @@ public class AiController { private static List getPlayableCounters(final CardCollection l) { final List spellAbility = Lists.newArrayList(); for (final Card c : l) { - for (final SpellAbility sa : c.getNonManaAbilities()) { - // Check if this AF is a Counterspell - if (sa.getApi() == ApiType.Counter) { - spellAbility.add(sa); + if (c.isForetold() && c.getAlternateState() != null) { + try { + for (final SpellAbility sa : c.getAlternateState().getNonManaAbilities()) { + // Check if this AF is a Counterspell + if (sa.getApi() == ApiType.Counter) { + spellAbility.add(sa); + } else { + if (sa.getApi() != null && sa.getApi().toString().contains("Foretell") && c.getAlternateState().getName().equalsIgnoreCase("Saw It Coming")) + spellAbility.add(sa); + } + } + } catch (Exception e) { + // facedown and alternatestate counters should be accessible + e.printStackTrace(); + } + } else { + for (final SpellAbility sa : c.getNonManaAbilities()) { + // Check if this AF is a Counterspell + if (sa.getApi() == ApiType.Counter) { + spellAbility.add(sa); + } } } } @@ -1323,9 +1345,7 @@ public class AiController { for (final Card element : combat.getAttackers()) { // tapping of attackers happens after Propaganda is paid for - final StringBuilder sb = new StringBuilder(); - sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker."); - Log.debug(sb.toString()); + Log.debug("Computer just assigned " + element.getName() + " as an attacker."); } } @@ -1369,7 +1389,7 @@ public class AiController { if (landsWannaPlay != null && !landsWannaPlay.isEmpty()) { // TODO search for other land it might want to play? Card land = chooseBestLandToPlay(landsWannaPlay); - if ((!player.canLoseLife() || player.cantLoseForZeroOrLessLife() || ComputerUtil.getDamageFromETB(player, land) < player.getLife()) + if (land != null && (!player.canLoseLife() || player.cantLoseForZeroOrLessLife() || ComputerUtil.getDamageFromETB(player, land) < player.getLife()) && (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land))) { final List abilities = land.getAllPossibleAbilities(player, true); // skip non Land Abilities @@ -1502,6 +1522,13 @@ public class AiController { } private SpellAbility getSpellAbilityToPlay() { + if (skipped != null) { + //FIXME: this is for failed SA to skip temporarily, don't know why AI computation for mana fails, maybe due to auto mana compute? + for (SpellAbility sa : skipped) { + //System.out.println("Unskip: " + sa.toString() + " (" + sa.getHostCard().getName() + ")."); + sa.setSkip(false); + } + } CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); cards = ComputerUtilCard.dedupeCards(cards); List saList = Lists.newArrayList(); @@ -1547,8 +1574,12 @@ public class AiController { Iterables.removeIf(saList, spellAbility -> { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card // TODO allow when experimental profile? - return spellAbility.isLandAbility() || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard())); + return spellAbility.isLandAbility() || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard())) || !spellAbility.canCastTiming(player); }); + //removed skipped SA + skipped = Lists.newArrayList(Iterables.filter(saList, SpellAbility::isSkip)); + if (!skipped.isEmpty()) + saList.removeAll(skipped); //update LivingEndPlayer useLivingEnd = Iterables.any(player.getZone(ZoneType.Library), CardPredicates.nameEquals("Living End")); @@ -1565,79 +1596,94 @@ public class AiController { if (all == null || all.isEmpty()) return null; - try { - all.sort(ComputerUtilAbility.saEvaluator); // put best spells first - ComputerUtilAbility.sortCreatureSpells(all); - } catch (IllegalArgumentException ex) { - System.err.println(ex.getMessage()); - String assertex = ComparatorUtil.verifyTransitivity(ComputerUtilAbility.saEvaluator, all); - Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); - } //avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast... boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0); + List> futures = new ArrayList<>(); + Queue spells = new ConcurrentLinkedQueue<>(); for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { - // Don't add Counterspells to the "normal" playcard lookups - if (skipCounter && sa.getApi() == ApiType.Counter) { - continue; - } - - if (sa.getHostCard().hasKeyword(Keyword.STORM) - && sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell - && player.getZone(ZoneType.Hand).contains(Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm"))))) { - if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) { - // skip evaluating Storm unless we reached the minimum Storm count - continue; + futures.add(CompletableFuture.supplyAsync(()-> { + // Don't add Counterspells to the "normal" playcard lookups + if (skipCounter && sa.getApi() == ApiType.Counter) { + return 0; } - } - // living end AI decks - // TODO: generalize the implementation so that superfluous logic-specific checks for life, library size, etc. aren't needed - AiPlayDecision aiPlayDecision = AiPlayDecision.CantPlaySa; - if (useLivingEnd) { - if (sa.isCycling() && sa.canCastTiming(player) && player.getCardsIn(ZoneType.Library).size() >= 10) { - if (ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { - if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostPayLife.class) - && !player.cantLoseForZeroOrLessLife() - && player.getLife() <= sa.getPayCosts().getCostPartByType(CostPayLife.class).getAbilityAmount(sa) * 2) { - aiPlayDecision = AiPlayDecision.CantAfford; + + if (sa.getHostCard().hasKeyword(Keyword.STORM) + && sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell + && player.getZone(ZoneType.Hand).contains(Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm"))))) { + if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) { + // skip evaluating Storm unless we reached the minimum Storm count + return 0; + } + } + // living end AI decks + // TODO: generalize the implementation so that superfluous logic-specific checks for life, library size, etc. aren't needed + AiPlayDecision aiPlayDecision = AiPlayDecision.CantPlaySa; + if (useLivingEnd) { + if (sa.isCycling() && sa.canCastTiming(player) && player.getCardsIn(ZoneType.Library).size() >= 10) { + if (ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { + if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostPayLife.class) + && !player.cantLoseForZeroOrLessLife() + && player.getLife() <= sa.getPayCosts().getCostPartByType(CostPayLife.class).getAbilityAmount(sa) * 2) { + aiPlayDecision = AiPlayDecision.CantAfford; + } else { + aiPlayDecision = AiPlayDecision.WillPlay; + } + } + } else if (sa.getHostCard().hasKeyword(Keyword.CASCADE)) { + if (isLifeInDanger) { //needs more tune up for certain conditions + aiPlayDecision = player.getCreaturesInPlay().size() >= 4 ? AiPlayDecision.CantPlaySa : AiPlayDecision.WillPlay; + } else if (CardLists.filter(player.getZone(ZoneType.Graveyard).getCards(), CardPredicates.Presets.CREATURES).size() > 4) { + if (player.getCreaturesInPlay().size() >= 4) // it's good minimum + return 0; + else if (!sa.getHostCard().isPermanent() && sa.canCastTiming(player) && ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) + aiPlayDecision = AiPlayDecision.WillPlay;// needs tuneup for bad matchups like reanimator and other things to check on opponent graveyard } else { - aiPlayDecision = AiPlayDecision.WillPlay; + return 0; } } - } else if (sa.getHostCard().hasKeyword(Keyword.CASCADE)) { - if (isLifeInDanger) { //needs more tune up for certain conditions - aiPlayDecision = player.getCreaturesInPlay().size() >= 4 ? AiPlayDecision.CantPlaySa : AiPlayDecision.WillPlay; - } else if (CardLists.filter(player.getZone(ZoneType.Graveyard).getCards(), CardPredicates.Presets.CREATURES).size() > 4) { - if (player.getCreaturesInPlay().size() >= 4) // it's good minimum - continue; - else if (!sa.getHostCard().isPermanent() && sa.canCastTiming(player) && ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) - aiPlayDecision = AiPlayDecision.WillPlay;// needs tuneup for bad matchups like reanimator and other things to check on opponent graveyard - } else { - continue; - } } - } - sa.setActivatingPlayer(player, true); - SpellAbility root = sa.getRootAbility(); + sa.setActivatingPlayer(player, true); + SpellAbility root = sa.getRootAbility(); - if (root.isSpell() || root.isTrigger() || root.isReplacementAbility()) { - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); - } - //override decision for living end player - AiPlayDecision opinion = useLivingEnd && AiPlayDecision.WillPlay.equals(aiPlayDecision) ? aiPlayDecision : canPlayAndPayFor(sa); + if (root.isSpell() || root.isTrigger() || root.isReplacementAbility()) { + sa.setLastStateBattlefield(game.getLastStateBattlefield()); + sa.setLastStateGraveyard(game.getLastStateGraveyard()); + } + //override decision for living end player + AiPlayDecision opinion = useLivingEnd && AiPlayDecision.WillPlay.equals(aiPlayDecision) ? aiPlayDecision : canPlayAndPayFor(sa); - // reset LastStateBattlefield - sa.clearLastState(); - // PhaseHandler ph = game.getPhaseHandler(); - // System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase()); + // reset LastStateBattlefield + sa.clearLastState(); + // PhaseHandler ph = game.getPhaseHandler(); + // System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase()); - if (opinion != AiPlayDecision.WillPlay) - continue; + if (opinion != AiPlayDecision.WillPlay) + return 0; - return sa; + spells.add(sa); + return 0; + })); } + //timeout 5 seconds? even the AI don't acquire all, there should be SA to cast if valid + CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[0]); + CompletableFuture.allOf(futuresArray).completeOnTimeout(null, 5, TimeUnit.SECONDS).join(); + futures.clear(); + if (!spells.isEmpty()) { + List spellAbilities = new ArrayList<>(spells); + if (spellAbilities.size() == 1) + return spellAbilities.get(0); + try { + spellAbilities.sort(ComputerUtilAbility.saEvaluator); // put best spells first + ComputerUtilAbility.sortCreatureSpells(spellAbilities); + } catch (IllegalArgumentException ex) { + System.err.println(ex.getMessage()); + String assertex = ComparatorUtil.verifyTransitivity(ComputerUtilAbility.saEvaluator, spellAbilities); + Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); + } + return spellAbilities.get(0); + } return null; } @@ -2205,7 +2251,7 @@ public class AiController { result.addAll(activePlayerSAs); //need to reverse because of magic stack - Collections.reverse(result); + CollectionUtil.reverse(result); return result; } diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 662a42b7318..eb6f4a92f0d 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -15,6 +15,7 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.TextUtil; import forge.util.collect.FCollectionView; import org.apache.commons.lang3.ObjectUtils; @@ -155,7 +156,7 @@ public class AiCostDecision extends CostDecisionMakerBase { List res = cost.getPotentialPlayers(player, ability); // I should only choose one of these right? // TODO Choose the "worst" player. - Collections.shuffle(res); + CollectionUtil.shuffle(res); return PaymentDecision.players(res.subList(0, 1)); } @@ -179,7 +180,7 @@ public class AiCostDecision extends CostDecisionMakerBase { CardCollection chosen = new CardCollection(); CardLists.sortByCmcDesc(valid); - Collections.reverse(valid); + CollectionUtil.reverse(valid); int totalCMC = 0; for (Card card : valid) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 0d83d91aefe..d6aacc9be91 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -68,6 +68,7 @@ import forge.game.trigger.WrappedAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.TextUtil; import forge.util.collect.FCollection; @@ -87,6 +88,8 @@ public class ComputerUtil { } public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) { final Card source = sa.getHostCard(); + final Card host = sa.getHostCard(); + final Zone hz = host.isCopiedSpell() ? null : host.getZone(); source.setSplitStateToPlayAbility(sa); if (sa.isSpell() && !source.isCopiedSpell()) { @@ -144,8 +147,15 @@ public class ComputerUtil { return true; } } - //Should not arrive here - System.out.println("AI failed to play " + sa.getHostCard()); + // FIXME: Should not arrive here, though the card seems to be stucked on stack zone and invalidated and nowhere to be found, try to put back to original zone and maybe try to cast again if possible at later time? + System.out.println("[" + sa.getActivatingPlayer() + "] AI failed to play " + sa.getHostCard() + " [" + sa.getHostCard().getZone() + "]"); + sa.setSkip(true); + if (host != null && hz != null) { + Card c = game.getAction().moveTo(hz.getZoneType(), host, null, null); + for (SpellAbility csa : c.getSpellAbilities()) { + csa.setSkip(true); + } + } return false; } @@ -673,7 +683,7 @@ public class ComputerUtil { // FIXME: This is suboptimal, maybe implement a single comparator that'll take care of all of this? CardLists.sortByCmcDesc(typeList); - Collections.reverse(typeList); + CollectionUtil.reverse(typeList); // TODO AI needs some improvements here @@ -727,7 +737,7 @@ public class ComputerUtil { // FIXME: This is suboptimal, maybe implement a single comparator that'll take care of all of this? CardLists.sortByCmcDesc(typeList); - Collections.reverse(typeList); + CollectionUtil.reverse(typeList); typeList.sort((a, b) -> { if (!a.isInPlay() && b.isInPlay()) return -1; else if (!b.isInPlay() && a.isInPlay()) return 1; @@ -757,7 +767,7 @@ public class ComputerUtil { final CardCollection list = new CardCollection(); if (zone != ZoneType.Hand) { - Collections.reverse(typeList); + CollectionUtil.reverse(typeList); } for (int i = 0; i < amount; i++) { @@ -807,7 +817,7 @@ public class ComputerUtil { typeList.remove(activate); } ComputerUtilCard.sortByEvaluateCreature(typeList); - Collections.reverse(typeList); + CollectionUtil.reverse(typeList); final CardCollection tapList = new CardCollection(); @@ -1759,7 +1769,7 @@ public class ComputerUtil { // align threatened with resolve order // matters if stack contains multiple activations (e.g. Temur Sabertooth) - Collections.reverse(objects); + CollectionUtil.reverse(objects); return objects; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index b23d4d6b250..aba20b8ed8d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -38,6 +38,7 @@ import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -466,18 +467,18 @@ public class ComputerUtilMana { public static String predictManafromSpellAbility(SpellAbility saPayment, Player ai, ManaCostShard toPay) { Card hostCard = saPayment.getHostCard(); - String manaProduced = predictManaReplacement(saPayment, ai, toPay); - String originalProduced = manaProduced; + StringBuilder manaProduced = new StringBuilder(predictManaReplacement(saPayment, ai, toPay)); + String originalProduced = manaProduced.toString(); if (originalProduced.isEmpty()) { - return manaProduced; + return manaProduced.toString(); } // Run triggers like Nissa final Map runParams = AbilityKey.mapFromCard(hostCard); runParams.put(AbilityKey.Activator, ai); // assuming AI would only ever gives itself mana runParams.put(AbilityKey.AbilityMana, saPayment); - runParams.put(AbilityKey.Produced, manaProduced); + runParams.put(AbilityKey.Produced, manaProduced.toString()); for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) { SpellAbility trSA = tr.ensureAbility(); if (trSA == null) { @@ -489,7 +490,7 @@ public class ComputerUtilMana { if (produced.equals("Chosen")) { produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor()); } - manaProduced += " " + StringUtils.repeat(produced, " ", pAmount); + manaProduced.append(" ").append(StringUtils.repeat(produced, " ", pAmount)); } else if (ApiType.ManaReflected.equals(trSA.getApi())) { final String colorOrType = trSA.getParamOrDefault("ColorOrType", "Color"); // currently Color or Type, Type is colors + colorless @@ -498,11 +499,11 @@ public class ComputerUtilMana { if (reflectProperty.equals("Produced") && !originalProduced.isEmpty()) { // check if a colorless shard can be paid from the trigger if (toPay.equals(ManaCostShard.COLORLESS) && colorOrType.equals("Type") && originalProduced.contains("C")) { - manaProduced += " " + "C"; + manaProduced.append(" " + "C"); } else if (originalProduced.length() == 1) { // if length is only one, and it either is equal C == Type if (colorOrType.equals("Type") || !originalProduced.equals("C")) { - manaProduced += " " + originalProduced; + manaProduced.append(" ").append(originalProduced); } } else { // should it look for other shards too? @@ -510,7 +511,7 @@ public class ComputerUtilMana { for (String s : originalProduced.split(" ")) { if (colorOrType.equals("Type") || !s.equals("C") && toPay.canBePaidWithManaOfColor(MagicColor.fromName(s))) { found = true; - manaProduced += " " + s; + manaProduced.append(" ").append(s); break; } } @@ -518,7 +519,7 @@ public class ComputerUtilMana { if (!found) { for (String s : originalProduced.split(" ")) { if (colorOrType.equals("Type") || !s.equals("C")) { - manaProduced += " " + s; + manaProduced.append(" ").append(s); break; } } @@ -527,7 +528,7 @@ public class ComputerUtilMana { } } } - return manaProduced; + return manaProduced.toString(); } public static CardCollection getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) { @@ -826,7 +827,8 @@ public class ComputerUtilMana { if (test) { resetPayment(paymentList); } else { - System.out.println("ComputerUtilMana: payManaCost() cost was not paid for " + sa.toString() + " (" + sa.getHostCard().getName() + "). Didn't find what to pay for " + toPay); + System.out.println("ComputerUtilMana: payManaCost() cost was not paid for " + sa + " (" + sa.getHostCard().getName() + "). Didn't find what to pay for " + toPay); + sa.setSkip(true); } return false; } @@ -1518,11 +1520,11 @@ public class ComputerUtilMana { sortedManaSources.addAll(sortedManaSources.size(), anyColorManaSources); //use better creatures later ComputerUtilCard.sortByEvaluateCreature(otherManaSources); - Collections.reverse(otherManaSources); + CollectionUtil.reverse(otherManaSources); sortedManaSources.addAll(sortedManaSources.size(), otherManaSources); // This should be things like sacrifice other stuff. ComputerUtilCard.sortByEvaluateCreature(useLastManaSources); - Collections.reverse(useLastManaSources); + CollectionUtil.reverse(useLastManaSources); sortedManaSources.addAll(sortedManaSources.size(), useLastManaSources); if (DEBUG_MANA_PAYMENT) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index ec8db182168..e7f0b8c15d6 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -40,6 +40,7 @@ import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; import forge.item.PaperCard; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.ITriggerEvent; import forge.util.MyRandom; import forge.util.collect.FCollection; @@ -650,7 +651,7 @@ public class PlayerControllerAi extends PlayerController { if(source == null || !source.hasParam("LibraryPosition") || AbilityUtils.calculateAmount(source.getHostCard(), source.getParam("LibraryPosition"), source) >= 0) { //Cards going to the top of a deck are returned in reverse order. - Collections.reverse(reordered); + CollectionUtil.reverse(reordered); } assert(reordered.size() == cards.size()); @@ -1565,8 +1566,13 @@ public class PlayerControllerAi extends PlayerController { } } - int i = MyRandom.getRandom().nextInt(dungeonNames.size()); - return Card.fromPaperCard(dungeonCards.get(i), ai); + try { + // if this fail somehow add fallback to get any from dungeonCards + int i = MyRandom.getRandom().nextInt(dungeonNames.size()); + return Card.fromPaperCard(dungeonCards.get(i), ai); + } catch (Exception e) { + return Card.fromPaperCard(Aggregates.random(dungeonCards), ai); + } } @Override diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 7731c20bf9e..c737cd27657 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -47,6 +47,7 @@ import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.TextUtil; import forge.util.maps.LinkedHashMapToAmount; @@ -319,7 +320,7 @@ public class SpecialCardAi { best = ComputerUtilCard.getBestCreatureAI(cardlist); if (best == null) { // If nothing on the battlefield has a nonmana ability choose something - Collections.shuffle(cardlist); + CollectionUtil.shuffle(cardlist); best = cardlist.getFirst(); } diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index 7d02582f652..5ea0778a711 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -1,6 +1,5 @@ package forge.ai.ability; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -18,6 +17,7 @@ import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.collect.FCollection; @@ -52,7 +52,7 @@ public class CharmAi extends SpellAbilityAi { } else { // only randomize if not all possible together if (num < choices.size()) { - Collections.shuffle(choices); + CollectionUtil.shuffle(choices); } /* @@ -101,7 +101,7 @@ public class CharmAi extends SpellAbilityAi { // Pawprint final int pawprintLimit = sa.hasParam("Pawprint") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Pawprint"), sa) : 0; if (pawprintLimit > 0) { - Collections.reverse(choices); // try to pay for the more expensive subs first + CollectionUtil.reverse(choices); // try to pay for the more expensive subs first } int pawprintAmount = 0; diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index 721c183f635..21b38f0b844 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -16,8 +16,8 @@ import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.CollectionUtil; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -226,7 +226,7 @@ public class ChooseCardAi extends SpellAbilityAi { choice = ComputerUtilCard.getWorstAI(options); } else { CardLists.sortByCmcDesc(creats); - Collections.reverse(creats); + CollectionUtil.reverse(creats); choice = creats.get(0); } } else if ("NegativePowerFirst".equals(logic)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java index e3e50950b18..ee7d0f57992 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java @@ -1,6 +1,5 @@ package forge.ai.ability; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -10,6 +9,7 @@ import forge.ai.SpellAbilityAi; import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.util.CollectionUtil; public class ChooseCompanionAi extends SpellAbilityAi { @@ -23,7 +23,7 @@ public class ChooseCompanionAi extends SpellAbilityAi { return null; } - Collections.shuffle(cards); + CollectionUtil.shuffle(cards); return cards.get(0); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java index b8ac075c536..4b2100aa5c7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -1,6 +1,5 @@ package forge.ai.ability; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -23,6 +22,7 @@ import forge.game.player.PlayerCollection; import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.MyRandom; public class DiscardAi extends SpellAbilityAi { @@ -148,7 +148,7 @@ public class DiscardAi extends SpellAbilityAi { private boolean discardTargetAI(final Player ai, final SpellAbility sa) { final PlayerCollection opps = ai.getOpponents(); - Collections.shuffle(opps); + CollectionUtil.shuffle(opps); for (Player opp : opps) { if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) { continue; diff --git a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java index 2f214ecd09a..7669dcf7275 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java @@ -1,7 +1,6 @@ package forge.ai.simulation; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import forge.ai.simulation.GameStateEvaluator.Score; @@ -9,6 +8,7 @@ import forge.game.GameObject; import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.util.CollectionUtil; public class SimulationController { private static boolean DEBUG = false; @@ -106,7 +106,7 @@ public class SimulationController { sequence.add(current); current = current.prevDecision; } - Collections.reverse(sequence); + CollectionUtil.reverse(sequence); // Merge targets & choices into their parents. int writeIndex = 0; for (int i = 0; i < sequence.size(); i++) { diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index 068e1956d6f..9d34b9922b9 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -7,6 +7,7 @@ import forge.card.CardRules; import forge.card.PrintSheet; import forge.item.*; import forge.token.TokenDb; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.ImageUtil; import forge.util.TextUtil; @@ -195,7 +196,7 @@ public class StaticData { sortedEditions.add(set); } Collections.sort(sortedEditions); - Collections.reverse(sortedEditions); //put newer sets at the top + CollectionUtil.reverse(sortedEditions); //put newer sets at the top } return sortedEditions; } diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index ae928fee725..d21715165df 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -28,6 +28,7 @@ import forge.deck.generation.IDeckGenPool; import forge.item.IPaperCard; import forge.item.PaperCard; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.Lang; import forge.util.TextUtil; import forge.util.lang.LangEnglish; @@ -842,7 +843,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { if (acceptedEditions.size() > 1) { Collections.sort(acceptedEditions); // CardEdition correctly sort by (release) date if (artPref.latestFirst) - Collections.reverse(acceptedEditions); // newest editions first + CollectionUtil.reverse(acceptedEditions); // newest editions first } final Iterator editionIterator = acceptedEditions.iterator(); diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 96c6caf33dc..6e02ef50f45 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -490,7 +490,7 @@ public final class CardEdition implements Comparable { return null; } - Collections.shuffle(boosterTypes); + CollectionUtil.shuffle(boosterTypes); return boosterTypes.get(0); } @@ -802,7 +802,7 @@ public final class CardEdition implements Comparable { public Iterable getOrderedEditions() { List res = Lists.newArrayList(this); Collections.sort(res); - Collections.reverse(res); + CollectionUtil.reverse(res); return res; } diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index 0846ae7ed49..34265f136d5 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -27,6 +27,7 @@ import forge.card.CardEdition; import forge.item.IPaperCard; import forge.item.PaperCard; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.ItemPool; import forge.util.ItemPoolSorter; import forge.util.MyRandom; @@ -349,7 +350,7 @@ public class CardPool extends ItemPool { pivotCandidates.sort(CardEdition::compareTo); boolean searchPolicyAndPoolAreCompliant = isLatestCardArtPreference == this.isModern(); if (!searchPolicyAndPoolAreCompliant) - Collections.reverse(pivotCandidates); // reverse to have latest-first. + CollectionUtil.reverse(pivotCandidates); // reverse to have latest-first. return pivotCandidates.get(0); } diff --git a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java index 812dd7ff779..2c3f88c2094 100644 --- a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java +++ b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java @@ -28,6 +28,7 @@ import forge.card.CardEdition.FoilType; import forge.item.*; import forge.item.IPaperCard.Predicates.Presets; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -58,7 +59,7 @@ public class BoosterGenerator { } private static PaperCard generateFoilCard(List cardList) { - Collections.shuffle(cardList, MyRandom.getRandom()); + CollectionUtil.shuffle(cardList, MyRandom.getRandom()); PaperCard randomCard = cardList.get(0); return randomCard.getFoiled(); } diff --git a/forge-core/src/main/java/forge/util/CollectionUtil.java b/forge-core/src/main/java/forge/util/CollectionUtil.java new file mode 100644 index 00000000000..6ce27407888 --- /dev/null +++ b/forge-core/src/main/java/forge/util/CollectionUtil.java @@ -0,0 +1,65 @@ +package forge.util; + +import forge.util.collect.FCollection; + +import java.util.*; + +public class CollectionUtil { + public static void shuffle(List list) { + shuffle(list, MyRandom.getRandom()); + } + + public static void shuffle(List list, Random random) { + if (list instanceof FCollection) { + //FCollection -> copyonwritearraylist is not compatible, use different method + shuffleList(list, random); + } else { + //use Collections -> shuffle(LIST, RANDOM) since it's not FCollection + Collections.shuffle(list, random); + } + } + + public static void shuffleList(List a, Random r) { + int n = a.size(); + for (int i = 0; i < n; i++) { + int change = i + r.nextInt(n - i); + swap(a, i, change); + } + } + + private static void swap(List a, int i, int change) { + T helper = a.get(i); + a.set(i, a.get(change)); + a.set(change, helper); + } + + public static void reverse(List list) { + if (list == null || list.isEmpty()) + return; + if (list instanceof FCollection) { + //FCollection -> copyonwritearraylist is not compatible, use different method + reverseWithRecursion(list, 0, list.size() - 1); + } else { + Collections.reverse(list); + } + } + + public static void reverseWithRecursion(List list) { + if (list.size() > 1) { + T value = list.remove(0); + reverseWithRecursion(list); + list.add(value); + } + } + + public static void reverseWithRecursion(List list, int startIndex, int lastIndex) { + if (startIndex < lastIndex) { + T t = list.get(lastIndex); + list.set(lastIndex, list.get(startIndex)); + list.set(startIndex, t); + startIndex++; + lastIndex--; + reverseWithRecursion(list, startIndex, lastIndex); + } + } +} \ No newline at end of file diff --git a/forge-core/src/main/java/forge/util/collect/FCollection.java b/forge-core/src/main/java/forge/util/collect/FCollection.java index d54fe2adb24..16ba3a21f3a 100644 --- a/forge-core/src/main/java/forge/util/collect/FCollection.java +++ b/forge-core/src/main/java/forge/util/collect/FCollection.java @@ -6,7 +6,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; @@ -43,12 +42,38 @@ public class FCollection implements List, /*Set,*/ FCollectionView, /** * The {@link Set} representation of this collection. */ - private final Set set = Sets.newHashSet(); + private Set SET; + private Set set() { + Set result = SET; + if (result == null) { + synchronized (this) { + result = SET; + if (result == null) { + result = Sets.newConcurrentHashSet(); + SET = result; + } + } + } + return SET; + } /** * The {@link List} representation of this collection. */ - private final LinkedList list = Lists.newLinkedList(); + private List LIST; + private List list() { + List result = LIST; + if (result == null) { + synchronized (this) { + result = LIST; + if (result == null) { + result = Lists.newCopyOnWriteArrayList(); + LIST = result; + } + } + } + return LIST; + } /** * Create an empty {@link FCollection}. @@ -139,7 +164,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public int hashCode() { - return list.hashCode(); + return list().hashCode(); } /** @@ -149,7 +174,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public String toString() { - return list.toString(); + return list().toString(); } /** @@ -158,7 +183,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public final FCollection clone() { - return new FCollection<>(list); + return new FCollection<>(list()); } /** @@ -169,7 +194,10 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public T getFirst() { - return list.getFirst(); + if (list().isEmpty()) + return null; + return list().get(0); + //return list.getFirst(); } /** @@ -180,7 +208,10 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public T getLast() { - return list.getLast(); + if (list().isEmpty()) + return null; + return list().get(list().size() - 1); + //return list.getLast(); } /** @@ -188,7 +219,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public int size() { - return set.size(); + return set().size(); } /** @@ -196,11 +227,11 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean isEmpty() { - return set.isEmpty(); + return set().isEmpty(); } - + public Set asSet() { - return set; + return set(); } /** @@ -211,7 +242,9 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean contains(final Object o) { - return set.contains(o); + if (o == null) + return false; + return set().contains(o); } /** @@ -219,7 +252,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public Iterator iterator() { - return list.iterator(); + return list().iterator(); } /** @@ -227,7 +260,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public Object[] toArray() { - return list.toArray(); + return list().toArray(); } /** @@ -236,7 +269,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, @Override @SuppressWarnings("hiding") public T[] toArray(final T[] a) { - return list.toArray(a); + return list().toArray(a); } /** @@ -248,8 +281,10 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean add(final T e) { - if (set.add(e)) { - list.add(e); + if (e == null) + return false; + if (set().add(e)) { + list().add(e); return true; } return false; @@ -264,8 +299,10 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean remove(final Object o) { - if (set.remove(o)) { - list.remove(o); + if (o == null) + return false; + if (set().remove(o)) { + list().remove(o); return true; } return false; @@ -273,8 +310,8 @@ public class FCollection implements List, /*Set,*/ FCollectionView, @Override public boolean removeIf(Predicate filter) { - if (list.removeIf(filter)) { - set.removeIf(filter); + if (list().removeIf(filter)) { + set().removeIf(filter); return true; } return false; @@ -285,7 +322,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean containsAll(final Collection c) { - return set.containsAll(c); + return set().containsAll(c); } /** @@ -307,6 +344,8 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ public boolean addAll(final Iterable i) { boolean changed = false; + if (i == null) + return false; for (final T e : i) { changed |= add(e); } @@ -370,6 +409,8 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ public boolean removeAll(final Iterable c) { boolean changed = false; + if (c == null) + return false; for (final Object o : c) { changed |= remove(o); } @@ -381,8 +422,8 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public boolean retainAll(final Collection c) { - if (set.retainAll(c)) { - list.retainAll(c); + if (set().retainAll(c)) { + list().retainAll(c); return true; } return false; @@ -393,9 +434,9 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public void clear() { - if (set.isEmpty()) { return; } - set.clear(); - list.clear(); + if (set().isEmpty()) { return; } + set().clear(); + list().clear(); } /** @@ -403,7 +444,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public T get(final int index) { - return list.get(index); + return list().get(index); } /** @@ -413,7 +454,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public T set(final int index, final T element) { //assume this isn't called except when changing list order, so don't worry about updating set - return list.set(index, element); + return list().set(index, element); } /** @@ -434,12 +475,12 @@ public class FCollection implements List, /*Set,*/ FCollectionView, * @return whether this collection changed as a result of this method call. */ private boolean insert(int index, final T element) { - if (set.add(element)) { - list.add(index, element); + if (set().add(element)) { + list().add(index, element); return true; } //re-position in list if needed - final int oldIndex = list.indexOf(element); + final int oldIndex = list().indexOf(element); if (index == oldIndex) { return false; } @@ -447,8 +488,8 @@ public class FCollection implements List, /*Set,*/ FCollectionView, if (index > oldIndex) { index--; //account for being removed } - list.remove(oldIndex); - list.add(index, element); + list().remove(oldIndex); + list().add(index, element); return true; } @@ -457,9 +498,9 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public T remove(final int index) { - final T removedItem = list.remove(index); + final T removedItem = list().remove(index); if (removedItem != null) { - set.remove(removedItem); + set().remove(removedItem); } return removedItem; } @@ -469,7 +510,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public int indexOf(final Object o) { - return list.indexOf(o); + return list().indexOf(o); } /** @@ -477,7 +518,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public int lastIndexOf(final Object o) { - return list.lastIndexOf(o); + return list().lastIndexOf(o); } /** @@ -485,7 +526,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public ListIterator listIterator() { - return list.listIterator(); + return list().listIterator(); } /** @@ -493,7 +534,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public ListIterator listIterator(final int index) { - return list.listIterator(index); + return list().listIterator(index); } /** @@ -506,7 +547,7 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public List subList(final int fromIndex, final int toIndex) { - return ImmutableList.copyOf(list.subList(fromIndex, toIndex)); + return ImmutableList.copyOf(list().subList(fromIndex, toIndex)); } /** @@ -525,7 +566,11 @@ public class FCollection implements List, /*Set,*/ FCollectionView, * {@inheritDoc} */ public void sort(final Comparator comparator) { - list.sort(comparator); + try { + list().sort(comparator); + } catch (Exception e) { + System.err.println("FCollection failed to sort: \n" + comparator + "\n" + e.getMessage()); + } } /** @@ -533,8 +578,9 @@ public class FCollection implements List, /*Set,*/ FCollectionView, */ @Override public Iterable threadSafeIterable() { + return list(); //create a new linked list for iterating to make it thread safe and avoid concurrent modification exceptions - return Iterables.unmodifiableIterable(new LinkedList<>(list)); + //return Iterables.unmodifiableIterable(new LinkedList<>(list)); } @Override diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 41be0638d13..ad3115a6802 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -47,6 +47,7 @@ import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.trackable.Tracker; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.Visitor; import forge.util.collect.FCollection; @@ -398,7 +399,7 @@ public class Game { return ingamePlayers; } final PlayerCollection players = new PlayerCollection(ingamePlayers); - Collections.reverse(players); + CollectionUtil.reverse(players); return players; } @@ -418,7 +419,7 @@ public class Game { final PlayerCollection players = new PlayerCollection(ingamePlayers); players.remove(phaseHandler.getPlayerTurn()); if (!getTurnOrder().isDefaultDirection()) { - Collections.reverse(players); + CollectionUtil.reverse(players); } return players; } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index e1ccedf9df1..c0cb8950264 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -2041,7 +2041,7 @@ public class GameAction { //shuffle List shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable()); - Collections.shuffle(shuffledCards); + CollectionUtil.shuffle(shuffledCards); //check a second hand List hand2 = shuffledCards.subList(0,p1.getMaxHandSize()); @@ -2171,7 +2171,7 @@ public class GameAction { if (!powerPlayers.isEmpty()) { List players = Lists.newArrayList(powerPlayers); - Collections.shuffle(players, MyRandom.getRandom()); + CollectionUtil.shuffle(players, MyRandom.getRandom()); return players.get(0); } @@ -2409,7 +2409,7 @@ public class GameAction { int numLookedAt = 0; if (toTop != null) { numLookedAt += toTop.size(); - Collections.reverse(toTop); // reverse to get the correct order + CollectionUtil.reverse(toTop); // reverse to get the correct order for (Card c : toTop) { moveToLibrary(c, cause, null); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 996a3fc998a..fbbd31041b2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -17,6 +17,7 @@ import forge.game.player.PlayerView; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.CardTranslation; +import forge.util.CollectionUtil; import forge.util.Lang; import forge.util.Localizer; import forge.util.TextUtil; @@ -174,7 +175,7 @@ public class DigEffect extends SpellAbilityEffect { CardCollection all = new CardCollection(p.getCardsIn(srcZone)); if (sa.hasParam("FromBottom")) { - Collections.reverse(all); + CollectionUtil.reverse(all); } int numToDig = Math.min(digNum, all.size()); @@ -356,7 +357,7 @@ public class DigEffect extends SpellAbilityEffect { if (sa.hasParam("ForgetOtherRemembered")) { host.clearRemembered(); } - Collections.reverse(movedCards); + CollectionUtil.reverse(movedCards); if (destZone1.equals(ZoneType.Battlefield) || destZone1.equals(ZoneType.Library)) { if (sa.hasParam("GainControl")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java index 2f41746a575..98d3f95e6e7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import java.util.Collections; import java.util.Map; import com.google.common.collect.Maps; @@ -18,6 +17,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.Localizer; public class DigMultipleEffect extends SpellAbilityEffect { @@ -160,7 +160,7 @@ public class DigMultipleEffect extends SpellAbilityEffect { } if (libraryPosition2 != -1) { // Closest to top - Collections.reverse(afterOrder); + CollectionUtil.reverse(afterOrder); } for (final Card c : afterOrder) { final ZoneType origin = c.getZone().getZoneType(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java index 93460c57a8e..b4f3850b55b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -18,6 +17,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.Lang; import forge.util.Localizer; import forge.util.MyRandom; @@ -261,7 +261,7 @@ public class DigUntilEffect extends SpellAbilityEffect { } if (sa.hasParam("RevealRandomOrder")) { - Collections.shuffle(revealed, MyRandom.getRandom()); + CollectionUtil.shuffle(revealed, MyRandom.getRandom()); } if (sa.hasParam("NoMoveRevealed") || sequential) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java index 77175b86dee..b323b101088 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java @@ -11,6 +11,7 @@ import forge.game.card.CardZoneTable; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.Localizer; import java.util.*; @@ -51,7 +52,7 @@ import java.util.*; CardCollection drafted = new CardCollection(); for (int i = 0; i < numToDraft; i++) { - Collections.shuffle(spellbook); + CollectionUtil.shuffle(spellbook); List draftOptions = new ArrayList<>(); for (String name : spellbook.subList(0, 3)) { // Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java index 4ab1ab560b5..ce24ca6bf65 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import java.util.Collections; import java.util.List; import forge.game.ability.SpellAbilityEffect; @@ -9,6 +8,7 @@ import forge.game.card.CardCollectionView; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; import forge.util.Lang; import forge.util.MyRandom; @@ -34,7 +34,7 @@ public class ReorderZoneEffect extends SpellAbilityEffect { CardCollection list = new CardCollection(p.getCardsIn(zone)); if (shuffle) { - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); p.getZone(zone).setCards(list); } else { CardCollectionView orderedCards = p.getController().orderMoveToZoneList(list, zone, sa); diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java index 3688b8668a5..0414714b1c2 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-game/src/main/java/forge/game/card/CardLists.java @@ -32,6 +32,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbilityCrewValue; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.collect.FCollectionView; @@ -153,7 +154,7 @@ public class CardLists { } public static void shuffle(List list) { - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); } public static CardCollection filterControlledBy(Iterable cardList, Player player) { diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 2570a8a36ee..31701856d3d 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -28,6 +28,7 @@ import forge.game.spellability.SpellAbilityStackInstance; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.PaperCard; +import forge.util.CollectionUtil; import forge.util.Expressions; import forge.util.TextUtil; import forge.util.collect.FCollection; @@ -623,13 +624,13 @@ public class CardProperty { } } else if (property.startsWith("TopGraveyardCreature")) { CardCollection cards = CardLists.filter(card.getOwner().getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES); - Collections.reverse(cards); + CollectionUtil.reverse(cards); if (cards.isEmpty() || !card.equals(cards.get(0))) { return false; } } else if (property.startsWith("TopGraveyard")) { final CardCollection cards = new CardCollection(card.getOwner().getCardsIn(ZoneType.Graveyard)); - Collections.reverse(cards); + CollectionUtil.reverse(cards); if (property.substring(12).matches("[0-9][0-9]?")) { int n = Integer.parseInt(property.substring(12)); int num = Math.min(n, cards.size()); @@ -665,7 +666,7 @@ public class CardProperty { if (property.startsWith("BottomLibrary_")) { cards = CardLists.getValidCards(cards, property.substring(14), sourceController, source, spellAbility); } - Collections.reverse(cards); + CollectionUtil.reverse(cards); if (cards.isEmpty() || !card.equals(cards.get(0))) { return false; } diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 37785ca8595..b06b20e0b17 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -53,19 +53,122 @@ public class Combat { private boolean legacyOrderCombatants; private AttackConstraints attackConstraints; // Defenders, as they are attacked by hostile forces - private final FCollection attackableEntries = new FCollection<>(); - + private FCollection _attackableEntries; + private FCollection attackableEntries() { + FCollection result = _attackableEntries; + if (result == null) { + synchronized (this) { + result = _attackableEntries; + if (result == null) { + result = new FCollection<>(); + _attackableEntries = result; + } + } + } + return _attackableEntries; + } // Keyed by attackable defender (player or planeswalker or battle) - private final Multimap attackedByBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); - private final Multimap blockedBands = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + private Multimap _attackedByBands; + private Multimap attackedByBands() { + Multimap result = _attackedByBands; + if (result == null) { + synchronized (this) { + result = _attackedByBands; + if (result == null) { + result = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + _attackedByBands = result; + } + } + } + return _attackedByBands; + } + private Multimap _blockedBands; + private Multimap blockedBands() { + Multimap result = _blockedBands; + if (result == null) { + synchronized (this) { + result = _blockedBands; + if (result == null) { + result = Multimaps.synchronizedMultimap(ArrayListMultimap.create()); + _blockedBands = result; + } + } + } + return _blockedBands; + } - private final Map attackersOrderedForDamageAssignment = Maps.newHashMap(); - private final Map blockersOrderedForDamageAssignment = Maps.newHashMap(); - private CardCollection lkiCache = new CardCollection(); - private CardDamageMap damageMap = new CardDamageMap(); + private Map _attackersOrderedForDamageAssignment; + private Map attackersOrderedForDamageAssignment() { + Map result = _attackersOrderedForDamageAssignment; + if (result == null) { + synchronized (this) { + result = _attackersOrderedForDamageAssignment; + if (result == null) { + result = Maps.newHashMap(); + _attackersOrderedForDamageAssignment = result; + } + } + } + return _attackersOrderedForDamageAssignment; + } + private Map _blockersOrderedForDamageAssignment; + private Map blockersOrderedForDamageAssignment() { + Map result = _blockersOrderedForDamageAssignment; + if (result == null) { + synchronized (this) { + result = _blockersOrderedForDamageAssignment; + if (result == null) { + result = Maps.newHashMap(); + _blockersOrderedForDamageAssignment = result; + } + } + } + return _blockersOrderedForDamageAssignment; + } + private CardCollection _lkiCache; + private CardCollection lkiCache() { + CardCollection result = _lkiCache; + if (result == null) { + synchronized (this) { + result = _lkiCache; + if (result == null) { + result = new CardCollection(); + _lkiCache = result; + } + } + } + return _lkiCache; + } + private CardDamageMap _damageMap; + private CardDamageMap damageMap() { + CardDamageMap result = _damageMap; + if (result == null) { + synchronized (this) { + result = _damageMap; + if (result == null) { + result = new CardDamageMap(); + _damageMap = result; + } + } + } + return _damageMap; + } // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) - private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection(); + private CardCollection _combatantsThatDealtFirstStrikeDamage; + private CardCollection combatantsThatDealtFirstStrikeDamage() { + CardCollection result = _combatantsThatDealtFirstStrikeDamage; + if (result == null) { + synchronized (this) { + result = _combatantsThatDealtFirstStrikeDamage; + if (result == null) { + result = new CardCollection(); + _combatantsThatDealtFirstStrikeDamage = result; + } + } + } + return _combatantsThatDealtFirstStrikeDamage; + } public Combat(final Player attacker) { playerWhoAttacks = attacker; @@ -75,12 +178,12 @@ public class Combat { public Combat(Combat combat, IEntityMap map) { playerWhoAttacks = map.map(combat.playerWhoAttacks); - for (GameEntity entry : combat.attackableEntries) { - attackableEntries.add(map.map(entry)); + for (GameEntity entry : combat.attackableEntries()) { + attackableEntries().add(map.map(entry)); } HashMap bandsMap = new HashMap<>(); - for (Entry entry : combat.attackedByBands.entries()) { + for (Entry entry : combat.attackedByBands().entries()) { AttackingBand origBand = entry.getValue(); ArrayList attackers = new ArrayList<>(); for (Card c : origBand.getAttackers()) { @@ -92,37 +195,37 @@ public class Combat { newBand.setBlocked(blocked); } bandsMap.put(origBand, newBand); - attackedByBands.put(map.map(entry.getKey()), newBand); + attackedByBands().put(map.map(entry.getKey()), newBand); } - for (Entry entry : combat.blockedBands.entries()) { - blockedBands.put(bandsMap.get(entry.getKey()), map.map(entry.getValue())); + for (Entry entry : combat.blockedBands().entries()) { + blockedBands().put(bandsMap.get(entry.getKey()), map.map(entry.getValue())); } - for (Entry entry : combat.attackersOrderedForDamageAssignment.entrySet()) { - attackersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); + for (Entry entry : combat.attackersOrderedForDamageAssignment().entrySet()) { + attackersOrderedForDamageAssignment().put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); } - for (Entry entry : combat.blockersOrderedForDamageAssignment.entrySet()) { - blockersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); + for (Entry entry : combat.blockersOrderedForDamageAssignment().entrySet()) { + blockersOrderedForDamageAssignment().put(map.map(entry.getKey()), map.mapCollection(entry.getValue())); } // Note: Doesn't currently set up lkiCache, since it's just a cache and not strictly needed... - for (Table.Cell entry : combat.damageMap.cellSet()) { - damageMap.put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue()); + for (Table.Cell entry : combat.damageMap().cellSet()) { + damageMap().put(map.map(entry.getRowKey()), map.map(entry.getColumnKey()), entry.getValue()); } attackConstraints = new AttackConstraints(this); } public void initConstraints() { - attackableEntries.clear(); + attackableEntries().clear(); // Create keys for all possible attack targets - attackableEntries.addAll(CombatUtil.getAllPossibleDefenders(playerWhoAttacks)); + attackableEntries().addAll(CombatUtil.getAllPossibleDefenders(playerWhoAttacks)); attackConstraints = new AttackConstraints(this); } @Override public String toString() { StringBuilder sb = new StringBuilder(); - for (GameEntity defender : attackableEntries) { + for (GameEntity defender : attackableEntries()) { CardCollection attackers = getAttackersOf(defender); if (attackers.isEmpty()) { continue; @@ -148,13 +251,13 @@ public class Combat { CardCollection blockers = getAllBlockers(); //clear all combat-related collections - attackableEntries.clear(); - attackedByBands.clear(); - blockedBands.clear(); - attackersOrderedForDamageAssignment.clear(); - blockersOrderedForDamageAssignment.clear(); - lkiCache.clear(); - combatantsThatDealtFirstStrikeDamage.clear(); + attackableEntries().clear(); + attackedByBands().clear(); + blockedBands().clear(); + attackersOrderedForDamageAssignment().clear(); + blockersOrderedForDamageAssignment().clear(); + lkiCache().clear(); + combatantsThatDealtFirstStrikeDamage().clear(); //clear tracking for cards that care about "this combat" Game game = playerWhoAttacks.getGame(); @@ -186,7 +289,7 @@ public class Combat { return attackConstraints; } public final FCollectionView getDefenders() { - return attackableEntries; + return attackableEntries(); } //gets attacked player opponents (ignores planeswalkers) @@ -204,7 +307,7 @@ public class Combat { public final FCollection getDefendersControlledBy(Player who) { FCollection res = new FCollection<>(); - for (GameEntity ge : attackableEntries) { + for (GameEntity ge : attackableEntries()) { // if defender is the player himself or his cards if (ge == who || ge instanceof Card && ((Card) ge).getController() == who) { res.add(ge); @@ -214,15 +317,15 @@ public class Combat { } public final FCollectionView getDefendingPlayers() { - return new FCollection<>(Iterables.filter(attackableEntries, Player.class)); + return new FCollection<>(Iterables.filter(attackableEntries(), Player.class)); } public final CardCollection getDefendingPlaneswalkers() { - return CardLists.filter(Iterables.filter(attackableEntries, Card.class), CardPredicates.isType("Planeswalker")); + return CardLists.filter(Iterables.filter(attackableEntries(), Card.class), CardPredicates.isType("Planeswalker")); } public final CardCollection getDefendingBattles() { - return CardLists.filter(Iterables.filter(attackableEntries, Card.class), CardPredicates.isType("Battle")); + return CardLists.filter(Iterables.filter(attackableEntries(), Card.class), CardPredicates.isType("Battle")); } public final Map getAttackersAndDefenders() { @@ -230,14 +333,14 @@ public class Combat { } public final List getAttackingBandsOf(GameEntity defender) { - return Lists.newArrayList(attackedByBands.get(defender)); + return Lists.newArrayList(attackedByBands().get(defender)); } public final CardCollection getAttackersOf(GameEntity defender) { CardCollection result = new CardCollection(); - if (!attackedByBands.containsKey(defender)) + if (!attackedByBands().containsKey(defender)) return result; - for (AttackingBand v : attackedByBands.get(defender)) { + for (AttackingBand v : attackedByBands().get(defender)) { result.addAll(v.getAttackers()); } return result; @@ -247,7 +350,7 @@ public class Combat { addAttacker(c, defender, null); } public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { - Collection attackersOfDefender = attackedByBands.get(defender); + Collection attackersOfDefender = attackedByBands().get(defender); if (attackersOfDefender == null) { System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); return; @@ -272,7 +375,7 @@ public class Combat { return getDefenderByAttacker(getBandOfAttacker(c)); } public final GameEntity getDefenderByAttacker(final AttackingBand c) { - for (Entry e : attackedByBands.entries()) { + for (Entry e : attackedByBands().entries()) { if (e.getValue() == c) { return e.getKey(); } @@ -305,12 +408,12 @@ public class Combat { if (c == null) { return null; } - for (AttackingBand ab : attackedByBands.values()) { + for (AttackingBand ab : attackedByBands().values()) { if (ab.contains(c)) { return ab; } } - CombatLki lki = lkiCache.get(c).getCombatLKI(); + CombatLki lki = lkiCache().get(c).getCombatLKI(); return lki == null || !lki.isAttacker ? null : lki.getFirstBand(); } @@ -323,12 +426,12 @@ public class Combat { } public final List getAttackingBands() { - return Lists.newArrayList(attackedByBands.values()); + return Lists.newArrayList(attackedByBands().values()); } public boolean isAttacking(Card card, GameEntity defender) { AttackingBand ab = getBandOfAttacker(card); - for (Entry ee : attackedByBands.entries()) { + for (Entry ee : attackedByBands().entries()) { if (ee.getValue() == ab) { return ee.getKey() == defender; } @@ -340,7 +443,7 @@ public class Combat { * Checks if a card is currently attacking, returns false if the card is not currently attacking, even if its LKI was. */ public final boolean isAttacking(Card card) { - for (AttackingBand ab : attackedByBands.values()) { + for (AttackingBand ab : attackedByBands().values()) { if (ab.contains(card)) { return true; } @@ -350,7 +453,7 @@ public class Combat { public final CardCollection getAttackers() { CardCollection result = new CardCollection(); - for (AttackingBand ab : attackedByBands.values()) { + for (AttackingBand ab : attackedByBands().values()) { result.addAll(ab.getAttackers()); } return result; @@ -368,9 +471,9 @@ public class Combat { public final void addBlocker(final Card attacker, final Card blocker) { final AttackingBand band = getBandOfAttackerNotNull(attacker); - blockedBands.put(band, blocker); + blockedBands().put(band, blocker); // If damage is already assigned, add this blocker as a "late entry" - if (blockersOrderedForDamageAssignment.containsKey(attacker)) { + if (blockersOrderedForDamageAssignment().containsKey(attacker)) { addBlockerToDamageAssignmentOrder(attacker, blocker); } blocker.updateBlockingForView(); @@ -379,7 +482,7 @@ public class Combat { // remove blocker from specific attacker public final void removeBlockAssignment(final Card attacker, final Card blocker) { AttackingBand band = getBandOfAttackerNotNull(attacker); - Collection cc = blockedBands.get(band); + Collection cc = blockedBands().get(band); if (cc != null) { cc.remove(blocker); } @@ -389,13 +492,13 @@ public class Combat { // remove blocker from everywhere public final void undoBlockingAssignment(final Card blocker) { CardCollection toRemove = new CardCollection(blocker); - blockedBands.values().removeAll(toRemove); + blockedBands().values().removeAll(toRemove); blocker.updateBlockingForView(); } public final CardCollection getAllBlockers() { CardCollection result = new CardCollection(); - for (Card blocker : blockedBands.values()) { + for (Card blocker : blockedBands().values()) { if (!result.contains(blocker)) { result.add(blocker); } @@ -406,8 +509,11 @@ public class Combat { public final CardCollection getDefendersCreatures() { CardCollection result = new CardCollection(); for (Card attacker : getAttackers()) { - CardCollection cc = getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); - result.addAll(cc); + Player defender = getDefenderPlayerByAttacker(attacker); + if (defender != null) { + CardCollection cc = defender.getCreaturesInPlay(); + result.addAll(cc); + } } return result; } @@ -417,13 +523,13 @@ public class Combat { return getBlockers(getBandOfAttacker(card)); } public final CardCollection getBlockers(final AttackingBand band) { - Collection blockers = blockedBands.get(band); + Collection blockers = blockedBands().get(band); return blockers == null ? new CardCollection() : new CardCollection(blockers); } public final CardCollection getAttackersBlockedBy(final Card blocker) { CardCollection blocked = new CardCollection(); - for (Entry s : blockedBands.entries()) { + for (Entry s : blockedBands().entries()) { if (s.getValue().equals(blocker)) { blocked.addAll(s.getKey().getAttackers()); } @@ -433,7 +539,7 @@ public class Combat { public final FCollectionView getAttackingBandsBlockedBy(Card blocker) { FCollection bands = new FCollection<>(); - for (Entry kv : blockedBands.entries()) { + for (Entry kv : blockedBands().entries()) { if (kv.getValue().equals(blocker)) { bands.add(kv.getKey()); } @@ -457,10 +563,10 @@ public class Combat { /** If there are multiple blockers, the Attacker declares the Assignment Order */ public void orderBlockersForDamageAssignment() { // this method performs controller's role List> blockersNeedManualOrdering = new ArrayList<>(); - for (AttackingBand band : attackedByBands.values()) { + for (AttackingBand band : attackedByBands().values()) { if (band.isEmpty()) continue; - Collection blockers = blockedBands.get(band); + Collection blockers = blockedBands().get(band); if (blockers == null || blockers.isEmpty()) { continue; } @@ -484,13 +590,13 @@ public class Combat { /** If there are multiple blockers, the Attacker declares the Assignment Order */ public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role if (blockers.size() <= 1 || !this.legacyOrderCombatants) { - blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blockers)); + blockersOrderedForDamageAssignment().put(attacker, new CardCollection(blockers)); return; } // Damage Ordering needs to take cards like Melee into account, is that happening? CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlockers(attacker, blockers); // we know there's a list - blockersOrderedForDamageAssignment.put(attacker, orderedBlockers); + blockersOrderedForDamageAssignment().put(attacker, orderedBlockers); // Display the chosen order of blockers in the log // TODO: this is best done via a combat panel update @@ -517,15 +623,15 @@ public class Combat { * @param blocker the blocking creature. */ public void addBlockerToDamageAssignmentOrder(Card attacker, Card blocker) { - final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker); - if (oldBlockers == null || oldBlockers.isEmpty()) { - blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blocker)); - } else if (this.legacyOrderCombatants) { + final CardCollection oldBlockers = blockersOrderedForDamageAssignment().get(attacker); + if (oldBlockers == null || oldBlockers.isEmpty()) { + blockersOrderedForDamageAssignment().put(attacker, new CardCollection(blocker)); + } else if (this.legacyOrderCombatants) { CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlocker(attacker, blocker, oldBlockers); - blockersOrderedForDamageAssignment.put(attacker, orderedBlockers); - } else { + blockersOrderedForDamageAssignment().put(attacker, orderedBlockers); + } else { oldBlockers.add(blocker); - blockersOrderedForDamageAssignment.put(attacker, oldBlockers); + blockersOrderedForDamageAssignment().put(attacker, oldBlockers); } } @@ -544,19 +650,19 @@ public class Combat { CardCollection orderedAttacker = attackers.size() <= 1 || !this.legacyOrderCombatants ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); // Damage Ordering needs to take cards like Melee into account, is that happening? - attackersOrderedForDamageAssignment.put(blocker, orderedAttacker); + attackersOrderedForDamageAssignment().put(blocker, orderedAttacker); } // removes references to this attacker from all indices and orders public void unregisterAttacker(final Card c, AttackingBand ab) { - blockersOrderedForDamageAssignment.remove(c); + blockersOrderedForDamageAssignment().remove(c); - Collection blockers = blockedBands.get(ab); + Collection blockers = blockedBands().get(ab); if (blockers != null) { for (Card b : blockers) { // Clear removed attacker from assignment order - if (attackersOrderedForDamageAssignment.containsKey(b)) { - attackersOrderedForDamageAssignment.get(b).remove(c); + if (attackersOrderedForDamageAssignment().containsKey(b)) { + attackersOrderedForDamageAssignment().get(b).remove(c); } } } @@ -582,10 +688,10 @@ public class Combat { // removes references to this defender from all indices and orders public void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) { - attackersOrderedForDamageAssignment.remove(c); + attackersOrderedForDamageAssignment().remove(c); for (Card atk : bandBeingBlocked.getAttackers()) { - if (blockersOrderedForDamageAssignment.containsKey(atk)) { - blockersOrderedForDamageAssignment.get(atk).remove(c); + if (blockersOrderedForDamageAssignment().containsKey(atk)) { + blockersOrderedForDamageAssignment().get(atk).remove(c); } } } @@ -601,16 +707,16 @@ public class Combat { } // if not found in attackers, look for this card in blockers - for (Entry be : blockedBands.entries()) { + for (Entry be : blockedBands().entries()) { if (be.getValue().equals(c)) { unregisterDefender(c, be.getKey()); } } - for (Card battleOrPW : Iterables.filter(attackableEntries, Card.class)) { + for (Card battleOrPW : Iterables.filter(attackableEntries(), Card.class)) { if (battleOrPW.equals(c)) { Multimap attackerBuffer = ArrayListMultimap.create(); - Collection bands = attackedByBands.get(c); + Collection bands = attackedByBands().get(c); for (AttackingBand abDef : bands) { unregisterDefender(c, abDef); // Rule 506.4c workaround to keep creatures in combat @@ -620,20 +726,20 @@ public class Combat { attackerBuffer.put(fake, abDef); } bands.clear(); - attackedByBands.putAll(attackerBuffer); + attackedByBands().putAll(attackerBuffer); break; } } // remove card from map - while (blockedBands.values().remove(c)); + while (blockedBands().values().remove(c)); c.updateBlockingForView(); } public final boolean removeAbsentCombatants() { // CR 506.4 iterate all attackers and remove illegal declarations CardCollection missingCombatants = new CardCollection(); - for (Entry ee : attackedByBands.entries()) { + for (Entry ee : attackedByBands().entries()) { for (Card c : ee.getValue().getAttackers()) { if (!c.isInPlay() || !c.isCreature()) { missingCombatants.add(c); @@ -647,7 +753,7 @@ public class Combat { } } - for (Entry be : blockedBands.entries()) { + for (Entry be : blockedBands().entries()) { Card blocker = be.getValue(); if (!blocker.isInPlay() || !blocker.isCreature()) { missingCombatants.add(blocker); @@ -666,8 +772,8 @@ public class Combat { public final void fireTriggersForUnblockedAttackers(final Game game) { boolean bFlag = false; List defenders = Lists.newArrayList(); - for (AttackingBand ab : attackedByBands.values()) { - Collection blockers = blockedBands.get(ab); + for (AttackingBand ab : attackedByBands().values()) { + Collection blockers = blockedBands().get(ab); boolean isBlocked = blockers != null && !blockers.isEmpty(); ab.setBlocked(isBlocked); @@ -706,23 +812,23 @@ public class Combat { } if (firstStrikeDamage) { - combatantsThatDealtFirstStrikeDamage.add(blocker); + combatantsThatDealtFirstStrikeDamage().add(blocker); } // Run replacement effects blocker.getGame().getReplacementHandler().run(ReplacementType.AssignDealDamage, AbilityKey.mapFromAffected(blocker)); - CardCollection attackers = attackersOrderedForDamageAssignment.get(blocker); + CardCollection attackers = attackersOrderedForDamageAssignment().get(blocker); final int damage = blocker.getNetCombatDamage(); - if (!attackers.isEmpty()) { + if (attackers != null && !attackers.isEmpty()) { Player attackingPlayer = getAttackingPlayer(); Player assigningPlayer = blocker.getController(); Player defender = null; boolean divideCombatDamageAsChoose = blocker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among " + - "defending player and/or any number of creatures they control.") + "defending player and/or any number of creatures they control.") && blocker.getController().getController().confirmStaticApplication(blocker, PlayerActionConfirmMode.AlternativeDamageAssignment, Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose", CardTranslation.getTranslatedName(blocker.getName())), null); @@ -740,10 +846,10 @@ public class Combat { for (Entry dt : map.entrySet()) { // Butcher Orgg if (dt.getKey() == null && dt.getValue() > 0) { - damageMap.put(blocker, defender, dt.getValue()); + damageMap().put(blocker, defender, dt.getValue()); } else { dt.getKey().addAssignedDamage(dt.getValue(), blocker); - damageMap.put(blocker, dt.getKey(), dt.getValue()); + damageMap().put(blocker, dt.getKey(), dt.getValue()); } } } @@ -764,7 +870,7 @@ public class Combat { } if (firstStrikeDamage) { - combatantsThatDealtFirstStrikeDamage.add(attacker); + combatantsThatDealtFirstStrikeDamage().add(attacker); } // Run replacement effects @@ -785,7 +891,7 @@ public class Combat { GameEntity defender = getDefenderByAttacker(band); Player assigningPlayer = getAttackingPlayer(); - orderedBlockers = blockersOrderedForDamageAssignment.get(attacker); + orderedBlockers = blockersOrderedForDamageAssignment().get(attacker); // Defensive Formation is very similar to Banding with Blockers // It allows the defending player to assign damage instead of the attacking player if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { @@ -814,8 +920,8 @@ public class Combat { attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among " + "defending player and/or any number of creatures they control.") && assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment, - Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose", - CardTranslation.getTranslatedName(attacker.getName())), null); + Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose", + CardTranslation.getTranslatedName(attacker.getName())), null); if (defender instanceof Card && divideCombatDamageAsChoose) { defender = getDefenderPlayerByAttacker(attacker); } @@ -849,7 +955,7 @@ public class Combat { } if (assignToPlayer) { attackers.remove(attacker); - damageMap.put(attacker, defender, damageDealt); + damageMap().put(attacker, defender, damageDealt); } else if (orderedBlockers == null || orderedBlockers.isEmpty()) { attackers.remove(attacker); @@ -857,9 +963,14 @@ public class Combat { final SpellAbility emptySA = new SpellAbility.EmptySa(ApiType.Cleanup, attacker); Card chosen = attacker.getController().getController().chooseCardsForEffect(getDefendersCreatures(), emptySA, Localizer.getInstance().getMessage("lblChooseCreature"), 1, 1, false, null).get(0); - damageMap.put(attacker, chosen, damageDealt); + damageMap().put(attacker, chosen, damageDealt); } else if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked - damageMap.put(attacker, defender, damageDealt); + if (defender == null) { + defender = getDefenderPlayerByAttacker(attacker); + System.err.println("[COMBAT] defender is null, getDefenderPlayerByAttacker(attacker) result: " + defender); + } + // this will fail if defender is null, and it doesn't allow null values.. + damageMap().put(attacker, defender, damageDealt); } // No damage happens if blocked but no blockers left } else { Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, attackers, @@ -879,11 +990,11 @@ public class Combat { if (defender instanceof Card) { ((Card) defender).addAssignedDamage(dt.getValue(), attacker); } - damageMap.put(attacker, defender, dt.getValue()); + damageMap().put(attacker, defender, dt.getValue()); } } else { dt.getKey().addAssignedDamage(dt.getValue(), attacker); - damageMap.put(attacker, dt.getKey(), dt.getValue()); + damageMap().put(attacker, dt.getKey(), dt.getValue()); } } } // if !hasFirstStrike ... @@ -900,7 +1011,7 @@ public class Combat { if (firstStrikeDamage && combatant.hasFirstStrike()) { return true; } - return !firstStrikeDamage && !combatantsThatDealtFirstStrikeDamage.contains(combatant); + return !firstStrikeDamage && !combatantsThatDealtFirstStrikeDamage().contains(combatant); } public final boolean assignCombatDamage(boolean firstStrikeDamage) { @@ -908,7 +1019,7 @@ public class Combat { assignedDamage |= assignBlockersDamage(firstStrikeDamage); if (!firstStrikeDamage) { // Clear first strike damage list since it doesn't matter anymore - combatantsThatDealtFirstStrikeDamage.clear(); + combatantsThatDealtFirstStrikeDamage().clear(); } return assignedDamage; } @@ -920,7 +1031,7 @@ public class Combat { CardDamageMap preventMap = new CardDamageMap(); GameEntityCounterTable counterTable = new GameEntityCounterTable(); - game.getAction().dealDamage(true, damageMap, preventMap, counterTable, null); + game.getAction().dealDamage(true, damageMap(), preventMap, counterTable, null); // copy last state again for dying replacement effects game.copyLastState(); @@ -933,7 +1044,7 @@ public class Combat { public final CardCollection getUnblockedAttackers() { CardCollection unblocked = new CardCollection(); - for (AttackingBand ab : attackedByBands.values()) { + for (AttackingBand ab : attackedByBands().values()) { if (Boolean.FALSE.equals(ab.isBlocked())) { unblocked.addAll(ab.getAttackers()); } @@ -942,13 +1053,13 @@ public class Combat { } public boolean isPlayerAttacked(Player who) { - for (GameEntity defender : attackedByBands.keySet()) { + for (GameEntity defender : attackedByBands().keySet()) { Card defenderAsCard = defender instanceof Card ? (Card)defender : null; if ((null != defenderAsCard && (defenderAsCard.getController() != who && defenderAsCard.getProtectingPlayer() != who)) || - (null == defenderAsCard && defender != who)) { + (null == defenderAsCard && defender != who)) { continue; // defender is not related to player 'who' } - for (AttackingBand ab : attackedByBands.get(defender)) { + for (AttackingBand ab : attackedByBands().get(defender)) { if (!ab.isEmpty()) { return true; } @@ -958,7 +1069,7 @@ public class Combat { } public boolean isBlocking(Card blocker) { - if (blockedBands.containsValue(blocker)) { + if (blockedBands().containsValue(blocker)) { return true; // is blocking something at the moment } @@ -966,13 +1077,13 @@ public class Combat { return false; } - CombatLki lki = lkiCache.get(blocker).getCombatLKI(); + CombatLki lki = lkiCache().get(blocker).getCombatLKI(); return null != lki && !lki.isAttacker; // was blocking something anyway } public boolean isBlocking(Card blocker, Card attacker) { AttackingBand ab = getBandOfAttacker(attacker); - Collection blockers = blockedBands.get(ab); + Collection blockers = blockedBands().get(ab); if (blockers != null && blockers.contains(blocker)) { return true; // is blocking the attacker's band at the moment } @@ -981,7 +1092,7 @@ public class Combat { return false; } - CombatLki lki = lkiCache.get(blocker).getCombatLKI(); + CombatLki lki = lkiCache().get(blocker).getCombatLKI(); return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band } @@ -994,7 +1105,7 @@ public class Combat { final boolean isAttacker = attackingBand != null; if (isAttacker) { boolean found = false; - for (AttackingBand ab : attackedByBands.values()) { + for (AttackingBand ab : attackedByBands().values()) { if (ab.contains(lki)) { found = true; break; @@ -1009,7 +1120,7 @@ public class Combat { return null; // card was not even in combat } } - lkiCache.add(lki); + lkiCache().add(lki); final FCollectionView relatedBands = isAttacker ? new FCollection<>(attackingBand) : attackersBlocked; return new CombatLki(isAttacker, relatedBands); } diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-game/src/main/java/forge/game/cost/CostPayment.java index 7c4ebf3fd2f..d7f48b5aa98 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayment.java @@ -229,8 +229,8 @@ public class CostPayment extends ManaConversionMatrix { * @return a {@link forge.game.mana.Mana} object. */ public static Mana getMana(final Player player, final ManaCostShard shard, final SpellAbility saBeingPaidFor, - final byte colorsPaid, Map xManaCostPaidByColor) { - final List> weightedOptions = selectManaToPayFor(player.getManaPool(), shard, + final byte colorsPaid, Map xManaCostPaidByColor) { // player.getManaPool() is not threadsafe if this is called somwewhere concurrently + final List> weightedOptions = selectManaToPayFor(new ManaPool(player), shard, saBeingPaidFor, colorsPaid, xManaCostPaidByColor); // Exclude border case diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index a9e7483e414..bed87be2071 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -23,6 +23,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; @@ -51,7 +53,20 @@ import forge.game.staticability.StaticAbilityUnspentMana; */ public class ManaPool extends ManaConversionMatrix implements Iterable { private final Player owner; - private final ArrayListMultimap floatingMana = ArrayListMultimap.create(); + private ListMultimap _floatingMana; + private ListMultimap floatingMana() { + ListMultimap result = _floatingMana; + if (result == null) { + synchronized (this) { + result = _floatingMana; + if (result == null) { + result = Multimaps.synchronizedListMultimap(ArrayListMultimap.create()); + _floatingMana = result; + } + } + } + return _floatingMana; + } public ManaPool(final Player player) { owner = player; @@ -59,7 +74,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } public final int getAmountOfColor(final byte color) { - Collection ofColor = floatingMana.get(color); + Collection ofColor = floatingMana().get(color); return ofColor == null ? 0 : ofColor.size(); } @@ -67,7 +82,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { addMana(mana, true); } public void addMana(final Mana mana, boolean updateView) { - floatingMana.put(mana.getColor(), mana); + floatingMana().put(mana.getColor(), mana); if (updateView) { owner.updateManaForView(); owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana)); @@ -88,7 +103,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { *

*/ public final boolean willManaBeLostAtEndOfPhase() { - if (floatingMana.isEmpty()) { + if (floatingMana().isEmpty()) { return false; } @@ -114,13 +129,13 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { public final void resetPool() { // This should only be used to reset the pool to empty by things like restores. - floatingMana.clear(); + floatingMana().clear(); } public final List clearPool(boolean isEndOfPhase) { // isEndOfPhase parameter: true = end of phase, false = mana drain effect List cleared = Lists.newArrayList(); - if (floatingMana.isEmpty()) { return cleared; } + if (floatingMana().isEmpty()) { return cleared; } Byte convertTo = null; @@ -128,17 +143,17 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { final Map runParams = AbilityKey.mapFromAffected(owner); runParams.put(AbilityKey.Mana, "C"); switch (owner.getGame().getReplacementHandler().run(ReplacementType.LoseMana, runParams)) { - case NotReplaced: - break; - case Skipped: - return cleared; - default: - convertTo = ManaAtom.fromName((String) runParams.get(AbilityKey.Mana)); - break; + case NotReplaced: + break; + case Skipped: + return cleared; + default: + convertTo = ManaAtom.fromName((String) runParams.get(AbilityKey.Mana)); + break; } - final List keys = Lists.newArrayList(floatingMana.keySet()); + final List keys = Lists.newArrayList(floatingMana().keySet()); if (isEndOfPhase) { keys.removeAll(StaticAbilityUnspentMana.getManaToKeep(owner)); } @@ -147,7 +162,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } for (Byte b : keys) { - Collection cm = floatingMana.get(b); + Collection cm = floatingMana().get(b); final List pMana = Lists.newArrayList(); if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { for (final Mana mana : cm) { @@ -163,7 +178,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } else { cleared.addAll(cm); cm.clear(); - floatingMana.putAll(b, pMana); + floatingMana().putAll(b, pMana); } } @@ -174,12 +189,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { private void convertManaColor(final byte originalColor, final byte toColor) { List convert = Lists.newArrayList(); - Collection cm = floatingMana.get(originalColor); + Collection cm = floatingMana().get(originalColor); for (Mana m : cm) { convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility())); } cm.clear(); - floatingMana.putAll(toColor, convert); + floatingMana().putAll(toColor, convert); owner.updateManaForView(); } @@ -187,7 +202,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { return removeMana(mana, true); } public boolean removeMana(final Mana mana, boolean updateView) { - boolean result = floatingMana.remove(mana.getColor(), mana); + boolean result = floatingMana().remove(mana.getColor(), mana); if (result && updateView) { owner.updateManaForView(); owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); @@ -216,7 +231,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { public boolean tryPayCostWithColor(byte colorCode, SpellAbility saPaidFor, ManaCostBeingPaid manaCost, List manaSpentToPay) { Mana manaFound = null; - Collection cm = floatingMana.get(colorCode); + Collection cm = floatingMana().get(colorCode); for (final Mana mana : cm) { if (mana.getManaAbility() != null && !mana.getManaAbility().meetsManaRestrictions(saPaidFor)) { @@ -253,11 +268,11 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } public final boolean isEmpty() { - return floatingMana.isEmpty(); + return floatingMana().isEmpty(); } public final int totalMana() { - return floatingMana.values().size(); + return floatingMana().values().size(); } //Account for mana part of ability when undoing it @@ -265,7 +280,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { if (ma == null) { return false; } - if (floatingMana.isEmpty()) { + if (floatingMana().isEmpty()) { return false; } @@ -274,7 +289,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { boolean manaNotAccountedFor = false; // loop over mana produced by mana ability for (Mana mana : ma.getLastManaProduced()) { - Collection poolLane = floatingMana.get(mana.getColor()); + Collection poolLane = floatingMana().get(mana.getColor()); if (poolLane != null && poolLane.contains(mana)) { removeFloating.add(mana); @@ -321,7 +336,6 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { * Checks if the given mana cost can be paid from floating mana. * @param cost mana cost to pay for * @param sa ability to pay for - * @param player activating player * @param test actual payment is made if this is false * @param manaSpentToPay list of mana spent * @return whether the floating mana is sufficient to pay the cost fully @@ -358,7 +372,10 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { @Override public Iterator iterator() { - return floatingMana.values().iterator(); + // use synchronizedListMultimap + synchronized (floatingMana()) { + return floatingMana().values().iterator(); + } } } diff --git a/forge-game/src/main/java/forge/game/mana/ManaRefundService.java b/forge-game/src/main/java/forge/game/mana/ManaRefundService.java index 5f73179aa6f..324cc8364bf 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaRefundService.java +++ b/forge-game/src/main/java/forge/game/mana/ManaRefundService.java @@ -6,8 +6,8 @@ import forge.game.event.GameEventZone; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.CollectionUtil; -import java.util.Collections; import java.util.List; public class ManaRefundService { @@ -39,7 +39,7 @@ public class ManaRefundService { List payingAbilities = sa.getPayingManaAbilities(); // start with the most recent - Collections.reverse(payingAbilities); + CollectionUtil.reverse(payingAbilities); for (final SpellAbility am : payingAbilities) { // What if am is owned by a different player? diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 75bd3d72c02..9d690cf4a62 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1142,7 +1142,7 @@ public class Player extends GameEntity implements Comparable { } if (toTop != null) { - Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. + CollectionUtil.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. for (Card c : toTop) { getGame().getAction().moveToLibrary(c, cause, params); numToTop++; @@ -1669,7 +1669,7 @@ public class Player extends GameEntity implements Comparable { final CardCollection list = new CardCollection(getCardsIn(ZoneType.Library)); // Note: Shuffling once is sufficient. - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); getZone(ZoneType.Library).setCards(getController().cheatShuffle(list)); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 43781bb594d..5d61b284d94 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -118,7 +118,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private boolean optionalTrigger = false; private ReplacementEffect replacementEffect = null; private int sourceTrigger = -1; - private List triggerRemembered = Lists.newArrayList(); + private List _triggerRemembered; + private List triggerRemembered() { + List result = _triggerRemembered; + if (result == null) { + synchronized (this) { + result = _triggerRemembered; + if (result == null) { + result = Lists.newArrayList(); + _triggerRemembered = result; + } + } + } + return _triggerRemembered; + } + private void _setTriggerRemembered(List tr) { + _triggerRemembered = tr; + } private AlternativeCost altCost = null; private EnumSet optionalCosts = EnumSet.noneOf(OptionalCost.class); @@ -126,30 +142,153 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private boolean aftermath = false; + private boolean skip = false; /** The pay costs. */ private Cost payCosts; private SpellAbilityRestriction restrictions; - private SpellAbilityCondition conditions = new SpellAbilityCondition(); + private SpellAbilityCondition _conditions; + private SpellAbilityCondition conditions() { + SpellAbilityCondition result = _conditions; + if (result == null) { + synchronized (this) { + result = _conditions; + if (result == null) { + result = new SpellAbilityCondition(); + _conditions = result; + } + } + } + return _conditions; + } + private void _setConditions(SpellAbilityCondition c) { + _conditions = c; + } private AbilitySub subAbility; - private Map additionalAbilities = Maps.newHashMap(); - private Map> additionalAbilityLists = Maps.newHashMap(); + private Map _additionalAbilities; + private Map additionalAbilities() { + Map result = _additionalAbilities; + if (result == null) { + synchronized (this) { + result = _additionalAbilities; + if (result == null) { + result = Maps.newHashMap(); + _additionalAbilities = result; + } + } + } + return _additionalAbilities; + } + private void _setAdditionalAbilities(Map aa) { + _additionalAbilities = aa; + } + private Map> _additionalAbilityLists; + private Map> additionalAbilityLists() { + Map> result = _additionalAbilityLists; + if (result == null) { + synchronized (this) { + result = _additionalAbilityLists; + if (result == null) { + result = Maps.newHashMap(); + _additionalAbilityLists = result; + } + } + } + return _additionalAbilityLists; + } + private void _setAdditionalAbilityLists(Map> al) { + _additionalAbilityLists = al; + } protected ApiType api = null; - private List payingMana = Lists.newArrayList(); - private List paidAbilities = Lists.newArrayList(); + private List _payingMana; + private List payingMana() { + List result = _payingMana; + if (result == null) { + synchronized (this) { + result = _payingMana; + if (result == null) { + result = Lists.newArrayList(); + _payingMana = result; + } + } + } + return _payingMana; + } + private void _setPayingMana(List m) { + _payingMana = m; + } + private List _paidAbilities; + private List paidAbilities() { + List result = _paidAbilities; + if (result == null) { + synchronized (this) { + result = _paidAbilities; + if (result == null) { + result = Lists.newArrayList(); + _paidAbilities = result; + } + } + } + return _paidAbilities; + } + private void _setPaidAbilities(List p) { + _paidAbilities = p; + } private Integer xManaCostPaid = null; - private TreeBasedTable paidLists = TreeBasedTable.create(); + private TreeBasedTable _paidLists; + private TreeBasedTable paidLists() { + TreeBasedTable result = _paidLists; + if (result == null) { + synchronized (this) { + result = _paidLists; + if (result == null) { + result = TreeBasedTable.create(); + _paidLists = result; + } + } + } + return _paidLists; + } + private void _setPaidLists(TreeBasedTable p) { + _paidLists = p; + } private EnumMap triggeringObjects = AbilityKey.newMap(); private EnumMap replacingObjects = AbilityKey.newMap(); - private final List pipsToReduce = new ArrayList<>(); + private List _pipsToReduce; + private List pipsToReduce() { + List result = _pipsToReduce; + if (result == null) { + synchronized (this) { + result = _pipsToReduce; + if (result == null) { + result = new ArrayList<>(); + _pipsToReduce = result; + } + } + } + return _pipsToReduce; + } private List chosenList = null; - private CardCollection tappedForConvoke = new CardCollection(); + private CardCollection _tappedForConvoke; + private CardCollection tappedForConvoke() { + CardCollection result = _tappedForConvoke; + if (result == null) { + synchronized (this) { + result = _tappedForConvoke; + if (result == null) { + result = new CardCollection(); + _tappedForConvoke = result; + } + } + } + return _tappedForConvoke; + } private Card sacrificedAsOffering; private Card sacrificedAsEmerge; @@ -163,7 +302,26 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private boolean isCastFromPlayEffect = false; private TargetRestrictions targetRestrictions; - private TargetChoices targetChosen = new TargetChoices(); + private TargetChoices _targetChosen; + private TargetChoices targetChosen() { + TargetChoices result = _targetChosen; + if (result == null) { + synchronized (this) { + result = _targetChosen; + if (result == null) { + result = new TargetChoices(); + _targetChosen = result; + } + } + } + return _targetChosen; + } + private void _setTargetChosen(TargetChoices c) { + _targetChosen = c; + } + private void resetTargetChosen() { + _targetChosen = new TargetChoices(); + } private Integer dividedValue = null; @@ -174,7 +332,20 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardCollection lastStateBattlefield; private CardCollection lastStateGraveyard; - private CardCollection rollbackEffects = new CardCollection(); + private CardCollection _rollbackEffects; + private CardCollection rollbackEffects() { + CardCollection result = _rollbackEffects; + if (result == null) { + synchronized (this) { + result = _rollbackEffects; + if (result == null) { + result = new CardCollection(); + _rollbackEffects = result; + } + } + } + return _rollbackEffects; + } private CardDamageMap damageMap; private CardDamageMap preventMap; @@ -242,12 +413,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setHostCard(c); } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { if (sa.getHostCard() != c) { sa.setHostCard(c); } } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { if (sa.getHostCard() != c) { sa.setHostCard(c); @@ -266,10 +437,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setKeyword(kw); } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { sa.setKeyword(kw); } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { sa.setKeyword(kw); } @@ -467,10 +638,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { updated |= subAbility.setActivatingPlayer(player, lki); } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { updated |= sa.setActivatingPlayer(player, lki); } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { updated |= sa.setActivatingPlayer(player, lki); } @@ -648,10 +819,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public SpellAbilityCondition getConditions() { - return conditions; + return conditions(); } public final void setConditions(final SpellAbilityCondition condition) { - conditions = condition; + _setConditions(condition); } public boolean metConditions() { @@ -659,13 +830,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public List getPayingMana() { - return payingMana; + return payingMana(); } public void setPayingMana(List paying) { - payingMana = Lists.newArrayList(paying); + _setPayingMana(Lists.newArrayList(paying)); } public final void clearManaPaid() { - payingMana.clear(); + payingMana( ).clear(); } public final int getSpendPhyrexianMana() { @@ -727,42 +898,42 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public ColorSet getPayingColors() { byte colors = 0; - for (Mana m : payingMana) { + for (Mana m : payingMana()) { colors |= m.getColor(); } return ColorSet.fromMask(colors); } public List getPayingManaAbilities() { - return paidAbilities; + return paidAbilities(); } // Combined PaidLists public TreeBasedTable getPaidHash() { - return paidLists; + return paidLists(); } public void setPaidHash(final TreeBasedTable hash) { - paidLists = TreeBasedTable.create(hash); + _setPaidLists(TreeBasedTable.create(hash)); } // use if it doesn't matter if payment was caused by extrinsic cost modifier public Iterable getPaidList(final String str) { - return Iterables.concat(paidLists.row(str).values()); + return Iterables.concat(paidLists().row(str).values()); } public CardCollection getPaidList(final String str, final boolean intrinsic) { - return paidLists.get(str, intrinsic); + return paidLists().get(str, intrinsic); } public void addCostToHashList(final Card c, final String str, final boolean intrinsic) { - if (!paidLists.contains(str, intrinsic)) { - paidLists.put(str, intrinsic, new CardCollection()); + if (!paidLists().contains(str, intrinsic)) { + paidLists().put(str, intrinsic, new CardCollection()); } - paidLists.get(str, intrinsic).add(c); + paidLists().get(str, intrinsic).add(c); } public void resetPaidHash() { - paidLists.clear(); + paidLists().clear(); } public Iterable getOptionalCosts() { @@ -774,7 +945,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit optionalCosts = EnumSet.copyOf(optionalCosts); optionalCosts.add(cost); if (!cost.getPip().isEmpty()) { - pipsToReduce.add(cost.getPip()); + pipsToReduce().add(cost.getPip()); } } @@ -788,7 +959,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean isKicked() { return isOptionalCostPaid(OptionalCost.Kicker1) || isOptionalCostPaid(OptionalCost.Kicker2) || - getRootAbility().getOptionalKeywordAmount(Keyword.MULTIKICKER) > 0; + getRootAbility().getOptionalKeywordAmount(Keyword.MULTIKICKER) > 0; } public boolean isEntwine() { @@ -834,13 +1005,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit @Override public List getTriggerRemembered() { - return triggerRemembered; + return triggerRemembered(); } public void setTriggerRemembered(List list) { - triggerRemembered = list; + _setTriggerRemembered(list); } public void resetTriggerRemembered() { - triggerRemembered = Lists.newArrayList(); + _setTriggerRemembered(Lists.newArrayList()); } public Map getReplacingObjects() { @@ -866,7 +1037,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void resetOnceResolved() { //resetPaidHash(); // FIXME: if uncommented, breaks Dragon Presence, e.g. Orator of Ojutai + revealing a Dragon from hand. - // Is it truly necessary at this point? The paid hash seems to be reset on all SA instance operations. + // Is it truly necessary at this point? The paid hash seems to be reset on all SA instance operations. // Epic spell keeps original targets if (!isEpic()) { resetTargets(); @@ -898,7 +1069,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void setStackDescription(final String s) { originalStackDescription = s; stackDescription = originalStackDescription; - if (StringUtils.isEmpty(description) && StringUtils.isEmpty(hostCard.getView().getText())) { + // FIXME: why would the view is null? freezed tracker and the view is not composed yet? + String compareHostText = hostCard.getView() == null ? "" : hostCard.getView().getText(); + if (StringUtils.isEmpty(description) && StringUtils.isEmpty(compareHostText)) { setDescription(s); } } @@ -944,7 +1117,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } sb.append(payCosts.toString()); sb.append(" or ").append(altOnlyMana ? alternateCost.toString() : - StringUtils.uncapitalize(alternateCost.toString())); + StringUtils.uncapitalize(alternateCost.toString())); sb.append(equip && !altOnlyMana ? "." : ""); } else { sb.append(payCosts.toString()); @@ -959,13 +1132,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public void rebuiltDescription() { - final StringBuilder sb = new StringBuilder(); // SubAbilities don't have Costs or Cost descriptors - sb.append(getCostDescription()); - sb.append(getParam("SpellDescription")); - setDescription(sb.toString()); + String sb = getCostDescription() + + getParam("SpellDescription"); + setDescription(sb); } /** {@inheritDoc} */ @@ -1015,37 +1187,37 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public Map getAdditionalAbilities() { - return additionalAbilities; + return additionalAbilities(); } public SpellAbility getAdditionalAbility(final String name) { if (hasAdditionalAbility(name)) { - return additionalAbilities.get(name); + return additionalAbilities().get(name); } return null; } public boolean hasAdditionalAbility(final String name) { - return additionalAbilities.containsKey(name); + return additionalAbilities().containsKey(name); } public void setAdditionalAbility(final String name, final SpellAbility sa) { if (sa == null) { - additionalAbilities.remove(name); + additionalAbilities().remove(name); } else { if (sa instanceof AbilitySub) { ((AbilitySub)sa).setParent(this); } - additionalAbilities.put(name, sa); + additionalAbilities().put(name, sa); } view.updateDescription(this); //description changes when sub-abilities change } public Map> getAdditionalAbilityLists() { - return additionalAbilityLists; + return additionalAbilityLists(); } public List getAdditionalAbilityList(final String name) { - if (additionalAbilityLists.containsKey(name)) { - return additionalAbilityLists.get(name); + if (additionalAbilityLists().containsKey(name)) { + return additionalAbilityLists().get(name); } else { return ImmutableList.of(); } @@ -1053,13 +1225,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void setAdditionalAbilityList(final String name, final List list) { if (list == null || list.isEmpty()) { - additionalAbilityLists.remove(name); + additionalAbilityLists().remove(name); } else { List result = Lists.newArrayList(list); for (AbilitySub sa : result) { sa.setParent(this); } - additionalAbilityLists.put(name, result); + additionalAbilityLists().put(name, result); } view.updateDescription(this); } @@ -1092,7 +1264,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean isPlotting() { return false; } - + /** * @return the aftermath */ @@ -1199,18 +1371,18 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit clone.changeZoneTable = new CardZoneTable(changeZoneTable); } - clone.payingMana = Lists.newArrayList(payingMana); - clone.paidAbilities = Lists.newArrayList(); + clone._setPayingMana(Lists.newArrayList(payingMana())); + clone._setPaidAbilities(Lists.newArrayList()); clone.setPaidHash(getPaidHash()); if (usesTargeting()) { // the targets need to be cloned, otherwise they might be cleared - clone.targetChosen = getTargets().clone(); + clone._setTargetChosen(getTargets().clone()); } // clear maps for copy, the values will be added later - clone.additionalAbilities = Maps.newHashMap(); - clone.additionalAbilityLists = Maps.newHashMap(); + clone._setAdditionalAbilities(Maps.newHashMap()); + clone._setAdditionalAbilityLists(Maps.newHashMap()); // run special copy Ability to make a deep copy CardFactory.copySpellAbility(this, clone, host, activ, lki, keepTextChanges); } catch (final CloneNotSupportedException e) { @@ -1381,15 +1553,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } switch (related) { - case "LEPower" : - if (c.getNetPower() > parentTarget.getNetPower()) { - return false; - } - break; - case "LECMC" : - if (c.getCMC() > parentTarget.getCMC()) { - return false; - } + case "LEPower" : + if (c.getNetPower() > parentTarget.getNetPower()) { + return false; + } + break; + case "LECMC" : + if (c.getCMC() > parentTarget.getCMC()) { + return false; + } } } @@ -1562,25 +1734,20 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public List getPipsToReduce() { - return pipsToReduce; + return pipsToReduce(); } public final void clearPipsToReduce() { - pipsToReduce.clear(); + pipsToReduce().clear(); } public CardCollection getTappedForConvoke() { - return tappedForConvoke; + return tappedForConvoke(); } public void addTappedForConvoke(final Card c) { - if (tappedForConvoke == null) { - tappedForConvoke = new CardCollection(); - } - tappedForConvoke.add(c); + tappedForConvoke().add(c); } public void clearTappedForConvoke() { - if (tappedForConvoke != null) { - tappedForConvoke.clear(); - } + tappedForConvoke().clear(); } public boolean isEmerge() { @@ -1754,16 +1921,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit * @return the chosenTarget */ public TargetChoices getTargets() { - return targetChosen; + return targetChosen(); } public void setTargets(TargetChoices targets) { // TODO should copy the target choices? - targetChosen = targets; + _setTargetChosen(targets); } public void resetTargets() { - targetChosen = new TargetChoices(); + resetTargetChosen(); } /** @@ -1879,7 +2046,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public Card getTargetCard() { - return targetChosen.getFirstTargetedCard(); + return targetChosen().getFirstTargetedCard(); } /** @@ -1898,7 +2065,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } resetTargets(); - targetChosen.add(card); + targetChosen().add(card); setStackDescription(getHostCard().getName() + " - targeting " + card); } @@ -2258,11 +2425,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit subAbility.changeText(); } } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { sa.changeText(); } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { sa.changeText(); } @@ -2283,11 +2450,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit subAbility.changeTextIntrinsic(colorMap, typeMap); } } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { sa.changeTextIntrinsic(colorMap, typeMap); } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { sa.changeTextIntrinsic(colorMap, typeMap); } @@ -2300,12 +2467,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (subAbility != null) { subAbility.setIntrinsic(i); } - for (SpellAbility sa : additionalAbilities.values()) { + for (SpellAbility sa : additionalAbilities().values()) { if (sa.isIntrinsic() != i) { sa.setIntrinsic(i); } } - for (List list : additionalAbilityLists.values()) { + for (List list : additionalAbilityLists().values()) { for (AbilitySub sa : list) { if (sa.isIntrinsic() != i) { sa.setIntrinsic(i); @@ -2505,6 +2672,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return sb.toString(); } + public boolean isSkip() { + return skip; + } + public void setSkip(boolean val) { + skip = val; + } public boolean canCastTiming(Player activator) { return canCastTiming(getHostCard(), activator); } @@ -2555,14 +2728,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public void addRollbackEffect(Card eff) { - rollbackEffects.add(eff); + rollbackEffects().add(eff); } public void rollback() { - for (Card c : rollbackEffects) { + for (Card c : rollbackEffects()) { c.getGame().getAction().ceaseToExist(c, true); } - rollbackEffects.clear(); + rollbackEffects().clear(); } public boolean isHidden() { diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-game/src/main/java/forge/game/zone/Zone.java index 641e33103dd..73ddff61596 100644 --- a/forge-game/src/main/java/forge/game/zone/Zone.java +++ b/forge-game/src/main/java/forge/game/zone/Zone.java @@ -18,7 +18,6 @@ package forge.game.zone; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -36,6 +35,7 @@ import forge.game.event.EventValueChangeType; import forge.game.event.GameEventZone; import forge.game.player.Player; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.maps.EnumMapOfLists; import forge.util.maps.MapOfLists; @@ -278,7 +278,7 @@ public class Zone implements java.io.Serializable, Iterable { } public void shuffle() { - Collections.shuffle(cardList, MyRandom.getRandom()); + CollectionUtil.shuffle(cardList, MyRandom.getRandom()); onChanged(); } diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java b/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java index 756dfe195fb..f2b57e1f7a6 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java @@ -18,6 +18,7 @@ import forge.screens.home.quest.DialogChooseFormats; import forge.screens.home.quest.DialogChooseSets; import forge.screens.match.controllers.CDetailPicture; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.Localizer; import javax.swing.*; @@ -98,7 +99,7 @@ public class CardManager extends ItemManager { // Use standard sort + index, for better performance! Collections.sort(acceptedEditions); if (StaticData.instance().cardArtPreferenceIsLatest()) - Collections.reverse(acceptedEditions); + CollectionUtil.reverse(acceptedEditions); Iterator editionIterator = acceptedEditions.iterator(); Entry candidateEntry = null; Entry firstCandidateEntryFound = null; diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CProbabilities.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CProbabilities.java index 2920544e72e..bc334f38bb9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CProbabilities.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CProbabilities.java @@ -1,7 +1,6 @@ package forge.screens.deckeditor.controllers; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -14,6 +13,7 @@ import forge.item.InventoryItem; import forge.item.PaperCard; import forge.screens.deckeditor.CDeckEditorUI; import forge.screens.deckeditor.views.VProbabilities; +import forge.util.CollectionUtil; import forge.util.ItemPool; import forge.util.MyRandom; @@ -63,7 +63,7 @@ public enum CProbabilities implements ICDoc { final List cardProbabilities = new ArrayList<>(); final List shuffled = deck.toFlatList(); - Collections.shuffle(shuffled, MyRandom.getRandom()); + CollectionUtil.shuffle(shuffled, MyRandom.getRandom()); // Log totals of each card for decrementing final Map cardTotals = new HashMap<>(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/DialogChooseSets.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/DialogChooseSets.java index 1bd34fdf521..6ffd7bfedac 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/DialogChooseSets.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/DialogChooseSets.java @@ -13,6 +13,7 @@ import forge.game.GameFormat; import forge.gui.SOverlayUtils; import forge.localinstance.skin.FSkinProp; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.Localizer; import net.miginfocom.swing.MigLayout; import forge.toolbox.FCheckBoxTree.FTreeNode; @@ -467,7 +468,7 @@ public class DialogChooseSets { FTreeNode setTypeNode = checkBoxTree.getNodeByKey(editionType); if (setTypeNode != null){ List activeChildNodes = checkBoxTree.getActiveChildNodes(setTypeNode); - Collections.shuffle(activeChildNodes); + CollectionUtil.shuffle(activeChildNodes); for (int i = 0; i < totalToSelect; i++) checkBoxTree.setNodeCheckStatus(activeChildNodes.get(i), true); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuDraft.java b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuDraft.java index d7e6e587cec..a9fd9d834e1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuDraft.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuDraft.java @@ -26,12 +26,12 @@ import forge.screens.deckeditor.controllers.CEditorDraftingProcess; import forge.screens.deckeditor.views.VProbabilities; import forge.screens.deckeditor.views.VStatistics; import forge.toolbox.FOptionPane; +import forge.util.CollectionUtil; import forge.util.Localizer; import javax.swing.*; import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -163,7 +163,7 @@ public enum CSubmenuDraft implements ICDoc { for(int i = 0; i < maxDecks; i++) { aiIndices.add(i); } - Collections.shuffle(aiIndices); + CollectionUtil.shuffle(aiIndices); aiIndices = aiIndices.subList(0, numOpponents); for(int i : aiIndices) { diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java index 4fd38e12023..8c36a5f347e 100644 --- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java +++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java @@ -2,7 +2,6 @@ package forge.view; import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -10,6 +9,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import forge.util.CollectionUtil; import org.apache.commons.lang3.time.StopWatch; import forge.LobbyPlayer; @@ -201,7 +201,7 @@ public class SimulateMatch { } else { log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS); } - Collections.reverse(log); + CollectionUtil.reverse(log); for (GameLogEntry l : log) { System.out.println(l); } diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java index d5afbe7aba2..c52b899dad4 100644 --- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java +++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java @@ -2,13 +2,13 @@ package forge.deck; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import forge.util.CollectionUtil; import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Predicate; @@ -701,7 +701,7 @@ public class DeckgenUtil { }else { String matrixKey = (format.equals(DeckFormat.TinyLeaders) ? DeckFormat.Commander : format).toString(); //use Commander for Tiny Leaders List> potentialCards = new ArrayList<>(CardRelationMatrixGenerator.cardPools.get(matrixKey).get(commander.getName())); - Collections.shuffle(potentialCards, MyRandom.getRandom()); + CollectionUtil.shuffle(potentialCards, MyRandom.getRandom()); for(Map.Entry pair:potentialCards){ if(format.isLegalCard(pair.getKey())) { preSelectedCards.add(pair.getKey()); @@ -800,7 +800,7 @@ public class DeckgenUtil { break; } List cardList = Lists.newArrayList(colorList); - Collections.shuffle(cardList, MyRandom.getRandom()); + CollectionUtil.shuffle(cardList, MyRandom.getRandom()); int shortlistlength=400; if(cardList.size() { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchiveBlock c = SGuiChoose.oneOrNone("Select a Net Deck Archive Block category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchiveLegacy.java b/forge-gui/src/main/java/forge/deck/NetDeckArchiveLegacy.java index 667fc26aed0..7892ff690ec 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchiveLegacy.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchiveLegacy.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchiveLegacy extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchiveLegacy c = SGuiChoose.oneOrNone("Select a Net Deck Archive Legacy category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchiveModern.java b/forge-gui/src/main/java/forge/deck/NetDeckArchiveModern.java index 2060490abc5..4092efb01c2 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchiveModern.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchiveModern.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchiveModern extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchiveModern c = SGuiChoose.oneOrNone("Select a Net Deck Archive Modern category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchivePauper.java b/forge-gui/src/main/java/forge/deck/NetDeckArchivePauper.java index ec60dcb8658..751bdb5b400 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchivePauper.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchivePauper.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchivePauper extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchivePauper c = SGuiChoose.oneOrNone("Select a Net Deck Archive Pauper category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchivePioneer.java b/forge-gui/src/main/java/forge/deck/NetDeckArchivePioneer.java index 903d120da1e..02efbee551d 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchivePioneer.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchivePioneer.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchivePioneer extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchivePioneer c = SGuiChoose.oneOrNone("Select a Net Deck Archive Pioneer category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchiveStandard.java b/forge-gui/src/main/java/forge/deck/NetDeckArchiveStandard.java index 62ae7f782a0..b7176c43cf7 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchiveStandard.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchiveStandard.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchiveStandard extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchiveStandard c = SGuiChoose.oneOrNone("Select a Net Deck Archive Standard category",category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/deck/NetDeckArchiveVintage.java b/forge-gui/src/main/java/forge/deck/NetDeckArchiveVintage.java index 97a917362f0..b8bc583f0e5 100644 --- a/forge-gui/src/main/java/forge/deck/NetDeckArchiveVintage.java +++ b/forge-gui/src/main/java/forge/deck/NetDeckArchiveVintage.java @@ -7,6 +7,7 @@ import forge.gui.GuiBase; import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SGuiChoose; import forge.localinstance.properties.ForgeConstants; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.WaitCallback; import forge.util.storage.StorageBase; @@ -71,7 +72,7 @@ public class NetDeckArchiveVintage extends StorageBase { } List category = new ArrayList<>(categories.values()); - Collections.reverse(category); + CollectionUtil.reverse(category); final NetDeckArchiveVintage c = SGuiChoose.oneOrNone("Select a Net Deck Archive Vintage category", category); if (c == null) { return null; } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java index 9fb12cbec15..b8ccac3bc89 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java @@ -36,6 +36,7 @@ import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgePreferences; import forge.model.CardBlock; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.ItemPool; import forge.util.Localizer; @@ -474,7 +475,7 @@ public class BoosterDraft implements IBoosterDraft { // Maybe the AI could have more knowledge about the other players. // Like don't pass to players that have revealed certain cards or colors // But random is probably fine for now - Collections.shuffle(dredgers); + CollectionUtil.shuffle(dredgers); passToPlayer = dredgers.get(0); } else { // Human player, so we need to ask them @@ -556,7 +557,7 @@ public class BoosterDraft implements IBoosterDraft { } } - Collections.shuffle(brokers); + CollectionUtil.shuffle(brokers); for(LimitedPlayer pl : brokers) { pl.activateBrokers(this.players); } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java index a7bb88aee3e..ec25878a6bf 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java @@ -1,7 +1,6 @@ package forge.gamemodes.limited; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -34,6 +33,7 @@ import forge.item.IPaperCard; import forge.item.PaperCard; import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.MyRandom; /** @@ -628,7 +628,7 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase { possibleList.removeAll(StaticData.instance().getCommonCards().getAllCards(secondKeyCard.getName())); } //Iterator iRandomPool = CardRanker.rankCardsInDeck(possibleList.subList(0, targetSize <= possibleList.size() ? targetSize : possibleList.size())).iterator(); - Collections.shuffle(possibleList); + CollectionUtil.shuffle(possibleList); Iterator iRandomPool = possibleList.iterator(); while (deckList.size() < targetSize) { if (logToConsole) { @@ -864,7 +864,7 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase { if (secondKeyCard != null) { possibleList.removeAll(StaticData.instance().getCommonCards().getAllCards(secondKeyCard.getName())); } - Collections.shuffle(possibleList); + CollectionUtil.shuffle(possibleList); //addManaCurveCards(CardRanker.rankCardsInDeck(possibleList.subList(0, targetSize*3 <= possibleList.size() ? targetSize*3 : possibleList.size())), //num, "Random Card"); addManaCurveCards(possibleList, num, "Random Card"); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java index 1a9bae2da9c..89e23687840 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -11,6 +11,7 @@ import forge.deck.DeckSection; import forge.gui.util.SGuiChoose; import forge.item.PaperCard; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.TextUtil; import java.util.*; @@ -723,7 +724,7 @@ public class LimitedPlayer { } public PaperCard pickFromArchdemonCurse(DraftPack chooseFrom) { - Collections.shuffle(chooseFrom); + CollectionUtil.shuffle(chooseFrom); reduceArchdemonOfPalianoCurse(); return chooseFrom.get(0); } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java index 21da70a76e5..f81da820c98 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java @@ -10,10 +10,10 @@ import forge.deck.DeckSection; import forge.deck.generation.DeckGeneratorBase; import forge.item.PaperCard; import forge.localinstance.properties.ForgePreferences; +import forge.util.CollectionUtil; import forge.util.MyRandom; import org.apache.commons.lang3.tuple.Pair; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -81,7 +81,7 @@ public class LimitedPlayerAI extends LimitedPlayer { // For Paliano, if player has revealed anything, try to avoid that color // For Regicide, don't choose one of my colors } - Collections.shuffle(colors); + CollectionUtil.shuffle(colors); return colors.get(0); } @@ -89,7 +89,7 @@ public class LimitedPlayerAI extends LimitedPlayer { protected String removeWithAny(PaperCard bestPick, List options) { // If we have multiple remove from draft options, do none of them for now - Collections.shuffle(options); + CollectionUtil.shuffle(options); if (options.get(0).equals("Animus of Predation")) { if (removeWithAnimus(bestPick)) { return "Animus of Predation"; @@ -254,7 +254,7 @@ public class LimitedPlayerAI extends LimitedPlayer { @Override protected CardEdition chooseEdition(List possibleEditions) { - Collections.shuffle(possibleEditions); + CollectionUtil.shuffle(possibleEditions); return possibleEditions.get(0); } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/SealedCardPoolGenerator.java b/forge-gui/src/main/java/forge/gamemodes/limited/SealedCardPoolGenerator.java index d110d7759e5..9d5fe9f7d0a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/SealedCardPoolGenerator.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/SealedCardPoolGenerator.java @@ -37,6 +37,7 @@ import forge.localinstance.skin.FSkinProp; import forge.model.CardBlock; import forge.model.FModel; import forge.model.UnOpenedMeta; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.Localizer; import forge.util.MyRandom; @@ -173,7 +174,7 @@ public class SealedCardPoolGenerator { case Prerelease: ArrayList editions = Lists.newArrayList(StaticData.instance().getEditions().getPrereleaseEditions()); Collections.sort(editions); - Collections.reverse(editions); + CollectionUtil.reverse(editions); CardEdition chosenEdition = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblChooseAnEdition"), editions); if (chosenEdition == null) { diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/WinstonDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/WinstonDraft.java index 613aa3a1828..a3a3fd9f935 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/WinstonDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/WinstonDraft.java @@ -1,7 +1,6 @@ package forge.gamemodes.limited; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Stack; @@ -12,6 +11,7 @@ import com.google.common.collect.Iterables; import forge.deck.CardPool; import forge.deck.Deck; import forge.item.PaperCard; +import forge.util.CollectionUtil; import forge.util.MyRandom; public class WinstonDraft extends BoosterDraft { @@ -47,7 +47,7 @@ public class WinstonDraft extends BoosterDraft { } } } - Collections.shuffle(this.deck, MyRandom.getRandom()); + CollectionUtil.shuffle(this.deck, MyRandom.getRandom()); // Create three Winston piles, adding the top card from the Winston deck to start each pile this.piles = new ArrayList<>(); diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java index ae7bee33b20..51d7142d96a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java @@ -42,6 +42,7 @@ import forge.localinstance.properties.ForgeConstants; import forge.localinstance.skin.ISkinImage; import forge.model.FModel; import forge.util.CardTranslation; +import forge.util.CollectionUtil; import forge.util.FileUtil; import forge.util.Localizer; import forge.util.XmlReader; @@ -589,7 +590,7 @@ public final class ConquestData { path.add(current.loc); current = current.came_from; } - Collections.reverse(path); //reverse path so it begins with start location + CollectionUtil.reverse(path); //reverse path so it begins with start location return path; } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java index e3d851abecc..76760120093 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java @@ -20,10 +20,10 @@ package forge.gamemodes.quest; import static forge.gamemodes.quest.QuestUtilCards.isLegalInQuestFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import forge.util.CollectionUtil; import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; @@ -236,7 +236,7 @@ public final class BoosterUtils { preferredColors.clear(); int numberOfColors = COLOR_COUNT_PROBABILITIES[(int) (MyRandom.getRandom().nextDouble() * COLOR_COUNT_PROBABILITIES.length)]; if (numberOfColors < 6) { - Collections.shuffle(possibleColors); + CollectionUtil.shuffle(possibleColors); for (int i = 0; i < numberOfColors; i++) { preferredColors.add(possibleColors.get(i)); } @@ -391,7 +391,7 @@ public final class BoosterUtils { final int size = allowedColors == null ? 0 : allowedColors.size(); if (allowedColors != null) { - Collections.shuffle(allowedColors); + CollectionUtil.shuffle(allowedColors); } int cntMade = 0, iAttempt = 0; diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/MainWorldEventDuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/MainWorldEventDuelManager.java index d7ac90f1454..d3779651930 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/MainWorldEventDuelManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/MainWorldEventDuelManager.java @@ -3,7 +3,6 @@ package forge.gamemodes.quest; import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import forge.gamemodes.quest.data.QuestPreferences; @@ -12,6 +11,7 @@ import forge.gamemodes.quest.data.QuestPreferences.QPref; import forge.gamemodes.quest.io.MainWorldDuelReader; import forge.model.FModel; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.maps.EnumMapOfLists; import forge.util.maps.MapOfLists; @@ -259,7 +259,7 @@ public class MainWorldEventDuelManager implements QuestEventDuelManagerInterface public void randomizeOpponents() { for (QuestEventDifficulty qd : sortedDuels.keySet()) { List list = (List) sortedDuels.get(qd); - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestController.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestController.java index 70412d60b60..9b6db1f4666 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestController.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestController.java @@ -19,7 +19,6 @@ package forge.gamemodes.quest; import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -51,6 +50,7 @@ import forge.item.PreconDeck; import forge.localinstance.properties.ForgeConstants; import forge.model.FModel; import forge.player.GamePlayerUtil; +import forge.util.CollectionUtil; import forge.util.storage.IStorage; import forge.util.storage.StorageBase; @@ -612,7 +612,7 @@ public class QuestController { } } - Collections.shuffle(unlockedChallengeIds); + CollectionUtil.shuffle(unlockedChallengeIds); maxChallenges = Math.min(maxChallenges, unlockedChallengeIds.size()); diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java index 686b37d7839..3b1b95445c0 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java @@ -1,7 +1,6 @@ package forge.gamemodes.quest; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import forge.deck.CardPool; @@ -12,6 +11,7 @@ import forge.deck.DeckProxy; import forge.gamemodes.quest.data.QuestPreferences; import forge.item.PaperCard; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.MyRandom; /** @@ -198,6 +198,6 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte * Randomizes the list of Commander Duels. */ public void randomizeOpponents(){ - Collections.shuffle(commanderDuels); + CollectionUtil.shuffle(commanderDuels); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java index 1a3a2137df2..1697f194a0f 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java @@ -46,6 +46,7 @@ import forge.item.PaperCard; import forge.model.CardBlock; import forge.model.FModel; import forge.player.GamePlayerUtil; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.NameGenerator; import forge.util.TextUtil; @@ -856,7 +857,7 @@ public class QuestEventDraft implements IQuestEvent { return null; } - Collections.shuffle(possibleFormats); + CollectionUtil.shuffle(possibleFormats); return getDraftOrNull(quest, possibleFormats.get(0)); } @@ -885,7 +886,7 @@ public class QuestEventDraft implements IQuestEvent { System.err.println("Warning: no valid set combinations were detected when trying to generate a draft tournament for the format: " + format); return null; } - Collections.shuffle(possibleSetCombinations); + CollectionUtil.shuffle(possibleSetCombinations); event.boosterConfiguration = possibleSetCombinations.get(0); } @@ -902,7 +903,7 @@ public class QuestEventDraft implements IQuestEvent { players.add("6"); players.add("7"); - Collections.shuffle(players); + CollectionUtil.shuffle(players); // Initialize tournament for (int i = 0; i < players.size(); i++) { diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDuelManager.java index 9f147e80cbc..23fdfcc2774 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDuelManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDuelManager.java @@ -20,7 +20,6 @@ package forge.gamemodes.quest; import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import forge.gamemodes.quest.data.QuestPreferences; @@ -29,6 +28,7 @@ import forge.gamemodes.quest.data.QuestPreferences.QPref; import forge.gamemodes.quest.io.QuestDuelReader; import forge.model.FModel; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.maps.EnumMapOfLists; import forge.util.maps.MapOfLists; @@ -236,7 +236,7 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { public void randomizeOpponents() { for (QuestEventDifficulty qd : sortedDuels.keySet()) { List list = (List) sortedDuels.get(qd); - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventLDADuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventLDADuelManager.java index e85d3a2f391..c110e1eeff4 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventLDADuelManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventLDADuelManager.java @@ -19,7 +19,6 @@ package forge.gamemodes.quest; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import forge.deck.CardArchetypeLDAGenerator; @@ -30,6 +29,7 @@ import forge.gamemodes.quest.data.QuestPreferences.DifficultyPrefs; import forge.gamemodes.quest.data.QuestPreferences.QPref; import forge.model.FModel; import forge.util.CollectionSuppliers; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.maps.EnumMapOfLists; import forge.util.maps.MapOfLists; @@ -236,7 +236,7 @@ public class QuestEventLDADuelManager implements QuestEventDuelManagerInterface public void randomizeOpponents() { for (QuestEventDifficulty qd : sortedDuels.keySet()) { List list = (List) sortedDuels.get(qd); - Collections.shuffle(list, MyRandom.getRandom()); + CollectionUtil.shuffle(list, MyRandom.getRandom()); } } } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java index 063cb5d0e36..bc03895ca91 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java @@ -42,13 +42,13 @@ import forge.item.generation.UnOpenedProduct; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; import forge.util.Aggregates; +import forge.util.CollectionUtil; import forge.util.ItemPool; import forge.util.MyRandom; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map.Entry; @@ -678,7 +678,7 @@ public final class QuestUtilCards { editions.add(e); } - Collections.shuffle(editions); + CollectionUtil.shuffle(editions); int numberOfBoxes = Math.min(Math.max(count / 2, 1), editions.size()); diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilUnlockSets.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilUnlockSets.java index 7e24021c3d4..12c7720699e 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilUnlockSets.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilUnlockSets.java @@ -17,12 +17,6 @@ */ package forge.gamemodes.quest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -36,6 +30,7 @@ import forge.item.PaperCard; import forge.item.SealedTemplate; import forge.item.generation.UnOpenedProduct; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.TextUtil; import forge.util.storage.IStorage; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -176,7 +171,7 @@ public class QuestUtilUnlockSets { options.add(set.left); // System.out.println("Padded with: " + fillers.get(i).getName()); } - Collections.reverse(options); + CollectionUtil.reverse(options); if (FModel.getQuestPreferences().getPrefInt(QPref.UNLIMITED_UNLOCKING) == 0) { return options.subList(0, Math.min(options.size(), Math.min(8, 2 + ((qData.getAchievements().getWin()) / 50)))); diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/setrotation/QueueRandomRotation.java b/forge-gui/src/main/java/forge/gamemodes/quest/setrotation/QueueRandomRotation.java index bc52df6736f..4a455fbe60d 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/setrotation/QueueRandomRotation.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/setrotation/QueueRandomRotation.java @@ -1,10 +1,10 @@ package forge.gamemodes.quest.setrotation; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Random; import forge.model.FModel; +import forge.util.CollectionUtil; import static java.lang.Integer.min; @@ -27,7 +27,7 @@ public class QueueRandomRotation implements ISetRotation { int seed = FModel.getQuest().getName().hashCode(); Random rnd = new Random(seed); List shuffledSets = new ArrayList<>(allSets); - Collections.shuffle(shuffledSets, rnd); + CollectionUtil.shuffle(shuffledSets, rnd); List currentCodes = new ArrayList<>(); int outRotations = FModel.getQuest().getAchievements().getWin() / rotateAfterWins; diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java index 9a12e54cdc2..79b9b7d71a5 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java @@ -2,7 +2,6 @@ package forge.gamemodes.tournament.system; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.google.common.collect.Lists; @@ -12,6 +11,7 @@ import forge.LobbyPlayer; import forge.deck.DeckGroup; import forge.game.player.RegisteredPlayer; import forge.player.GamePlayerUtil; +import forge.util.CollectionUtil; import forge.util.MyRandom; import forge.util.TextUtil; @@ -44,7 +44,7 @@ public abstract class AbstractTournament implements Serializable { public void initializeTournament() { // "Randomly" seed players to start tournament - Collections.shuffle(remainingPlayers, MyRandom.getRandom()); + CollectionUtil.shuffle(remainingPlayers, MyRandom.getRandom()); generateActivePairings(); initialized = true; } diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java index 3f617912ed2..1ecff24af74 100644 --- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java +++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java @@ -1,13 +1,13 @@ package forge.gamemodes.tournament.system; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import com.google.common.collect.Lists; +import forge.util.CollectionUtil; @SuppressWarnings("serial") public class TournamentSwiss extends AbstractTournament { @@ -40,7 +40,7 @@ public class TournamentSwiss extends AbstractTournament { activeRound++; // Randomize players, then sort by scores - Collections.shuffle(allPlayers); + CollectionUtil.shuffle(allPlayers); sortAllPlayers("swiss"); if (allPlayers.size() % 2 == 1) { diff --git a/forge-gui/src/main/java/forge/itemmanager/GroupDef.java b/forge-gui/src/main/java/forge/itemmanager/GroupDef.java index fb921e447c6..6238d0f051f 100644 --- a/forge-gui/src/main/java/forge/itemmanager/GroupDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/GroupDef.java @@ -15,6 +15,7 @@ import forge.deck.DeckProxy; import forge.item.InventoryItem; import forge.item.PaperCard; import forge.model.FModel; +import forge.util.CollectionUtil; import forge.util.Localizer; public enum GroupDef { @@ -239,7 +240,7 @@ public enum GroupDef { //build sorted list of sets List sortedSets = Lists.newArrayList(FModel.getMagicDb().getEditions()); Collections.sort(sortedSets); - Collections.reverse(sortedSets); + CollectionUtil.reverse(sortedSets); int groupNum = 0; String[] setGroups = new String[sortedSets.size()]; diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 73402c00b5a..84e833a70bf 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -465,7 +465,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { } private PaymentDecision exileFromTopGraveType(final int nNeeded, final CardCollection typeList) { - Collections.reverse(typeList); + CollectionUtil.reverse(typeList); return PaymentDecision.card(Iterables.limit(typeList, nNeeded)); } diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index b5f64a0fcf9..024bbf7b8d0 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1141,7 +1141,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } endTempShowCards(); if(topOfDeck) - Collections.reverse(choices); + CollectionUtil.reverse(choices); CardCollection result = new CardCollection(); gameCacheMove.addToList(choices, result); return result; @@ -1663,7 +1663,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont Collections.sort(sortedResults); if (!call) { - Collections.reverse(sortedResults); + CollectionUtil.reverse(sortedResults); } return getGui().one(sa.getHostCard().getName() + " - " + localizer.getMessage("lblChooseAResult"), sortedResults).equals(labelsSrc[0]); }