mtgrares wrote:Thanks MageKing. You can post or e-mail the newest version of Incantus. I just want to use it and see how Shards of Alara plays.
Let me think of a few random questions.
1. Do you have one object that takes care of all the rules?
2. How did you program exalted?
3. How hard was
Angelic Benediction? I find cards with text like "Whenever a creature you control attacks alone, you may tap target creature" to be hard and confusing to code.
4.
Angelsong prevents all combat damage, how do you program that?
5.
Ethersworn Canonist is a great card, how did you restrict the player from playing nonartifact spells?
6.
Etherium Sculptor reduces the cost of artifacts, how do you code that?
Hopefully I haven't asked too many questions. Programming Magic is a fun topic
Not too many questions at all. Good questions, actually.
1. Each card contains its own code, but we define lots of objects and functions that can help with this. MemoryVariables let us do things like keep track of what damaged what else, which cards went into the graveyard, which spells have been played each turn, etc. We also define a lot of "effects functions" to handle things like regeneration, altering power and/or toughness, and (in my version, now) scrying. There's a main "GameKeeper" object that handles turn orders, giving priority, and (if I'm remembering properly) state-based effects. No one object handles everything, unless you were to count the entire user interface as "one object".
2. Exalted was fairly easy, truth be told. I created a new source code file called "AlaraAbility.py", imported a few objects (like TriggeredAbility, the necessary events to hook into from GameEvent, etc.), and wrote this code (well, not exactly this code, but something close to it... it's been upgraded since):
- Code: Select all
def exalted():
def condition(source, sender, attackers):
return sender.current_player == source.controller and len(attackers) == 1
def effects(controller, source, attackers):
yield NoTarget()
until_end_of_turn(attackers[0].augment_power_toughness(1, 1))
yield
return TriggeredAbility(Trigger(DeclareAttackersEvent()), condition, effects, zone="play", keyword='exalted')
To use this with a card, all they have to do is add "abilities.add(exalted())" to their card. It's that simple.
3. Exactly as hard as Exalted, considering the card code looks basically like the function above, except using our decorators (which are easier to code cards with than the TriggeredAbility, CardStaticAbility, StaticTrackingAbility, ActivatedAbility, ManaAbility, or CiPAbility objects themselves), and having the effect tap the target instead of giving it a P/T boost for this turn.
4. Well, I originally had the card itself create a replacement for each of the three damagable objects' (Creature, Planeswalker, Player) assignDamage function (all our damage prevention and replacement targets the assignDamage function of the target, not the dealDamage function of the damager, so that all replacements for damage are offered at the same time), but since this was cumbersome and would need to be copy&pasted for every card with a fog-type effect, I added a "prevent_all_combat_damage()" function to Effects.py which did the same thing.
5. First, I created a new MemoryVariable that kept a log of all the spells a player has played this turn. Then I made
Ethersworn Canonist override the "playable" function of CastingAbility (the base class that all casting abilities are based off of, like CastPermanentSpell and CastInstantSpell) to check if the player had previously played an artifact spell and, if so, return False. The actual static ability in
Ethersworn Canonist's code looks like this:
- Code: Select all
@static(txt=text[0])
def ability():
def effects(card):
def playable(self, source):
return isArtifactCard(source) or len(spell_record.get(source.controller, lambda s: not s.types == "Artifact")) == 0
yield do_override(CastSpell, "playable", playable, combiner=logical_and)
return no_condition, effects
abilities.add(ability)
spell_record is the MemoryVariable (we could, if we wanted, create a new instance of this MemoryVariable just for this card, but that would be a waste of resources, and not interact properly with, for example, copy effects), and as you can see, it basically says "If this spell is an artifact or this player hasn't played a nonartifact spell this turn, they can play it."
6. By overriding ManaCost's compute function that reduces colorless mana by one, and using the logical_and combiner (as with
Ethersworn Canonist) to make sure it's run in addition to the regular compute function, not instead of it. The card's static ability looks like this:
- Code: Select all
@static(txt=text[0])
def ability():
def effects(card):
def compute(self, source, player):
if str(source.zone) == "stack" and source.types == "Artifact" and player == card.controller:
self.final_cost[-1] -= 1
return True
yield do_override(ManaCost, "compute", compute, combiner=logical_and)
return no_condition, effects
abilities.add(ability)
self.final_cost[-1] would be the last item in the "final_cost" list, which would be colorless mana. You could, of course, also do "self.final_cost[5] -= 1", since it's a 0-based list, and there are only five colors, making the sixth element be [5]. If we wanted to do
Edgewalker, it would instead do "self.final_cost[0] -= 1" and "self.final_cost[2] -= 1". I assume, at any rate, that the colors are in WUBRG order. I could be wrong, Incantus doesn't always remember to sort things in WUBRG order. I'm pretty sure mana cost lists are, though.
I hope that answered all of your questions, and feel free to ask more. In the meantime, I'll whip up a version and let you see what mistakes I made.