Tuesday, January 13, 2009

Supertype, Type, and Subtype

Before I started programming I didn't really understand a card's supertype, type, and subtype. Phage the Untouchable is a good example, "Legendary Creature - Zombie Minion". Legendary is a supertype, creature is the type, and zombie minion are both subtypes.

According to Magic's Comprehensive rules 205.4, a card can have multiple supertypes. Basic, snow, world, and legendary are all supertypes. A card's type can be artifact, creature, enchantment, instant, land, planeswalker, sorcery, and tribal, 205.2a. Once again a card may have one or more type, like an "Artifact Creature". Subtypes are easy to spot since they always come after the dash, 205.3.

OK, that previous paragraph is a little boring but what I am trying to say is that you need to keep the supertype, type, and subtype separate in your Magic program. Currently MTG Forge keeps everything grouped together as one big type line, while this makes some operations easy, it is really hard to do other things like adding that stupid little dash.

Most of the time in a game of Magic the type line is transparent and easy to understand. The problem arises when a card like Blood Moon (2R, enchantment, Nonbasic lands are Mountains) changes the type line and then things get confusing. Coat of Arms is another card that looks at the type line and then pumps up your creature if you have any more creatures with the same creature subtype. While Coat of Arms seems like a popular card, it seems a little confusing to program so I haven't tried yet. You can post your hints on how to program this card.

Basically a card's type line is simple until it gets complicated. Part of Magic's appeal has always been creating those weird complications on purpose.

12 comments:

Anonymous said...

You forgot "Planeswalker".

Forge said...

Arg, I did forget Planeswalker. I'll update my post. (I guess I need to download the latest Comprehensive Rules.)

Gando the Wandering Fool said...

It seems to me again an object model works best here.
Then you can have a member for Supertypes, Types and Subtypes and not find them confusing in the least.

nantuko84 said...

Gando the Wandering Fool> agree, object model is good enough

but you also can leave current MTGForge model and still implement
Coat of Arms easily

as Forge asked for hints, I can suggest one:

first we'll create utility class for types with such methods:

boolean TypeUtil.isSuperType(String type)
boolean TypeUtil.isSubType(String type)

(no need to say that it's just comparing to such words as "Creature", "Legendary", etc. that is rather easy to implement)

so the code for Coat of Arms can look like that:

CardList list = new CardList();
list.addAll(AllZone.Human_Play.getCards());
list.addAll(AllZone.Computer_Play.getCards());
list = list.getType("Creature");

for (Card card: list.getArrayList()) {
for (String type: card.getType())
if (TypeUtil.isSubtype(type)) {
CardList sameType = list.getType(type);
if (sameType.size() - 1 > 0) {
card.addAttack(sameType.size() - 1);
card.addDefence(sameType.size() - 1);
}
}
}
}

Silly Freak said...

keep in mind that i you have 2 "human soldier"s, each one should get +1/+1, not +2/+2 (one for each type)!
if your card's don't have equals overridden, you have instance-equality in a HashSet. so, every card would would only be contained once.

it would look something like this
Card creature = ...;
HashSet<Card> hs = new HashSet<Card>();

for(every card of list) {
for(every type of card) {
if(a shared subtype) {
//each card can only be added once, because the HashSet just won't add equal cards more than once.
hs.add(card);
}
}
}
creature.addAttack(hs.size()-1);
creature.addDefense(hs.size()-1);

Forge said...

Since a Magic's card type, supertype, and subtype are all strings, my opinion is that I just use string, "creature" versus a creature subclass. I understand strings and they are very simple and unsophisticated (like myself).

Unknown said...

Forge said "I understand strings and they are very simple and unsophisticated (like myself)."

I take that as canon proof that counters should be implemented as arrays of strings, not some crazy counter object.

Forge said...

Well for version 1 I don't mind what people do with it. If you feel that counters should be objects, go for it. I'm just for what works.

Anonymous said...

I'm afraid your troubles don't end at supertype/type/subtypes seperation. :P

Within subtypes, there are groupings. You have creature types, spell types, artifact types, planeswalker types, land types... it wouldn't really matter about all these types except that a permanent with a subtype for which it does not have the corrosponding type is considered to not have that subtype. In other words, if you have a Creature - Elf, and an effect turns it into an Enchantment, it ceases to be an Elf. Why? Because Enchantments can only be Auras or Shrines, not Elves.

Trust me, not even Incantus handles this 100% properly... however, we have made things slightly easier for ourselves. In one of our source code files, we have lists of all of these various subtype groupings (well, sets, technically), and can therefore determine (on a card-by-card basis, if need be) whether or not something's types allow it to have a given subtype. These lists (sets) were taken straight from the comprehensive rules, and we update them as needed (for example, after Shards of Alara came out, we added Elspeth, Sarkhan, and Tezzeret to all_planeswalkers).

Forge said...

Truthfully I don't understand when type/subtype/supertype can be a problem because most of the time I just view it has flavor text. "This card is an elf, cool."

I know that Forests are a subtype and they grant the card a mana ability. I don't like the idea of a subtype magically adding an ability to a card but it works for lands I guess, thankfully I can't think of any other instances where a subtype "grants" an ability.

Anonymous said...

"Truthfully I don't understand when type/subtype/supertype"

If not properly implemented, this is going to go horribly wrong. Imagine enchantments, instants and sorceries that only target specific types of cards or grant bonuses based on the number of them in play. It'll become really inefficient to take string apart, look at the contents and then return the string, then using something like switch(){} to go through the motions and eventually ending up where you want to be.

Off the top of my head I would just create SuperType and SubType classes that extend Type which extends from something like ArrayList. Or maybe you could make your own datastructure based on the interface that exists for List. It'll come in handy in the future when card interactions become even more complicated then they are now. Knuth may have said that "premature optimization is the root of all evil", but it's the handling of these kind of problems as an afterthought that will ultimately make you give up on a project like this.

Keep up the good work :D

./ktn

P.S. the implementation of types as strings is easy, but in the end it'll kill you. I suggest you revise your data model to consider implementing it in the Card object. Mostly because the Card object is responsible for storing all the info present on a card.

Anonymous said...

guaranteed cheapest viagra fda on viagra buy viagra in canada over the counter viagra cheap viagra tablets viagra on line cheap viagra nz cialis super viagra cialis super viagra viagra 6 free samples viagra commercial canyon filmed splitting viagra what does viagra do viagra rrp australia cost