Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions forge-ai/src/main/java/forge/ai/ComputerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,10 @@ public static CardCollection chooseTapType(final Player ai, final String type, f
return null;
}

if (sa.isKeyword(Keyword.STATION)) {
typeList.removeAll(CardLists.filter(typeList, c -> c.getNetPower() <= 0));
}

CardLists.sortByPowerAsc(typeList);
// TODO prefer noncreatures without tap abilities

Expand Down
44 changes: 44 additions & 0 deletions forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
return doChargeToOppCtrlCMCLogic(ai, sa);
} else if (logic.equals("TheOneRing")) {
return SpecialCardAi.TheOneRing.consider(ai, sa);
} else if (sa.isKeyword(Keyword.STATION)) {
return doStationAi(ai, sa);
}

if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
Expand Down Expand Up @@ -1237,4 +1239,46 @@ private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa)
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}

private AiAbilityDecision doStationAi(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
PhaseHandler ph = source.getGame().getPhaseHandler();

int numStation = source.getKeywordMagnitude(Keyword.STATION);
int numCharge = source.getCounters(CounterEnumType.CHARGE);
CardCollection canTap = CardLists.filter(ai.getCreaturesInPlay(), c -> c.getNetPower() > 0 && c.isUntapped());
if (canTap.isEmpty()) {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}

CardLists.sortByPowerAsc(canTap); // Note: matches the way chooseTapType sorts them; maybe worth sorting in descending order for Station?

// TODO: make this smarter so that the AI is better at predicting conditions when this is safe
// (also needs a modification to willPayCosts and ComputerUtil.chooseTapType to make better choices for what exactly to tap)

// If a single creature is enough to turn an untapped station into a creature, allow it
if (ph.is(PhaseType.MAIN1, ai)) {
Card firstToTap = canTap.getFirst();
if (source.getType().hasSubtype("Spacecraft") && !source.isCreature() && source.isUntapped()) {
if (numCharge < numStation && numCharge + firstToTap.getNetPower() >= numStation
&& firstToTap.getNetPower() <= source.getBasePower()) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
}
}
}

// If there's nothing to possibly block next turn, or we can reasonably stay on high enough life, go for it
if (ph.is(PhaseType.MAIN2, ai)) {
List<Card> nextTurnAttackers = CardLists.filter(ai.getStrongestOpponent().getCreaturesInPlay(), c -> CombatUtil.canAttackNextTurn(c, ai));
CardCollection blockerList = CardLists.filter(canTap, CardPredicates.possibleBlockerForAtLeastOne(nextTurnAttackers));

if (numCharge < numStation) {
if (blockerList.isEmpty() || ComputerUtil.predictNextCombatsRemainingLife(ai, false, true, 0, blockerList) > ai.getStartingLife() * 2 / 3) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
}
}
}

return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
}
}
Loading