It is currently 23 Apr 2024, 10:46
   
Text Size

untapping, upkeep cost

General Discussion of the Intricacies

Moderator: CCGHQ Admins

untapping, upkeep cost

Postby frwololo » 08 Apr 2009, 05:50

I'll have to rewrite my untaping/upkeep costs mechanism sooner or later, and I was wondering how other applications handle it?

Right now I have a complex system of "blockers" associated to each card.
At the beginning of each turn, any blocker goes to the blockers list, and it is removed from the list only if a "cost" is paid.

A blocker is usually represented by a "cost" (man cost, creature sacrifice, ...) but can sometimes be more. That "cost" explains how to unlock the blocker.

so for example, Brass man has a blocker added to its list at the beginning of each turn. This blocker is removed only if "{3}" is paid during upkeep, and brass man can then be untapped.

Smoke puts a blocker on all creatures. Clicking on one creature during the untap phase removes the blocker and untaps the creature. clicking on other creatures after this one doesn't do anything.
The other creatures consequently have a "blocker" still active in their list, and don't get untapped.
At the end of a turn, all "blockers" are cleared from the list, and will be added again at the following turn if necessary.

This system is too complex right now and completely buggy, unfortunately. Another issue is that right now the AI doesn't handle upkeep costs at all, and is in deep trouble if a Lord of the pit makes its way to its deck :lol: . So I'll rewrite it.
Before I start thinking, I'd like to know how other engines deal with the whole "untap/upkeep" phase. Anyone has a simple system to handle complex cases such as " Smoke is in play, I have a Brass man in play, and Grizzly bears with Paralysis " ?

Thanks in advance :mrgreen:
frwololo
DEVELOPER
 
Posts: 265
Joined: 21 Jun 2008, 04:33
Has thanked: 0 time
Been thanked: 3 times

Re: untapping, upkeep cost

Postby MageKing17 » 09 Apr 2009, 04:37

Here's our code for Winter Orb:
Code: Select all
@static(txt=text[0])
def ability():
    def effects(source):
        def checkUntapStep(self, cards):
            if not source.tapped:
                return len([True for card in cards if isLand(card)]) <= 1
            else: return True
        yield do_override(Player, 'checkUntapStep', checkUntapStep)
    return no_condition, effects
abilities.add(ability)
And here's our code for Player.untapStep:
Code: Select all
    def untapStep(self):
        permanents = untapCards = set([card for card in self.play if card.canUntapDuringUntapStep()])
        prompt = "Select cards to untap"
        valid_untap = self.checkUntapStep(permanents)
        while not valid_untap:
            permanents = set()
            done_selecting = False
            perm = self.getPermanentInPlay(prompt=prompt)
            while not done_selecting:
                if perm == True:
                    done_selecting = True
                    self.send(AllDeselectedEvent())
                    break
                elif perm == False:
                    # reset untap
                    permanents = untapCards
                    self.send(AllDeselectedEvent())
                    prompt = "Selection canceled - select cards to untap"
                    break
                else:
                    if not perm in permanents and perm in untapCards:
                        permanents.add(perm)
                        self.send(CardSelectedEvent(), card=perm)
                        prompt = "%s selected - select another"%perm
                    elif perm in permanents:
                        self.send(InvalidTargetEvent(), target=perm)
                        prompt = "%s already selected - select another"%perm
                    else:
                        self.send(InvalidTargetEvent(), target=perm)
                        prompt = "%s can't be untapped - select another"%perm
                perm = self.getPermanentInPlay(prompt=prompt)
            if done_selecting:
                valid_untap = self.checkUntapStep(permanents)
                if not valid_untap:
                    prompt = "Invalid selection - select again"
        for card in permanents: card.untap()
I didn't write any of this (it's all Incantus' code), but if you have any questions, I can probably answer them.
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: untapping, upkeep cost

Postby frwololo » 09 Apr 2009, 05:11

