The first time I mentioned simultaneity is with combat. It was just a coincidence that this was the first time, but it is very reasonable, because simultaneity happens very often during combat: Technically, creatures become attacking creatures simultaneously, then the same happens with blockers, damage etc.
Simultaneity means multiple things, and I'm not sure if I have a full understanding of these. I'll take the example of Warp World, which is a classic example for discussing timing:
Each player shuffles all permanents he or she owns into his or her library, then reveals that many cards from the top of his or her library. Each player puts all artifact, creature, and land cards revealed this way onto the battlefield, then does the same for enchantment cards, then puts all cards revealed this way that weren't put onto the battlefield on the bottom of his or her library.
First, all permanents are shuffled into their respective owner's library, triggering from leaving play. All these abilities see all the cards leaving play, meaning that an ability like "leaves the battlefield" sees all the permanents. There is no variant where that ability leaves early and isn't active when the permanents leave play.
The "then"s in the card's text neatly separate the parts that happen simultaneously. All "enters the battlefield" abilities of cards trigger for cards that enter the battlefield at the same time as them; it seems like the abilities are active just a little earlier than they are in play... it's a little awkward in my opinion.
now hold on - Auras with "Enchant Enchantment" won't make it on the battlefield, because Enchant is not a triggered ability. The Comprehensive rules don't specify a wording for Enchant but rather give it extra rules. It feels, however, like a replacement effect, just like "As ... enters the battlefield" (which don't use the stack at all - they are effects generated by static abilities).
Lands with additional costs, like the painlands, are worded like this, because if it was a triggered ability, you could play the mana ability in response to the cost. So, just like "Enchant Enchantment" enchantments (^^) don't make it, lands like "As ... comes into play, return a land you control to your hand. If you don't, put ... into your graveyard instead." won't, too. Champion, however, is a triggered ability, so your champions can survive. you can even enchant the creatures you wish to exile, because the triggered abilities won't resolve or even be put onto the stack until Warp World finished resolving.
One last thing: after resolving the spells, all the abilities are put onto the stack, in an order depending on player seating and player choice. you can put abilities from entering the battlefield on the stack earlier that those from leaving the battlefield, even though these happened in another sequence...
Tuesday, August 3, 2010
Simultaneity: Warp World
Saturday, July 31, 2010
Damage
I just re-read my post about destruction and realised I should show what it took me to implement damage. This might not be my most interesting post, as it is just code, but maybe some of you are interested. Get ready... there are about 200 lines of code coming for you!
public abstract class DamageEvent extends ReplaceableEvent {
private MagicObject source;
private int ammount;
private boolean combat, preventable;
public DamageEvent(MagicObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
this(you(ofInstance(affected)), source, ammount, combat, preventable);
}
public DamageEvent(Player affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
this(ofInstance(affected), source, ammount, combat, preventable);
}
public DamageEvent(Supplier affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
super(affected);
this.source = source;
this.ammount = ammount;
this.combat = combat;
this.preventable = preventable;
}
public MagicObject getSource() {
return source;
}
/**
* This method can be used by replacement effects that prevent or increase damage.
*/
public void setAmmount(int ammount) {
this.ammount = ammount;
}
public int getAmmount() {
return ammount;
}
public boolean isCombatDamage() {
return combat;
}
public boolean isPreventable() {
return preventable;
}
}
public class DamagePlayerEvent extends DamageEvent {
private Player p;
public DamagePlayerEvent(Player p, MagicObject source, int ammount, boolean combat, boolean preventable) {
super(p, source, ammount, combat, preventable);
this.p = p;
}
public Player getPlayer() {
return p;
}
@Override
protected boolean execute0() {
CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
//118.3a. Damage dealt to a player causes that player to lose that much life.
getPlayer().getLifeTotal().loseLife(getAmmount());
ed.end();
return true;
}
}
public class DamagePermanentEvent extends DamageEvent {
private static final Predicate legalAffected = and(isIn(ofInstance(BATTLEFIELD)),
card(or(CREATURE, PLANESWALKER)));
private static final Predicate isPlaneswalker = card(has(PLANESWALKER));
//TODO implement wither and lifelink
private static final Predicate hasWither = alwaysFalse();
private static final Predicate hasLifelink = alwaysFalse();
private CardObject permanent;
public DamagePermanentEvent(CardObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
super(affected, source, ammount, combat, preventable);
if(!legalAffected.apply(affected)) throw new IllegalArgumentException(
"affected must be a creature or planeswalker permanent: " + affected);
permanent = affected;
}
public CardObject getPermanent() {
return permanent;
}
@Override
protected boolean execute0() {
CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
//118.3b. Damage dealt to a planeswalker causes that many loyalty counters to be removed from that
// planeswalker.
if(isPlaneswalker.apply(getPermanent())) {
//TODO remove counters
}
//118.3c. Damage dealt to a creature by a source with wither causes that many -1/-1 counters to be put on
// that creature.
else if(hasWither.apply(getSource())) {
//TODO add counters
}
//118.3d. Damage dealt to a creature by a source without wither causes that much damage to be marked on
// that creature.
else {
getPermanent().setMarkedDamage(getPermanent().getMarkedDamage() + getAmmount());
}
//118.3e. Damage dealt to an object or player by a source with lifelink causes that source's controller to
// gain that much life, in addition to the damage's other results.
if(hasLifelink.apply(getSource())) {
getSource().getController().getLifeTotal().gainLife(getAmmount());
}
ed.end();
return true;
}
}
I don't really like this code, because it has the effects of damage hard coded, but I think it's fine for now. extracting these effects later if it's necessary shouldn't be too hard, as the different effects of damage will only be mentioned here, so I can safely delay this task.
public abstract class DamageEvent extends ReplaceableEvent {
private MagicObject source;
private int ammount;
private boolean combat, preventable;
public DamageEvent(MagicObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
this(you(ofInstance(affected)), source, ammount, combat, preventable);
}
public DamageEvent(Player affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
this(ofInstance(affected), source, ammount, combat, preventable);
}
public DamageEvent(Supplier
super(affected);
this.source = source;
this.ammount = ammount;
this.combat = combat;
this.preventable = preventable;
}
public MagicObject getSource() {
return source;
}
/**
* This method can be used by replacement effects that prevent or increase damage.
*/
public void setAmmount(int ammount) {
this.ammount = ammount;
}
public int getAmmount() {
return ammount;
}
public boolean isCombatDamage() {
return combat;
}
public boolean isPreventable() {
return preventable;
}
}
public class DamagePlayerEvent extends DamageEvent {
private Player p;
public DamagePlayerEvent(Player p, MagicObject source, int ammount, boolean combat, boolean preventable) {
super(p, source, ammount, combat, preventable);
this.p = p;
}
public Player getPlayer() {
return p;
}
@Override
protected boolean execute0() {
CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
//118.3a. Damage dealt to a player causes that player to lose that much life.
getPlayer().getLifeTotal().loseLife(getAmmount());
ed.end();
return true;
}
}
public class DamagePermanentEvent extends DamageEvent {
private static final Predicate
card(or(CREATURE, PLANESWALKER)));
private static final Predicate
//TODO implement wither and lifelink
private static final Predicate
private static final Predicate
private CardObject permanent;
public DamagePermanentEvent(CardObject affected, MagicObject source, int ammount, boolean combat, boolean preventable) {
super(affected, source, ammount, combat, preventable);
if(!legalAffected.apply(affected)) throw new IllegalArgumentException(
"affected must be a creature or planeswalker permanent: " + affected);
permanent = affected;
}
public CardObject getPermanent() {
return permanent;
}
@Override
protected boolean execute0() {
CompoundEdit ed = new CompoundEdit(getGame(), true, "Deal damage");
//118.3b. Damage dealt to a planeswalker causes that many loyalty counters to be removed from that
// planeswalker.
if(isPlaneswalker.apply(getPermanent())) {
//TODO remove counters
}
//118.3c. Damage dealt to a creature by a source with wither causes that many -1/-1 counters to be put on
// that creature.
else if(hasWither.apply(getSource())) {
//TODO add counters
}
//118.3d. Damage dealt to a creature by a source without wither causes that much damage to be marked on
// that creature.
else {
getPermanent().setMarkedDamage(getPermanent().getMarkedDamage() + getAmmount());
}
//118.3e. Damage dealt to an object or player by a source with lifelink causes that source's controller to
// gain that much life, in addition to the damage's other results.
if(hasLifelink.apply(getSource())) {
getSource().getController().getLifeTotal().gainLife(getAmmount());
}
ed.end();
return true;
}
}
I don't really like this code, because it has the effects of damage hard coded, but I think it's fine for now. extracting these effects later if it's necessary shouldn't be too hard, as the different effects of damage will only be mentioned here, so I can safely delay this task.
Friday, July 23, 2010
Trying out the card editor
I just tried out my card editor to download "a few" cards; all the vanilla creatures and old (non-pain) dual lands. If I haven't done anything wrong in my gatherer search, there are 177 vanilla creatures. it's in fact not so easy to search for textless cards; I had to use the regular expressing "m/^$/" ("m/.../" is the gatherer identifier for regexes, "^$" essentially means "the beginning of the text immediately followed by the end")
by the way, I haven't done a full fledged form for gatherer searches; I did the search on the web site and pasted the search URL into my program. takes me much less time^^
there were two problems: first, my download just discontinued after 89 cards. Looking at what card that was, it was Little Girl from Unhinged with a half mana symbol. Second, even after refining my search to exclude Un-cards, my program only wound up downloading 100 cards. my workaround for that problem was simply to download the other cards by reverting the sort order^^
so, putting things short, I now have over 200 cards implemented, including 177 vanilla creatures, 10 classic dual lands, 10 SHM/EVE dual lands, 5 basic lands, Wrath of God, Damnation and Llanowar Elves. It's really time for combat^^
by the way, I haven't done a full fledged form for gatherer searches; I did the search on the web site and pasted the search URL into my program. takes me much less time^^
there were two problems: first, my download just discontinued after 89 cards. Looking at what card that was, it was Little Girl from Unhinged with a half mana symbol. Second, even after refining my search to exclude Un-cards, my program only wound up downloading 100 cards. my workaround for that problem was simply to download the other cards by reverting the sort order^^
so, putting things short, I now have over 200 cards implemented, including 177 vanilla creatures, 10 classic dual lands, 10 SHM/EVE dual lands, 5 basic lands, Wrath of God, Damnation and Llanowar Elves. It's really time for combat^^
Thursday, July 22, 2010
Event Handling
If you recall what I wrote a long time ago about challenges in programming, event handling would be one of the easy, boring parts. The point of event handling is to inform parts of your program when something happens in another part. This is espacially important for the user interface, as you always want to see what's currently going on, but in the context of Magic, there is another very important reason for event handling: Triggered abilities.
If you now think that the User Interface and Triggered abilities can share the event handling system, therefore effectively reducing the ammount of programming needed for each of them, I fear you'll be disappointed. The reason for this is the undo system. While it enables things like handling illegal actions, the most direct interpretation of what it does prevents me from sharing one event system: Undo means that every action and event can happen in two directions in time.
When you attack with a creature, it becomes tapped. An ability could trigger from that tapping. If you now determine that the creature isn't allowed to attack, everything's undone and the creature is untapped again. You want to see the creature untapped in the user interface, but you don't want abilities trigger from it untapping.
Of course the event handling functions after the same principle, but the events are duplicated - in some sense. There's no real 1:1 mapping between GUI and game events, because the GUI is satisfied by state-changes, while the game needs more high level events: The gui doesn't care if you've drawn a card or just put it into your hand. It just needs to know that your library has become smaller and your hand grew larger.
Fortunately, Java has some built in functionality for handling these low-level events, namely PropertyChangeSupport. It allows you to keep track of properties and notify the listeners whenever a change occurs. Well, you still have to call the listeners on a change, but the major work, managing all the listeners and what properties they are interested in, is done for you.
Along with adding PropertyChangeSupport to Laterna Magica, I have restructured a lot of code. Previously, every class implemented undo support for its properties itself. This is now separated into an extra class, which also allows to use PropertyChangeSupport more easily:
public class EditableProperty extends AbstractGameContent {
private EditablePropertyChangeSupport s;
private String name;
private T value;
public EditableProperty(Game game, EditablePropertyChangeSupport s, String name) {
this(game, s, name, null);
}
public EditableProperty(Game game, EditablePropertyChangeSupport s, String name, T initialValue) {
super(game);
this.s = s;
this.name = name;
value = initialValue;
}
public void setValue(T value) {
new SetValueEdit(value).execute();
}
public T getValue() {
return value;
}
@Override
public String toString() {
return valueOf(getValue());
}
private class SetValueEdit extends Edit {
private static final long serialVersionUID = 93955529563844615L;
private T oldValue, newValue;
public SetValueEdit(T newValue) {
super(EditableProperty.this.getGame());
this.newValue = newValue;
}
@Override
protected void execute() {
oldValue = value;
value = newValue;
if(s != null) s.firePropertyChange(name, oldValue, newValue);
}
@Override
protected void rollback() {
value = oldValue;
if(s != null) s.firePropertyChange(name, newValue, oldValue);
}
@Override
public String toString() {
return "Set " + s.getSourceBean() + "'s " + name + " to " + newValue;
}
}
}
If you now think that the User Interface and Triggered abilities can share the event handling system, therefore effectively reducing the ammount of programming needed for each of them, I fear you'll be disappointed. The reason for this is the undo system. While it enables things like handling illegal actions, the most direct interpretation of what it does prevents me from sharing one event system: Undo means that every action and event can happen in two directions in time.
When you attack with a creature, it becomes tapped. An ability could trigger from that tapping. If you now determine that the creature isn't allowed to attack, everything's undone and the creature is untapped again. You want to see the creature untapped in the user interface, but you don't want abilities trigger from it untapping.
Of course the event handling functions after the same principle, but the events are duplicated - in some sense. There's no real 1:1 mapping between GUI and game events, because the GUI is satisfied by state-changes, while the game needs more high level events: The gui doesn't care if you've drawn a card or just put it into your hand. It just needs to know that your library has become smaller and your hand grew larger.
Fortunately, Java has some built in functionality for handling these low-level events, namely PropertyChangeSupport. It allows you to keep track of properties and notify the listeners whenever a change occurs. Well, you still have to call the listeners on a change, but the major work, managing all the listeners and what properties they are interested in, is done for you.
Along with adding PropertyChangeSupport to Laterna Magica, I have restructured a lot of code. Previously, every class implemented undo support for its properties itself. This is now separated into an extra class, which also allows to use PropertyChangeSupport more easily:
public class EditableProperty
private EditablePropertyChangeSupport s;
private String name;
private T value;
public EditableProperty(Game game, EditablePropertyChangeSupport s, String name) {
this(game, s, name, null);
}
public EditableProperty(Game game, EditablePropertyChangeSupport s, String name, T initialValue) {
super(game);
this.s = s;
this.name = name;
value = initialValue;
}
public void setValue(T value) {
new SetValueEdit(value).execute();
}
public T getValue() {
return value;
}
@Override
public String toString() {
return valueOf(getValue());
}
private class SetValueEdit extends Edit {
private static final long serialVersionUID = 93955529563844615L;
private T oldValue, newValue;
public SetValueEdit(T newValue) {
super(EditableProperty.this.getGame());
this.newValue = newValue;
}
@Override
protected void execute() {
oldValue = value;
value = newValue;
if(s != null) s.firePropertyChange(name, oldValue, newValue);
}
@Override
protected void rollback() {
value = oldValue;
if(s != null) s.firePropertyChange(name, newValue, oldValue);
}
@Override
public String toString() {
return "Set " + s.getSourceBean() + "'s " + name + " to " + newValue;
}
}
}
Thursday, July 15, 2010
Combat in sight?
Both destruction and damage are more or less completed concepts by now, and at this point I'm doing combat. What does this mean?
Every turn, you and your opponent can arrange your creatures and planeswalkers in a more or less complex relationship
Every turn, you and your opponent can arrange your creatures and planeswalkers in a more or less complex relationship
- some creatures attack a player or planeswalker
- other creatures block those attackers. A single attacker may be blocked by multiple blockers and vice-versa (granted you have the right abilities around)
- all these attackers and blockers have a damage assignment order
- in the end, they all deal damage according to it, and they do so simultaneously. my ideas of simultaneity are... well, limited.
Thursday, July 1, 2010
Destruction
The last one and half a month were really sparse on working on Laterna Magica. It was the countdown of my school year and I was busy with a lot of stuff. There were loads of very work-intensive homeworks, and my year's project was also due. Anyone with experience on projects knows that half of the work is done in the two weeks before the end date and the other half is done in the two weeks after ;)
I can tell you, I didn't find a single minute to work on my program, but today I finally got back to it. I don't know if I can work on it regularly again, but I will try.
Before my big break, I told you that one of my next steps will be combat, but combat is a big thing and it takes many parts to work. One of these is damage.
Until very recent, damage was a simple concept: Players and creatures can receive damage. When a player receives damage, he looses life; when a creature receives enough damage, it is destroyed. However, two recent changes turned things around a bit: Planeswalkers, but more importantly Wither and the accompanying changes of Lifelink etc. To make things worse, damage has also another special concept found (nearly) nowhere else in the game: sources.
I suspect damage to be very annoying to program, but happily there's another thing to be done before damage really makes sense... Which happens to be what I've done today: Destruction, which was in fact so easy that I can fit it here without worrying that it will overload my post:
public class DestroyPermanentEvent extends ReplaceableEvent {
private MagicObject o;
public DestroyPermanentEvent(MagicObject o) {
super(o);
this.o = o;
}
@Override
protected boolean execute0() {
if(o.getZone().getType() != Zones.BATTLEFIELD) return false;
o.setZone(o.getOwner().getGraveyard());
return true;
}
}
execute0() is very straightforward, which is possible because "destroy" only has one meaning, as opposed to damage, which would make a decision based on the receiver's type and both source's and receiver's abilities. Making DestroyPermanentEvent a ReplaceableEvent easily takes care of Regeneration and indestructibility. The only thing I'm not sure about is "... can't be regenerated".
I can tell you, I didn't find a single minute to work on my program, but today I finally got back to it. I don't know if I can work on it regularly again, but I will try.
Before my big break, I told you that one of my next steps will be combat, but combat is a big thing and it takes many parts to work. One of these is damage.
Until very recent, damage was a simple concept: Players and creatures can receive damage. When a player receives damage, he looses life; when a creature receives enough damage, it is destroyed. However, two recent changes turned things around a bit: Planeswalkers, but more importantly Wither and the accompanying changes of Lifelink etc. To make things worse, damage has also another special concept found (nearly) nowhere else in the game: sources.
I suspect damage to be very annoying to program, but happily there's another thing to be done before damage really makes sense... Which happens to be what I've done today: Destruction, which was in fact so easy that I can fit it here without worrying that it will overload my post:
public class DestroyPermanentEvent extends ReplaceableEvent {
private MagicObject o;
public DestroyPermanentEvent(MagicObject o) {
super(o);
this.o = o;
}
@Override
protected boolean execute0() {
if(o.getZone().getType() != Zones.BATTLEFIELD) return false;
o.setZone(o.getOwner().getGraveyard());
return true;
}
}
execute0() is very straightforward, which is possible because "destroy" only has one meaning, as opposed to damage, which would make a decision based on the receiver's type and both source's and receiver's abilities. Making DestroyPermanentEvent a ReplaceableEvent easily takes care of Regeneration and indestructibility. The only thing I'm not sure about is "... can't be regenerated".
Wednesday, May 12, 2010
The card editor
The last weeks had no updates in my main project, but there's a good reason for that. I worked on a card editor that lets you download gatherer card descriptions, edit them and lets you test if the card is correct.
I think I'll explain a bit more how Laterna Magica handles cards. A fundamental thought in my system was extensibility. I'm currently using a text format, but Laterna Magica allows you to use any format, even multiple ones at the same time. So, if I sometime see a reason to use e.g. XML, I can still use the old cards while developing cards for the new one.
A similar thought is true for the text format itself. It is key-value based, and every key is handled by its own class. If a line is "name Llanowar Elves", then the class for "name" gets the input "Llanowar Elves" and sets the name accordingly. If the line is "text {T}: Add {G} to your mana pool.", the "text" handler tries to find an ability parser that can parse the line. Even if I can't represent an ability, I can make a class specifically for that ability and add a line handler that applies an arbitrary class to the card, like "apply laterna.magica.cards.DestroyAllCreatures".
The output of the card parser is a CardTemplate object. All the CardTemplates created are stored in a zip file, so you can think of the card parser like a compiler: There's input of different types, like plain text and XML, but the parser(s) convert it into a uniform format that the program can work with (serialized java objects).
And here, my editor comes into play. After creating a card, the editor can directly feed it into the parser, which implicitly checks if all the lines in the file are correct. The card parser will ignore lines that can't be parsed (like "Dryad Arbor is green." in my last post), but the card creator (e.g. you^^) won't: You can see the parser's warning and correct the lines.
Basic editing, opening, saving and even downloading already works in the editor, but it still needs a few tweaks before it really has a benefit over normal text editors (which you will be more familiar with for sure):
This editor is like a pet to me. I know I have better things to do - combat, namely - but right now I want to get this working.
I think I'll explain a bit more how Laterna Magica handles cards. A fundamental thought in my system was extensibility. I'm currently using a text format, but Laterna Magica allows you to use any format, even multiple ones at the same time. So, if I sometime see a reason to use e.g. XML, I can still use the old cards while developing cards for the new one.
A similar thought is true for the text format itself. It is key-value based, and every key is handled by its own class. If a line is "name Llanowar Elves", then the class for "name" gets the input "Llanowar Elves" and sets the name accordingly. If the line is "text {T}: Add {G} to your mana pool.", the "text" handler tries to find an ability parser that can parse the line. Even if I can't represent an ability, I can make a class specifically for that ability and add a line handler that applies an arbitrary class to the card, like "apply laterna.magica.cards.DestroyAllCreatures".
The output of the card parser is a CardTemplate object. All the CardTemplates created are stored in a zip file, so you can think of the card parser like a compiler: There's input of different types, like plain text and XML, but the parser(s) convert it into a uniform format that the program can work with (serialized java objects).
And here, my editor comes into play. After creating a card, the editor can directly feed it into the parser, which implicitly checks if all the lines in the file are correct. The card parser will ignore lines that can't be parsed (like "Dryad Arbor is green." in my last post), but the card creator (e.g. you^^) won't: You can see the parser's warning and correct the lines.
Basic editing, opening, saving and even downloading already works in the editor, but it still needs a few tweaks before it really has a benefit over normal text editors (which you will be more familiar with for sure):
- Compiling cards or creating the zip file doesn't work yet
- The card downloader does not convert a card's name, I would like to have "Shock deals two damage to target creature or player." as "~ deals two damage to target creature or player."
Maybe even more complex transformations like "target((zone/battlefield & type/creature) | player)" ;)
(that's just speculation. i have no idea how to do targets or even target parsing) - There is a file system tree with all the cards, but currently you can't open a file by double-clicking it
- "Save As" should be possible by dragging an opened file (they are shown in a list) onto a folder in the tree.
- I want to display already opened/modified files with a different icon
This editor is like a pet to me. I know I have better things to do - combat, namely - but right now I want to get this working.
Subscribe to:
Posts (Atom)