From afa59c0596a1d32775a6d477a10fd130898b6fbd Mon Sep 17 00:00:00 2001 From: mcrawford620 Date: Sun, 15 Jul 2012 05:37:10 +0000 Subject: [PATCH] - New DeckWants SVar and class. Provides a way to give instructions for selecting related combo cards in random and Limited decks. --- .gitattributes | 2 + res/cardsfolder/a/angels_feather.txt | 1 + res/cardsfolder/c/call_to_the_grave.txt | 1 + res/cardsfolder/c/crown_of_empires.txt | 1 + res/cardsfolder/d/demons_horn.txt | 1 + res/cardsfolder/d/door_to_nothingness.txt | 1 + res/cardsfolder/d/dragons_claw.txt | 1 + res/cardsfolder/g/griffin_rider.txt | 1 + res/cardsfolder/k/krakens_eye.txt | 1 + res/cardsfolder/m/mesa_enchantress.txt | 1 + res/cardsfolder/m/mwonvuli_beast_tracker.txt | 1 + res/cardsfolder/p/phylactery_lich.txt | 1 + res/cardsfolder/q/quirion_dryad.txt | 1 + res/cardsfolder/s/scepter_of_empires.txt | 1 + res/cardsfolder/t/throne_of_empires.txt | 1 + res/cardsfolder/w/war_falcon.txt | 1 + res/cardsfolder/w/wurms_tooth.txt | 1 + src/main/java/forge/Card.java | 30 ++- src/main/java/forge/CardList.java | 17 ++ src/main/java/forge/CardUtil.java | 12 +- src/main/java/forge/card/DeckWants.java | 159 +++++++++++++++ .../java/forge/game/limited/BoosterDeck.java | 96 ++++++--- .../forge/game/limited/BoosterDraftAI.java | 16 +- .../java/forge/game/player/ComputerUtil.java | 3 +- src/test/java/forge/DeckWantsTest.java | 189 ++++++++++++++++++ 25 files changed, 498 insertions(+), 42 deletions(-) create mode 100644 src/main/java/forge/card/DeckWants.java create mode 100644 src/test/java/forge/DeckWantsTest.java diff --git a/.gitattributes b/.gitattributes index afe40103355..a359fe8736e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11845,6 +11845,7 @@ src/main/java/forge/card/CardRules.java -text src/main/java/forge/card/CardRulesReader.java svneol=native#text/plain src/main/java/forge/card/CardSuperType.java -text src/main/java/forge/card/CardType.java -text +src/main/java/forge/card/DeckWants.java -text src/main/java/forge/card/EditionCollection.java svneol=native#text/plain src/main/java/forge/card/EditionInfo.java svneol=native#text/plain src/main/java/forge/card/FatPackData.java -text @@ -12408,6 +12409,7 @@ src/test/java/forge/BoosterDraft1Test.java svneol=native#text/plain src/test/java/forge/BoosterDraftTest.java svneol=native#text/plain src/test/java/forge/CardColorTest.java svneol=native#text/plain src/test/java/forge/CardReaderTest.java svneol=native#text/plain +src/test/java/forge/DeckWantsTest.java -text src/test/java/forge/GuiDownloadPicturesLQTest.java svneol=native#text/plain src/test/java/forge/GuiDownloadSetPicturesLQTest.java svneol=native#text/plain src/test/java/forge/GuiMigrateLocalMWSSetPicturesHQTest.java svneol=native#text/plain diff --git a/res/cardsfolder/a/angels_feather.txt b/res/cardsfolder/a/angels_feather.txt index 206c5e1d117..9f37d282893 100644 --- a/res/cardsfolder/a/angels_feather.txt +++ b/res/cardsfolder/a/angels_feather.txt @@ -5,6 +5,7 @@ Text:no text T:Mode$ SpellCast | ValidCard$ Card.White | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a white spell, you may gain 1 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | LifeAmount$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Color$white SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/angels_feather.jpg SetInfo:DST|Uncommon|http://magiccards.info/scans/en/ds/92.jpg diff --git a/res/cardsfolder/c/call_to_the_grave.txt b/res/cardsfolder/c/call_to_the_grave.txt index c417727a04a..2c08ebd7e55 100644 --- a/res/cardsfolder/c/call_to_the_grave.txt +++ b/res/cardsfolder/c/call_to_the_grave.txt @@ -7,6 +7,7 @@ SVar:TrigSac:AB$Sacrifice | Cost$ 0 | SacValid$ Creature.nonZombie | Defined$ Tr T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | IsPresent$ Creature | PresentCompare$ EQ0 | Execute$ TrigSacSelf | TriggerDescription$ At the beginning of the end step, if no creatures are on the battlefield, sacrifice CARDNAME. SVar:TrigSacSelf:AB$Sacrifice | Cost$ 0 | Defined$ Self SVar:RemRandomDeck:True +SVar:DeckWants:Type$Zombie SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/call_to_the_grave.jpg SetInfo:SCG|Rare|http://magiccards.info/scans/en/sc/58.jpg diff --git a/res/cardsfolder/c/crown_of_empires.txt b/res/cardsfolder/c/crown_of_empires.txt index 7fdf298aa17..3fbbbd35ce2 100644 --- a/res/cardsfolder/c/crown_of_empires.txt +++ b/res/cardsfolder/c/crown_of_empires.txt @@ -5,6 +5,7 @@ Text:no text A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature | TgtPrompt$ Select target creature | ConditionNotAllM12Empires$ True | SubAbility$ DBControl | SpellDescription$ Tap target creature. Gain control of that creature instead if you control artifacts named Scepter of Empires and Throne of Empires. SVar:DBControl:DB$ GainControl | Defined$ Targeted | ConditionAllM12Empires$ True SVar:RemRandomDeck:True +SVar:DeckWants:ListAll$Scepter of Empires|Throne of Empires SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/crown_of_empires.jpg SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/203.jpg diff --git a/res/cardsfolder/d/demons_horn.txt b/res/cardsfolder/d/demons_horn.txt index 5000170790a..5f0ed2e1d69 100644 --- a/res/cardsfolder/d/demons_horn.txt +++ b/res/cardsfolder/d/demons_horn.txt @@ -5,6 +5,7 @@ Text:no text T:Mode$ SpellCast | ValidCard$ Card.Black | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a black spell, you may gain 1 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | Defined$ You | LifeAmount$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Color$Black SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/demons_horn.jpg SetInfo:DST|Uncommon|http://magiccards.info/scans/en/ds/116.jpg diff --git a/res/cardsfolder/d/door_to_nothingness.txt b/res/cardsfolder/d/door_to_nothingness.txt index 8644fc42e12..0e885359a2f 100644 --- a/res/cardsfolder/d/door_to_nothingness.txt +++ b/res/cardsfolder/d/door_to_nothingness.txt @@ -5,6 +5,7 @@ Text:no text K:CARDNAME enters the battlefield tapped. A:AB$ LosesGame | Cost$ W W U U B B R R G G T Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Target player loses the game. SVar:RemRandomDeck:True +SVar:DeckWants:ColorAll$white|blue|black|red|green SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/door_to_nothingness.jpg SetInfo:M13|Rare|http://magiccards.info/scans/en/m13/203.jpg diff --git a/res/cardsfolder/d/dragons_claw.txt b/res/cardsfolder/d/dragons_claw.txt index 645dab1f02c..517231cb093 100644 --- a/res/cardsfolder/d/dragons_claw.txt +++ b/res/cardsfolder/d/dragons_claw.txt @@ -5,6 +5,7 @@ Text:no text T:Mode$ SpellCast | ValidCard$ Card.Red | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a red spell, you may gain 1 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | Defined$ You | LifeAmount$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Color$Red SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/dragons_claw.jpg SetInfo:DST|Uncommon|http://magiccards.info/scans/en/ds/117.jpg diff --git a/res/cardsfolder/g/griffin_rider.txt b/res/cardsfolder/g/griffin_rider.txt index 41f4e3edb6d..e6d5bb7dd05 100644 --- a/res/cardsfolder/g/griffin_rider.txt +++ b/res/cardsfolder/g/griffin_rider.txt @@ -7,6 +7,7 @@ S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | AddToughness$ 3 | AddKe SVar:X:Count$Valid Griffin.YouCtrl SVar:BuffedBy:Griffin SVar:RemRandomDeck:True +SVar:DeckWants:Type$Griffin SVar:Rarity:Common SVar:Picture:http://www.wizards.com/global/images/magic/general/griffin_rider.jpg SetInfo:M12|Common|http://magiccards.info/scans/en/m12/20.jpg diff --git a/res/cardsfolder/k/krakens_eye.txt b/res/cardsfolder/k/krakens_eye.txt index 68f47507aa5..fc467bbdbc5 100644 --- a/res/cardsfolder/k/krakens_eye.txt +++ b/res/cardsfolder/k/krakens_eye.txt @@ -5,6 +5,7 @@ Text:no text T:Mode$ SpellCast | ValidCard$ Card.Blue | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a blue spell, you may gain 1 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | Defined$ You | LifeAmount$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Color$Blue SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/krakens_eye.jpg SetInfo:DST|Uncommon|http://magiccards.info/scans/en/ds/126.jpg diff --git a/res/cardsfolder/m/mesa_enchantress.txt b/res/cardsfolder/m/mesa_enchantress.txt index 1ba5596b86f..f31d65ddace 100644 --- a/res/cardsfolder/m/mesa_enchantress.txt +++ b/res/cardsfolder/m/mesa_enchantress.txt @@ -6,6 +6,7 @@ PT:0/2 T:Mode$ SpellCast | ValidCard$ Enchantment | ValidActivatingPlayer$ You | Execute$ TrigDraw | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever you cast an enchantment spell, draw a card. SVar:TrigDraw:AB$Draw | Cost$ 0 | Defined$ You | NumCards$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Type$Enchantment SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/mesa_enchantress.jpg SetInfo:M10|Rare|http://magiccards.info/scans/en/m10/20.jpg diff --git a/res/cardsfolder/m/mwonvuli_beast_tracker.txt b/res/cardsfolder/m/mwonvuli_beast_tracker.txt index 18a0c1ea046..6cbb1ed87b6 100644 --- a/res/cardsfolder/m/mwonvuli_beast_tracker.txt +++ b/res/cardsfolder/m/mwonvuli_beast_tracker.txt @@ -6,6 +6,7 @@ PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ScoutSearch | TriggerDescription$ When CARDNAME enters the battlefield, search your library for a creature card with deathtouch, hexproof, reach or trample and reveal it. Shuffle your library, then put that card on top of it. SVar:ScoutSearch:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeNum$ 1 | ChangeType$ Creature.withDeathtouch+YouCtrl,Creature.withHexproof+YouCtrl,Creature.withReach+YouCtrl,Creature.withTrample+YouCtrl SVar:RemRandomDeck:True +SVar:DeckWants:KeywordAny$Deathtouch|Hexproof|Reach|Trample SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/mwonvuli_beast_tracker.jpg SetInfo:M13|Uncommon|http://magiccards.info/scans/en/m13/177.jpg diff --git a/res/cardsfolder/p/phylactery_lich.txt b/res/cardsfolder/p/phylactery_lich.txt index be6bc2ec6ae..ab656a8b464 100644 --- a/res/cardsfolder/p/phylactery_lich.txt +++ b/res/cardsfolder/p/phylactery_lich.txt @@ -7,6 +7,7 @@ K:Indestructible T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Permanent.counters_GE1_PHYLACTERY+YouCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ When you control no permanents with phylactery counters on them, sacrifice CARDNAME. SVar:TrigSac:AB$Sacrifice | Cost$ 0 | Defined$ Self SVar:RemRandomDeck:True +SVar:DeckWants:Type$Artifact SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/phylactery_lich.jpg SetInfo:M11|Rare|http://magiccards.info/scans/en/m11/110.jpg diff --git a/res/cardsfolder/q/quirion_dryad.txt b/res/cardsfolder/q/quirion_dryad.txt index d474f1df927..558785afe59 100644 --- a/res/cardsfolder/q/quirion_dryad.txt +++ b/res/cardsfolder/q/quirion_dryad.txt @@ -7,6 +7,7 @@ T:Mode$ SpellCast | ValidCard$ Card.White,Card.Blue,Card.Black,Card.Red | ValidA SVar:TrigPutCounter:AB$PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 SVar:BuffedBy:Card.White,Card.Blue,Card.Black,Card.Red SVar:RemRandomDeck:True +SVar:DeckWants:ColorAny$white|blue|black|red SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/quirion_dryad.jpg SetInfo:M13|Rare|http://magiccards.info/scans/en/m13/184.jpg diff --git a/res/cardsfolder/s/scepter_of_empires.txt b/res/cardsfolder/s/scepter_of_empires.txt index ff4afbbaf62..89982f664fe 100644 --- a/res/cardsfolder/s/scepter_of_empires.txt +++ b/res/cardsfolder/s/scepter_of_empires.txt @@ -5,6 +5,7 @@ Text:no text A:AB$ DealDamage | Cost$ T | Tgt$ TgtP | NumDmg$ X | References$ X | SpellDescription$ CARDNAME deals 1 damage to target player. It deals 3 damage to that player instead if you control artifacts named Crown of Empires and Throne of Empires. SVar:X:Count$AllM12Empires.3.1 SVar:RemRandomDeck:True +SVar:DeckWants:ListAll$Throne of Empires|Crown of Empires SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/scepter_of_empires.jpg SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/216.jpg diff --git a/res/cardsfolder/t/throne_of_empires.txt b/res/cardsfolder/t/throne_of_empires.txt index 0cbd8f5fead..275eb814eb9 100644 --- a/res/cardsfolder/t/throne_of_empires.txt +++ b/res/cardsfolder/t/throne_of_empires.txt @@ -5,6 +5,7 @@ Text:no text A:AB$ Token | Cost$ 1 T | TokenAmount$ X | References$ X | TokenName$ Soldier | TokenTypes$ Creature,Soldier | TokenOwner$ You | TokenColors$ White | TokenPower$ 1 | TokenToughness$ 1 | SpellDescription$ Put a 1/1 white Soldier creature token onto the battlefield. Put five of those tokens onto the battlefield instead if you control artifacts named Crown of Empires and Scepter of Empires. SVar:X:Count$AllM12Empires.5.1 SVar:RemRandomDeck:True +SVar:DeckWants:ListAll$Scepter of Empires|Crown of Empires SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/throne_of_empires.jpg SetInfo:M12|Rare|http://magiccards.info/scans/en/m12/221.jpg diff --git a/res/cardsfolder/w/war_falcon.txt b/res/cardsfolder/w/war_falcon.txt index 456cb7cb886..667952efa5f 100644 --- a/res/cardsfolder/w/war_falcon.txt +++ b/res/cardsfolder/w/war_falcon.txt @@ -7,6 +7,7 @@ K:Flying S:Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ HIDDEN CARDNAME can't attack. | CheckSVar$ X | SVarCompare$ EQ0 | Description$ CARDNAME can't attack unless you control a Knight or Soldier. SVar:X:Count$Valid Kinght.YouCtrl,Soldier.YouCtrl SVar:RemRandomDeck:True +SVar:DeckWants:TypeAny$Knight|Soldier SVar:Rarity:Common SVar:Picture:http://www.wizards.com/global/images/magic/general/war_falcon.jpg SetInfo:M13|Common|http://magiccards.info/scans/en/m13/38.jpg diff --git a/res/cardsfolder/w/wurms_tooth.txt b/res/cardsfolder/w/wurms_tooth.txt index 1010c46cf6c..6500280dc85 100644 --- a/res/cardsfolder/w/wurms_tooth.txt +++ b/res/cardsfolder/w/wurms_tooth.txt @@ -5,6 +5,7 @@ Text:no text T:Mode$ SpellCast | ValidCard$ Card.Green | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a green spell, you may gain 1 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | Defined$ You | LifeAmount$ 1 SVar:RemRandomDeck:True +SVar:DeckWants:Color$Green SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/wurms_tooth.jpg SetInfo:DST|Uncommon|http://magiccards.info/scans/en/ds/162.jpg diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 0a76a0dc358..498bae82038 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -35,6 +35,7 @@ import com.esotericsoftware.minlog.Log; import forge.card.CardCharacteristics; import forge.card.CardManaCost; +import forge.card.DeckWants; import forge.card.EditionInfo; import forge.card.abilityfactory.AbilityFactory; import forge.card.cardfactory.CardFactoryUtil; @@ -224,6 +225,9 @@ public class Card extends GameEntity implements Comparable { private Card haunting = null; private Card effectSource = null; + private DeckWants deckWants = null; + private boolean isDeckWantsConstructed = false; + //private Map sVars = new TreeMap(); // hacky code below, used to limit the number of times an ability @@ -8956,6 +8960,9 @@ public class Card extends GameEntity implements Comparable { this.castFrom = castFrom0; } + /** + * @return CardDamageHistory + */ public CardDamageHistory getDamageHistory() { return damageHistory; } @@ -8968,7 +8975,7 @@ public class Card extends GameEntity implements Comparable { } /** - * @param effectSource0 the effectSource to set + * @param src the effectSource to set */ public void setEffectSource(Card src) { this.effectSource = src; @@ -8982,10 +8989,29 @@ public class Card extends GameEntity implements Comparable { } /** - * @param startsGameInPlay0 the startsGameInPlay to set + * @param startsGameInPlay the startsGameInPlay to set */ public void setStartsGameInPlay(boolean startsGameInPlay) { this.startsGameInPlay = startsGameInPlay; } + /** + * @return the deckWants + */ + public DeckWants getDeckWants() { + if (!isDeckWantsConstructed) { + deckWants = new DeckWants(getSVar("DeckWants")); + isDeckWantsConstructed = true; + } + return deckWants; + } + + /** + * @param deckWants the deckWants to set + */ + public void setDeckWants(DeckWants deckWants) { + this.deckWants = deckWants; + } + + } // end Card class diff --git a/src/main/java/forge/CardList.java b/src/main/java/forge/CardList.java index 4e6a59e7c09..a9db730983c 100644 --- a/src/main/java/forge/CardList.java +++ b/src/main/java/forge/CardList.java @@ -140,6 +140,23 @@ public class CardList implements Iterable { return list; } // getColor() + /** + * Get cards that match the given color string. Use the card's ManaCost + * to determine color. + * @param cardColor desired color + * @return CardList + */ + public final CardList getColorByManaCost(final String cardColor) { + final CardList ret = new CardList(); + for (final Card c : this) { + ArrayList colors = CardUtil.getOnlyColors(c); + if (colors.contains(cardColor)) { + ret.add(c); + } + } + return ret; + } // getColorByManaCost() + /** *