Thanks, this gives me inspiration.
Do you have a mechanism that untaps cards automatically if they have nothing specifically blocking them?
It seems to me that with this code the player has to manually untap the cards even if there is nothing preventing them from untapping? (I'm at work and can't test by myself right now :oops: )
For example (and this is what Wagic does, although it's buggy), if you have only a Winter Orb in place, couldn't it be possible to automatically untap all cards but lands ?


Also, I'm not entirely sure of what the "do_override" method does.
Does it mean the player untap check is replaced by a call to the untap_check method of the Winter orb object, or does the "override" actually adds the method to a list of other methods? If it's the first answer, what happens if you have both a Smoke and Winter Orb objects in play ?

Could I see the contents of the method canUntapDuringUntapStep() of the card objects?
frwololo
DEVELOPER
 
Posts: 265
Joined: 21 Jun 2008, 04:33
Has thanked: 0 time
Been thanked: 3 times

Re: untapping, upkeep cost

Postby Incantus » 09 Apr 2009, 12:52

frwololo wrote:Thanks, this gives me inspiration.
Do you have a mechanism that untaps cards automatically if they have nothing specifically blocking them?
It seems to me that with this code the player has to manually untap the cards even if there is nothing preventing them from untapping? (I'm at work and can't test by myself right now :oops: )
For example (and this is what Wagic does, although it's buggy), if you have only a Winter Orb in place, couldn't it be possible to automatically untap all cards but lands ?
Sort of. That's what the first part of the untapStep() function does. First it generates a set of all cards that are untappable (by calling each card's canUntapDuringUntapStep(), and then calls Player.checkUntapStep with that set. If all are untappable, then it skips the loop, and untaps everything in the set. So winter orb overrides the check part, making sure that no lands are set to be untapped. It would probably be possible to change the logic, where instead of checking whether the set of cards is valid, it modifies the set (for Winter Orb - removes all lands except one you've chosen) but that would make interaction with other cards difficult (this is almost like combat restrictions - you have to follow all overrides). Incidentally, cards that let you choose whether to untap (Dwarven Hold for example) override the Card.canUntapDuringUntapStep() to ask the player if they want to untap (it returns True by default).

Also, I'm not entirely sure of what the "do_override" method does.
Does it mean the player untap check is replaced by a call to the untap_check method of the Winter orb object, or does the "override" actually adds the method to a list of other methods?
Yeah, basically. do_override() replaces the function in the class with what we call a stacked_function (which is basically just a list of functions that pretends to be a single function), which handles interactions between the two.

If it's the first answer, what happens if you have both a Smoke and Winter Orb objects in play ?
Well, they both would override the checkUntapStep function, so it would actually be a list of [WinterOrbOverride(), SmokeOveride()] (the order would be determined by play order, ie timestep). So after generating a set of cards to untap, you call Player.checkUntapStep (which is now a stacked_function, an object that handles calling all the overrides but pretends to be a function), which calls each override function in turn and AND's the results. So basically if any of the overrides returns False, then the whole function will return False, and it's not a valid set of cards to untap (at which point you go into the loop which asks the player to specifically choose which cards to untap).

This isn't the most ideal system, but it's the only way I could think of to combine all the particular effects that affect the untap step. Actually, I change my mind on what I said above. I think it would be possible to change the checkUntapStep to actually modify the set of cards, and any cards left in the set are automatically untapped. That would just complicate the logic of each override function. For example, in Winter Orb, you would have to check if any cards in the set are lands, and then ask the player to choose which one to untap, and remove all the others from the set, whereas in Smoke, you would take the set of Creatures and ask the player which one to untap again. Of course, if you play a card that doesn't let you untap anything, then its override would remove all cards from the set. Since set logic parallels boolean logic pretty closely, this might be a nice improvement.

Could I see the contents of the method canUntapDuringUntapStep() of the card objects?
The default version does nothing and returns True. Abilities that override this function are things like optional untaps (mentioned above), abilities like "[cardname] doesn't untap during your untap step." (Colossus of Sardia), and effects that prevent a card from untapping during a controller's next untap step (Ajani Vengeant and Entangling Trap)

Incidentally, all the cards i mentioned are implemented, which suggests that this approach is general enough. I haven't found a card yet that won't fit into this framework (if you can think of any, I would love to know. Implementing a rules engine is basically one big exercise in generating a design, later finding a card that breaks that design, and then trying to redesign to incorporate all possibilities for that aspect of the rules)
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times

Re: untapping, upkeep cost

Postby mtgrares » 09 Apr 2009, 19:45

I'll have to rewrite my untaping/upkeep costs mechanism sooner or later, and I was wondering how other applications handle it?
MTG Forge doesn't currently handle upkeep costs like Brass Man but MTG Forge does support Juzam Djinn. If MTG Forge did support Brass Man, I would just put the on the stack an optional ability to pay the cost. The user would be presented with the cost but the user could also cancel. If multiple Brass Man cards were in play, all of their abilities would go on the stack and then you could choose which ones to pay for.

In further explanation, the regular untap code would always be executed first and would untap stuff as normal but would never untap Brass Man, then Brass Man's "pay cost" abilities would go on the stack and would be untapped if you paid the cost.

Smoke would be implemented directly in the untap code and would check to see if Smoke was in play and then would give the user a choice. The AI code for smoke would just untap the biggest creature or a creature with evasion like flying.

Winter Orb would be implemented like Smoke, the code would check for Winter Orb and then do its thing.

Hopefully this is a little helpful but the truth is that I don't have a quick and easy way to do it. I know that ideas are only useful if you can apply them to your specific project and many times you can only partially apply ideas since everyone's project is very different.

Once a project become mature enough, the project has its own "internal card language" like Incantus. Incantus' cards are written in Python but they use the framework that has already been written. And I definitely don't understand their Winter Orb but I do know that the code is very short which is always a good sign :) Also Incantus tries to focus on generalities of what Winter Orb does without have pieces of the code look for a card named "Winter Orb", which is more my style. Handling generalities is better but only if you can "see it in your head" and get the code working. Hopefully MTG Forge will become more general and less specific in the future.
mtgrares
DEVELOPER
 
Posts: 1352
Joined: 08 Sep 2008, 22:10
Has thanked: 3 times
Been thanked: 12 times


Return to Magic Rules Engine Programming

Who is online

Users browsing this forum: No registered users and 29 guests


Who is online

In total there are 29 users online :: 0 registered, 0 hidden and 29 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 29 guests

Login Form