Page 1 of 2

New 2 Color Deck Generator

PostPosted: 30 Sep 2010, 11:56
by Rob Cashwalker
I just added a new random deck generator. It allows the user to specify the colors.

At the moment it doesn't handle dual lands, nor does it add any artifacts.

It attempts to promote a slight mana curve, and most cards should not be singleton.

Code: Select all

package forge;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import forge.error.ErrorViewer;

public class Generate2ColorDeck
{
   private String color1 = "";
   private String color2 = "";
   private Random r = null;
   private Map<String,String> ClrMap = null;
   private ArrayList<String> notColors = null;
   
   public Generate2ColorDeck(String Clr1, String Clr2)
   {            
      r = new Random();
      
      ClrMap = new HashMap<String,String>();
      ClrMap.put("White", "W");
      ClrMap.put("Blue", "U");
      ClrMap.put("Black", "B");
      ClrMap.put("Red", "R");
      ClrMap.put("Green", "G");
      
      notColors = new ArrayList<String>();
      notColors.add("White");
      notColors.add("Blue");
      notColors.add("Black");
      notColors.add("Red");
      notColors.add("Green");
      
      if (Clr1.equals("AI"))
      {
         // choose first color
         color1 = notColors.get(r.nextInt(5));
         
         // choose second color
         String c2 = notColors.get(r.nextInt(5));
         while (c2.equals(color1))
            c2 = notColors.get(r.nextInt(5));
         color2 = c2;
      }
      else
      {
         color1 = Clr1;
         color2 = Clr2;
      }
      
      notColors.remove(color1);
      notColors.remove(color2);
   }

