Merge pull request #2217 from tool4ever/dedupe

Performance: improve deduping
This commit is contained in:
Anthony Calosa
2023-01-09 19:07:20 +08:00
committed by GitHub
14 changed files with 45 additions and 35 deletions

View File

@@ -1646,7 +1646,8 @@ public class AiController {
}
private final SpellAbility getSpellAbilityToPlay() {
final CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
cards = ComputerUtilCard.dedupeCards(cards);
List<SpellAbility> saList = Lists.newArrayList();
SpellAbility top = null;

View File

@@ -2052,6 +2052,28 @@ public class ComputerUtilCard {
return false;
}
public static CardCollection dedupeCards(CardCollection cc) {
if (cc.size() <= 1) {
return cc;
}
CardCollection deduped = new CardCollection();
for (Card c : cc) {
boolean unique = true;
if (c.isInZone(ZoneType.Hand)) {
for (Card d : deduped) {
if (d.isInZone(ZoneType.Hand) && d.getOwner().equals(c.getOwner()) && d.getName().equals(c.getName())) {
unique = false;
break;
}
}
}
if (unique) {
deduped.add(c);
}
}
return deduped;
}
// Determine if the AI has an AI:RemoveDeck:All or an AI:RemoveDeck:Random hint specified.
// Includes a NPE guard on getRules() which might otherwise be tripped on some cards (e.g. tokens).
public static boolean isCardRemAIDeck(final Card card) {

View File

@@ -200,8 +200,8 @@ public class ComputerUtilCombat {
return 0;
}
damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
sum = predictDamageTo(attacked, damage, attacker, true);
if (attacker.hasDoubleStrike()) {
sum *= 2;
@@ -2480,8 +2480,7 @@ public class ComputerUtilCombat {
// intern toxic effect
poison += attacker.getKeywordMagnitude(Keyword.TOXIC);
}
if (attacker.hasDoubleStrike())
{
if (attacker.hasDoubleStrike()) {
poison *= 2;
}
return poison;

View File

@@ -114,10 +114,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
value += addValue(power * 15, "infect");
}
else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(power * 10, "Wither");
value += addValue(power * 10, "wither");
}
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
value += addValue(c.getKeywordMagnitude(Keyword.TOXIC) * 5, "toxic");
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
}
value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");

View File

@@ -1066,9 +1066,7 @@ public abstract class GameState {
}
top.addMergedCard(bottom);
if (top.getMutatedTimestamp() != -1) {
top.removeCloneState(top.getMutatedTimestamp());
}
top.removeMutatedStates();
final long ts = game.getNextTimestamp();
top.setMutatedTimestamp(ts);

View File

@@ -73,7 +73,7 @@ public class MultiTargetSelector {
public void reset() {
for (PossibleTargetSelector selector : selectors) {
selector.reset();
selector.reset();
}
currentIndex = -1;
}

View File

@@ -2,7 +2,6 @@ package forge.ai.simulation;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Set;
@@ -10,6 +9,7 @@ import java.util.Set;
import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
@@ -66,25 +66,14 @@ public class SpellAbilityPicker {
private List<SpellAbility> getCandidateSpellsAndAbilities() {
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
cards = ComputerUtilCard.dedupeCards(cards);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
HashMap<String, Card> landsDeDupe = new HashMap<>();
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
int writeIndex = 0;
for (int i = 0; i < candidateSAs.size(); i++) {
SpellAbility sa = candidateSAs.get(i);
for (SpellAbility sa : candidateSAs) {
if (sa.isManaAbility()) {
continue;
}
// Skip identical lands.
if (sa instanceof LandAbility) {
Card land = sa.getHostCard();
Card previousLand = landsDeDupe.get(sa.getHostCard().getName());
if (previousLand != null && previousLand.getZone() == land.getZone() &&
previousLand.getOwner() == land.getOwner()) {
continue;
}
landsDeDupe.put(land.getName(), land);
}
sa.setActivatingPlayer(player, true);
AiPlayDecision opinion = canPlayAndPayForSim(sa);

View File

@@ -66,9 +66,7 @@ public class MutateEffect extends SpellAbilityEffect {
}
// First remove current mutated states
if (target.getMutatedTimestamp() != -1) {
target.removeCloneState(target.getMutatedTimestamp());
}
target.removeMutatedStates();
// Now add all abilities from bottom cards
final Long ts = game.getNextTimestamp();
target.setMutatedTimestamp(ts);

View File

@@ -1240,7 +1240,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final void removeMutatedStates() {
if (getMutatedTimestamp() != -1) {
if (isMutated()) {
removeCloneState(getMutatedTimestamp());
}
}
@@ -5801,6 +5801,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
return getCastSA().isMadness();
}
public boolean wasDiscarded() { return discarded; }
public void setDiscarded(boolean state) { discarded = state; }

View File

@@ -4,6 +4,6 @@ Types:Enchantment Aura Curse
K:Enchant player
A:SP$ Attach | Cost$ 4 U | ValidTgts$ Player | AILogic$ Curse
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ Player.EnchantedBy | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever enchanted player casts an instant or sorcery spell, each other player may copy that spell and may choose new targets for the copy they control.
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Controller$ TriggeredCardOpponent | Optional$ True | MayChooseTarget$ True
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Controller$ NonTriggeredCardController | Optional$ True | MayChooseTarget$ True
AI:RemoveDeck:All
Oracle:Enchant player\nWhenever enchanted player casts an instant or sorcery spell, each other player may copy that spell and may choose new targets for the copy they control.

View File

@@ -2,6 +2,6 @@ Name:Hive Mind
ManaCost:5 U
Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ Player | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever a player casts an instant or sorcery spell, each other player copies that spell. Each of those players may choose new targets for their copy.
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Controller$ TriggeredCardOpponent | MayChooseTarget$ True
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Controller$ NonTriggeredCardController | MayChooseTarget$ True
AI:RemoveDeck:Random
Oracle:Whenever a player casts an instant or sorcery spell, each other player copies that spell. Each of those players may choose new targets for their copy.

View File

@@ -4,9 +4,10 @@ Types:Legendary Creature Human Wizard
PT:5/5
S:Mode$ Continuous | Affected$ Creature.Legendary+YouCtrl | AddPower$ X | AddToughness$ X | Description$ Legendary creatures you control get +X/+X, where X is the number of legendary creatures you control.
T:Mode$ SpellCast | ValidCard$ Card.Legendary+wasCastFromYourHandByYou | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ DBDigUntil | TriggerDescription$ Whenever you cast a legendary spell from your hand, exile cards from the top of your library until you exile a legendary nonland card with lesser mana value. You may cast that card without paying its mana cost. Put the rest on the bottom of your library in a random order.
SVar:DBDigUntil:DB$ DigUntil | Defined$ You | Valid$ Card.nonLand+Legendary+cmcLTY | FoundDestination$ Exile | RevealedDestination$ Library| RememberFound$ True | RestRandomOrder$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:DBDigUntil:DB$ DigUntil | Defined$ You | Valid$ Card.nonLand+Legendary+cmcLTY | FoundDestination$ Exile | RevealedDestination$ Exile | ImprintFound$ True | RememberRevealed$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | Defined$ Imprinted | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBChange
SVar:DBChange:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:X:Count$Valid Creature.Legendary+YouCtrl
SVar:Y:TriggeredCard$CardManaCost
DeckHints:Type$Legendary

View File

@@ -4,7 +4,7 @@ Types:Legendary Snow Creature Shapeshifter
PT:0/0
K:Changeling
K:ETBReplacement:Copy:DBCopy:Optional
SVar:DBCopy:DB$ Clone | Choices$ Permanent.Other+YouCtrl | AddTypes$ Legendary & Snow | SubAbility$ DBConditionEffect | AddKeywords$ Changeling | SpellDescription$ You may have Moritte of the Frost enter the battlefield as a copy of a permanent you control, except it's legendary and snow in addition to its other types and, if it's a creature, it enters with two additional +1/+1 counters on it and has changeling.
SVar:DBCopy:DB$ Clone | Choices$ Permanent.Other+YouCtrl | AddTypes$ Legendary & Snow | SubAbility$ DBConditionEffect | AddKeywords$ Changeling | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of a permanent you control, except it's legendary and snow in addition to its other types and, if it's a creature, it enters with two additional +1/+1 counters on it and has changeling.
SVar:DBConditionEffect:DB$ Effect | RememberObjects$ Self | Name$ Moritte of the Frost Effect | ReplacementEffects$ ETBCreat
SVar:ETBCreat:Event$ Moved | ValidCard$ Creature.IsRemembered | Destination$ Battlefield | ReplaceWith$ DBPutP1P1 | ReplacementResult$ Updated | Description$ If it's a creature, it enters with two additional +1/+1 counters on it.
SVar:DBPutP1P1:DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ 2 | SubAbility$ DBExile

View File

@@ -124,7 +124,7 @@ public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSele
? String.format(message, selected.size())
: String.format(message, max - selected.size()));
if (sa != null && sa.hasParam("Crew")) {
if (sa != null && sa.hasParam("Crew") && sa.getPayCosts().hasSpecificCostType(CostTapType.class)) {
msg.append("\nCrewing: ").
append(CardLists.getTotalPower((FCollection<Card>)getSelected(), true, true)).
append(" / ").append(TextUtil.fastReplace(sa.getPayCosts().getCostPartByType(CostTapType.class).getType(), "Creature.Other+withTotalPowerGE", ""));