CountersMultiply: add new API type for double counters on cards

remove it from RepeatEachAi
add more AI logic into it. it does support Strive Spell
This commit is contained in:
Hanmac
2016-11-20 15:03:27 +00:00
parent df1557831d
commit c9d6f72d77
7 changed files with 290 additions and 28 deletions

2
.gitattributes vendored
View File

@@ -68,6 +68,7 @@ forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java -text
forge-ai/src/main/java/forge/ai/ability/CounterAi.java -text forge-ai/src/main/java/forge/ai/ability/CounterAi.java -text
forge-ai/src/main/java/forge/ai/ability/CountersAi.java svneol=native#text/plain forge-ai/src/main/java/forge/ai/ability/CountersAi.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java -text
forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java -text svneol=unset#text/plain
forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java -text
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java -text
forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java -text
@@ -369,6 +370,7 @@ forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java -te
forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java -text svneol=unset#text/plain
forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text

View File

@@ -43,7 +43,6 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.zone.MagicStack; import forge.game.zone.MagicStack;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -1381,9 +1380,8 @@ public class ComputerUtilCard {
public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) { public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true); final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
final CardCollection threatenedTargets = new CardCollection(); final CardCollection threatenedTargets = new CardCollection();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) { if (!sa.usesTargeting()) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
for (final Card card : cards) { for (final Card card : cards) {
if (objects.contains(card)) { if (objects.contains(card)) {
@@ -1393,9 +1391,8 @@ public class ComputerUtilCard {
// For pumps without targeting restrictions, just return immediately until this is fleshed out. // For pumps without targeting restrictions, just return immediately until this is fleshed out.
return false; return false;
} }
CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
CardCollection targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
targetables = CardLists.getTargetableCards(targetables, sa);
targetables = ComputerUtil.getSafeTargets(ai, sa, targetables); targetables = ComputerUtil.getSafeTargets(ai, sa, targetables);
for (final Card c : targetables) { for (final Card c : targetables) {
if (objects.contains(c)) { if (objects.contains(c)) {
@@ -1405,13 +1402,14 @@ public class ComputerUtilCard {
if (!threatenedTargets.isEmpty()) { if (!threatenedTargets.isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(threatenedTargets); ComputerUtilCard.sortByEvaluateCreature(threatenedTargets);
for (Card c : threatenedTargets) { for (Card c : threatenedTargets) {
sa.getTargets().add(c); if (sa.canAddMoreTarget()) {
if (sa.getTargets().getNumTargeted() >= tgt.getMaxTargets(sa.getHostCard(), sa)) { sa.getTargets().add(c);
break; if (!sa.canAddMoreTarget()) {
break;
}
} }
} }
if (sa.getTargets().getNumTargeted() > tgt.getMaxTargets(sa.getHostCard(), sa) if (!sa.isTargetNumberValid()) {
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets(); sa.resetTargets();
return false; return false;
} }

View File

@@ -88,6 +88,7 @@ public enum SpellApiToAi {
.put(ApiType.Mill, MillAi.class) .put(ApiType.Mill, MillAi.class)
.put(ApiType.MoveCounter, CountersMoveAi.class) .put(ApiType.MoveCounter, CountersMoveAi.class)
.put(ApiType.MultiplePiles, CannotPlayAi.class) .put(ApiType.MultiplePiles, CannotPlayAi.class)
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
.put(ApiType.MustAttack, MustAttackAi.class) .put(ApiType.MustAttack, MustAttackAi.class)
.put(ApiType.MustBlock, MustBlockAi.class) .put(ApiType.MustBlock, MustBlockAi.class)
.put(ApiType.NameCard, ChooseCardNameAi.class) .put(ApiType.NameCard, ChooseCardNameAi.class)

View File

@@ -0,0 +1,212 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class CountersMultiplyAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final CounterType counterType = getCounterType(sa);
if (!sa.usesTargeting()) {
// defined are mostly Self or Creatures you control
CardCollection list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
} else {
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
// has negative counter it would double
if (ComputerUtil.isNegativeCounter(e.getKey(), c)) {
return false;
}
}
}
return true;
}
});
if (list.isEmpty()) {
return false;
}
} else {
return setTargets(ai, sa);
}
return super.checkApiLogic(ai, sa);
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final CounterType counterType = getCounterType(sa);
if (!CounterType.P1P1.equals(counterType) && counterType != null) {
if (!sa.hasParam("ActivationPhases")) {
// Don't use non P1P1/M1M1 counters before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa)) {
return false;
}
}
}
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting() && !setTargets(ai, sa) && !mandatory) {
return false;
}
return true;
}
private CounterType getCounterType(SpellAbility sa) {
if (sa.hasParam("CounterType")) {
try {
return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa);
} catch (Exception e) {
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
return null;
}
}
return null;
}
private boolean setTargets(Player ai, SpellAbility sa) {
final CounterType counterType = getCounterType(sa);
final Game game = ai.getGame();
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
// pre filter targetable cards with counters and can receive one of
// them
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
}
return true;
}
});
CardCollection aiList = CardLists.filterControlledBy(list, ai);
if (!aiList.isEmpty()) {
// counter type list to check
// first loyalty, then P1P!, then Charge Counter
List<CounterType> typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
for (CounterType type : typeList) {
// enough targets
if (!sa.canAddMoreTarget()) {
break;
}
if (counterType == null || counterType == type) {
addTargetsByCounterType(ai, sa, aiList, type);
}
}
}
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty()) {
// not enough targets
if (sa.canAddMoreTarget()) {
final CounterType type = CounterType.M1M1;
if (counterType == null || counterType == type) {
addTargetsByCounterType(ai, sa, oppList, type);
}
}
}
// targeting does failed
if (!sa.isTargetNumberValid()) {
sa.resetTargets();
return false;
}
return true;
}
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
final CounterType type) {
CardCollection newList = CardLists.filter(list, CardPredicates.hasCounter(type));
if (newList.isEmpty()) {
return;
}
newList.sort(Collections.reverseOrder(CardPredicates.compareByCounterType(type)));
while (sa.canAddMoreTarget()) {
if (newList.isEmpty()) {
break;
}
Card c = newList.remove(0);
sa.getTargets().add(c);
// check if Spell with Strive is still playable
if (sa.isSpell() && sa.getHostCard().hasStartOfKeyword("Strive")) {
// if not remove target again and break list
if (!ComputerUtilCost.canPayCost(sa, ai)) {
sa.getTargets().remove(c);
break;
}
}
}
}
}

