It is currently 04 Sep 2025, 16:55
   
Text Size

Implementation of abilities using generators

General Discussion of the Intricacies

Moderator: CCGHQ Admins

Implementation of abilities using generators

Postby Incantus » 14 Sep 2008, 23:23

Incantus uses python generators (which are similar to coroutines, although not quite as powerful) to implement the ability engine. Here's a good intro http://loveandtheft.org/2008/08/29/introduction-to-generators-in-python/. Generators allow you to stop and resume a function from predefined points within that function, using the 'yield' keyword.

For example, here is the code for Corrupt:

Code: Select all
name = 'Corrupt'
cardnum = 0
expansion = ''
types = characteristic('Sorcery')
supertypes = no_characteristic()
subtypes = no_characteristic()
cost = '5B'
color = characteristic('B')
text = ['Corrupt deals damage equal to the number of Swamps you control to target creature or player. You gain life equal to the damage dealt this way.']

@play_sorcery()
def effects(controller, source):
    payment = yield source.cost
    target = yield Target(target_types=isCreatureOrPlayer)
    num_swamps = len(controller.play.get(isLand.with_condition(lambda l: l.subtypes == "Swamp")))
    dmg = source.dealDamage(target, num_swamps)
    controller.life += dmg
    yield
play_spell = effects
As you can see, Incantus defines a mini domain-specific language for programming effects. For example, almost all the actions you would want to take are defined in the Player class (such as player.add_mana, player.choose_from_zone, etc) and the Permanent class (card.move_to, card.add_counters), which can be called from within card code.

Back to generators. Basically, the yield statement allows you to yield back to the caller of the function (sort of like return), but the next time the function is called it resumes at the most recent point where it yielded. One common use of a generator is simulate an infinite sequence, without actually having to generate it all. For example, you can write an infinite counter:

Code: Select all
def count():
    i = 0
    while True:
        yield i
        i += 1
As you can see, the loop never terminates, but the program won't run forever (unless the caller of count never stops calling it)

So back to Incantus. When an ability/spell is played, the effect function is called, a cost is yielded back to the rules engine, then the function is called again, passing back the payment, and then the effects function yields the Target object and gets back the actual target selected. Finally, when the ability resolves, the effects function is called, resuming at the second yield call. One major benefit of this approach is that all important objects (the cost payment, the target) are local to the function and can be referred to later in the effects code.
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times

Return to Magic Rules Engine Programming

Who is online

Users browsing this forum: No registered users and 17 guests

Main Menu

User Menu

Our Partners


Who is online

In total there are 17 users online :: 0 registered, 0 hidden and 17 guests (based on users active over the past 10 minutes)
Most users ever online was 7303 on 15 Jul 2025, 20:46

Users browsing this forum: No registered users and 17 guests

Login Form