   public CardList get2ColorDeck(int Size)
   {
      String tmpDeck = "";
      CardList tDeck = new CardList();
      
      Map<String, Integer> CardCounts = new HashMap<String, Integer>();
      
      int LandsPercentage = 42;
      int CreatPercentage = 34;
      int SpellPercentage = 24;
      
      // start with all cards
      CardList AllCards = AllZone.CardFactory.getAllCards();

      // remove cards that generated decks don't like
      AllCards = AllCards.filter(new CardListFilter(){
         public boolean addCard(Card c){
            return (!c.getSVar("RemAIDeck").equals("True"));
         }
      });
      
      // reduce to cards that match the colors
      CardList CL1 = AllCards.getColor(ClrMap.get(color1));
      CardList CL2 = AllCards.getColor(ClrMap.get(color2));
      
      // remove multicolor cards that don't match the colors
      CardListFilter clrF = new CardListFilter(){
         public boolean addCard(Card c){
            for (int i=0; i<notColors.size(); i++)
            {
               if (c.getManaCost().contains(ClrMap.get(notColors.get(i))))
                  return false;
            }
            return true;
         }
      };
      CL1 = CL1.filter(clrF);
      CL2 = CL2.filter(clrF);
      
      // build subsets based on type
      CardList Cr1 = CL1.getType("Creature");
      CardList Cr2 = CL2.getType("Creature");
      
      String ISE[] = {"Instant", "Sorcery", "Enchantment", "Planeswalker"};
      CardList Sp1 = CL1.getValidCards(ISE);
      CardList Sp2 = CL2.getValidCards(ISE);

      // final card pools
      CardList Cr12 = new CardList();
      CardList Sp12 = new CardList();
            
      // used for mana curve in the card pool
      final int MinCMC[] = {1}, MaxCMC[] = {2};
      CardListFilter cmcF = new CardListFilter(){
         public boolean addCard(Card c){
            int cCMC = c.getCMC();
            return (cCMC >= MinCMC[0]) && (cCMC <= MaxCMC[0]);               
         }
      };
      
      // select cards to build card pools using a mana curve
      for (int i=4; i>0; i--)
      {
         CardList Cr1CMC = Cr1.filter(cmcF);
         CardList Cr2CMC = Cr2.filter(cmcF);
         CardList Sp1CMC = Sp1.filter(cmcF);
         CardList Sp2CMC = Sp2.filter(cmcF);
         
         for (int j=0; j<i; j++)
         {
            Card c = Cr1CMC.get(r.nextInt(Cr1CMC.size()));
            Cr12.add(c);
            CardCounts.put(c.getName(), 0);
            
            c = Cr2CMC.get(r.nextInt(Cr2CMC.size()));
            Cr12.add(c);
            CardCounts.put(c.getName(), 0);
            
            c = Sp1CMC.get(r.nextInt(Sp1CMC.size()));
            Sp12.add(c);
            CardCounts.put(c.getName(), 0);
            
            c = Sp2CMC.get(r.nextInt(Sp2CMC.size()));
            Sp12.add(c);
            CardCounts.put(c.getName(), 0);
         }
         
         MinCMC[0] += 2; MaxCMC[0] +=2;
         // resulting mana curve of the card pool
         //16x 1 - 2
         //12x 3 - 4
         //8x 5 - 6
         //4x 7 - 8
         //=40x - card pool could support up to a 275 card deck (all 4-ofs plus basic lands)
      }
      
      // shuffle card pools
      Cr12.shuffle();
      Sp12.shuffle();

      // calculate card counts
      float p = (float) ((float)CreatPercentage * .01);
      int CreatCnt = (int)(p * (float)Size);
      tmpDeck += "Creature Count:" + CreatCnt + "\n";
      
      p = (float) ((float)SpellPercentage * .01);
      int SpellCnt = (int)(p * (float)Size);
      tmpDeck += "Spell Count:" + SpellCnt + "\n";
      
      // build deck from the card pools
      for (int i=0; i<CreatCnt; i++)
      {
         Card c = Cr12.get(r.nextInt(Cr12.size()));
         while (CardCounts.get(c.getName()) > 3)
            c = Cr12.get(r.nextInt(Cr12.size()));
         
         tDeck.add(AllZone.CardFactory.getCard(c.getName(), Constant.Player.Computer));
         int n = CardCounts.get(c.getName());
         CardCounts.put(c.getName(), n + 1);
         tmpDeck += c.getName() + " " + c.getManaCost() + "\n";
      }
      
      for (int i=0; i<SpellCnt; i++)
      {
         Card c = Sp12.get(r.nextInt(Sp12.size()));
         while (CardCounts.get(c.getName()) > 3)
            c = Sp12.get(r.nextInt(Sp12.size()));
         
         tDeck.add(AllZone.CardFactory.getCard(c.getName(), Constant.Player.Computer));
         int n = CardCounts.get(c.getName());
         CardCounts.put(c.getName(), n + 1);
         tmpDeck += c.getName() + " " + c.getManaCost() + "\n";
      }
      
      // Add basic lands
      // TODO: dual lands?
      int numBLands = 0;
      if (LandsPercentage > 0)
      {
         p = (float)((float)LandsPercentage * .01);
         numBLands = (int)(p * (float)Size);
      }
      else    // otherwise, just fill in the rest of the deck with basic lands
         numBLands = Size - tDeck.size();
      
      tmpDeck += "numBLands:" + numBLands + "\n";
      
      if (numBLands > 0)   // attempt to optimize basic land counts according to color representation
      {
         CCnt ClrCnts[] = {new CCnt("Plains", 0),
                       new CCnt("Island", 0),
                       new CCnt("Swamp", 0),
                       new CCnt("Mountain", 0),
                       new CCnt("Forest", 0)};
               
         // count each card color using mana costs
         // TODO: count hybrid mana differently?
         // TODO: count all color letters? ie: 2 W W counts as 2
         for (int i=0;i<tDeck.size(); i++)
         {
            Card c = tDeck.get(i);
            String mc = c.getManaCost();
            
            if (mc.contains("W"))
               ClrCnts[0].Count++;
            
            if (mc.contains("U"))
               ClrCnts[1].Count++;
            
            if (mc.contains("B"))
               ClrCnts[2].Count++;
            
            if (mc.contains("R"))
               ClrCnts[3].Count++;
   
            if (mc.contains("G"))
               ClrCnts[4].Count++;
         }
   
         // total of all ClrCnts
         int totalColor = 0;
         for (int i=0;i<5; i++)
         {
            totalColor += ClrCnts[i].Count;
            tmpDeck += ClrCnts[i].Color + ":" + ClrCnts[i].Count + "\n";
         }
         
         tmpDeck += "totalColor:" + totalColor + "\n";
         
         for (int i=0; i<5; i++)
         {
            if (ClrCnts[i].Count > 0)
            {   // calculate number of lands for each color
               p = (float)ClrCnts[i].Count / (float)totalColor;
               int nLand = (int)((float)numBLands * p);
               tmpDeck += "numLand-" + ClrCnts[i].Color + ":" + nLand + "\n";
               
               // just to prevent a null exception by the deck size fixing code
               CardCounts.put(ClrCnts[i].Color, nLand);
            
               for (int j=0; j<=nLand; j++)
                  tDeck.add(AllZone.CardFactory.getCard(ClrCnts[i].Color, Constant.Player.Computer));
            }
         }
      }
      tmpDeck += "DeckSize:" + tDeck.size() + "\n";
      
      // fix under-sized or over-sized decks, due to integer arithmetic
      if (tDeck.size() < Size)
      {
         int diff = Size - tDeck.size();
         
         for (int i=0; i<diff; i++)
         {
            Card c = tDeck.get(r.nextInt(tDeck.size()));
            
            while (CardCounts.get(c.getName()) >= 4)
               c = tDeck.get(r.nextInt(tDeck.size()));
            
            int n = CardCounts.get(c.getName());
            tDeck.add(AllZone.CardFactory.getCard(c.getName(), Constant.Player.Computer));
            CardCounts.put(c.getName(), n + 1);
            tmpDeck += "Added:" + c.getName() + "\n";
         }
      }
      else if (tDeck.size() > Size)
      {
         int diff = tDeck.size() - Size;
         
         for (int i=0; i<diff; i++)
         {
            Card c = tDeck.get(r.nextInt(tDeck.size()));
            
            while (c.getType().contains("Basic"))   // don't remove basic lands
               c = tDeck.get(r.nextInt(tDeck.size()));
            
            tDeck.remove(c);
            tmpDeck += "Removed:" + c.getName() + "\n";
         }
      }

      tmpDeck += "DeckSize:" + tDeck.size() + "\n";
      //ErrorViewer.showError(tmpDeck);
      
      return tDeck;
   }
   
