Monday, November 3, 2008

How to Program Royal Assassin

From time to time I like looking at how other Magic projects implemnt a card. I love Royal Assassin, this is how Firemox (formally the Magic-Project) and Incantus encode it. Incantus has card characteristics and if you read Magic’s comprehensive rules you will notice that it uses characteristics also. Since both Firemox and Incantus put each card in a separate file, all you have to do is download the file from the Internet and put it in the right directory and voila you have just added a new card.

In an effort to be complete, I’ll also show you the code for Royal Assassin taken from MTG Forge. The code is a little long and complicated compared to Firemox and Incantus. The Input class handles all mouse input, so a new Input object is created and used to target a tapped creature.


Incantus
//filename: Royal Assassin
name = 'Royal Assassin'
cardnum = 174
type = characteristic('Creature')
supertype = characteristic('')
subtypes = characteristic(['Human', 'Assassin'])
cost = '1BB'
color = characteristic(['B'])
text = ['T Destroy target tapped creature.']

in_play_role = Creature(card, 1, 1)
in_play_role.abilities = [ActivatedAbility(card, TapCost(),
target=Target(target_types=isCreature.with(lambda c: c.tapped)),
effects=Destroy())
]
in_play_role.triggered_abilities = []
in_play_role.static_abilities = []


Firemox
//filename: Royal_Assassin.xml
//all brackets were converted to quotes
//in order to be displayed correctly

"?xml version="1.0"?"
"card xmlns="http://sourceforge.net/projects/firemox" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=http://sourceforge.net/projects/firemox ../../validator.xsd name="Royal Assassin""
"rules-author-comment"By fabdouglas"/rules-author-comment"

"init"
"registers"
"register index="colorless" value="1"/"
"register index="black" value="2"/"
"register index="power" value="1"/"
"register index="toughness" value="1"/"
"/registers"

"colors"black"/colors"
"idcards"creature "/idcards"
"properties"assassin"/properties"
"/init"

"abilities"
"ability ref="cast-spell"/"
"activated-ability playable="instant" name="" zone="play""
"cost"
"action ref="T"/"
"action ref="target-creature""
"test"
"in-zone zone="playANDtapped"/"
"/test"
"/action"
"/cost"

"effects"
"action ref="destroy"/"
"/effects"
"/activated-ability"
"/abilities"
"/card"


MTG Forge
//taken from the infamous CardFactory

if(cardName.equals("Royal Assassin"))
{

final Ability_Tap ability = new Ability_Tap(card)
{

public boolean canPlayAI()
{
CardList human =
CardFactoryUtil.AI_getHumanCreature();

human = human.filter(new CardListFilter()
{
public boolean addCard(Card c)
{return c.isTapped();}
});

CardListUtil.sortAttack(human);
CardListUtil.sortFlying(human);

if(0 < human.size())
setTargetCard(human.get(0));

return 0 < human.size();
}//canPlayAI()

public void resolve()
{
Card c = getTargetCard();

if(AllZone.GameAction.isCardInPlay(c)
&& c.isTapped())
{
AllZone.GameAction.destroy(c);
}

}//resolve()
};//SpellAbility

Input target = new Input()
{
public void showMessage()
{
AllZone.Display.showMessage("Select target tapped creature to destroy");

ButtonUtil.enableOnlyCancel();
}

public void selectButtonCancel()
{stop();}

public void selectCard(Card c, PlayerZone zone)
{
if(c.isCreature() &&
zone.is(Constant.Zone.Play) &&
c.isTapped())
{
//tap ability
card.tap();

ability.setTargetCard(c);
AllZone.Stack.add(ability);
stop();
}//if
}//selectCard()
};//Input

card.addSpellAbility(ability);
ability.setDescription("tap: Destroy target tapped creature.");
ability.setBeforePayMana(target);
}

15 comments:

Anonymous said...

Interesting to see those different approaches to reacht the overall same goal.

Tim said...

Don't get me wrong - I think MTG Forge is a fantastic product, I really do. You've spent an enormous amount of time producing this game. And the approach obviously works - you've got a magic game written - I haven't!

But I think that it should be possible to write a magic program so that not a single piece of code should have to be tailored to a single card.

From the components of the ability on the card ("tap", "destroy", "target", "tapped", and "creature") it should be possible to use that ability, without having a custom function to deal with "Royal Assassin".

