mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
- New DeckWants SVar and class. Provides a way to give instructions for selecting related combo cards in random and Limited decks.
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Card> {
|
||||
private Card haunting = null;
|
||||
private Card effectSource = null;
|
||||
|
||||
private DeckWants deckWants = null;
|
||||
private boolean isDeckWantsConstructed = false;
|
||||
|
||||
//private Map<String, String> sVars = new TreeMap<String, String>();
|
||||
|
||||
// hacky code below, used to limit the number of times an ability
|
||||
@@ -8956,6 +8960,9 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
this.castFrom = castFrom0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CardDamageHistory
|
||||
*/
|
||||
public CardDamageHistory getDamageHistory() {
|
||||
return damageHistory;
|
||||
}
|
||||
@@ -8968,7 +8975,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<Card> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
@@ -140,6 +140,23 @@ public class CardList implements Iterable<Card> {
|
||||
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<String> colors = CardUtil.getOnlyColors(c);
|
||||
if (colors.contains(cardColor)) {
|
||||
ret.add(c);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} // getColorByManaCost()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getOnly2Colors.
|
||||
|
||||
@@ -258,22 +258,22 @@ public final class CardUtil {
|
||||
*/
|
||||
public static ArrayList<String> getOnlyColors(final Card c) {
|
||||
final CardManaCost m = c.getManaCost();
|
||||
final byte color_profile = m.getColorProfile();
|
||||
final byte colorProfile = m.getColorProfile();
|
||||
|
||||
final Set<String> colors = new HashSet<String>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
159
src/main/java/forge/card/DeckWants.java
Normal file
159
src/main/java/forge/card/DeckWants.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -177,7 +195,8 @@ public class BoosterDeck extends Deck {
|
||||
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++) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
189
src/test/java/forge/DeckWantsTest.java
Normal file
189
src/test/java/forge/DeckWantsTest.java
Normal file
@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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());
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user