Page 1 of 2

Card.getText()

PostPosted: 12 Sep 2010, 13:05
by Chris H.
I have recently spent some time in this section. We have a number of new keywords that were not showing up on Instants and Sorceries. I just added "CARDNAME is {color} for Ghostfire and Ancestral Vision. I also cleaned up and consolidated the code in this section.

I think that I am starting to understand why we have had this problem ever since Rob added the cantrip code. Instants and Sorceries have the spell abilities and the spellText combined but it had no code for adding keywords which do not create a spell ability with it's own spell ability description.

So things are a little bit better now but with the large number of new keywords added this summer it is possible that I may have overlooked a few. So the Card.getText() code for Instants and Sorceries now look like this:

Code: Select all
    public String getText() {
        if(isInstant() || isSorcery()) {
            String s = getSpellText();
            StringBuilder sb = new StringBuilder();
           
            // Give spellText line breaks for easier reading
            sb.append(s.replaceAll("\\\\r\\\\n", "\r\n"));
           
            // NOTE:
            if (sb.toString().contains(" (NOTE: ")) {
                sb.insert(sb.indexOf("(NOTE: "), "\r\n");
            }
            if (sb.toString().contains("(NOTE: ") && sb.toString().endsWith(".)") && !sb.toString().endsWith("\r\n")) {
                sb.append("\r\n");
            }
           
            // Add SpellAbilities
            SpellAbility[] sa = getSpellAbility();
            for (int i = 0; i < sa.length; i++) {
                sb.append(sa[i].toString() + "\r\n");
            }
           
            // Add Keywords
            ArrayList<String> kw = getKeyword();
           
            // Ripple + Dredge + Madness + CARDNAME is {color}.
            for (int i = 0; i < kw.size(); i++) {
                if ((kw.get(i).startsWith("Ripple") && !sb.toString().contains("Ripple"))
                        || (kw.get(i).startsWith("Dredge") && !sb.toString().contains("Dredge"))
                        || (kw.get(i).startsWith("Madness") && !sb.toString().contains("Madness"))
                        || (kw.get(i).startsWith("CARDNAME is ") && !sb.toString().contains("CARDNAME is "))) {
                    sb.append(kw.get(i).replace(":", " ")).append("\r\n");
                }
            }
           
            // Draw a card. + Changeling + CARDNAME can't be countered. + Cascade
            for (int i = 0; i < kw.size(); i++) {
                if ((kw.get(i).contains("Draw a card.") && !sb.toString().contains("Draw a card."))
                        || (kw.get(i).contains("Changeling") && !sb.toString().contains("Changeling"))
                        || (kw.get(i).contains("CARDNAME can't be countered.") && !sb.toString().contains("CARDNAME can't be countered."))
                        || (kw.get(i).contains("Cascade") && !sb.toString().contains("Cascade"))) {
                    sb.append(kw.get(i)).append("\r\n");
                }
            }
           
            // Storm
            if (getKeyword().contains("Storm") && !sb.toString().contains("Storm (When you ")) {
                if (sb.toString().endsWith("\r\n\r\n")) {
                    sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n")+3);
                }
                sb.append("Storm (When you cast this spell, copy it for each spell cast before it this turn.");
                if (sb.toString().contains("Target") || sb.toString().contains("target")) {
                    sb.append(" You may choose new targets for the copies.");
                }
                sb.append(")\r\n");
            }
           
            // Scry
            if(!sb.toString().contains("Scry")) for(int i = 0; i < getKeyword().size(); i++) {
                String k = getKeyword().get(i);
                if(k.startsWith("Scry")) {
                    String kk[] = k.split(" ");
                    //sb.append("Scry " + kk[1] + " (To scry X, look at the top X cards of your library, then put any number of them on the bottom of your library and the rest on top in any order.)\r\n");
                    sb.append("Scry ");
                    sb.append(kk[1]);
                    sb.append(" (To scry X, look at the top X cards of your library, then put any number of them on the bottom of your library and the rest on top in any order.)\r\n");
                }
            }
           
            while (sb.toString().endsWith("\r\n")) {
                sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n")+3);
            }
           
            return sb.toString().replaceAll("CARDNAME", getName());
        }

Re: Card.getText()

PostPosted: 12 Sep 2010, 13:44
by Rob Cashwalker
I'm sure this is on the top end of "best we can do now", but I also have a sneaking suspicion that it could be done cleaner, without including so much rules text that could be changed with any new set release when they revise oracle text.

Maybe we shouldn't try to use the spell descriptions for each spell ability. Instead, use the text field (now with line feeds!) for what it was intended. It's a field that the python script can maintain easily.
The spell description is only used for the multiple choice dialog.

Re: Card.getText()

PostPosted: 12 Sep 2010, 18:38
by Chris H.
Yeah, I too have considered similar thoughts. The cards.txt file has gotten so large that it is hard to maintain it without conflicts arising. Imagine a folder with a separate text file for each and every cards. While we are it it we could have a label for each field. This would allow multi-line spell text descriptions without jumping through any additional hoops.

We can always dream.

Re: Card.getText()

PostPosted: 13 Sep 2010, 00:45
by Rob Cashwalker
I'm quickly warming up to that concept....
Short of figuring out how to iterate all files in a folder, I think I know for sure that by the next beta cards.txt will become a cards.folder.

Edit - I have this working!

This is ReadCard:
Code: Select all
package forge;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;

import forge.error.ErrorViewer;
import forge.properties.ForgeProps;
import forge.properties.NewConstants;


public class ReadCard implements Runnable, NewConstants {
    private BufferedReader  in;
    private String fileList[];
    private ArrayList<Card> allCards = new ArrayList<Card>();
   
    public static void main(String args[]) throws Exception {
        try {
            ReadCard read = new ReadCard(ForgeProps.getFile(CARDS));
           
            javax.swing.SwingUtilities.invokeAndWait(read);
            //    read.run();
           
            Card c[] = new Card[read.allCards.size()];
            read.allCards.toArray(c);
            for(int i = 0; i < c.length; i++) {
                System.out.println(c[i].getName());
                System.out.println(c[i].getManaCost());
                System.out.println(c[i].getType());
                System.out.println(c[i].getSpellText());
                System.out.println(c[i].getKeyword());
                System.out.println(c[i].getBaseAttack() + "/" + c[i].getBaseDefense() + "\n");
            }
        } catch(Exception ex) {
            ErrorViewer.showError(ex);
            System.out.println("Error reading file " + ex);
        }
    }
   
    public ArrayList<Card> getCards() {
        return new ArrayList<Card>(allCards);
    }
   
    public ReadCard(String filename) {
        this(new File(filename));
    }
   
    public ReadCard(File file) {
        if(!file.exists())
            throw new RuntimeException("ReadCard : constructor error -- file not found -- filename is "
                    + file.getAbsolutePath());
       
        if (!file.isDirectory())
           throw new RuntimeException("ReadCard : constructor error -- not a direcotry -- "
                 + file.getAbsolutePath());
       
        fileList = file.list();
        //makes the checked exception, into an unchecked runtime exception
        //try {
        //    in = new BufferedReader(new FileReader(file));
        //} catch(Exception ex) {
        //    ErrorViewer.showError(ex, "File \"%s\" not found", file.getAbsolutePath());
        //    throw new RuntimeException("ReadCard : constructor error -- file not found -- filename is "
        //            + file.getPath());
        //}
    }//ReadCard()
   
    public void run() {
       Card c = null;
       ArrayList<String> cardNames = new ArrayList<String>();
       File fl = null;
       
       for (int i=0; i<fileList.length; i++)
       {
            try {
               fl = new File("res/cardsfolder/" + fileList[i]);
                in = new BufferedReader(new FileReader(fl));
            } catch(Exception ex) {
                ErrorViewer.showError(ex, "File \"%s\" exception", fl.getAbsolutePath());
                throw new RuntimeException("ReadCard : run error -- file exception -- filename is "
                        + fl.getPath());
            }
           
            c = new Card();
           
            String s = readLine();
            while (!s.equals("End"))
            {
               if (s.startsWith("Name:"))
               {
                  String t = s.substring(5);
                  System.out.println(s);
                  if (cardNames.contains(t))
                  {
                        System.out.println("ReadCard:run() error - duplicate card name: " + t);
                        throw new RuntimeException("ReadCard:run() error - duplicate card name: " + t);
                  }
                  else
                     c.setName(t);
               }
               
               else if (s.startsWith("ManaCost:"))
               {
                  String t = s.substring(9);
                  System.out.println(s);
                  if (!t.equals("no cost"))
                     c.setManaCost(t);
               }
               
               else if (s.startsWith("Types:"))
                  addTypes(c, s.substring(6));
               
               else if (s.startsWith("Text:"))
               {
                  String t = s.substring(5);
                  if (!t.equals("no text"));
                     c.setText(t);
               }
               
               else if (s.startsWith("PT:"))
               {
                  String t = s.substring(3);
                  String pt[] = t.split("/");
                  int att = Integer.parseInt(pt[0]);
                  int def = Integer.parseInt(pt[1]);
                  c.setBaseAttack(att);
                  c.setBaseDefense(def);
               }
               
               else if (s.startsWith("K:"))
               {
                  String t = s.substring(2);
                  c.addIntrinsicKeyword(t);
               }
               
               s = readLine();
            } // while !End

            cardNames.add(c.getName());
            allCards.add(c);
           
            try {
            in.close();
         } catch (IOException ex) {
                ErrorViewer.showError(ex, "File \"%s\" exception", fl.getAbsolutePath());
                throw new RuntimeException("ReadCard : run error -- file exception -- filename is "
                        + fl.getPath());
         }
       }
       
    }//run()
   
    private void addTypes(Card c, String types) {
        StringTokenizer tok = new StringTokenizer(types);
        while(tok.hasMoreTokens())
            c.addType(tok.nextToken());
    }
   
    private String readLine() {
        //makes the checked exception, into an unchecked runtime exception
        try {
            String s = in.readLine();
            if(s != null) s = s.trim();
            return s;
        } catch(Exception ex) {
            ErrorViewer.showError(ex);
            throw new RuntimeException("ReadCard : readLine(Card) error");
        }
    }//readLine(Card)
}
This is where CardFactory calls it:
Code: Select all
ReadCard read = new ReadCard(ForgeProps.getFile(CARDSFOLDER));
These are the first two cards converted:
abandoned_outpost.txt: (filenames really don't matter)
Code: Select all
Name:Abandoned Outpost
ManaCost:no cost
Types:Land
Text:no text
K:tap: add W
K:tap, Sacrifice CARDNAME: Add W to your mana pool.
K:tap, Sacrifice CARDNAME: Add U to your mana pool.
K:tap, Sacrifice CARDNAME: Add B to your mana pool.
K:tap, Sacrifice CARDNAME: Add R to your mana pool.
K:tap, Sacrifice CARDNAME: Add G to your mana pool.
K:Comes into play tapped.
K:SVar:Rarity:Common
K:SVar:Picture:http://www.wizards.com/global/images/magic/general/abandoned_outpost.jpg
End
abbey_gargoyles.txt:
Code: Select all
Name:Abbey Gargoyles
ManaCost:2 W W W
Types:Creature Gargoyle
Text:no text
PT:3/4
K:Flying
K:Protection from red
K:SVar:Rarity:Uncommon
K:SVar:Picture:http://www.wizards.com/global/images/magic/general/abbey_gargoyles.jpg
End

Re: Card.getText()

PostPosted: 13 Sep 2010, 13:05
by Chris H.
Wow. I am glad that I came back and took another look at your message. :D The additional work that you appended onto the end of your message is awesome!

As far as file names I think you have the right idea. That system works well for the jpg pics and I can't think of any reasons why this should not be used.

Would Sol be able to write a python script to create the card files from the existing cards.txt file?

I guess that this would be a several step sort of process. Divide up the cards.txt file and add in your code. Bug test and fix any problems that come up.

Once things are stable we can then consider what it would take to replace all of the values included in the "Text:" field with the rules data at gathers. Comment out the code inside of Card.getText().

We may have some card and/or keyword code that would then have to be modified to handle the new system. The gather rules data would also need a little editing to add in the missing notes and we have the braces to remove.

Re: Card.getText()

PostPosted: 13 Sep 2010, 13:54
by Rob Cashwalker
I'm thinking I can actually re-hack the original ReadCard methods to make it spit out what we need...

Once it's broken out, it shouldn't be a huge change for the python script to maintain.

Re: Card.getText()

PostPosted: 13 Sep 2010, 20:54
by Rob Cashwalker
This is the output from hacking ReadCard to also dump it to individual card files.

Re: Card.getText()

PostPosted: 13 Sep 2010, 22:54
by Chris H.
I downloaded the archive. Nice work. When should we make the change over?

Re: Card.getText()

PostPosted: 14 Sep 2010, 02:54
by Rob Cashwalker
Well, I uploaded everything.

You changed Arashi, the Sky Asunder between the afternoon and evening, that will need to be re-applied.

Re: Card.getText()

PostPosted: 14 Sep 2010, 03:04
by friarsol
Rob Cashwalker wrote:Well, I uploaded everything.

You changed Arashi, the Sky Asunder between the afternoon and evening, that will need to be re-applied.
We should probably put a post up on the change so everyone is aware of the new format. And probably actually submit the new folder to the SVN. This will make mass-changes more difficult since devs can no longer just search through one file for a specific string.

Re: Card.getText()

PostPosted: 14 Sep 2010, 03:41
by DennisBergkamp
Yup, I don't see any of the individual card files committed....

Re: Card.getText()

PostPosted: 14 Sep 2010, 03:46
by Rob Cashwalker
I selected all the card files and then did "Add to Version control". In fact, I did that before I committed the source code changes.

I just did a commit, see if that makes them show up.

Re: Card.getText()

PostPosted: 14 Sep 2010, 05:33
by DennisBergkamp
Now I do, great stuff :)

Re: Card.getText()

PostPosted: 14 Sep 2010, 11:36
by Rob Cashwalker
FYI - Google's SVN chokes on this many files.... And it seems like it doubles the data transmitted.

Re: Card.getText()

PostPosted: 14 Sep 2010, 12:43
by Chris H.
Rob Cashwalker wrote:Well, I uploaded everything.

You changed Arashi, the Sky Asunder between the afternoon and evening, that will need to be re-applied.
`
No problem, it was a simple replace "T:" with "tap:" change for readability. I can make this mod once the new card files are all in place. :D