- Update for ProtectAI. AI can now use Protect effects to 1) counter certain targeted spells 2) protect against lethal damage 3) create an unblockable attacker depending on expected damage

This commit is contained in:
excessum
2014-04-07 13:46:10 +00:00
parent 544afe181e
commit 67a5848b26
6 changed files with 233 additions and 38 deletions

View File

@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi; import forge.ai.ability.AnimateAi;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactory; import forge.game.card.CardFactory;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -82,7 +83,7 @@ public class AiAttackController {
public AiAttackController(final Player ai) { public AiAttackController(final Player ai) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer();
getOpponentCreatures(); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<Card>(); this.attackers = new ArrayList<Card>();
for (Card c : myList) { for (Card c : myList) {
@@ -96,7 +97,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) { public AiAttackController(final Player ai, Card attacker) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer();
getOpponentCreatures(); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<Card>(); this.attackers = new ArrayList<Card>();
if (CombatUtil.canAttack(attacker, this.defendingOpponent)) { if (CombatUtil.canAttack(attacker, this.defendingOpponent)) {
@@ -105,30 +106,31 @@ public class AiAttackController {
this.blockers = this.getPossibleBlockers(oppList, this.attackers); this.blockers = this.getPossibleBlockers(oppList, this.attackers);
} // overloaded constructor to evaluate single specified attacker } // overloaded constructor to evaluate single specified attacker
private void getOpponentCreatures() { public static List<Card> getOpponentCreatures(final Player defender) {
this.oppList = Lists.newArrayList(); List<Card> defenders = Lists.newArrayList();
this.oppList.addAll(this.defendingOpponent.getCreaturesInPlay()); defenders.addAll(defender.getCreaturesInPlay());
Predicate<Card> canAnimate = new Predicate<Card>() { Predicate<Card> canAnimate = new Predicate<Card>() {
@Override @Override
public boolean apply(Card c) { public boolean apply(Card c) {
return !c.isCreature() && !c.isPlaneswalker(); return !c.isCreature() && !c.isPlaneswalker();
} }
}; };
for (Card c : CardLists.filter(this.defendingOpponent.getCardsIn(ZoneType.Battlefield), canAnimate)) { for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
if (c.isToken() && !c.isCopiedToken()) { if (c.isToken() && !c.isCopiedToken()) {
continue; continue;
} }
for (SpellAbility sa : c.getSpellAbilities()) { for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() == ApiType.Animate) { if (sa.getApi() == ApiType.Animate) {
if (ComputerUtilCost.canPayCost(sa, this.defendingOpponent) if (ComputerUtilCost.canPayCost(sa, defender)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, this.defendingOpponent)) { && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = CardFactory.getCard(c.getPaperCard(), this.defendingOpponent); Card animatedCopy = CardFactory.getCard(c.getPaperCard(), defender);
AnimateAi.becomeAnimated(animatedCopy, sa); AnimateAi.becomeAnimated(animatedCopy, sa);
this.oppList.add(animatedCopy); defenders.add(animatedCopy);
} }
} }
} }
} }
return defenders;
} }
/** Choose opponent for AI to attack here. Expand as necessary. */ /** Choose opponent for AI to attack here. Expand as necessary. */
@@ -1074,6 +1076,62 @@ public class AiAttackController {
return shouldAttack(ai, attacker, oppList, combat); return shouldAttack(ai, attacker, oppList, combat);
} }
public String toProtectAttacker(SpellAbility sa) {
if (sa.getApi() != ApiType.Protection || oppList.isEmpty()) {
return null;
}
final List<String> choices = ProtectEffect.getProtectionList(sa);
String color = ComputerUtilCard.getMostProminentColor(oppList), artifact = null;
if (choices.contains("artifacts")) {
artifact = "artifacts";
}
if (!choices.contains(color)) {
color = null;
}
for (Card c : oppList) {
if (!c.isArtifact()) {
artifact = null;
}
switch (color) {
case "black":
if (!c.isBlack()) {
color = null;
}
break;
case "blue":
if (!c.isBlue()) {
color = null;
}
break;
case "green":
if (!c.isGreen()) {
color = null;
}
break;
case "red":
if (!c.isRed()) {
color = null;
}
break;
case "white":
if (!c.isWhite()) {
color = null;
}
break;
}
if (color == null && artifact == null) {
return null;
}
}
if (color != null) {
return color;
}
if (artifact != null) {
return artifact;
}
return null;
}
public static boolean shouldThisAttack(final Player ai, Card attacker) { public static boolean shouldThisAttack(final Player ai, Card attacker) {
AiAttackController aiAtk = new AiAttackController(ai, attacker); AiAttackController aiAtk = new AiAttackController(ai, attacker);
Combat combat = ai.getGame().getCombat(); Combat combat = ai.getGame().getCombat();

View File

@@ -20,6 +20,7 @@ package forge.ai;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.ability.ProtectAi;
import forge.card.CardType; import forge.card.CardType;
import forge.card.CardType.Constant; import forge.card.CardType.Constant;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -1358,6 +1359,11 @@ public class ComputerUtil {
|| saviour.getParam("KW").endsWith("Hexproof"))) { || saviour.getParam("KW").endsWith("Hexproof"))) {
continue; continue;
} }
// cannot protect against source
if (saviourApi == ApiType.Protection && (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
// don't bounce or blink a permanent that the human // don't bounce or blink a permanent that the human
// player owns or is a token // player owns or is a token
@@ -1385,7 +1391,7 @@ public class ComputerUtil {
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump
|| saviourApi == null)) { || saviourApi == ApiType.Protection || saviourApi == null)) {
for (final Object o : objects) { for (final Object o : objects) {
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; final Card c = (Card) o;
@@ -1405,6 +1411,11 @@ public class ComputerUtil {
|| saviour.getParam("KW").endsWith("Hexproof"))) { || saviour.getParam("KW").endsWith("Hexproof"))) {
continue; continue;
} }
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
// don't bounce or blink a permanent that the human // don't bounce or blink a permanent that the human
// player owns or is a token // player owns or is a token
@@ -1422,7 +1433,8 @@ public class ComputerUtil {
} }
// Exiling => bounce/shroud // Exiling => bounce/shroud
else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll) else if ((threatApi == ApiType.ChangeZone || threatApi == ApiType.ChangeZoneAll)
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == null) && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump
|| saviourApi == ApiType.Protection || saviourApi == null)
&& topStack.hasParam("Destination") && topStack.hasParam("Destination")
&& topStack.getParam("Destination").equals("Exile")) { && topStack.getParam("Destination").equals("Exile")) {
for (final Object o : objects) { for (final Object o : objects) {
@@ -1433,6 +1445,11 @@ public class ComputerUtil {
&& (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) {
continue; continue;
} }
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
// don't bounce or blink a permanent that the human // don't bounce or blink a permanent that the human
// player owns or is a token // player owns or is a token
@@ -1446,7 +1463,8 @@ public class ComputerUtil {
} }
//GainControl //GainControl
else if (threatApi == ApiType.GainControl else if (threatApi == ApiType.GainControl
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == null)) { && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.Protection
|| saviourApi == null)) {
for (final Object o : objects) { for (final Object o : objects) {
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; final Card c = (Card) o;
@@ -1455,6 +1473,11 @@ public class ComputerUtil {
&& (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) { && (saviour.getParam("KW").endsWith("Shroud") || saviour.getParam("KW").endsWith("Hexproof"))) {
continue; continue;
} }
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
threatened.add(c); threatened.add(c);
} }
} }

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Multimap;
import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.CharmAi; import forge.ai.ability.CharmAi;
import forge.ai.ability.ProtectAi;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
@@ -28,6 +29,8 @@ import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.LobbyPlayer; import forge.game.player.LobbyPlayer;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -600,6 +603,41 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) { public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
String choice = choices.get(0); String choice = choices.get(0);
if (game.stack.size() > 1) {
for (SpellAbilityStackInstance si : game.getStack()) {
SpellAbility spell = si.getSpellAbility();
if (sa != spell) {
String s = ProtectAi.toProtectFrom(spell.getHostCard(), sa);
if (s != null) {
return s;
}
break;
}
}
}
final Combat combat = game.getCombat();
if (combat != null ) {
Card toSave = sa.getTargetCard();
List<Card> threats = null;
if (combat.isBlocked(toSave)) {
threats = combat.getBlockers(toSave);
}
if (combat.isBlocking(toSave)) {
threats = combat.getAttackersBlockedBy(toSave);
}
if (threats != null) {
ComputerUtilCard.sortByEvaluateCreature(threats);
String s = ProtectAi.toProtectFrom(threats.get(0), sa);
if (s != null) {
return s;
}
}
}
final PhaseHandler ph = game.getPhaseHandler();
if (ph.getPlayerTurn() == sa.getActivatingPlayer() && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(sa.getActivatingPlayer(), sa.getTargetCard());
return aiAtk.toProtectAttacker(sa);
}
final String logic = sa.getParam("AILogic"); final String logic = sa.getParam("AILogic");
if (logic == null || logic.equals("MostProminentHumanCreatures")) { if (logic == null || logic.equals("MostProminentHumanCreatures")) {
List<Card> list = new ArrayList<Card>(); List<Card> list = new ArrayList<Card>();

View File

@@ -1,23 +1,28 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.*; import forge.ai.*;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect; import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
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.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random;
public class ProtectAi extends SpellAbilityAi { public class ProtectAi extends SpellAbilityAi {
private static boolean hasProtectionFrom(final Card card, final String color) { private static boolean hasProtectionFrom(final Card card, final String color) {
@@ -50,6 +55,38 @@ public class ProtectAi extends SpellAbilityAi {
} }
return protect && !isEmpty; return protect && !isEmpty;
} }
/**
* \brief Find a choice for a Protect SpellAbility that protects from a specific threat card.
* @param threat Card to protect against
* @param sa Protect SpellAbility
* @return choice that can protect against the given threat, null if no such choice exists
*/
public static String toProtectFrom(final Card threat, SpellAbility sa) {
if (sa.getApi() != ApiType.Protection) {
return null;
}
final List<String> choices = ProtectEffect.getProtectionList(sa);
if (threat.isArtifact() && choices.contains("artifacts")) {
return "artifacts";
}
if (threat.isBlack() && choices.contains("black")) {
return "black";
}
if (threat.isBlue() && choices.contains("blue")) {
return "blue";
}
if (threat.isGreen() && choices.contains("green")) {
return "green";
}
if (threat.isRed() && choices.contains("red")) {
return "red";
}
if (threat.isWhite() && choices.contains("white")) {
return "white";
}
return null;
}
/** /**
* <p> * <p>
@@ -64,6 +101,7 @@ public class ProtectAi extends SpellAbilityAi {
final List<String> gains = ProtectEffect.getProtectionList(sa); final List<String> gains = ProtectEffect.getProtectionList(sa);
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
List<Card> list = ai.getCreaturesInPlay(); List<Card> list = ai.getCreaturesInPlay();
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@@ -85,22 +123,51 @@ public class ProtectAi extends SpellAbilityAi {
return true; return true;
} }
if( combat != null ) { if (!game.stack.isEmpty()) {
// is the creature blocking and unable to destroy the attacker //counter bad effect on stack
// or would be destroyed itself? if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(c)) {
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) { Card threat = game.getStack().peekAbility().getHostCard();
return true; //check to see if threat has already been countered by resolved protect
} if (!c.hasProtectionFrom(threat) && (ProtectAi.toProtectFrom(threat, sa) != null)) {
return true;
// is the creature in blocked and the blocker would survive }
// TODO Potential NPE here if no blockers are actually left }
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS) }
&& combat.isAttacking(c) && combat.isBlocked(c)
&& ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) { if (combat != null) {
return true; //creature is blocking and would be destroyed itself
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
List<Card> threats = combat.getAttackersBlockedBy(c);
return (true && (ProtectAi.toProtectFrom(threats.get(0), sa) != null));
}
//creature is attacking and would be destroyed itself
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& combat.isAttacking(c) && combat.isBlocked(c) ) {
List<Card> blockers = combat.getBlockers(c);
if (!blockers.isEmpty() && ComputerUtilCombat.blockerWouldBeDestroyed(ai, blockers.get(0), combat)) {
List<Card> threats = combat.getBlockers(c);
ComputerUtilCard.sortByEvaluateCreature(threats);
return (true && (ProtectAi.toProtectFrom(threats.get(0), sa) != null));
}
}
}
//make unblockable
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(ai, c);
String s = aiAtk.toProtectAttacker(sa);
if (s==null) {
return false;
} else {
Combat combat = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, ai.getOpponent(), combat);
float ratio = 1.0f * dmg / ai.getOpponent().getLife();
Random r = MyRandom.getRandom();
return r.nextFloat() < ratio;
} }
} }
return false; return false;
} }
}); });
@@ -111,6 +178,7 @@ public class ProtectAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler();
// if there is no target and host card isn't in play, don't activate // if there is no target and host card isn't in play, don't activate
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) { if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
return false; return false;
@@ -136,16 +204,27 @@ public class ProtectAi extends SpellAbilityAi {
} }
// Phase Restrictions // Phase Restrictions
if (game.getStack().isEmpty() && game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE)) { boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
// Instant-speed protections should not be cast outside of combat if (SpellAbilityAi.isSorcerySpeed(sa)) {
// when the stack is empty //only non-instants are Floating Shield, Midvast Protector, Sejiri Steppe
if (!SpellAbilityAi.isSorcerySpeed(sa)) { //sorceries can only give protection in order to create an unblockable attacker
if (notAiMain1) {
return false; return false;
} }
} else if (!game.getStack().isEmpty()) { } else {
// TODO protection something only if the top thing on the stack will if (game.getStack().isEmpty()) {
// kill it via damage or destroy //try to save attacker or blocker
return false; if (ph.getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
if (notAiMain1) {
return false;
}
}
} else {
//prevent repeated protects
if (game.getStack().peekAbility().getApi() == ApiType.Protection) {
return false;
}
}
} }
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {

View File

@@ -2,7 +2,5 @@ Name:Apostle's Blessing
ManaCost:1 PW ManaCost:1 PW
Types:Instant Types:Instant
A:SP$ Protection | Cost$ 1 PW | ValidTgts$ Creature.YouCtrl,Artifact.YouCtrl | TgtPrompt$ Select target artifact or creature you control | Gains$ Choice | Choices$ AnyColor,artifacts | SpellDescription$ Target artifact or creature you control gains protection from artifacts or from the color of your choice until end of turn. A:SP$ Protection | Cost$ 1 PW | ValidTgts$ Creature.YouCtrl,Artifact.YouCtrl | TgtPrompt$ Select target artifact or creature you control | Gains$ Choice | Choices$ AnyColor,artifacts | SpellDescription$ Target artifact or creature you control gains protection from artifacts or from the color of your choice until end of turn.
#Computer isn't very good at picking a color to get protection from yet
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/apostles_blessing.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/apostles_blessing.jpg
Oracle:({W/P} can be paid with either {W} or 2 life.)\nTarget artifact or creature you control gains protection from artifacts or from the color of your choice until end of turn. Oracle:({W/P} can be paid with either {W} or 2 life.)\nTarget artifact or creature you control gains protection from artifacts or from the color of your choice until end of turn.

View File

@@ -3,6 +3,5 @@ ManaCost:W
Types:Instant Types:Instant
A:SP$ Protection | Cost$ W | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | Gains$ Choice | Choices$ AnyColor | SubAbility$ DBScry | SpellDescription$ Target creature you control gains protection from the color of your choice until end of turn. Scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) A:SP$ Protection | Cost$ W | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | Gains$ Choice | Choices$ AnyColor | SubAbility$ DBScry | SpellDescription$ Target creature you control gains protection from the color of your choice until end of turn. Scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.)
SVar:DBScry:DB$ Scry | ScryNum$ 1 SVar:DBScry:DB$ Scry | ScryNum$ 1
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/gods_willing.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/gods_willing.jpg
Oracle:Target creature you control gains protection from the color of your choice until end of turn. Scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) Oracle:Target creature you control gains protection from the color of your choice until end of turn. Scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.)