   class CCnt
   {
      public String Color;
      public int Count;
      
      public CCnt(String clr, int cnt)
      {
         Color = clr;
         Count = cnt;
      }
   }
}

Re: New 2 Color Deck Generator

PostPosted: 30 Sep 2010, 17:19
by mtgrares
A better random deck generator sounds great. Sometimes the current deck generator (which I wrote) is too random since it doesn't consider a mana curve at all. (I think the old deck generator did create decks with at least 12 creatures.)

Thanks of the update because I enjoy using and playing against generated decks. (I turn on the AI Land Stack option in order to make it more challenging.)

Re: New 2 Color Deck Generator

PostPosted: 04 Oct 2010, 03:37
by Rob Cashwalker
Made some changes this weekend.

First off, I added support for dual lands in the 2-color generator.

Then I changed how both the them deck generator and the 2-color generator count mana colors for the ratio of lands. It now counts each mana symbol.

I also changed the NewGame screen. Instead of listing all the different deck generators, I left only one "Generate Deck". It then presents a list dialog with the different generators.

Finally, I added a property to enable/disable the display of the 2-color deck list. "showdeck/2color=true"

Re: New 2 Color Deck Generator

PostPosted: 04 Oct 2010, 13:58
by friarsol
Rob Cashwalker wrote:Then I changed how both the them deck generator and the 2-color generator count mana colors for the ratio of lands. It now counts each mana symbol.
For Hybrid Mana do those count as half mana or full mana?

Re: New 2 Color Deck Generator

PostPosted: 04 Oct 2010, 16:13
by Rob Cashwalker
Alas, I still haven't decided how those should count. For now, they count full.

Re: New 2 Color Deck Generator

PostPosted: 04 Oct 2010, 18:22
by friarsol
Rob Cashwalker wrote:Alas, I still haven't decided how those should count. For now, they count full.
How about this?
2/R counts as a full.
R/G in a red/green deck count as a half each.
R/G in a red/black deck counts as a full.

Re: New 2 Color Deck Generator

PostPosted: 04 Oct 2010, 18:55
by Rob Cashwalker
R/G in a R-B THEME deck, I guess... but how to know that the deck is supposed to be R-B, when there's G in a mana cost..?
R/G in a R-B GENERATED deck, will never occur, because of the color-selection filter - it removes any card that has a mana symbol outside of the decks two chosen colors.

In the long run I don't think it makes a huge difference:

given the mana costs for a simple R/G deck:
R
1 R
R R
2 R
G
1 G
G G
2 G
R/G
1 R/G
R/G R/G
2 R/G

colors always count as full: 10R + 10G = 20total (50%R 50%G)
hybrids count as half: 7.5R + 7.5G = 15total (50%R 50%G)

Re: New 2 Color Deck Generator

PostPosted: 05 Oct 2010, 16:17
by Bog Wraith
Since the 1002 release I find myself, when not questing, playing allot of the 2 colour generated deck mode. I really like it & it seems very well balanced.

Just saw that dual lands are now included. Is there a way to eventually get a smattering of Artifacts in the mix too? Maybe colour specific Arty's like Scepter of Fugue when playing Black as one of your colours as an example. Would Arty's with a specific mana colour cost make it easier for them to be included and for randomization of those picks as well?

