Merge branch 'seravy-ai-fixes' into 'master'

Reformatted/revised AI updates from Seravy: Fog AI, Protection from chosen color AI

See merge request core-developers/forge!503
This commit is contained in:
Sol
2018-05-04 00:35:07 +00:00
14 changed files with 106 additions and 15 deletions

View File

@@ -38,6 +38,7 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import java.util.ArrayList;
@@ -263,6 +264,34 @@ public class AiAttackController {
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
return attackers;
}
// no need to block (already holding mana to cast fog next turn)
if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
// Don't send the card that'll do the fog effect to attack, it's unsafe!
if (attackers.contains(AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
attackers.remove(AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
}
return attackers;
}
// no need to block if an effect is in play which untaps all creatures (pseudo-Vigilance akin to
// Awakening or Prophet of Kruphix)
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
if (untapsEachTurn || untapsEachOtherTurn) {
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
: card.getSVar("UntapsEachOtherPlayerTurn");
for (String aff : TextUtil.split(affected, ',')) {
if (aff.equals("Creature")
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
return attackers;
}
}
}
}
List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
@Override

View File

@@ -44,20 +44,24 @@ public class AiCardMemory {
private final Set<Card> memTrickAttackers;
private final Set<Card> memHeldManaSources;
private final Set<Card> memHeldManaSourcesForCombat;
private final Set<Card> memHeldManaSourcesForEnemyCombat;
private final Set<Card> memAttachedThisTurn;
private final Set<Card> memAnimatedThisTurn;
private final Set<Card> memBouncedThisTurn;
private final Set<Card> memActivatedThisTurn;
private final Set<Card> memChosenFogEffect;
public AiCardMemory() {
this.memMandatoryAttackers = new HashSet<>();
this.memHeldManaSources = new HashSet<>();
this.memHeldManaSourcesForCombat = new HashSet<>();
this.memHeldManaSourcesForEnemyCombat = new HashSet<>();
this.memAttachedThisTurn = new HashSet<>();
this.memAnimatedThisTurn = new HashSet<>();
this.memBouncedThisTurn = new HashSet<>();
this.memActivatedThisTurn = new HashSet<>();
this.memTrickAttackers = new HashSet<>();
this.memChosenFogEffect = new HashSet<>();
}
/**
@@ -70,10 +74,12 @@ public class AiCardMemory {
TRICK_ATTACKERS,
HELD_MANA_SOURCES_FOR_MAIN2,
HELD_MANA_SOURCES_FOR_DECLBLK,
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK,
ATTACHED_THIS_TURN,
ANIMATED_THIS_TURN,
BOUNCED_THIS_TURN,
ACTIVATED_THIS_TURN,
CHOSEN_FOG_EFFECT,
//REVEALED_CARDS // stub, not linked to AI code yet
}
@@ -87,6 +93,8 @@ public class AiCardMemory {
return memHeldManaSources;
case HELD_MANA_SOURCES_FOR_DECLBLK:
return memHeldManaSourcesForCombat;
case HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK:
return memHeldManaSourcesForEnemyCombat;
case ATTACHED_THIS_TURN:
return memAttachedThisTurn;
case ANIMATED_THIS_TURN:
@@ -95,6 +103,8 @@ public class AiCardMemory {
return memBouncedThisTurn;
case ACTIVATED_THIS_TURN:
return memActivatedThisTurn;
case CHOSEN_FOG_EFFECT:
return memChosenFogEffect;
//case REVEALED_CARDS:
// return memRevealedCards;
default:
@@ -267,10 +277,12 @@ public class AiCardMemory {
clearMemorySet(MemorySet.TRICK_ATTACKERS);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
clearMemorySet(MemorySet.ACTIVATED_THIS_TURN);
clearMemorySet(MemorySet.CHOSEN_FOG_EFFECT);
}
// Static functions to simplify access to AI card memory of a given AI player.

View File

@@ -581,10 +581,10 @@ public class AiController {
}
public void reserveManaSources(SpellAbility sa) {
reserveManaSources(sa, PhaseType.MAIN2);
reserveManaSources(sa, PhaseType.MAIN2, false);
}
public void reserveManaSources(SpellAbility sa, PhaseType phaseType) {
public void reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
@@ -595,7 +595,8 @@ public class AiController {
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
break;
case COMBAT_DECLARE_BLOCKERS:
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
memSet = enemy ? AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK
: AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
break;
default:
System.out.println("Warning: unsupported mana reservation phase specified for reserveManaSources: "

View File

@@ -1512,7 +1512,7 @@ public class ComputerUtilCard {
// Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use
// the combat trick
if (ai.getController().isAI()) {
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS);
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false);
}
return false;
} else {

View File

@@ -865,9 +865,12 @@ public class ComputerUtilMana {
// For combat tricks, always obey mana reservation
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
}
else {
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) {
} else if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai)) && (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP)) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
} else {
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
(AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK))) {
// This mana source is held elsewhere for a combat trick.
return true;
}

View File

@@ -1311,6 +1311,22 @@ public class AttachAi extends SpellAbilityAi {
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
}
// Don't play if would choose a color the target is already protected from
if (sa.getHostCard().hasSVar("ChosenProtection")) {
CardCollectionView oppAllCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
String cc = ComputerUtilCard.getMostProminentColor(oppAllCards);
if (card.hasKeyword("Protection from " + cc.toLowerCase())) {
return false;
}
// Also don't play if it would destroy own Aura
for (Card c : card.getEnchantedBy(false)) {
if ((c.getController().equals(ai)) && (c.isOfColor(cc))) {
return false;
}
}
}
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|| keyword.equals("Flying") || keyword.equals("Horsemanship")

View File

@@ -1,10 +1,9 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
@@ -12,6 +11,8 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import java.util.List;
public class FogAi extends SpellAbilityAi {
/* (non-Javadoc)
@@ -20,6 +21,33 @@ public class FogAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
final Card hostCard = sa.getHostCard();
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
// if card would be destroyed, react and use immediately if it's not own turn
if ((AiCardMemory.isRememberedCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
&& (!game.getStack().isEmpty())
&& (!game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, null);
if (objects.contains(hostCard)) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
return true;
}
}
// Reserve mana to cast this card if it will be likely needed
if (((game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))
|| (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)))
&& (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
&& (ComputerUtil.aiLifeInDanger(ai, false, 0))) {
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, true);
AiCardMemory.rememberCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
}
// AI should only activate this during Human's Declare Blockers phase
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
@@ -33,11 +61,6 @@ public class FogAi extends SpellAbilityAi {
return false;
}
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
int dmg = 0;
for (Card atk : game.getCombat().getAttackersOf(ai)) {

View File

@@ -3,6 +3,7 @@ ManaCost:2 G G
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigUntapAll | TriggerDescription$ At the beginning of each upkeep, untap all creatures and lands.
SVar:TrigUntapAll:DB$UntapAll | ValidCards$ Creature,Land | SpellDescription$ untap all creatures and lands.
SVar:UntapsEachTurn:Creature,Land
SVar:RemRandomDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/awakening.jpg
Oracle:At the beginning of each upkeep, untap all creatures and lands.

View File

@@ -8,4 +8,5 @@ SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInHumanD
A:SP$ Attach | Cost$ W W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
SVar:Picture:http://www.wizards.com/global/images/magic/general/cho_mannos_blessing.jpg
SVar:ChosenProtection:True
Oracle:Flash\nEnchant creature\nAs Cho-Manno's Blessing enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Cho-Manno's Blessing.

View File

@@ -8,5 +8,6 @@ A:SP$ Attach | Cost$ W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
A:AB$ ChangeZone | Cost$ W | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand.
SVar:RemAIDeck:True
SVar:ChosenProtection:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/flickering_ward.jpg
Oracle:Enchant creature\nAs Flickering Ward enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Flickering Ward.\n{W}: Return Flickering Ward to its owner's hand.

View File

@@ -8,5 +8,6 @@ A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
A:AB$ Protection | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Gains$ ChosenColor | SpellDescription$ Target creature gains protection from the chosen color until end of turn.
SVar:RemAIDeck:True
SVar:ChosenProtection:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/floating_shield.jpg
Oracle:Enchant creature\nAs Floating Shield enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Floating Shield.\nSacrifice Floating Shield: Target creature gains protection from the chosen color until end of turn.

View File

@@ -9,4 +9,5 @@ SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 1
A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
SVar:Picture:http://www.wizards.com/global/images/magic/general/pentarch_ward.jpg
SVar:ChosenProtection:True
Oracle:Enchant creature\nAs Pentarch Ward enters the battlefield, choose a color.\nWhen Pentarch Ward enters the battlefield, draw a card.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Pentarch Ward.

View File

@@ -4,5 +4,6 @@ Types:Creature Human Wizard
PT:2/3
S:Mode$ Continuous | Affected$ Creature.YouCtrl,Land.YouCtrl | AddHiddenKeyword$ CARDNAME untaps during each other player's untap step. | Description$ Untap all creatures and lands you control during each other player's untap step.
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ Flash | AffectedZone$ Exile,Graveyard,Hand,Library,Command | Description$ You may cast creature spells as though they had flash.
SVar:UntapsEachOtherPlayerTurn:Creature,Land
SVar:Picture:http://www.wizards.com/global/images/magic/general/prophet_of_kruphix.jpg
Oracle:Untap all creatures and lands you control during each other player's untap step.\nYou may cast creature spells as though they had flash.

View File

@@ -11,5 +11,6 @@ SVar:ChooseColor:DB$ ChooseColor | Defined$ You | SpellDescription$ As CARDNAME
A:SP$ Attach | Cost$ W W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:Protection from ChosenColor:Card.CardUID_HostCardUID | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove CARDNAME.
SVar:RemAIDeck:True
SVar:ChosenProtection:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/ward_of_lights.jpg
Oracle:You may cast Ward of Lights as though it had flash. If you cast it any time a sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning of the next cleanup step.\nEnchant creature\nAs Ward of Lights enters the battlefield, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Ward of Lights.