* getOnly2Colors. diff --git a/src/main/java/forge/CardUtil.java b/src/main/java/forge/CardUtil.java index ac0b030630b..4cc9bfb14a9 100644 --- a/src/main/java/forge/CardUtil.java +++ b/src/main/java/forge/CardUtil.java @@ -258,22 +258,22 @@ public final class CardUtil { */ public static ArrayList getOnlyColors(final Card c) { final CardManaCost m = c.getManaCost(); - final byte color_profile = m.getColorProfile(); + final byte colorProfile = m.getColorProfile(); final Set colors = new HashSet(); - if ((color_profile & forge.card.CardColor.WHITE) > 0) { + if ((colorProfile & forge.card.CardColor.WHITE) > 0) { colors.add(Constant.Color.WHITE); } - if ((color_profile & forge.card.CardColor.BLACK) > 0) { + if ((colorProfile & forge.card.CardColor.BLACK) > 0) { colors.add(Constant.Color.BLACK); } - if ((color_profile & forge.card.CardColor.BLUE) > 0) { + if ((colorProfile & forge.card.CardColor.BLUE) > 0) { colors.add(Constant.Color.BLUE); } - if ((color_profile & forge.card.CardColor.RED) > 0) { + if ((colorProfile & forge.card.CardColor.RED) > 0) { colors.add(Constant.Color.RED); } - if ((color_profile & forge.card.CardColor.GREEN) > 0) { + if ((colorProfile & forge.card.CardColor.GREEN) > 0) { colors.add(Constant.Color.GREEN); } diff --git a/src/main/java/forge/card/DeckWants.java b/src/main/java/forge/card/DeckWants.java new file mode 100644 index 00000000000..e7cb4733c27 --- /dev/null +++ b/src/main/java/forge/card/DeckWants.java @@ -0,0 +1,159 @@ +package forge.card; + +import forge.CardList; + +/** + * DeckWants provides the ability for a Card to "want" another Card or type of + * Cards in its random deck. + * + */ +public class DeckWants { + + /** + * Enum of types of DeckWants. + */ + public enum Type { CARD, COLOR, COLORANY, COLORALL, KEYWORDANY, LISTALL, TYPE, TYPEANY, NONE } + + private Type type = Type.NONE; + private String filterParam = null; + + /** + * Construct a DeckWants from the SVar string. + * + * @param wants + * SVar for DeckWants + */ + public DeckWants(String wants) { + String[] pieces = wants.split("\\$"); + if (pieces.length == 2) { + try { + Type typeValue = Type.valueOf(pieces[0].toUpperCase()); + for (Type t : Type.values()) { + if (typeValue == t) { + type = t; + break; + } + } + } catch (IllegalArgumentException e) { + // type will remain NONE + } + + filterParam = pieces[1]; + } + } + + /** + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Returns a list of Cards from the given CardList that match this + * DeckWants. I.e., other cards that this Card needs in its deck. + * + * @param cardList + * list of cards to be filtered + * @return CardList of Cards that match this DeckWants. + */ + public CardList filter(CardList cardList) { + CardList ret; + switch (type) { + case TYPE: + ret = cardList.getType(filterParam); + break; + case TYPEANY: + ret = new CardList(); + String[] types = filterParam.split("\\|"); + for (String type : types) { + CardList found = cardList.getType(type.trim()); + if (found.size() > 0) { + ret.addAll(found); + } + } + case CARD: + ret = cardList.getName(filterParam); + break; + case COLOR: + ret = cardList.getColorByManaCost(filterParam.toLowerCase()); + break; + case COLORANY: + ret = new CardList(); + String[] colors = filterParam.split("\\|"); + for (String color : colors) { + CardList found = cardList.getColorByManaCost(color.trim().toLowerCase()); + if (found.size() > 0) { + ret.addAll(found); + } + } + break; + case COLORALL: + ret = new CardList(); + int numFound = 0; + colors = filterParam.split("\\|"); + for (String color : colors) { + CardList found = cardList.getColorByManaCost(color.trim().toLowerCase()); + if (found.size() > 0) { + ret.addAll(found); + numFound++; + } + } + if (numFound < colors.length) { + ret.clear(); + } + break; + case KEYWORDANY: + ret = new CardList(); + String[] keywords = filterParam.split("\\|"); + for (String keyword : keywords) { + CardList found = cardList.getKeyword(keyword.trim()); + if (found.size() > 0) { + ret.addAll(found); + } + } + break; + case LISTALL: + ret = new CardList(); + numFound = 0; + String[] names = filterParam.split("\\|"); + for (String name : names) { + CardList found = cardList.getName(name.trim()); + if (found.size() > 0) { + ret.addAll(found); + numFound++; + } + } + if (numFound < names.length) { + ret.clear(); + } + break; + default: + ret = cardList; + break; + } + return ret; + } + + /** + * + * Get the minimum number of "wanted" cards needed for the current card + * to be used in an AI deck. + * + * @return int + */ + public int getMinCardsNeeded() { + int ret; + switch (type) { + case LISTALL: + String[] names = filterParam.split("\\|"); + ret = names.length; + break; + default: + ret = 1; + break; + } + return ret; + } + +} diff --git a/src/main/java/forge/game/limited/BoosterDeck.java b/src/main/java/forge/game/limited/BoosterDeck.java index a55f15ea3ea..114537ee30e 100644 --- a/src/main/java/forge/game/limited/BoosterDeck.java +++ b/src/main/java/forge/game/limited/BoosterDeck.java @@ -11,6 +11,7 @@ import forge.CardListUtil; import forge.Constant; import forge.card.CardColor; import forge.card.CardManaCost; +import forge.card.DeckWants; import forge.card.mana.ManaCostShard; import forge.deck.Deck; import forge.util.MyRandom; @@ -23,7 +24,7 @@ public class BoosterDeck extends Deck { private static final long serialVersionUID = -7818685851099321964L; - private int cardsNeeded = 22; + private int numSpellsNeeded = 22; private int landsNeeded = 18; private DeckColors colors; private CardList draftedList; @@ -32,6 +33,14 @@ public class BoosterDeck extends Deck { private CardList deckList = new CardList(); + /** + * Constructor. + * + * @param dList + * list of cards drafted + * @param pClrs + * colors + */ public BoosterDeck(CardList dList, DeckColors pClrs) { super(""); this.draftedList = dList; @@ -55,34 +64,40 @@ public class BoosterDeck extends Deck { aiPlayables = draftedList.filter(new CardListFilter() { @Override public boolean addCard(final Card c) { - return !(c.getSVar("RemAIDeck").equals("True") || c.getSVar("RemRandomDeck").equals("True")); + boolean unPlayable = c.getSVar("RemAIDeck").equals("True"); + unPlayable |= c.getSVar("RemRandomDeck").equals("True") && c.getSVar("DeckWants").equals(""); + return !unPlayable; } }); draftedList.removeAll(aiPlayables); - // 1. Add any planeswalkers - CardList walkers = aiPlayables.getType("Planeswalker"); + // 0. Add any planeswalkers + CardList walkers = aiPlayables.getOnly2Colors(colors.getColor1(), colors.getColor2()).getType("Planeswalker"); deckList.addAll(walkers); - cardsNeeded -= walkers.size(); + aiPlayables.removeAll(walkers); if (walkers.size() > 0) { - System.out.println("Added " + walkers.get(0).toString()); + System.out.println("Planeswalker: " + walkers.get(0).toString()); } + // 0.5. Add combo cards (should this be done later? or perhaps within + // each method?) + addComboCards(); + // 1. Add creatures, trying to follow mana curve addManaCurveCreatures(15); // 2.Try to fill up to 22 with on-color non-creature cards - addNonCreatures(cardsNeeded); + addNonCreatures(numSpellsNeeded - deckList.size()); // 3.Try to fill up to 22 with on-color creatures cards (if more than 15 // are present) - addBestCreatures(cardsNeeded); + addBestCreatures(numSpellsNeeded - deckList.size()); CardList nonLands = aiPlayables.getNotType("Land").getOnly2Colors(colors.getColor1(), colors.getColor2()); // 4. If there are still on-color cards and the average cmc is low add a // 23rd card. - if (cardsNeeded == 0 && CardListUtil.getAverageCMC(deckList) < 3 && !nonLands.isEmpty()) { + if (deckList.size() == numSpellsNeeded && CardListUtil.getAverageCMC(deckList) < 3 && !nonLands.isEmpty()) { Card c = nonLands.get(0); deckList.add(c); aiPlayables.remove(0); @@ -91,14 +106,14 @@ public class BoosterDeck extends Deck { // 5. If there are still less than 22 non-land cards add off-color // cards. - addRandomCards(cardsNeeded); + addRandomCards(numSpellsNeeded - deckList.size()); // 6. If it's not a mono color deck, add non-basic lands that were // drafted. addNonBasicLands(); + // 7. Fill up with basic lands. final CCnt[] clrCnts = calculateLandNeeds(); - if (landsNeeded > 0) { addLands(clrCnts); } @@ -135,23 +150,26 @@ public class BoosterDeck extends Deck { this.getMain().add(deckList); this.getSideboard().add(aiPlayables); this.getSideboard().add(draftedList); - /* + int i = 0; + System.out.println("DECK"); for (Card c : deckList) { i++; System.out.println(i + ". " + c.toString() + ": " + c.getManaCost().toString()); } i = 0; + System.out.println("NOT PLAYABLE"); for (Card c : draftedList) { i++; System.out.println(i + ". " + c.toString() + ": " + c.getManaCost().toString()); } i = 0; + System.out.println("NOT PICKED"); for (Card c : aiPlayables) { i++; System.out.println(i + ". " + c.toString() + ": " + c.getManaCost().toString()); } - */ + } else { throw new RuntimeException("BoosterDraftAI : buildDeck() error, decksize not 40"); } @@ -172,12 +190,13 @@ public class BoosterDeck extends Deck { int landsAdded = 0; for (int i = 0; i < 5; i++) { - if (clrCnts[i].getCount() > 0) { + if (clrCnts[i].getCount() > 0) { // calculate number of lands for each color final float p = (float) clrCnts[i].getCount() / (float) totalColor; final int nLand = (int) (landsNeeded * p) + 1; if (Constant.Runtime.DEV_MODE[0]) { - System.out.println("Basics[" + clrCnts[i].getColor() + "]: " + clrCnts[i].getCount() + "/" + totalColor + " = " + p + " = " + nLand); + System.out.println("Basics[" + clrCnts[i].getColor() + "]: " + clrCnts[i].getCount() + "/" + + totalColor + " = " + p + " = " + nLand); } for (int j = 0; j <= nLand; j++) { @@ -188,7 +207,7 @@ public class BoosterDeck extends Deck { } } } - + landsNeeded = landsNeeded - landsAdded; int n = 0; while (landsNeeded > 0) { @@ -209,7 +228,7 @@ public class BoosterDeck extends Deck { } /** - * attempt to optimize basic land counts according to color representation + * attempt to optimize basic land counts according to color representation. * * @return CCnt */ @@ -218,7 +237,6 @@ public class BoosterDeck extends Deck { new CCnt("Mountain", 0), new CCnt("Forest", 0) }; // count each card color using mana costs - // TODO: count hybrid mana differently? for (int i = 0; i < deckList.size(); i++) { final CardManaCost mc = deckList.get(i).getManaCost(); @@ -279,7 +297,6 @@ public class BoosterDeck extends Deck { final Card c = z.get(MyRandom.getRandom().nextInt(z.size() - 1)); deckList.add(c); - cardsNeeded--; nCards--; aiPlayables.remove(c); z.remove(c); @@ -308,7 +325,6 @@ public class BoosterDeck extends Deck { final Card c = others.get(index); deckList.add(c); - cardsNeeded--; nCards--; aiPlayables.remove(c); others.remove(c); @@ -333,7 +349,6 @@ public class BoosterDeck extends Deck { final Card c = creatures.get(0); deckList.add(c); - cardsNeeded--; nCreatures--; aiPlayables.remove(c); creatures.remove(c); @@ -347,9 +362,9 @@ public class BoosterDeck extends Deck { } /** - * Add creatures to the deck, trying to follow some mana curve. Trying to have generous - * limits at each cost, but perhaps still too strict. But we're trying to prevent the AI - * from adding everything at a single cost. + * Add creatures to the deck, trying to follow some mana curve. Trying to + * have generous limits at each cost, but perhaps still too strict. But + * we're trying to prevent the AI from adding everything at a single cost. * * @param nCreatures */ @@ -397,7 +412,6 @@ public class BoosterDeck extends Deck { if (willAddCreature) { deckList.add(c); - cardsNeeded--; nCreatures--; aiPlayables.remove(c); creatureCosts.put(cmc, creatureCosts.get(cmc) + 1); @@ -419,4 +433,36 @@ public class BoosterDeck extends Deck { } } + /** + * Add cards that need other cards to be in the deck. + */ + public void addComboCards() { + CardList onColorPlayables = aiPlayables.getOnly2Colors(colors.getColor1(), colors.getColor2()); + for (Card c : onColorPlayables) { + if (!c.getSVar("DeckWants").equals("")) { + DeckWants wants = c.getDeckWants(); + CardList cards = wants.filter(onColorPlayables); + if (cards.size() >= wants.getMinCardsNeeded()) { + if (Constant.Runtime.DEV_MODE[0]) { + System.out.println("Adding " + c.getName() + " with up to " + cards.size() + " combo cards (e.g., " + + cards.get(0).getName() + ")."); + } + deckList.add(c); + aiPlayables.remove(c); + if (cards.size() <= 4) { + deckList.addAll(cards); + aiPlayables.removeAll(cards); + } else { + cards.shuffle(); + for (int i = 0; i < 4; i++) { + Card theCard = cards.get(i); + deckList.add(theCard); + aiPlayables.remove(theCard); + } + } + } + } + } + } + } diff --git a/src/main/java/forge/game/limited/BoosterDraftAI.java b/src/main/java/forge/game/limited/BoosterDraftAI.java index 1ab8fdfe9a9..fa2ec5ce0e6 100644 --- a/src/main/java/forge/game/limited/BoosterDraftAI.java +++ b/src/main/java/forge/game/limited/BoosterDraftAI.java @@ -93,10 +93,9 @@ public class BoosterDraftAI { final CardList aiPlayables = chooseFrom.filter(new CardListFilter() { @Override public boolean addCard(final Card c) { - if (c.getSVar("RemAIDeck").equals("True") || c.getSVar("RemRandomDeck").equals("True")) { - return false; - } - return true; + boolean unPlayable = c.getSVar("RemAIDeck").equals("True"); + unPlayable |= c.getSVar("RemRandomDeck").equals("True") && c.getSVar("DeckWants").equals(""); + return !unPlayable; } }); @@ -159,8 +158,9 @@ public class BoosterDraftAI { hasPicked = true; } } - if (hasPicked && !pickedCard.isColorless()) { - this.playerColors.get(player).setColor2(pickedCard.getColor().get(0).toStringArray().get(0)); + String pickedCardColor = pickedCard.getColor().get(0).toStringArray().get(0); + if (hasPicked && !pickedCard.isColorless() && !pickedCardColor.equals(this.playerColors.get(player).getColor1())) { + this.playerColors.get(player).setColor2(pickedCardColor); if (Constant.Runtime.DEV_MODE[0]) { System.out.println("Player[" + player + "] Color2: " + this.playerColors.get(player).getColor2()); } @@ -352,7 +352,7 @@ public class BoosterDraftAI { } return out; } // getDecks() - + // returns 7 different ints, within the range of 0-9 /** @@ -388,7 +388,7 @@ public class BoosterDraftAI { this.deckColor[i] = this.deckColorChoices[n[i]]; } - // initilize color map + // initialize color map BoosterDraftAI.colorToLand.put(Constant.Color.BLACK, "Swamp"); BoosterDraftAI.colorToLand.put(Constant.Color.BLUE, "Island"); BoosterDraftAI.colorToLand.put(Constant.Color.GREEN, "Forest"); diff --git a/src/main/java/forge/game/player/ComputerUtil.java b/src/main/java/forge/game/player/ComputerUtil.java index 78d1ccf96af..c1b8e69ebc0 100644 --- a/src/main/java/forge/game/player/ComputerUtil.java +++ b/src/main/java/forge/game/player/ComputerUtil.java @@ -2038,7 +2038,8 @@ public class ComputerUtil { /** * Is it OK to cast this for less than the Max Targets? - * @param source + * @param source the source Card + * @return true if it's OK to cast this Card for less than the max targets */ public static boolean shouldCastLessThanMax(final Card source) { boolean ret = true; diff --git a/src/test/java/forge/DeckWantsTest.java b/src/test/java/forge/DeckWantsTest.java new file mode 100644 index 00000000000..06aeede3aae --- /dev/null +++ b/src/test/java/forge/DeckWantsTest.java @@ -0,0 +1,189 @@ +package forge; + +import java.io.File; +import java.util.List; + +import org.testng.annotations.Test; + +import junit.framework.Assert; + +import forge.card.DeckWants; +import forge.properties.ForgeProps; +import forge.properties.NewConstants; +import forge.util.FileUtil; + +/** + * Tests for DeckWants. + * + */ +@Test(timeOut = 1000, enabled = true) +public class DeckWantsTest { + + /** + * Card test. + */ + @Test(timeOut = 1000, enabled = true) + void test() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/g", "griffin_rider.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertEquals("Griffin Rider", c.getName()); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.TYPE, c.getDeckWants().getType()); + + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "assault_griffin.txt")); + Card assaultGriffin = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(assaultGriffin); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "auramancer.txt")); + Card auramancer = CardReader.readCard(cardLines); + cl.add(auramancer); + + Assert.assertEquals(1, c.getDeckWants().filter(cl).size()); + Assert.assertEquals("Assault Griffin", c.getDeckWants().filter(cl).get(0).getName()); + Assert.assertEquals(1, c.getDeckWants().getMinCardsNeeded()); + } + + /** + * Filter for cards. + */ + @Test(timeOut = 1000, enabled = true) + void testCards() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/t", "throne_of_empires.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertEquals("Throne of Empires", c.getName()); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.LISTALL, c.getDeckWants().getType()); + + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "assault_griffin.txt")); + Card assaultGriffin = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(assaultGriffin); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/s", "scepter_of_empires.txt")); + Card sc = CardReader.readCard(cardLines); + cl.add(sc); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/c", "crown_of_empires.txt")); + Card cr = CardReader.readCard(cardLines); + cl.add(cr); + + Assert.assertEquals(2, c.getDeckWants().filter(cl).size()); + Assert.assertEquals(2, c.getDeckWants().getMinCardsNeeded()); + } + + /** + * Filter for keywords. + */ + @Test(timeOut = 1000, enabled = true) + void testKeywords() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/m", "mwonvuli_beast_tracker.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.KEYWORDANY, c.getDeckWants().getType()); + + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "acidic_slime.txt")); + Card card = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(card); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "ajanis_sunstriker.txt")); + Card card2 = CardReader.readCard(cardLines); + cl.add(card2); + + Assert.assertEquals(1, c.getDeckWants().filter(cl).size()); + Assert.assertEquals(1, c.getDeckWants().getMinCardsNeeded()); + } + + + /** + * Filter for color. + */ + @Test(timeOut = 1000, enabled = true) + void testColor() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/w", "wurms_tooth.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.COLOR, c.getDeckWants().getType()); + + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/l", "llanowar_elves.txt")); + Card card = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(card); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/u", "unsummon.txt")); + card = CardReader.readCard(cardLines); + cl.add(card); + + cl.getOnly2Colors("green", "white"); + Assert.assertEquals(1, c.getDeckWants().filter(cl).size()); + Assert.assertEquals(1, c.getDeckWants().getMinCardsNeeded()); + } + + /** + * Failing filter for cards. + */ + @Test(timeOut = 1000, enabled = true) + void testFailCards() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/t", "throne_of_empires.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertEquals("Throne of Empires", c.getName()); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.LISTALL, c.getDeckWants().getType()); + + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "assault_griffin.txt")); + Card assaultGriffin = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(assaultGriffin); + cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/s", "scepter_of_empires.txt")); + Card sc = CardReader.readCard(cardLines); + cl.add(sc); + + Assert.assertEquals(0, c.getDeckWants().filter(cl).size()); + Assert.assertEquals(2, c.getDeckWants().getMinCardsNeeded()); + } + + /** + * Card test for junk deck wants. + */ + @Test(timeOut = 1000, enabled = true) + void testJunk() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/g", "griffin_rider.txt")); + Card c = CardReader.readCard(cardLines); + c.setSVar("DeckWants", "Junk$Junk"); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.NONE, c.getDeckWants().getType()); + } + + /** + * + * Test for no wants. + */ + @Test(timeOut = 1000, enabled = true) + void testNoFilter() { + List cardLines = FileUtil + .readFile(new File(ForgeProps.getFile(NewConstants.CARDSFOLDER) + "/a", "assault_griffin.txt")); + Card c = CardReader.readCard(cardLines); + Assert.assertEquals("Assault Griffin", c.getName()); + Assert.assertNotNull(c.getDeckWants()); + Assert.assertEquals(DeckWants.Type.NONE, c.getDeckWants().getType()); + + Card assaultGriffin = CardReader.readCard(cardLines); + CardList cl = new CardList(); + cl.add(assaultGriffin); + Assert.assertEquals(1, c.getDeckWants().filter(cl).size()); + + } +}