In any event, I really like the balance I've seen with this so far Rob, thanks very much for all the work! 8)

Re: New 2 Color Deck Generator

PostPosted: 05 Oct 2010, 18:00
by Rob Cashwalker
I think if artifacts came into the mix, then it would end up diluting the colored cards.... This generator tries to promote card multiples.
I was also trying to think of an easy way to eliminate on-color cards with off-color activations, and I couldn't think of a simple, universal way, since not all abilities provide the activation cost, and not all cards include them in the card text.... Plus, how to differentiate "W" as a mana cost and not as a character in the word "Whenever".
Definitely something to look at at some point.

I played a number of matches with both players using these 2 color decks, and the computer was doing a kick-ass job, much better win % than the original generator.

I'm going to add a color option to the color list - "Random".

Re: New 2 Color Deck Generator

PostPosted: 05 Oct 2010, 19:31
by Bog Wraith
I get what you mean about artifacts & it's no big deal not to have them.

I too have noticed that the AI is performing at a higher level then before. I find the games closer and the AI making more logical choices with the cards in hand vs what is already played to the table. I also find that the deck compositions are well balanced and that the randomization of the cards in the decks in multiple replays are very fair and playable.

As I said earlier, I find myself playing this more & more & enjoying it immensely! 8)

Re: New 2 Color Deck Generator

PostPosted: 05 Oct 2010, 21:44
by zerker2000
Rob Cashwalker wrote:I was also trying to think of an easy way to eliminate on-color cards with off-color activations, and I couldn't think of a simple, universal way, since not all abilities provide the activation cost
Uh, they don't? Since when is getManaCost() depreciated?

Re: New 2 Color Deck Generator

PostPosted: 06 Oct 2010, 03:26
by Rob Cashwalker
Heh... you know, I was so busy working at this from the card level, I didn't really think about the SpellAbility.getManaCost(). On the other hand, some abilities (and most future ones) use the Ability_Cost, maybe the mana portion is copied to the SpellAbility?

Re: New 2 Color Deck Generator

PostPosted: 06 Oct 2010, 04:45
by friarsol
Ability_Cost sets the ManaCost during creation of an ability.
Code: Select all
    public Ability_Activated(Card sourceCard, Ability_Cost abCost, Target tgt) {
        super(SpellAbility.Ability, sourceCard);
        setManaCost(abCost.getMana());
        setPayCosts(abCost);
        if (tgt != null && tgt.doesTarget())
           setTarget(tgt);
    }
Even if it didn't we could have getManaCost() check if abCost was set, and return the ManaCost from there instead of the unintiated ManaCost.

Re: New 2 Color Deck Generator

PostPosted: 06 Oct 2010, 14:35
by slapshot5
I am getting an exception generating a 5 color deck:

Code: Select all
An error has occured. You can copy/paste this message or save it to a file.
Please report this, plus what you tried to do, to:
    http://www.slightlymagic.net/forum/viewforum.php?f=26
If you don't want to register an account, you can mail it directly to
    mtgerror@yahoo.com


GenerateConstructedDeck() : generateDeck() error, deck size it not 60, deck size is 29


Version:
Forge -- official beta: $Date: 2010-09-14 07:34:27 -0500 (Tue, 14 Sep 2010) $, SVN revision: $Revision: 2039 $

OS: Windows XP Version: 5.1 Architecture: x86

Java Version: 1.6.0_21 Vendor: Sun Microsystems Inc.

Detailed error trace:
java.lang.RuntimeException: GenerateConstructedDeck() : generateDeck() error, deck size it not 60, deck size is 29
    at forge.GenerateConstructedMultiColorDeck.generate5ColorDeck(GenerateConstructedMultiColorDeck.java:96)
    at forge.Gui_NewGame.generateConstructed5ColorDeck(Gui_NewGame.java:659)
    at forge.Gui_NewGame.genDecks(Gui_NewGame.java:625)
    at forge.Gui_NewGame.startButton_actionPerformed(Gui_NewGame.java:554)
    at forge.Gui_NewGame$11.actionPerformed(Gui_NewGame.java:420)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)
I pick a constructed deck for human and generate Deck for AI, then choose 5-color -> OK -> exception.

-slapshot5

Re: New 2 Color Deck Generator

PostPosted: 06 Oct 2010, 17:04
by Rob Cashwalker
I'm getting that too, on my work pc that hasn't been updated in a couple weeks, since before my updates. I didn't make any changes to the 5 color generator.

I'm suspecting it might be due to the changes involving card color.