spDrawCards

This is by far my most involved keyword development ever. It is also the most modular keyword - it has to support a wide variety of slight differences in the "Draw n cards" mechanic.
syntax:
spDrawCards[Tgt]:num:drawback
where num is:
a number.. like 1, 2, 3
Domain
NumCardsType/type/zone
where type is any single card type (artifact, creature, elf)
where zone is:
YouCtrl, InPlay, InYourYard or InAllYards
where drawback is:
Discard/{num | X}[/[AtRandom | UnlessDiscardType/type]]
LoseLife/{num | X}
NumToLibrary/{num | X}/{Top | Bottom | TopOrBottom}
NumOppDraw/{num | X}
where X really means "X" and will take on the number of cards calculated.
If you include "Tgt" to form "spDrawCardsTgt:" then it will request player targeting prior to resolution.
These are all the draw spells already included:
CardFactory:
The hand to library "TopOrBottom" function puts BOTH cards either on the top or the bottom. The usual wording of the general mechanic allows the top or bottom decision for each card. But the version of it that's used on a draw spell does specify BOTH. So until the basic code is hacked for the other form, to be used in other spells (scry anyone?) just be aware that it asks once for top or bottom, and then the second (or third, or last) card you select will be the very top card, followed by the prior cards.
The spells are playable by the AI. It will only play them to refill the hand as long as after the discard drawback, its hand will be less than 7. So if it has 5 cards, it won't play Ancestral Recall, but it would play Brainstorm or Sift, when it's added.
Also, I chose 8 as the cut-off for losing life drawbacks. I don't recall any gain life (self or opponent) "drawbacks" on drawspells, but I could easily add it just in case it does show up at some point, or to help support possible fan-made cards.
For targeted spells, the AI will always choose itself... unless and until milling is a viable strategy. (hint hint add the check in SBE, it's so easy)
syntax:
spDrawCards[Tgt]:num:drawback
where num is:
a number.. like 1, 2, 3
Domain
NumCardsType/type/zone
where type is any single card type (artifact, creature, elf)
where zone is:
YouCtrl, InPlay, InYourYard or InAllYards
where drawback is:
Discard/{num | X}[/[AtRandom | UnlessDiscardType/type]]
LoseLife/{num | X}
NumToLibrary/{num | X}/{Top | Bottom | TopOrBottom}
NumOppDraw/{num | X}
where X really means "X" and will take on the number of cards calculated.
If you include "Tgt" to form "spDrawCardsTgt:" then it will request player targeting prior to resolution.
These are all the draw spells already included:
- Code: Select all
Brainstorm
U
Instant
Draw three cards, then put two cards from your hand on top of your library in any order.
spDrawCards:3:NumToLibrary/2/Top
Minions' Murmurs
2 B B
Sorcery
You draw X cards and you lose X life, where X is the number of creatures you control.
spDrawCards:NumCardsType/Creature/YouCtrl:LoseLife/X
Harmonize
2 G G
Sorcery
Draw three cards.
spDrawCards:3
Concentrate
2 U U
Sorcery
Draw three cards.
spDrawCards:3
Night's Whisper
1 B
Sorcery
You draw two cards and you lose 2 life.
spDrawCards:2:LoseLife/2
Counsel of the Soratami
2 U
Sorcery
Draw two cards.
spDrawCards:2
Words of Wisdom
1 U
Instant
You draw two cards, then each other player draws a card.
spDrawCards:2:NumOppDraw/1
Ancestral Recall
U
Instant
Target player draws three cards.
spDrawCardsTgt:3
CardFactory:
- Code: Select all
private final int shouldSpDrawCards(Card c){
ArrayList<String> a = c.getKeyword();
for (int i = 0; i < a.size(); i++)
if (a.get(i).toString().startsWith("spDrawCards"))
return i;
return -1;
}
- Code: Select all
if (shouldSpDrawCards(card) != -1)
{
int n = shouldSpDrawCards(card);
String parse = card.getKeyword().get(n).toString();
card.removeIntrinsicKeyword(parse);
String k[] = parse.split(":");
final boolean Tgt[] = {false};
Tgt[0] = k[0].contains("Tgt");
final int NumCards[] = {-1};
final String NumCardsType[] = {"none"};
final boolean NumCardsTypeYouCtrl[] = {false};
final boolean NumCardsTypeInPlay[] = {false};
final boolean NumCardsTypeInYourYard[] = {false};
final boolean NumCardsTypeInAllYards[] = {false};
final boolean NumCardsDomain[] = {false};
if (k[1].length() == 1)
NumCards[0] = Integer.parseInt(k[1]);
else
{
if (k[1].startsWith("NumCardsType"))
{
String kk[] = k[1].split("/");
NumCardsType[0] = kk[1];
NumCardsTypeYouCtrl[0] = kk[2].equals("YouCtrl");
NumCardsTypeInPlay[0] = kk[2].equals("InPlay");
NumCardsTypeInYourYard[0] = kk[2].equals("InYourYard");
NumCardsTypeInAllYards[0] = kk[2].equals("InAllYards");
}
NumCardsDomain[0] = k[1].equals("Domain");
}
final int NumDiscard[] = {0};
final String UnlessDiscardType[] = {"none"};
final boolean AtRandom[] = {false};
final int NumLoseLife[] = {0};
final int NumToLibrary[] = {0};
final String LibraryPosition[] = {"none"};
final int NumOppDraw[] = {0};
if (k.length > 2)
{
if (k[2].contains("Discard"))
{
String kk[] = k[2].split("/");
if (kk[1].equals("X"))
NumDiscard[0] = -1;
else
NumDiscard[0] = Integer.parseInt(kk[1]);
if (kk.length > 2)
{
if (kk[2].equals("UnlessDiscardType"))
UnlessDiscardType[0] = kk[3];
AtRandom[0] = kk[2].equals("AtRandom");
}
}
if (k[2].contains("LoseLife"))
{
String kk[] = k[2].split("/");
if (kk[1].equals("X"))
NumLoseLife[0] = -1;
else
NumLoseLife[0] = Integer.parseInt(kk[1]);
}
if (k[2].contains("NumToLibrary"))
{
String kk[] = k[2].split("/");
if (kk[1].equals("X"))
NumToLibrary[0] = -1;
else
NumToLibrary[0] = Integer.parseInt(kk[1]);
LibraryPosition[0] = kk[2];
}
if (k[2].contains("NumOppDraw"))
{
String kk[] = k[2].split("/");
if (kk[1].equals("X"))
NumOppDraw[0] = -1;
else
NumOppDraw[0] = Integer.parseInt(kk[1]);
}
}
final SpellAbility spDraw = new Spell(card)
{
private static final long serialVersionUID = -7049779241008089696L;
public int getNumCards()
{
if (NumCards[0] != -1)
return NumCards[0];
int n = 0;
String cardController = card.getController();
PlayerZone myPlay = AllZone.getZone(Constant.Zone.Play, cardController);
PlayerZone opPlay = AllZone.getZone(Constant.Zone.Play, AllZone.GameAction.getOpponent(cardController));
PlayerZone myYard = AllZone.getZone(Constant.Zone.Graveyard, cardController);
PlayerZone opYard = AllZone.getZone(Constant.Zone.Graveyard, AllZone.GameAction.getOpponent(cardController));
CardList AllCards = new CardList();
if (! NumCardsType[0].equals("none"))
{
if (NumCardsTypeInYourYard[0] == false)
AllCards.addAll(myYard.getCards());
if (NumCardsTypeInAllYards[0] == false)
{
AllCards.addAll(myYard.getCards());
AllCards.addAll(opYard.getCards());
}
if (NumCardsTypeYouCtrl[0] == true)
AllCards.addAll(myPlay.getCards());
if (NumCardsTypeInPlay[0] == true)
{
AllCards.addAll(myPlay.getCards());
AllCards.addAll(opPlay.getCards());
}
AllCards = AllCards.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
if (c.getType().contains(NumCardsType[0]))
return true;
return false;
}
});
n = AllCards.size();
}
if (NumCardsDomain[0] == true)
{
AllCards.addAll(myPlay.getCards());
String basic[] = {"Forest", "Plains", "Mountain", "Island", "Swamp"};
for(int i = 0; i < basic.length; i++)
if (! AllCards.getType(basic[i]).isEmpty())
n++;
}
if (NumDiscard[0] == -1)
NumDiscard[0] = n;
if (NumLoseLife[0] == -1)
NumLoseLife[0] = n;
if (NumToLibrary[0] == -1)
NumToLibrary[0] = n;
if (NumOppDraw[0] == -1)
NumOppDraw[0] = n;
return n;
}
public boolean canPlayAI()
{
int n = getNumCards();
int h = AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer).size();
Random r = new Random();
if (((h + n) - (NumDiscard[0] + NumToLibrary[0]) <= 7)
&& (AllZone.GameAction.getPlayerLife(Constant.Player.Computer).getLife() - NumLoseLife[0]) >= 8
&& (r.nextInt(10) > 4))
{
setTargetPlayer(Constant.Player.Computer);
return true;
}
return false;
}
public void resolve()
{
int n = getNumCards();
String TgtPlayer = card.getController();
if (Tgt[0])
TgtPlayer = getTargetPlayer();
for (int i=0; i < n; i++)
AllZone.GameAction.drawCard(TgtPlayer);
if (NumDiscard[0] > 0)
{
if (!UnlessDiscardType[0].equals("none"))
AllZone.GameAction.discardUnless(TgtPlayer, NumDiscard[0], UnlessDiscardType[0]);
else if (AtRandom[0] == true)
AllZone.GameAction.discardRandom(TgtPlayer, NumDiscard[0]);
else
AllZone.GameAction.discard(TgtPlayer, NumDiscard[0]);
}
if (NumLoseLife[0] > 0)
AllZone.GameAction.getPlayerLife(TgtPlayer).subtractLife(NumLoseLife[0]);
if (NumToLibrary[0] > 0)
AllZone.GameAction.handToLibrary(TgtPlayer, NumToLibrary[0], LibraryPosition[0]);
if (NumOppDraw[0] > 0)
for (int i = 0; i < NumOppDraw[0]; i++)
AllZone.GameAction.drawCard(AllZone.GameAction.getOpponent(TgtPlayer));
}
};
if (Tgt[0])
spDraw.setBeforePayMana(CardFactoryUtil.input_targetPlayer(spDraw));
spDraw.setDescription(card.getText());
card.setText("");
card.clearSpellAbility();
card.addSpellAbility(spDraw);
}
- Code: Select all
public void discardRandom(String player, int numDiscard)
{
for (int i=0; i < numDiscard; i++)
{
Card[] c = AllZone.getZone(Constant.Zone.Hand, player).getCards();
if (c.length != 0)
discard(CardUtil.getRandom(c));
}
}
public void discard(String player, int numDiscard)
{
for (int i=0; i < numDiscard; i++)
if (player.equals(Constant.Player.Human))
AllZone.InputControl.setInput(CardFactoryUtil.input_discard());
else
AI_discard();
}
public void discardUnless(String player, int numDiscard, String uType)
{
if (player.equals(Constant.Player.Human))
AllZone.InputControl.setInput(CardFactoryUtil.input_discardNumUnless(numDiscard, uType));
else
AI_discardNumUnless(numDiscard, uType);
}
public void AI_discardNumUnless(int numDiscard, String uType)
{
CardList hand = new CardList();
hand.addAll(AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer).getCards());
CardList tHand = hand.getType(uType);
if (tHand.size() > 0)
{
CardListUtil.sortCMC(tHand);
tHand.reverse();
discard(tHand.get(0));
return;
}
for (int i=0; i < numDiscard; i++)
AI_discard();
}
public void AI_discard()
{
CardList hand = new CardList();
hand.addAll(AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer).getCards());
CardList blIP = new CardList();
blIP.addAll(AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer).getCards());
blIP = blIP.getType("Basic");
if (blIP.size() > 5)
{
CardList blIH = hand.getType("Basic");
if (blIH.size() > 0)
{
discard(blIH.get(CardUtil.getRandomIndex(blIH)));
return;
}
CardListUtil.sortAttackLowFirst(hand);
CardListUtil.sortNonFlyingFirst(hand);
discard(hand.get(0));
return;
}
else
{
CardListUtil.sortCMC(hand);
discard(hand.get(0));
return;
}
}
public void handToLibrary(String player, int numToLibrary, String libPos)
{
if (player.equals(Constant.Player.Human))
{
if (libPos.equals("Top") || libPos.equals("Bottom"))
libPos = libPos.toLowerCase();
else
{
Object o = new Object();
String s = "card";
if (numToLibrary > 1)
s = "cards";
o = AllZone.Display.getChoice("Do you want to put the " + s + " on the top or bottom of your library?", new Object[] {"top", "bottom"});
libPos = o.toString();
}
AllZone.InputControl.setInput(CardFactoryUtil.input_putFromHandToLibrary(libPos, numToLibrary));
}
else
{
for (int i=0; i < numToLibrary; i++)
{
if (libPos.equals("Top") || libPos.equals("Bottom"))
libPos = libPos.toLowerCase();
else
{
Random r = new Random();
if (r.nextBoolean())
libPos = "top";
else
libPos = "bottom";
}
AI_handToLibrary(libPos);
}
}
}
public void AI_handToLibrary(String libPos)
{
CardList hand = new CardList();
hand.addAll(AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer).getCards());
CardList blIP = new CardList();
blIP.addAll(AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer).getCards());
blIP = blIP.getType("Basic");
if (blIP.size() > 5)
{
CardList blIH = hand.getType("Basic");
if (blIH.size() > 0)
{
Card card = blIH.get(CardUtil.getRandomIndex(blIH));
AllZone.Computer_Hand.remove(card);
if (libPos.equals("top"))
AllZone.Computer_Library.add(card, 0);
else
AllZone.Computer_Library.add(card);
return;
}
CardListUtil.sortAttackLowFirst(hand);
CardListUtil.sortNonFlyingFirst(hand);
discard(hand.get(0));
return;
}
else
{
CardListUtil.sortCMC(hand);
discard(hand.get(0));
return;
}
}
- Code: Select all
public static Input input_putFromHandToLibrary(final String TopOrBottom, final int num)
{
Input target = new Input()
{
private static final long serialVersionUID = 5178077952030689103L;
public int n = 0;
public void showMessage()
{
AllZone.Display.showMessage("Select a card to put on the " + TopOrBottom + " of your library.");
ButtonUtil.disableAll();
if (n == num || AllZone.Human_Hand.getCards().length == 0)
stop();
}
public void selectButtonCancel() {stop();}
public void selectCard(Card card, PlayerZone zone)
{
if (zone.is(Constant.Zone.Hand))
{
AllZone.Human_Hand.remove(card);
if (TopOrBottom.equals("top"))
AllZone.Human_Library.add(card, 0);
else if (TopOrBottom.equals("bottom"))
AllZone.Human_Library.add(card);
n++;
if (n == num)
stop();
showMessage();
}
}
};
return target;
}
public static Input input_discardNumUnless(final int nCards, final String uType)
{
Input target = new Input()
{
private static final long serialVersionUID = 8822292413831640944L;
int n = 0;
public void showMessage()
{
AllZone.Display.showMessage("Select " + (nCards - n) + " cards to discard, unless you discard a " + uType + ".");
ButtonUtil.disableAll();
if (n == nCards || AllZone.Human_Hand.getCards().length == 0)
stop();
}
public void selectButtonCancel() {stop();}
public void selectCard(Card card, PlayerZone zone)
{
if (zone.is(Constant.Zone.Hand))
{
AllZone.GameAction.discard(card);
n++;
if (card.getType().contains(uType))
stop();
showMessage();
}
}
};
return target;
}
The hand to library "TopOrBottom" function puts BOTH cards either on the top or the bottom. The usual wording of the general mechanic allows the top or bottom decision for each card. But the version of it that's used on a draw spell does specify BOTH. So until the basic code is hacked for the other form, to be used in other spells (scry anyone?) just be aware that it asks once for top or bottom, and then the second (or third, or last) card you select will be the very top card, followed by the prior cards.
The spells are playable by the AI. It will only play them to refill the hand as long as after the discard drawback, its hand will be less than 7. So if it has 5 cards, it won't play Ancestral Recall, but it would play Brainstorm or Sift, when it's added.
Also, I chose 8 as the cut-off for losing life drawbacks. I don't recall any gain life (self or opponent) "drawbacks" on drawspells, but I could easily add it just in case it does show up at some point, or to help support possible fan-made cards.
For targeted spells, the AI will always choose itself... unless and until milling is a viable strategy. (hint hint add the check in SBE, it's so easy)