Monday, October 25, 2010

Forge's Architecture or "How to program your own Magic engine" - Part 2

In Forge each player's life and zones (hand, library, battlefield, exile, stack) are all global variables. Much of the user interface and card code needs to access this information, so instead of passing the data everywhere, I just made it global.

GameAction is a loose collection of methods:

newGame()
drawCard(String player),
moveToGraveyard(Card c)
discard(Card c)
discardRandom(String player)
checkStateEffects()
sacrifice(Card c)
destroy(Card c)
destroyNoRegeneration(Card c)
shuffle(String player)
getOpponent(String p) : String
isCardInPlay(Card c) : boolean

Most of the code in GameAction could be done by the individual card code but putting all of the code in one place promotes code reuse and DRY (don't repeat yourself). GameAction at first only had newGame() and drawCard(String player). The other methods were added as needed.

GameAction.checkStateEffects() checks for state effects like "should a creature go to the graveyard" and the "legendary rule". At first I had no idea how to implement ongoing state effects like Glorious Anthem but then I hacked this method, voila! At first I thought I was bending the rules but this is completely correct after all.

(Forge has GameActionUtil which helps GameAction and is made up of static methods. GameActionUtil continues Forge's tradition of huge classes is 1/2 MB of code. And no that isn't Forge's largest class, CardFactory is a cool 1 MB all by itself. 1 MB of code is absolutely huge and is as long as a novel.)

Playing a spell or ability seems easy but there are many checks that you need to do. When choosing targets for a spell or ability, first you have to check if the chosen card has protection from that spell. Then each time a targeted spell/ability resolves, it has to check "is this card still on the battlefield" by GameAction.isCardInPlay(Card c). (I wrote this back when the battlefield was called "in play".)


Input

The Input class implements the State Pattern and handles all of the mouse clicks. Input is used to implement phases and choosing targets. Initially the Input class can be confusing but if you look at the code, you should be able to understand my design.

Input is just an interface (abstract class). The getMessage() method would return something like "Main1" or "Choose target creature to receive 2 damage".
interface Input
{
  String  getMessage();
  void    clickCard(Card c, Zone z);
}
The class InputControl is attached to the user interface and is just a "wrapper" for Input.
class InputControl
{
  private Input i;

  void setInput(Input input)
  {
      i = input;
      String s = i.getMessage();
      //display s on the user interface (gui)
  }

  void clickCard(Card c, Zone z)
  {
      i.clickCard(c, z);
  }
}
Hopefully you can see how InputControl changes. InputMain.getMessage() would return "Main Phase" and InputMain.clickCard() would allow the player to play a card in his hand or an activated ability on a creature on the battlefield. Input is also used to pay mana costs, to mulligan at the beginning of the game and to declare attackers and blockers.

Since InputControl.setInput() always calls Input.getMessage() first, you can put hacky stuff in getMessage(). Planeswalkers were introduced to Magic after I had much of Forge written and I was wondering if I could add them somehow. I put a bunch of hacky code in Input.getMessage() to restrict the player to only using one ability per turn. Also at that time Forge didn't even have the concept of loyalty counters, so I just used an int. Finally I just created another Combat object to simulate planeswalker combat. (For a long time if the AI had two planeswalkers, you could only attack the first one.)


A more complete Input class would also have clickPlayer(String player) and clickManaPool(). Technically the Input class does not process all of the mouse clicks since dialog boxes are used to get additional information from the player. The Input class could be modified to include dialog boxes which would be more "unifying" versus allowing the card code create dialog boxes at will.

The Input class may not seem very important but it was one of the first, big problems that I faced. I have no idea how Shandalar or other Magic programs process the mouse. Without stumbling onto the state pattern, I have no idea how badly I would have coded an inferior Input class.

Well that covers the main classes in Forge: Card, SpellAbility, Input, and GameAction. Forge has numerous other classes like CardList but these are the most important ones. If you understand these 5 classes, you can write your own Magic rules engine.

--mtgrares

p.s. Feel free to post any further technical questions about Forge's architecture.

Monday, October 18, 2010

Forge's Architecture or "How to program your own Magic engine" - Part 1

The old Rocky and Bullwinkle cartoon would always announce the next episode with two separate but related titles like "Bombs Away" or "My Ears are Ringing". Today I wanted to talk about Forge's architecture which should point you in the right direction for programming your own Magic rules engine. (You should also download a copy of Magic's comprehensive rules and read through as much as you can. I remember thinking that trample worked even when a creature was blocking.)

For interested non-programmers, the word "class" or "object" means "lines of Java code". Card.getName() means that getName() is code that exists inside the Card object.


Card

The most important classes in Forge are Card, SpellAbility, Input, and GameAction. The Card class represents a physical Magic card. Card objects are used everywhere that a real Magic card is used: player's library, graveyard, hand, battlefield, and exiled. Each Card object has a unique id so the player can know which card the AI is targeting. Shandalar had an option that allowed you to see each card's unique id.

(I was tempted to use only the card's name (String) in the library, hand and graveyard. I'm glad that I didn't because flashback was a late addition but it works beautifully because a card is a Card object and not a lousy String.)

The Card class has become more and more complicated and has grown from 200 lines to 17,000 (including whitespace and blank lines). Important Card methods are getName() : String, getSpellAbility() : SpellAbility[], getUniqueID() : int.


SpellAbility

The SpellAbility class represents every spell or ability. A Card object holds one or more SpellAbility objects. A Card object representing Elvish Piper would hold two SpellAbility objects. One representing the 1/1 creature and one representing the activated ability. The SpellAbility for the 1/1 creature can only be played if the Card object is in the player's hand, so SpellAbility.canPlay() checks to see which zone the card is in. Likewise the SpellAbility representing the activated ability, so canPlay() only returns true if the card is on the battlefield.

SpellAbility.canPlayAI() returns true if the AI should play the card. This means that each card is responsible for evaluating the game state. SpellAbility.canPlayAI() for Giant Growth checks to see if any of the AI's creatures are attacking and then targets one creature. This type of AI is very basic but it allows the AI to use a wide variety of cards. Since each card is evaluated separately the AI won't kill a 4/4 flyer with 2 Shocks. SpellAbility.resolve() is self-explanatory and does the "action" of the card like destroying all creatures.

In Forge only SpellAbility objects can go on the stack. This has caused some complications because after you pay for a card, you can't simply move the Card object from your hand to the stack. In Forge 2, I have been thinking about technically allowing a Card object to go on the stack and then get the SpellAbility using a method like, Card.getStackSpellAbility().

The methods setTargetCard() and setTargetPlayer() are used when the card targets only one card or player. The AI also uses these methods which allows the resolve() to be the same for both the human player and the AI. (Having different resolve() methods for you and the computer is a dangerous, dangerous road that I almost went down. Duplicating resolve() for different players seems wrong because of the code duplication.)

SpellAbility.setDescription(String s)
SpellAbility.getDescription() : String

SpellAbility.setStackDescription(String s)
SpellAbility.getStackDescription() : String


These are typical get/set methods. getDescription() is used when the card is in your hand or in play, while getStackDescription() is used when the card (SpellAbility) is on the stack and should say something like "Shock - targeting Elvish Piper (23)". While these 4 methods are minor, they convey much needed information to the player.

Important SpellAbility methods include getManaCost() : String, canPlay() : boolean, canPlayAI() : boolean, resolve(), setTargetCard(Card), setTargetPlayer(String player).

(SpellAbility should use an abstract Cost class instead of getManaCost() because Cost could include tap and sacrifice costs. Forge has to hack tap and sacrifice costs in order to make them work.)

Since this was just an overview of Forge and some of you may want more details. Please leave comments about the technical details you are interested in. I have breezed over many of the small details like "how exactly do you pay for mana" and "what should the user interface look like". These are important decisions that you, the lead programmer, need to decide. Programming is the result of thousands of tiny decisions that need to miraculously work together.

Next week I'll talk about Input and GameAction.

~~mtgrares

p.s.
Forge just uses Strings to represent mana and the reason that all mana strings have extra spaces "2 G G" was because it made the parsing code extra easy. This was a design decision that I had to make early on. It really doesn't matter if there are spaces or not but a decision has to be made.

Monday, October 11, 2010

Features You Probably Don't Know About

(Sorry for the long title.) With over 4,000 cards, Forge has a heaping-load of card slapping fun. (Hey, I remember when Forge had only 1,200 cards and I could practically name all of them.) Since Forge is soooo big, there are many features/cards that you may not be aware of.

You can change the basic land card pictures. For each land Forge looks for 4 files: forest.jpg, forest1.jpg, forest2.jpg, forest3.jpg. So you can change these files to whatever land strikes your fancy. The full frame Zendikar lands are beautiful and you can download them here. Put them in your /res/pics/ directory.

There are a number of unusual keywords that I didn't know Forge had like suspend. Suspend lets you pay less now and then the card resolves later after a few turns. Durkwood Baloth (Creature, 5/5) is a good example. You can cast it for 4GG or using suspend, pay G and then wait 5 turns until it resolves. Forge has 18 suspend cards, you can find them using the Filter menu in the Deck Editor. (Leave the "name" field blank and in the "card text" field type "suspend".)

If you are tired of seeing the same cards during a draft or quest, download this file. To change the draft, extract it into your /res/draft directory. To change your quest, extract it in into your /res/quest directory. These files are "flat" and have no rarity, all cards have an equal chance of appearing.

I absolutely love the "Generate Deck" option. You can remove creatures with 1 or 0 power or artifacts by using the menu. In the New Game screen, click on "Options" then look in the "Generate Deck" submenu. I have had some fabulous games. One time I actually used Fastbond to win and Fastbond is a pretty bad card. (Fastboand - G, Enchantment - You may play as many lands as you choose on your turn. Whenever you play a land other than the first land of the turn). I just happen to get a bunch of lands and a 3/3 creature, so I took the some damage and played my 3/3 on turn 2, boo-ya!

If you want a change of pace you can download a new mana pool picture here, put it in your /res/pics/ directory. It features all five Unhinged basic lands.

If you have other questions about Forge, feel free to post it to the forum.

--mtgrares

Monday, October 4, 2010

New Version

Forge now has 113 new cards, which brings the grand total up to 4,102 cards. Right now Forge only has only 8 Scars of Mirrodin cards but more cards will be added. Everybody loves planeswalkers because they are so versatile and now you can use Elspeth Tirel.

Forge also supports the new Scars of Mirrodin keyword "infect" which means "This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters." Infect is a combination of wither, which is cool, plus old-school poison counters. Plague Stinger (1B, 1/1, flying, infect) is a basic, common card with infect while the mythic rare Skithiryx, the Blight Dragon (3BB, 4/4, flying, infect) will definitely be winning some games. He gains haste for B and regenerates for BB.

Zero cost creatures just got upgraded, Memnite costs 0 and is a 1/1 artifact creature. Darksteel Axe is an indestructible artifact equipment that give +2/+0 that costs 1 and has an equip cost of 2. (By the way all cards that have the word "Darksteel" in the name are indestructible.) Tempered Steel (1WW, enchantment) is the new Glorious Anthem for artifact decks, it says " Artifact creatures you control get +2/+2". Tempered Steel will easily be one of the most expensive cards in Scars of Mirrodin.

Forge also supports the keyword "proliferate" which means "You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there." In other words, add 1 to all counters including poison counters. Keep in mind that proliferate does not use the word "target" so you can choose cards that have protection from blue.

Steady Progress (2U, Instant) says "Draw a card and proliferate" is a basic, utility card while Thrummingbird can be used to proliferate over and over again. Thrummingbird (1U, 1/1, flying) has "Whenever Thrummingbird deals combat damage to a player, proliferate." (I love cards like Steady Progress. It is cool to see sorcery/instant cards with keywords.)

Download - Forge 9-12 (10 MB) - Requires Java and runs on Windows, Mac, Linux. This version is the 9-12 version because that is when it was first posted on the forums.

Download - LQ (low quality) Card Pictures (109 MB) - delete your /res/pic/ directory because this file creates a new directory named "pic"

Forge_09-12.dmg (10 MB) - Macintosh binary, makes Forge seem like a regular Mac application

--mtgrares

p.s.
Many people have helped with this version. A very special thank you goes out to them. Thanks for your time and effort. I (and many other people) enjoy the fruits of your labor.

Dennis Bergkamp
Rob Cashwalker
Silly Freak
Snacko
Sloth
Hellfish
Friar Sol
Slapshot5
Chris H

Friday, October 1, 2010

Best Magic Article - State of Design 2010

Mark Rosewater, head Magic designer, writes one article a year that summarizes the previous year of Magic with pros and cons. If you only read one Magic article a year, it should be this one.

State of Design 2010