View File

@@ -1,11 +1,12 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
@@ -14,8 +15,6 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.List;
public class RepeatEachAi extends SpellAbilityAi { public class RepeatEachAi extends SpellAbilityAi {
@@ -38,21 +37,6 @@ public class RepeatEachAi extends SpellAbilityAi {
if (compTokenCreats.size() <= humTokenCreats.size()) { if (compTokenCreats.size() <= humTokenCreats.size()) {
return false; return false;
} }
} else if ("DoubleCounters".equals(logic)) {
// TODO Improve this logic, double Planeswalker counters first, then +1/+1 on Useful creatures
// Then Charge Counters, then -1/-1 on Opposing Creatures
CardCollection perms = new CardCollection(aiPlayer.getCardsIn(ZoneType.Battlefield));
perms = CardLists.filter(CardLists.getTargetableCards(perms, sa), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasCounters();
}
});
if (perms.isEmpty()) {
return false;
}
CardLists.shuffle(perms);
sa.setTargetCard(perms.get(0));
} else if ("RemoveAllCounters".equals(logic)) { } else if ("RemoveAllCounters".equals(logic)) {
// Break Dark Depths // Break Dark Depths
CardCollectionView depthsList = aiPlayer.getCardsIn(ZoneType.Battlefield, "Dark Depths"); CardCollectionView depthsList = aiPlayer.getCardsIn(ZoneType.Battlefield, "Dark Depths");

View File

@@ -86,6 +86,7 @@ public enum ApiType {
Mill (MillEffect.class), Mill (MillEffect.class),
MoveCounter (CountersMoveEffect.class), MoveCounter (CountersMoveEffect.class),
MultiplePiles (MultiplePilesEffect.class), MultiplePiles (MultiplePilesEffect.class),
MultiplyCounter (CountersMultiplyEffect.class),
MustAttack (MustAttackEffect.class), MustAttack (MustAttackEffect.class),
MustBlock (MustBlockEffect.class), MustBlock (MustBlockEffect.class),
NameCard (ChooseCardNameEffect.class), NameCard (ChooseCardNameEffect.class),

View File

@@ -0,0 +1,64 @@
package forge.game.ability.effects;
import java.util.Map;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
public class CountersMultiplyEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final CounterType counterType = getCounterType(sa);
sb.append("Double the number of ");
if (counterType != null) {
sb.append(counterType.getName());
sb.append(" counters");
} else {
sb.append("each kind of counter");
}
sb.append(" on ");
sb.append(Lang.joinHomogenous(getTargetCards(sa)));
sb.append(".");
return sb.toString();
}
@Override
public void resolve(SpellAbility sa) {
final CounterType counterType = getCounterType(sa);
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
for (final Card tgtCard : getTargetCards(sa)) {
if (counterType != null) {
tgtCard.addCounter(counterType, tgtCard.getCounters(counterType) * n, false);
} else {
for (Map.Entry<CounterType, Integer> e : tgtCard.getCounters().entrySet()) {
tgtCard.addCounter(e.getKey(), e.getValue() * n, false);
}
}
}
}
private CounterType getCounterType(SpellAbility sa) {
if (sa.hasParam("CounterType")) {
try {
return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa);
} catch (Exception e) {
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
return null;
}
}
return null;
}
}