That way, cardFactory wouldn't need to be nearly so big, since there's going to be a heck of a lot of duplication duplication in there, and even better, you would be able to create a whole load of new cards without writing a single piece of code! (which has got to be a good thing!)

So for example, you could come up the following (made up) cards, without writing any new code:

Royal Killer
t: destroy target untapped creature

Royal Mercenary
1: destroy target tapped creature

Royal Mage
t: destroy target enchantment

Royal Demolisher
t: destroy target artifact

and so on.

I honestly think that doing this would be a worthwhile investment and save you time in the long run!

Keep up the good work!

WilLoW :--) said...

@Tim : my own experience with my MTG game (Check it out, link to the site in my name !) has shown the following :
A simple parsing mechanism allows to code between 10 and 20% of the magic cards.
After that, adding 1% to that number can become really painful.

MTGForge has already such a basic parsing mechanism, and I'm sure Forge is working on improving that for version 2.
But reaching 100% of the cards without some ad-hoc code from time to time is probably impossible.

@forge : here is how I plan to implement royal assassin in a future release (not implemented yet) :
[card]
text={T}: Destroy target tapped creature.
auto={T}:destroy target(creature[tapped])
id=1175
name=Royal Assassin
rarity=R
type=Creature
mana={1}{B}{B}
power=1
subtype=Human Assassin
toughness=1
[/card]

I have other cool stuff. Internally of course, the parser is a very dirty piece of crap, but from the outside it looks cool:
for example goblin king:
[card]
text=Other Goblin creatures get +1/+1 and have mountainwalk. (They're unblockable as long as defending player controls a Mountain.)
id=1296
auto=lord(goblin) 1/1 mountainwalk
name=Goblin King
rarity=R
type=Creature
mana={1}{R}{R}
power=2
subtype=Goblin
toughness=2
[/card]

I do not suggest going this way though. I think Incantus's way is far better, but it requires to hook your game to a scripting language. (I highly suggest that for MTGForge, I believe a language such as LUA can be hooked to Java)

by the way, a windows version of my game is now available, if you want to give it a try !

Gando the Wandering Fool said...

As has been said often in the past MTGForge 2 should be modularly written to use a scripting language to implement card construction. Will it happen? I don't know...its up to Rares whether he wants to improve the way the code works or simply add more functionality to what already exists. I know from my own experiences as a programmer, if you are self-taught sometimes developing your own tools (as is needed with a game like magic) can be a huge chore. As can UI development...But luckily there is a community here apparently willing to share some of the burden...

I highly recommend Sourceforge as a better focal point for this project. You still get to be in charge but now you can have people taking care of tasks you don't care for and accounting to you when they finish as opposed to fumbling around alone in the dark.

That's my two.five cents for today.

Btw Willow, sorry to say I do not have a psp but if I did Id be sure to get your app to check it out.

Incantus said...

Hi forge,

That was the old way to implement Royal Assassin :) This is the new way:


name = 'Royal Assassin'
types = characteristic('Creature')
supertypes = no_characteristic()
subtypes = characteristic('Assassin', 'Human')
cost = '1BB'
color = characteristic('B')
power = '1'
toughness = '1'
text = ['T Destroy target tapped creature.']

play_spell = play_permanent(cost)

#################################

@activated(txt=text[0])
def ability():
def effects(controller, source):
payment = yield TapCost()
target = yield Target(isCreature.with_condition(tapped)
# Code for effect
target.destroy()
yield
return effects
abilities.add(ability)

(sorry, all the indentations is screwed up)

As you can see, it's a bit more complicated than the original version, but it's just as willow said: with my old framework i could only implement maybe 50-60% of the cards, but this new framework means I can do about 95% of all magic cards.

Forge said...

These types of posts always generate the most number of comments :) :)

Tim,
"But I think that it should be possible to write a magic program so that not a single piece of code should have to be tailored to a single card."

Yes read http://mtgrares.blogspot.com/2008/10/more-plaintext-cards.html

Willow,
Thanks for a Windows port of your project. I'll download it.

"But reaching 100% of the cards without some ad-hoc code from time to time is probably impossible."

Well just programming a whole set like Lorwyn or Shards of Alara would be nice.

"I highly suggest that for MTGForge, I believe a language such as LUA can be hooked to Java"

I've read a little about LUA, World of Warcraft uses it. How would LUA help?

Incantus,
Yes you keep updating your project for some reason, joke, lol. You can add 95% of all cards, that is awesome. What set do you plan to program first?

