Improve AI logic for Connive / Change of Plans (#2367)

* - Update the MTGDecksNet conversion and AI playability test toolchain to the latest version (Python 3 compatible).

* - Make the input/output folders generic

* - Make the input/output folders generic, part 2.

* - Add the Scryfall all-prices.txt generator script.

* - Minor code cleanup.

* - Improve ConniveAi for Change of Plans.

* - Check if the AI can draw cards for ConniveAI
This commit is contained in:
Agetian
2023-02-02 12:07:21 +03:00
committed by GitHub
parent aff8518e22
commit 7c50111129
3 changed files with 71 additions and 8 deletions

View File

@@ -12,16 +12,67 @@ import forge.game.zone.ZoneType;
public class ConniveAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
boolean preferred = true;
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (!ai.canDraw()) {
return false; // can't draw anything
}
CardCollection list;
list = CardLists.getTargetableCards(new CardCollection(ai.getCardsIn(ZoneType.Battlefield)), sa);
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
int totalTargets = list.size();
int maxTargets = sa.getMaxTargets();
if ("X".equals(sa.getParam("TargetMax")) && "Count$xPaid".equals(sa.getSVar("X"))) {
// TODO: consider making the library margin (currently hardcoded to 5) a configurable AI parameter
maxTargets = Math.min(list.size(), Math.max(0, ai.getCardsIn(ZoneType.Library).size() - 5));
sa.setXManaCostPaid(maxTargets);
}
sa.resetTargets();
while (sa.canAddMoreTarget()) {
if ((list.isEmpty() && sa.isTargetNumberValid() && !sa.getTargets().isEmpty())) {
return true;
}
if (list.isEmpty()) {
// Still an empty list, but we have to choose something (mandatory); expand targeting to
// include AI's own cards to see if there's anything targetable (e.g. Plague Belcher).
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
}
if (list.isEmpty()) {
// Not mandatory, or the the list was regenerated and is still empty,
// so return whether or not we found enough targets
return sa.isTargetNumberValid();
}
Card choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice != null) {
sa.getTargets().add(choice);
list.remove(choice);
} else {
// Didn't want to choose anything?
list.clear();
}
}
return !sa.getTargets().isEmpty() && sa.isTargetNumberValid();
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (!ai.canDraw() && !mandatory) {
return false; // can't draw anything
}
boolean preferred = true;
CardCollection list;
list = CardLists.getTargetableCards(new CardCollection(ai.getCardsIn(ZoneType.Battlefield)), sa);
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
sa.resetTargets();
while (sa.canAddMoreTarget()) {

View File

@@ -1,13 +1,11 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
@@ -19,6 +17,10 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class PhasesAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
@@ -126,4 +128,14 @@ public class PhasesAi extends SpellAbilityAi {
return false;
}
@Override
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
// TODO: improve the selection logic, e.g. for cards like Change of Plans. Currently will
// confirm everything unless AILogic is "DontPhaseOut", in which case it'll confirm nothing.
if ("DontPhaseOut".equals(sa.getParam("AILogic"))) {
return null;
}
return super.chooseSingleEntity(ai, sa, options, isOptional, targetedPlayer, params);
}
}

View File

@@ -2,7 +2,7 @@ Name:Change of Plans
ManaCost:X 1 U
Types:Instant
A:SP$ Connive | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select X target creatures you control | TargetMin$ X | TargetMax$ X | SubAbility$ DBPhase | SpellDescription$ Each of X target creatures you control connive.
SVar:DBPhase:DB$ Phases | Defined$ Targeted | AnyNumber$ True | StackDescription$ {p:You} may have any number of them phase out. | SpellDescription$ You may have any number of them phase out. (To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature. Treat phased out permanents and anything attached to them as though they don't exist until their controller's next turn.)
SVar:DBPhase:DB$ Phases | Defined$ Targeted | AnyNumber$ True | StackDescription$ {p:You} may have any number of them phase out. | AILogic$ DontPhaseOut | SpellDescription$ You may have any number of them phase out. (To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature. Treat phased out permanents and anything attached to them as though they don't exist until their controller's next turn.)
SVar:X:Count$xPaid
DeckHas:Ability$Discard|Counters
Oracle:Each of X target creatures you control connive. You may have any number of them phase out. (To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature. Treat phased out permanents and anything attached to them as though they don't exist until their controller's next turn.)