/** * chooseBoonTarget. * * @param list a {@link forge.CardList} object. * @param type a {@link java.lang.String} object. * @return a {@link forge.game.card.Card} object. */ public static Card chooseBoonTarget(final CardCollectionView list, final String type) { Card choice = null; if (type.equals("P1P1")) { choice = ComputerUtilCard.getBestCreatureAI(list); if (choice == null) { // We'd only get here if list isn't empty, maybe we're trying to animate a land? choice = ComputerUtilCard.getBestLandToAnimate(list); } } else if (type.equals("DIVINITY")) { final CardCollection boon = CardLists.filter( list, new Predicate<Card>() { @Override public boolean apply(final Card c) { return c.getCounters(CounterType.DIVINITY) == 0; } }); choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false); } else { // The AI really should put counters on cards that can use it. // Charge counters on things with Charge abilities, etc. Expand // these above choice = Aggregates.random(list); } return choice; }
/* * (non-Javadoc) * * @see * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override public final boolean canPay(final SpellAbility ability) { final Player activator = ability.getActivatingPlayer(); final Card source = ability.getHostCard(); CardCollectionView validCards = activator.getCardsIn(ZoneType.Battlefield); validCards = CardLists.getValidCards(validCards, this.getType().split(";"), activator, source); validCards = CardLists.filter( validCards, new Predicate<Card>() { @Override public boolean apply(final Card c) { return c.hasCounters(); } }); if (validCards.isEmpty()) { return false; } Integer i = this.convertAmount(); if (i == null) { i = AbilityUtils.calculateAmount(source, this.getAmount(), ability); } int allCounters = 0; for (Card c : validCards) { final Map<CounterType, Integer> tgtCounters = c.getCounters(); for (Integer value : tgtCounters.values()) { allCounters += value; } } return i <= allCounters; }
/** * chooseCursedTarget. * * @param list a {@link forge.CardList} object. * @param type a {@link java.lang.String} object. * @param amount a int. * @return a {@link forge.game.card.Card} object. */ public static Card chooseCursedTarget( final CardCollectionView list, final String type, final int amount) { Card choice; if (type.equals("M1M1")) { // try to kill the best killable creature, or reduce the best one final List<Card> killable = CardLists.filter( list, new Predicate<Card>() { @Override public boolean apply(final Card c) { return c.getNetToughness() <= amount; } }); if (killable.size() > 0) { choice = ComputerUtilCard.getBestCreatureAI(killable); } else { choice = ComputerUtilCard.getBestCreatureAI(list); } } else { // improve random choice here choice = Aggregates.random(list); } return choice; }
@Override public PaymentDecision visit(CostUntapType cost) { final String amount = cost.getAmount(); Integer c = cost.convertAmount(); if (c == null) { final String sVar = ability.getSVar(amount); if (sVar.equals("XChoice")) { List<Card> typeList = player.getGame().getCardsIn(ZoneType.Battlefield); typeList = CardLists.getValidCards( typeList, cost.getType().split(";"), player, ability.getHostCard()); if (!cost.canUntapSource) { typeList.remove(source); } typeList = CardLists.filter(typeList, Presets.TAPPED); c = typeList.size(); source.setSVar("ChosenX", "Number$" + Integer.toString(c)); } else { c = AbilityUtils.calculateAmount(source, amount, ability); } } List<Card> list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c); if (list == null) { System.out.println("Couldn't find a valid card to untap for: " + source.getName()); return null; } return PaymentDecision.card(list); }
@Override public PaymentDecision visit(CostTapType cost) { final String amount = cost.getAmount(); Integer c = cost.convertAmount(); if (c == null) { final String sVar = ability.getSVar(amount); if (sVar.equals("XChoice")) { List<Card> typeList = CardLists.getValidCards( player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), ability.getActivatingPlayer(), ability.getHostCard()); typeList = CardLists.filter(typeList, Presets.UNTAPPED); c = typeList.size(); source.setSVar("ChosenX", "Number$" + Integer.toString(c)); } else { c = AbilityUtils.calculateAmount(source, amount, ability); } } if (cost.getType().contains("sharesCreatureTypeWith") || cost.getType().contains("withTotalPowerGE")) { return null; } List<Card> totap = ComputerUtil.chooseTapType(player, cost.getType(), source, !cost.canTapSource, c); if (totap == null) { System.out.println("Couldn't find a valid card to tap for: " + source.getName()); return null; } return PaymentDecision.card(totap); }
@Override public PaymentDecision visit(CostPutCardToLib cost) { Integer c = cost.convertAmount(); final Game game = player.getGame(); List<Card> chosen = new ArrayList<Card>(); List<Card> list; if (cost.isSameZone()) { list = new ArrayList<Card>(game.getCardsIn(cost.getFrom())); } else { list = new ArrayList<Card>(player.getCardsIn(cost.getFrom())); } if (c == null) { final String sVar = ability.getSVar(cost.getAmount()); // Generalize cost if (sVar.equals("XChoice")) { return null; } c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); } list = CardLists.getValidCards(list, cost.getType().split(";"), player, source); if (cost.isSameZone()) { // Jotun Grunt // TODO: improve AI final List<Player> players = game.getPlayers(); for (Player p : players) { List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p)); if (enoughType.size() >= c) { chosen.addAll(enoughType); break; } } chosen = chosen.subList(0, c); } else { chosen = ComputerUtil.choosePutToLibraryFrom( player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c); } return chosen.isEmpty() ? null : PaymentDecision.card(chosen); }
@Override public Card chooseSingleCard( final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { if ("NeedsPrevention".equals(sa.getParam("AILogic"))) { final Player ai = sa.getActivatingPlayer(); final Game game = ai.getGame(); if (!game.getStack().isEmpty()) { Card choseCard = chooseCardOnStack(sa, ai, game); if (choseCard != null) { return choseCard; } } final Combat combat = game.getCombat(); List<Card> permanentSources = CardLists.filter( options, new Predicate<Card>() { @Override public boolean apply(final Card c) { if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield || combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; } }); return ComputerUtilCard.getBestCreatureAI(permanentSources); } else { return ComputerUtilCard.getBestAI(options); } }
@Override public PaymentDecision visit(CostRemoveAnyCounter cost) { final String amount = cost.getAmount(); final int c = AbilityUtils.calculateAmount(source, amount, ability); final String type = cost.getType(); List<Card> typeList = CardLists.getValidCards( player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source); List<Card> hperms = CardLists.filter( typeList, new Predicate<Card>() { @Override public boolean apply(final Card crd) { for (final CounterType c1 : CounterType.values()) { if (crd.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, crd)) { return true; } } return false; } }); if (hperms.isEmpty()) return null; PaymentDecision result = PaymentDecision.card(hperms); Card valid = hperms.get(0); for (CounterType c1 : valid.getCounters().keySet()) { if (valid.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, valid)) { result.ct = c1; break; } } // Only find cards with enough negative counters // TODO: add ai for Chisei, Heart of Oceans return result; }
private boolean pumpTgtAI( final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory) { final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>(); final Game game = ai.getGame(); final Card source = sa.getHostCard(); if (!mandatory && !sa.isTrigger() && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !(sa.isCurse() && defense < 0) && !this.containsNonCombatKeyword(keywords) && !sa.hasParam("UntilYourNextTurn")) { return false; } final Player opp = ai.getOpponent(); final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); sa.setTargetingPlayer(targetingPlayer); return targetingPlayer.getController().chooseTargetsFor(sa); } List<Card> list = new ArrayList<Card>(); if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("HighestPower")) { list = CardLists.getValidCards( CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source); list = CardLists.getTargetableCards(list, sa); CardLists.sortByPowerDesc(list); if (!list.isEmpty()) { sa.getTargets().add(list.get(0)); return true; } else { return false; } } if (sa.getParam("AILogic").equals("Fight") || sa.getParam("AILogic").equals("PowerDmg")) { final AbilitySub tgtFight = sa.getSubAbility(); List<Card> aiCreatures = ai.getCreaturesInPlay(); aiCreatures = CardLists.getTargetableCards(aiCreatures, sa); aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures); ComputerUtilCard.sortByEvaluateCreature(aiCreatures); // sort is suboptimal due to conflicting needs depending on game state: // -deathtouch for deal damage // -max power for damage to player // -survivability for generic "fight" // -no support for "heroic" List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay(); humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight); ComputerUtilCard.sortByEvaluateCreature(humCreatures); if (humCreatures.isEmpty() || aiCreatures.isEmpty()) { return false; } int buffedAtk = attack, buffedDef = defense; for (Card humanCreature : humCreatures) { for (Card aiCreature : aiCreatures) { if (sa.isSpell()) { // heroic triggers adding counters for (Trigger t : aiCreature.getTriggers()) { if (t.getMode() == TriggerType.SpellCast) { final Map<String, String> params = t.getMapParams(); if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer")) && params.containsKey("Execute")) { SpellAbility heroic = AbilityFactory.getAbility( aiCreature.getSVar(params.get("Execute")), aiCreature); if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { int amount = AbilityUtils.calculateAmount( aiCreature, heroic.getParam("CounterNum"), heroic); buffedAtk += amount; buffedDef += amount; } break; } } } } if (sa.getParam("AILogic").equals("PowerDmg")) { if (FightAi.canKill(aiCreature, humanCreature, buffedAtk)) { sa.getTargets().add(aiCreature); tgtFight.getTargets().add(humanCreature); return true; } } else { if (FightAi.shouldFight(aiCreature, humanCreature, buffedAtk, buffedDef)) { sa.getTargets().add(aiCreature); tgtFight.getTargets().add(humanCreature); return true; } } } } } return false; } else if (sa.isCurse()) { if (sa.canTarget(opp)) { sa.getTargets().add(opp); return true; } list = this.getCurseCreatures(ai, sa, defense, attack, keywords); } else { if (!tgt.canTgtCreature()) { ZoneType zone = tgt.getZone().get(0); list = game.getCardsIn(zone); } else { list = this.getPumpCreatures(ai, sa, defense, attack, keywords); } if (sa.canTarget(ai)) { sa.getTargets().add(ai); return true; } } list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source); if (game.getStack().isEmpty()) { // If the cost is tapping, don't activate before declare // attack/block if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) { if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && game.getPhaseHandler().isPlayerTurn(ai)) { list.remove(sa.getHostCard()); } if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && game.getPhaseHandler().isPlayerTurn(opp)) { list.remove(sa.getHostCard()); } } } if (list.isEmpty()) { return mandatory && this.pumpMandatoryTarget(ai, sa, mandatory); } if (!sa.isCurse()) { // Don't target cards that will die. list = ComputerUtil.getSafeTargets(ai, sa, list); } while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) { Card t = null; // boolean goodt = false; if (list.isEmpty()) { if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || (sa.getTargets().getNumTargeted() == 0)) { if (mandatory) { return this.pumpMandatoryTarget(ai, sa, mandatory); } sa.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } t = ComputerUtilCard.getBestAI(list); // option to hold removal instead only applies for single targeted removal if (!sa.isTrigger() && tgt.getMaxTargets(source, sa) == 1 && sa.isCurse()) { if (!ComputerUtilCard.useRemovalNow(sa, t, -defense, ZoneType.Graveyard)) { return false; } } sa.getTargets().add(t); list.remove(t); } return true; } // pumpTgtAI()
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) { final Card source = sa.getHostCard(); final TargetRestrictions tgt = sa.getTargetRestrictions(); final boolean destroy = sa.hasParam("Destroy"); Player opp = ai.getOpponent(); if (tgt != null) { sa.resetTargets(); if (!opp.canBeTargetedBy(sa)) { return false; } sa.getTargets().add(opp); final String valid = sa.getParam("SacValid"); String num = sa.getParam("Amount"); num = (num == null) ? "1" : num; final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa); List<Card> list = CardLists.getValidCards( ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard()); for (Card c : list) { if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) { return false; } } if (!destroy) { list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(sa)); } else { if (!CardLists.getKeyword(list, "Indestructible").isEmpty()) { // human can choose to destroy indestructibles return false; } } if (list.isEmpty()) { return false; } if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); source.setSVar("PayX", Integer.toString(xPay)); } final int half = (amount / 2) + (amount % 2); // Half of amount // rounded up // If the Human has at least half rounded up of the amount to be // sacrificed, cast the spell if (!sa.isTrigger() && list.size() < half) { return false; } } final String defined = sa.getParam("Defined"); final String valid = sa.getParam("SacValid"); if (defined == null) { // Self Sacrifice. } else if (defined.equals("Each") || (defined.equals("Opponent") && !sa.isTrigger())) { // If Sacrifice hits both players: // Only cast it if Human has the full amount of valid // Only cast it if AI doesn't have the full amount of Valid // TODO: Cast if the type is favorable: my "worst" valid is // worse than his "worst" valid final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1"; int amount = AbilityUtils.calculateAmount(source, num, sa); if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); } List<Card> humanList = CardLists.getValidCards( opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard()); // Since all of the cards have remAIDeck:True, I enabled 1 for 1 // (or X for X) trades for special decks if (humanList.size() < amount) { return false; } } else if (defined.equals("You")) { List<Card> computerList = CardLists.getValidCards( ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard()); for (Card c : computerList) { if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) <= 135) { return true; } } return false; } return true; }
/* (non-Javadoc) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) */ @Override protected boolean canPlayAI(final Player ai, SpellAbility sa) { // TODO: AI Support! Currently this is copied from AF ChooseCard. // When implementing AI, I believe AI also needs to be made aware of the damage sources // chosen // to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any // damage // to the player because a CoP was pre-activated on it - unless, of course, there's // another // possible reason to attack with that creature). final Card host = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); final Card source = sa.getHostCard(); if (abCost != null) { // AI currently disabled for these costs if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) { return false; } if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { return false; } if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { return false; } if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { return false; } } final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { sa.resetTargets(); if (sa.canTarget(ai.getOpponent())) { sa.getTargets().add(ai.getOpponent()); } else { return false; } } if (sa.hasParam("AILogic")) { final Game game = ai.getGame(); if (sa.getParam("AILogic").equals("NeedsPrevention")) { if (!game.getStack().isEmpty()) { final SpellAbility topStack = game.getStack().peekAbility(); if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) { return false; } final ApiType threatApi = topStack.getApi(); if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { return false; } final Card threatSource = topStack.getHostCard(); List<? extends GameObject> objects = getTargets(topStack); if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) { objects = AbilityUtils.getDefinedPlayers( threatSource, topStack.getParam("ValidPlayers"), topStack); } if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) { return false; } int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack); if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) { return false; } return true; } if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) { return false; } CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); if (sa.hasParam("Choices")) { choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); } final Combat combat = game.getCombat(); choices = CardLists.filter( choices, new Predicate<Card>() { @Override public boolean apply(final Card c) { if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; } }); if (choices.isEmpty()) { return false; } } } return true; }