Forge said...

These types of posts always generate the most number of comments :) :)

Tim,
"But I think that it should be possible to write a magic program so that not a single piece of code should have to be tailored to a single card."

Yes read http://mtgrares.blogspot.com/2008/10/more-plaintext-cards.html

Willow,
Thanks for a Windows port of your project. I'll download it.

"But reaching 100% of the cards without some ad-hoc code from time to time is probably impossible."

Well just programming a whole set like Lorwyn or Shards of Alara would be nice.

"I highly suggest that for MTGForge, I believe a language such as LUA can be hooked to Java"

I've read a little about LUA, World of Warcraft uses it. How would LUA help?

Incantus,
Yes you keep updating your project for some reason, joke, lol. You can add 95% of all cards, that is awesome. What set do you plan to program first?

Forge said...

This is how MTG Forge 2.0 will hopefully encode Royal Assassin.

Royal Assassin
1 B
B
Creature Human Assassin
1/1

Activated Ability
Text: Destroy target tapped creature.
Cost: tap
Target: creature, tapped
Resolve: destroy target creature
---------------

Note that Resolve uses Target to check for legal, tapped creature.

MageKing17 said...

We of the Incantus project have already implemented (almost) an entire set, that of Shards of Alara. I know exactly how much of the set we did because I did basically the entire set (Incantus did Qasali Ambusher, although the code for that is extremely hacky, so we almost certainly won't use that in the "final" version).

While the new code looks more complicated, it's actually much easier, since the editor automatically inserts basic templates for abilities, so you basically just add costs, targets, and effects, so coding Royal Assassin would take less than twenty seconds from parsing the oracle spoiler (yes, our Editor parses oracle text to generate a barebones card).

WilLoW said...

@forge: sent you my reply by email, but basically LUA allows you to call your Java functions from an external script file. Which would allow to add new cards with a powerful language without having to recompile the application.
I have yet to try it myself, though.

a bit off topic here, but I think what Incantus is missing now is a community.
Allowing people to create cards boosts the motivation of people like crazy. The first alpha of Wagic (my psp game) was released 3 months ago, and the number of available cards went from 250 to 800 over that period (I personally added something like 50 cards only :p)

Forge said...

mageking and Incantus,

Already programming the whole Shards of Alara set is insane and generating the barebones template from Oracle is just a dream. I kneel before you excellency :-) You should post a link. And send me a copy (and the source) if you want to. (mtgrares yahoo com)

Willow,
Thanks for the e-mail and mentioning LUA.

WilLoW said...

@Gando : MTGForge was formerly hosted on sourceforge but got kicked out. He's using google code, now, I believe

Gando the Wandering Fool said...

@willow Ah thats right...I had forgotten that...What a shame. What do you have to do to get booted from Sourceforge???

THE OMD project has been on there since sourceforge's beta...never a word to us about content violations or anything. (admittedly its been very inactive (re: dead) for 6+ years but its still there.)

Forge said...

About Sourceforge, I applied for a job at Wizards and used that Sourceforge website to show that I understood Magic, well I got a phone interview and then a few months later Sourceforge received some long legal e-mail concerning the DMCA (Digital Millenium Copyright Act). The only part of the email that I could understand is that Wizards owns the initials "MTG". Basically my sourceforge website was taken down because I practically sent it to Wizards.

Wizards and their parent company Hasbro has never sued anyone over software infringement. I know this because I'm taking 2 lame paralegal classes and I can search all of the court cases in the U.S. using lexis.com.

Wizards has only sued one person because they were making their own Magic cards but that is the only case where Wizards of the Coast has ever sued anyone. The term "Magic Online" does not appear in any of the court cases.

I hope that makes sense, :)

Gando the Wandering Fool said...

Wow talk about bearding the lion in its den...you got out alive thankfully :)

Well as far as I know Sourceforge does not check into people's backgrounds when it allows them an account. I have 2 individual accounts there...(one because I forgot the password to the first and my email addy had changed) and neither time have I filled out any information that would lead me to beleive they know the two accounts are for the same person...so it sounds like with a bit of a name change you could rejoin and not be bothered in the least.

On the other hand if Google code has a decent source control and webspace alotted to the project(the way sourceforge does) then why not move the focus there?

My thinking is...there are changes people have come up with for more than a month and no one has compiled them and posted them because they expect you to do it. Which we know isn't going to happen... The necro fix and draw step bug alone are worth the effort imho.