Esempio n. 1
0
    def init(self, battlefield, stack, opponents):
        self.opponents = opponents
        self.library = LibraryZone()
        self.hand = HandZone()
        self.graveyard = GraveyardZone()
        self.exile = ExileZone()
        self.battlefield = battlefield.get_view(self)
        self.stack = stack
        self.manapool = ManaPool()

        self.loadDeck()
        self.shuffle()
Esempio n. 2
0
class Player(MtGObject):
    def life():
        doc = "Player life property"
        def fget(self):
            return life(self._life)
        def fset(self, value):
            amount = value - self._life
            if amount > 0: self.gain_life(amount)
            elif amount < 0: self.lose_life(amount)
        return locals()
    life = property(**life())

    # For overriding by replacement effects
    def gain_life(self, amount):
        self._life += amount
        self.send(LifeGainedEvent(), amount=amount)
    def lose_life(self, amount):
        self._life += amount  # This seems weird, but amount is negative
        self.send(LifeLostEvent(), amount=amount)

    def __init__(self, name, deck):
        self.name = name
        self._life = 20
        self.poison = 0
        self.land_actions = -1
        self.hand_limit = 7
        self.draw_empty = False
        self.decklist = deck
    def init(self, battlefield, stack, opponents):
        self.opponents = opponents
        self.library = LibraryZone()
        self.hand = HandZone()
        self.graveyard = GraveyardZone()
        self.exile = ExileZone()
        self.command = CommandZone()
        self.battlefield = battlefield.get_view(self)
        self.stack = stack
        self.manapool = ManaPool()

        self.loadDeck()
        self.shuffle()
    def newTurn(self):
        self.land_actions = 1
    def reset(self):
        # return all cards to library
        for from_location in [self.battlefield, self.hand, self.graveyard, self.exile]:
            for card in from_location: card.move_to("library")
    def loadDeck(self):
        for num, name in self.decklist:
            num = int(num)
            for n in range(num):
                self.library.add_new_card(Card.create(name, owner=self))

    # The following functions are part of the card code DSL
    def win(self, msg=''):
        if msg: msg = " %s and"%msg
        self.getIntention(msg="%s%s wins the game!"%(self, msg), notify=True)
        raise GameOverException()
    def lose(self, msg=''):
        if msg: msg = " %s and"%msg
        self.getIntention(msg="%s%s loses the game!"%(self, msg), notify=True)
        raise GameOverException()
    def concede(self):
        self.lose("concedes")
    def add_mana(self, *amount):
        if len(amount) > 1:
            amount = self.make_selection([('Add %s'%''.join(['{%s}'%c for c in col]), col) for col in amount], 1, prompt="Choose mana to add")
        else: amount = amount[0]
        # XXX This is a bit hacky - used by, ex Calciform Pools
        # Add X mana in any combination of W and/or U to your manapool
        if "(" in amount:
            amount = self.make_selection([('Add {%s}'%col, col) for col in generate_hybrid_choices(amount)], 1, prompt="Choose mana to add")
        self.manapool.add(amount)
    def shuffle(self):
        self.library.shuffle()
    def you_may(self, msg): return self.getIntention(prompt="You may %s"%msg,msg="Would you like to %s?"%msg)
    def you_may_pay(self, source, cost):
        if isinstance(cost, str): cost = ManaCost(cost)
        intent = self.getIntention(prompt="You may pay %s"%cost, msg="Would you like to pay %s"%cost)
        if intent and cost.precompute(source, self) and cost.compute(source, self):
            cost.pay(source, self)
            return True
        else: return False
    def create_copy(self, card):
        copy = CardCopy.create(name=str(card.name), owner=self)
        copy.clone(card)
        # Make sure it copies when it moves to the stack
        def modifyNewRole(self, new, zone):
            if str(zone) == "stack": new.clone(card)
        override(copy, "modifyNewRole", modifyNewRole)
        return self.exile.add_new_card(copy)
    def create_tokens(self, info, number=1, tag=''):
        return [self.exile.add_new_card(Token.create(info, owner=self, tag=tag)) for _ in range(number)]
    def play_tokens(self, info, number=1, tag=''):
        return [token.move_to("battlefield") for token in self.create_tokens(info, number, tag)]
    def create_emblem(self, ability):
        emblem = EmblemObject.create(ability=ability, owner=self)
        return self.command.add_new_card(emblem)
    def make_selection(self, sellist, number=1, required=True, prompt=''):
        if isinstance(sellist[0], tuple): idx=False
        else: idx=True
        return self.getSelection(sellist, numselections=number, required=required, idx=idx, prompt=prompt)
    # A hack to make cards like Door of Destinies work. -MageKing17
    def choose_creature_type(self):
        import symbols
        subtypes = set()
        creature_types = lambda c: c.types.intersects(set((symbols.Creature, symbols.Tribal)))
        for cards in (getattr(player, zone).get(creature_types) for zone in ["battlefield", "graveyard", "exile", "library", "hand", "command"] for player in Keeper.players):
            for card in cards: subtypes.update(card.subtypes.current)
        subtypes.intersection_update(symbols.all_creatures)
        return self.make_selection(sorted(subtypes), prompt='Choose a creature type')
    def choose_opponent(self):
        if len(self.opponents) == 1:
            return tuple(self.opponents)[0]
        else:
            return self.getTarget(target_types=OpponentMatch(self), required=True, prompt="Select an opponent")
    def choose_player(self):
        return self.getTarget(target_types=isPlayer, required=True, prompt="Select player")
    def reveal_cards(self, cards, msg=''):
        # XXX I can't do a true asynchronous reveal until i move to the new networking
        # You can only reveal from hand or library
        self.doRevealCard(cards, all=True)
    def look_at(self, cards):
        # You can only look at cards from hand or library
        self.doRevealCard(cards, all=False, prompt="look at %s"%', '.join(map(str, cards)))
    def reveal_hand(self):
        pass
    def choose_from(self, cards, number, cardtype=isCard, required=True, prompt=''):
        if not prompt: prompt = "Choose %d card(s)"%number
        selected = self.getCardSelection(cards, number, cardtype=cardtype, required=required, prompt=prompt)
        if number == 1: return selected[0] if selected else None
        else: return selected
    def choose_from_zone(self, number=1, cardtype=isCard, zone="battlefield", action='', required=True, all=False):
        cards_in_zone = getattr(self, zone).get(cardtype)
        # If all is True, we should extend cards_in_zone; otherwise, it may falsely register that nothing is there when it's just controlled by your opponents.
        if all == True and zone == "battlefield": # Should this extend to any other zones?
            for opponent in self.opponents:
                cards_in_zone.extend(getattr(opponent, zone).get(cardtype)) # Well, I'll leave this a getattr() just in case.
        if zone == "library" and not cardtype == isCard: required = False
        if len(cards_in_zone) == 0 and not zone == "library": cards = []
        elif number >= len(cards_in_zone) and required: cards = cards_in_zone
        else:
            cards = []
            if zone == "battlefield" or zone == "hand":
                if number > -1:
                    a = 's' if number > 1 else ''
                    total = number
                    prompt = "Select %s%s to %s: %d left of %d"%(cardtype, a, action, number, total)
                    while number > 0:
                        card = self.getTarget(cardtype, zone=zone, from_player=None if all else "you", required=required, prompt=prompt)
                        if card == False: break
                        if card in cards:
                            prompt = "Card already selected - select again"
                            self.send(InvalidTargetEvent(), target=card)
                        else:
                            cards.append(card)
                            number -= 1
                            prompt = "Select %s%s to %s: %d left of %d"%(cardtype, a, action, number, total)
                else:
                    number = 0
                    prompt = "Select any number of %s to %s: %d selected so far"%(cardtype, action, number)
                    while number < cards_in_zone:
                        card = self.getTarget(cardtype, zone=zone, from_player=None if all else "you", required=required, prompt=prompt)
                        if card == False: break
                        if card in cards:
                            prompt = "Card already selected - select again"
                            self.send(InvalidTargetEvent(), target=card)
                        else:
                            cards.append(card)
                            number += 1
                            prompt = "Select any number of %s to %s: %d selected so far"%(cardtype, action, number)
            else:
                selection = list(getattr(self, zone))
                if number >= -1:
                    if number == 1: a = 'a'
                    elif number == -1: a = 'any number of'
                    else: a = str(number)
                    cards = self.getCardSelection(selection, number=number, cardtype=cardtype, required=required, prompt="Search your %s for %s %s to %s."%(zone, a, cardtype, action))
                    if zone == "library": self.shuffle()
        return cards
    def draw(self, number=1):
        return sum([self.draw_single() for i in range(number)])
    def draw_single(self):
        card = self.library.top()
        num = 0
        if card == None: self.draw_empty = True
        else:
            num = 1
            card.move_to("hand")
            self.send(DrawCardEvent())
        return num
    def discard(self, card):
        if str(card.zone) == "hand":
            card = card.move_to("graveyard")
            self.send(DiscardCardEvent(), card=card)
            return card
        else: return None
    def force_discard(self, number=1, cardtype=isCard):
        if number == -1: number = len(self.hand)
        cards = self.choose_from_zone(number, cardtype, "hand", "discard")
        return [self.discard(card) for card in cards]
    def discard_at_random(self, number=1):
        import random
        if len(self.hand) <= number: return self.force_discard(-1)
        else:
            return [self.discard(card) for card in random.sample(self.hand, number)]
    def discard_down(self):
        number = len(self.hand) - self.hand_limit
        if number > 0: return self.force_discard(number)
        else: return []
    def flip_coin(self):
        import random
        return random.choice((True, False))
    def sacrifice(self, perm):
        if perm.controller == self and str(perm.zone) == "battlefield":
            card = perm.move_to("graveyard")
            self.send(PermanentSacrificedEvent(), card=perm)
            return card
        else: return None
    def force_sacrifice(self, number=1, cardtype=isPermanent):
        perms = self.choose_from_zone(number, cardtype, "battlefield", "sacrifice")
        newperms = []
        for perm in perms: newperms.append(self.sacrifice(perm))
        return newperms
    def skip_next_turn(self, msg):
        def condition(keeper):
            if keeper.players.peek() == self:
                keeper.players.next()
                return True
            else: return False
        def skipTurn(keeper):
            keeper.newTurn()
            skipTurn.expire()
        return replace(Keeper, "newTurn", skipTurn, condition=condition, msg=msg)
    def take_extra_turn(self):
        Keeper.players.insert(self)
    @overridable(do_sum)
    def get_special_actions(self):
        return [check_concede]
    def setup_special_action(self, action):
        #408.2i. Some effects allow a player to take an action at a later time, usually to end a continuous effect or to stop a delayed triggered ability. This is a special action. A player can stop a delayed triggered ability from triggering or end a continuous effect only if the ability or effect allows it and only when he or she has priority. The player who took the action gets priority after this special action.
        #408.2j. Some effects from static abilities allow a player to take an action to ignore the effect from that ability for a duration. This is a special action. A player can take an action to ignore an effect only when he or she has priority. The player who took the action gets priority after this special action. (Only 3 cards use this: Damping Engine, Lost in Thought, Volrath's Curse)
        return override(self, "get_special_actions", lambda self: [action])

    # Rule engine functions
    def mulligan(self, number):
        self.send(LogEvent(), msg="%s mulligans"%self)
        for card in self.hand: card.move_to(self.library)
        self.shuffle()
        self.draw(number)
    def checkUntapStep(self, cards): return True
    def untapStep(self):
        permanents = untapCards = set([card for card in self.battlefield if card.canUntapDuringUntapStep()])
        prompt = "Select cards to untap"
        valid_untap = self.checkUntapStep(permanents)
        while not valid_untap:
            permanents = set()
            done_selecting = False
            perm = self.getPermanentOnBattlefield(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.getPermanentOnBattlefield(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()
    def assignDamage(self, amt, source, combat=False):
        # amt is greater than 0
        self.life -= amt
        source.send(DealsDamageToEvent(), to=self, amount=amt, combat=combat)
        return amt
    def canBeTargetedBy(self, targetter):
        # For protection spells - XXX these should be stackable
        return True
    def isTargetedBy(self, targeter):
        self.send(TargetedByEvent(), targeter=targeter)
    def manaBurn(self):
        manaburn = self.manapool.manaBurn()
        if manaburn > 0:
            #take_burn = self.getIntention("Take mana burn?", "Take mana burn?")
            #if not take_burn: return False
            self.manapool.clear()
            self.life -= manaburn
        return True
    def declareDefendingPlayer(self):
        return self.choose_opponent()
    def attackingIntention(self):
        # First check to make sure you have cards on the battlefield
        # XXX although if you have creatures with Flash this might not work since you can play it anytime
        has_creature = False
        for creature in self.battlefield.get(isCreature):
            if creature.canAttack():
                has_creature = True
        if not has_creature: return False
        else: return True #self.getIntention("Declare intention to attack", msg="...attack this turn?")
    def declareAttackers(self, opponents):
        multiple_opponents = len(opponents) > 1
        all_on_attacking_side = self.battlefield.get(isCreature)
        invalid_attack = True
        prompt = "Declare attackers (Enter to accept, Escape to reset)"
        while invalid_attack:
            attackers = set()
            done_selecting = False
            creature = self.getCombatCreature(mine=True, prompt=prompt)
            while not done_selecting:
                if creature == True:
                    done_selecting = True
                    break
                elif creature == False:
                    self.send(AttackersResetEvent())
                    break
                else:
                    if not creature in attackers and creature.canAttack():
                        possible_opponents = [opponent for opponent in opponents if creature.canAttackSpecific(opponent)]
                        if possible_opponents:
                            attackers.add(creature)
                            self.send(AttackerSelectedEvent(), attacker=creature)

                            # Now select who to attack
                            if len(possible_opponents) > 1:
                                while True:
                                    target = self.getTarget(target_types=(OpponentMatch(self), isPlaneswalker), zone="battlefield", prompt="Select opponent to attack")
                                    if target in possible_opponents:
                                        creature.setOpponent(target)
                                        break
                                    else: prompt = "Can't attack %s. Select again"%target
                            else: creature.setOpponent(possible_opponents[0])
                            prompt = "%s selected - select another"%creature
                        else:
                            self.send(InvalidTargetEvent(), target=creature)
                            prompt = "%s can't attack any available opponent - select another"%creature
                    elif creature in attackers:
                        self.send(InvalidTargetEvent(), target=creature)
                        prompt = "%s already in combat - select another"%creature
                    else:
                        self.send(InvalidTargetEvent(), target=creature)
                        prompt = "%s cannot attack - select another"%creature
                creature = self.getCombatCreature(mine=True, prompt=prompt)

            if done_selecting:
                not_attacking = set(all_on_attacking_side)-attackers
                invalid_attackers = [creature for creature in all_on_attacking_side if not creature.checkAttack(attackers, not_attacking)] + [creature for creature in attackers if not creature.computeAttackCost()]
                invalid_attack = len(invalid_attackers) > 0
                if not invalid_attack:
                    for creature in attackers:
                        creature.payAttackCost()
                        creature.setAttacking()
                else:
                    prompt = "Invalid attack - choose another"
                    for creature in invalid_attackers: self.send(InvalidTargetEvent(), target=creature)
            else: prompt = "Declare attackers (Enter to accept, Escape to reset)"
        return list(attackers)
    def reorderBlockers(self, combat_assignment):
        blocker_sets = {}
        do_reorder = False
        for attacker, blockers in combat_assignment.items():
            if len(blockers) > 1:
                for blocker in blockers: blocker_sets[blocker] = (attacker, blockers)
                do_reorder = True
        if do_reorder:
            prompt = "Order blockers (enter to accept)"
            # Select blocker
            while True:
                blocker = self.getCombatCreature(mine=False, prompt=prompt)
                if blocker == True: # Done reordering
                    break
                elif blocker == False: pass
                elif blocker in blocker_sets:
                    attacker, blockers = blocker_sets[blocker]
                    i = blockers.index(blocker)
                    blockers[:] = [blocker] + blockers[:i] + blockers[i+1:]
                    self.send(BlockersReorderedEvent(), attacker=attacker, blockers=blockers)
                else:
                    self.send(InvalidTargetEvent(), target=blocker)
                    prompt = "Invalid creature. Select blocker"
        return combat_assignment

    def declareBlockers(self, attackers):
        combat_assignment = dict([(attacker, []) for attacker in attackers])
        # Make sure you have creatures to block
        all_on_blocking_side = self.battlefield.get(isCreature)
        if len(all_on_blocking_side) == 0: return combat_assignment

        invalid_block = True
        blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"
        while invalid_block:
            total_blockers = set()
            done_selecting = False
            while not done_selecting:
                blocker = self.getCombatCreature(mine=True, prompt=blocker_prompt)
                if blocker == True:
                    done_selecting = True
                    break
                elif blocker == False:
                    # Reset the block
                    self.send(BlockersResetEvent())
                    combat_assignment = dict([(attacker, []) for attacker in attackers])
                    break
                else:
                    if blocker in total_blockers or not blocker.canBlock():
                        if blocker in total_blockers: reason = "already blocking"
                        elif not blocker.canBlock(): reason = "can't block"
                        self.send(InvalidTargetEvent(), target=blocker)
                        blocker_prompt = "%s %s - select another blocker"%(blocker, reason)
                    else:
                        # Select attacker
                        valid_attacker = False
                        attacker_prompt = "Select attacker to block"
                        while not valid_attacker:
                            attacker = self.getCombatCreature(mine=False, prompt=attacker_prompt)
                            if attacker == True: pass # XXX What does enter mean here?
                            elif attacker == False: break # Pick a new blocker
                            elif attacker.attacking and attacker.canBeBlocked() and blocker.canBlockAttacker(attacker) and attacker.canBeBlockedBy(blocker):
                                valid_attacker = True
                                total_blockers.add(blocker)
                                combat_assignment[attacker].append(blocker)
                                self.send(BlockerSelectedEvent(), attacker=attacker, blocker=blocker)
                                attacker_prompt = "Select attacker to block"
                                blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"
                            else:
                                if not attacker.attacking: reason = "cannot block non attacking %s"%attacker
                                else: reason = "cannot block %s"%attacker
                                self.send(InvalidTargetEvent(), target=blocker)
                                self.send(InvalidTargetEvent(), target=attacker)
                                attacker_prompt = "%s %s - select a new attacker"%(blocker,reason)

            if done_selecting:
                nonblockers = set(all_on_blocking_side)-total_blockers
                invalid_blockers = [creature for creature in attackers+all_on_blocking_side if not creature.checkBlock(combat_assignment, nonblockers)] + [creature for creature in total_blockers if not creature.computeBlockCost()]
                invalid_block = len(invalid_blockers) > 0
                if not invalid_block:
                    for attacker, blockers in combat_assignment.items():
                        if blockers:
                            attacker.setBlocked(blockers)
                            for blocker in blockers:
                                blocker.payBlockCost()
                                blocker.setBlocking(attacker)
                else:
                    blocker_prompt = "Invalid defense - choose another"
                    for creature in invalid_blockers: self.send(InvalidTargetEvent(), target=creature)
            else: blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"

        return combat_assignment

    def doNonInstantAction(self):
        return self.getAction(prompt="Play Spells, Activated Abilities, or Pass Priority")
    def doInstantAction(self):
        return self.getAction(prompt="Play Instants, Activated Abilities, or Pass Priority")
    def chooseAndDoAbility(self, card, abilities):
        numabilities = len(abilities)
        if numabilities == 0: return False
        elif numabilities == 1: ability = abilities[0]
        else:
            ability = self.make_selection(abilities, 1, required=False, prompt="%s: Select ability"%card)
            if ability == False: return False
        return ability.play(self)

    def __str__(self): return self.name
    def __repr__(self): return "%s at %s"%(self.name, id(self))

    # The following functions interface with the GUI of the game, and as a result they are kind
    # of convoluted. All interaction with the GUI is carried out through the input function (which
    # is set to dirty_input from the Incantus app) with a context object which indicates the action to perform
    # as well as a filtering function (process) that can convert user actions into game actions (or even
    # discard improper actions (see dirty_input).
    def input(self, context, prompt):
        self.send(GameFocusEvent())
        return self.dirty_input(context, prompt)
    def getMoreMana(self, required): # if necessary when paying a cost
        def convert_gui_action(action):
            if isinstance(action, PassPriority): return False
            elif isinstance(action, CancelAction): return action
            sel = action.selection
            if not isPlayer(sel) and sel.controller == self: return action
            else: return False

        context = {"get_ability": True, "process": convert_gui_action}
        prompt = "Need %s - play mana abilities (Esc to cancel)"%"".join(['{%s}'%r for r in required])
        # This loop seems really ugly - is there a way to structure it better?
        cancel = False    # This is returned - it's a way to back out of playing an ability
        while True:
            action = self.input(context, "%s: %s"%(self.name, prompt))
            if isinstance(action, CancelAction):
                cancel = True
                break
            card = action.selection
            if self.chooseAndDoAbility(card, card.abilities.mana_abilities()): break
        return not cancel
    def getAction(self, prompt=''):
        def convert_gui_action(action):
            if isinstance(action, PassPriority) or isinstance(action, OKAction): return action
            elif isinstance(action, CancelAction): return False
            sel = action.selection
            if isinstance(sel, StackAbility): return False
            elif isinstance(sel, CastSpell): sel = sel.source
            if sel == self or (not isPlayer(sel) and sel.controller == self): return action
            else: return False

        #context = {"get_ability": True, "process": convert_gui_action}
        context = {"get_choice": True, 'msg': prompt, 'notify': True, 'options': 'Pass Priority', 'process': convert_gui_action}
        # This loop seems really ugly - is there a way to structure it better?
        passed = False
        while True:
            action = self.input(context, "%s: %s"%(self.name,prompt))
            if isinstance(action, PassPriority) or isinstance(action, OKAction):
                passed = True
                break
            object = action.selection
            if object == self:
                abilities = [(action.__doc__, action) for action in object.get_special_actions()]
            else:
                abilities = [(str(ability), ability) for ability in object.abilities.activated()]

                # Special actions!
                abilities.extend([("SPECIAL: "+action.__doc__, action) for action in object.get_special_actions()])

                # Include the playing ability if not on the battlefield
                if not str(object.zone) == "battlefield" and object.playable():
                    abilities.append(("Play %s"%object, object))

            num = len(abilities)
            if num == 0: continue
            elif num == 1:
                ability = abilities[0][1]
            else:
                ability = self.make_selection(abilities, 1, required=False, prompt="%s: Select"%object)
                if ability == False: continue
            if hasattr(ability, "play"):
                if ability.play(self): break
            else:
                if ability(object, self): break
        return not passed

    def getIntention(self, prompt='', msg="", options=None, notify=False):
        def filter(action):
            if not (isinstance(action, OKAction) or isinstance(action, CancelAction)): return False
            else: return action
        if not msg: msg = prompt
        if options is None: options = ("OK" if notify else ("Yes", "No"))
        context = {'get_choice': True, 'msg': msg, 'notify': notify, 'options': options, 'process': filter}
        #if not prompt: prompt = "Declare intention"
        result = self.input(context, "%s: %s"%(self.name,prompt))
        return isinstance(result, OKAction)
    def getSelection(self, sellist, numselections=1, required=True, idx=True, msg='', prompt=''):
        def filter(action):
            if isinstance(action, CancelAction) and not required: return action
            if not isinstance(action, PassPriority): return action.selection
            return False
        if msg == '': msg = prompt
        if idx == True: idx_sellist = [(val, i) for i, val in enumerate(sellist)]
        else:
            idx_sellist = [(val[0], i) for i, val in enumerate(sellist)]
            sellist = [val[1] for val in sellist]
        context = {'get_selection': True, 'list':idx_sellist, 'numselections': numselections, 'required': required, 'msg': msg, 'process': filter}
        # get_selection only returns indices into the given list
        sel = self.input(context,"%s: %s"%(self.name,prompt))
        if isinstance(sel, CancelAction): return False
        if numselections == 1: return sellist[sel]
        elif numselections == -1: return [sellist[i] for i in sel]
        else: return [sellist[i] for i in sel][:numselections]
    def getCardSelection(self, selection, number, cardtype=isCard, zone=None, player=None, required=True, prompt=''):
        def filter(action):
            if isinstance(action, CancelAction):
                if not required: return action
                else: return False
            if not isinstance(action, PassPriority): return action.selection
            else: return False
        if not isiterable(cardtype): cardtype = (cardtype,)
        def check_card(card):
            valid = True
            for ctype in cardtype:
                if ctype(card): break
            else: valid = False
            return valid
        if number > len(selection): number = len(selection)
        if not zone: zone = str(selection[0].zone)
        if not player: player = selection[0].controller
        context = {'get_cards': True, 'list':selection, 'numselections': number, 'required': required, 'process': filter, 'from_zone': zone, 'from_player': player, 'check_card': check_card}
        sel = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(sel, CancelAction): return []
        else: return sel
    def getPermanentOnBattlefield(self, prompt='Select permanent'):
        def filter(action):
            if isinstance(action, CancelAction) or isinstance(action, PassPriority):
                return action

            card = action.selection
            if isPermanent(card) and (card.controller == self):
                return card
            else:
                self.send(InvalidTargetEvent(), target=card)
                return False
        context = {'get_target': True, 'process': filter}
        card = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(card, PassPriority): return True
        elif isinstance(card, CancelAction): return False
        else: return card
    def getCombatCreature(self, mine=True, prompt='Select target'):
        def filter(action):
            if isinstance(action, CancelAction) or isinstance(action, PassPriority):
                return action

            target = action.selection
            if isCreature(target) and ((mine and target.controller == self) or
                (not mine and target.controller in self.opponents)): return target
            else:
                self.send(InvalidTargetEvent(), target=target)
                return False
        context = {'get_target': True, 'process': filter}
        target = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(target, PassPriority): return True
        elif isinstance(target, CancelAction): return False
        else: return target
    def getTarget(self, target_types, zone=None, from_player=None, required=True, prompt='Select target'):
        # If required is True (default) then you can't cancel the request for a target
        if not isiterable(target_types): target_types = (target_types,)
        def filter(action):
            # This function is really convoluted
            # If I return False here then the input function will keep cycling until valid input
            if isinstance(action, CancelAction):
                if not required: return action
                else: return False
            elif isinstance(action, PassPriority): return False

            target = action.selection
            if isPlayer(target) or ((not zone or str(target.zone) == zone) and (not from_player or (from_player == "you" and target.controller == self) or (from_player == "opponent" and target.controller in self.opponents))):
                for target_type in target_types:
                    if target_type(target): return target
            # Invalid target
            self.send(InvalidTargetEvent(), target=target)
            return False
        context = {'get_target': True, 'process': filter}
        target = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(target, CancelAction) or isinstance(target, PassPriority): return False
        return target
    def getManaChoice(self, manapool_str, total_required, prompt="Select mana to spend"):
        def filter(action):
            if isinstance(action, CancelAction): return action
            else: return action.mana
        context = {'get_mana_choice': True, 'manapool': manapool_str, 'required': total_required, "process": filter, "from_player": self}
        result = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(result, CancelAction): return False
        else: return result
    def getX(self, prompt="Select amount for X"):
        def filter(action):
            if isinstance(action, CancelAction): return action
            if isinstance(action, PassPriority): return False
            else: return action.amount
        context = {'get_X': True, "process": filter, "from_player": self}
        result = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(result, CancelAction): return -1
        else: return result
    def getDistribution(self, amount, targets, prompt=''):
        if not prompt: prompt = "Distribute %d to target permanents"%amount
        def filter(action):
            if isinstance(action, CancelAction): return False
            return action.assignment
        context = {'get_distribution': True, 'targets': targets, 'amount': amount, 'process': filter}
        return self.input(context, "%s: %s"%(self.name,prompt))
    def getDamageAssignment(self, blocking_list, prompt="Assign damage to blocking creatures", trample=False, deathtouch=False):
        def filter(action):
            if isinstance(action, CancelAction): return False
            else:
                assn = action.assignment
                # Check damage assignment
                for attacker, blockers in blocking_list:
                    total = attacker.combatDamage()
                    valid_lethal = True
                    for blocker, dmg in assn:
                        total -= dmg
                        if (dmg < blocker.lethalDamage() and total > 0):
                            valid_lethal = False
                if not ((deathtouch or valid_lethal) and
                        ((trample and valid_lethal and total > 0) or (total == 0))):
                    return False
                else:
                    return action.assignment
        context = {'get_damage_assign': True, 'blocking_list': blocking_list, 'trample': trample, 'deathtouch': deathtouch, 'process': filter}
        return dict(self.input(context, "%s: %s"%(self.name,prompt)))
    def doRevealCard(self, cards, all=True, prompt=''):
        import operator
        if not operator.isSequenceType(cards): cards = [cards]
        if not prompt: prompt = "reveals card(s) "+', '.join(map(str,cards))
        zone = str(cards[0].zone)
        player = cards[0].controller
        context = {'reveal_card': True, 'cards': cards, 'from_zone': zone, 'from_player': player, 'all': all, 'process': lambda action: True} #isinstance(action, PassPriority)}
        return self.input(context, "%s: %s"%(self.name, prompt))
Esempio n. 3
0
class Player(MtGObject):
    def life():
        doc = "Player life property"
        def fget(self):
            return life(self._life)
        def fset(self, value):
            amount = value - self._life
            if amount > 0: self.gain_life(amount)
            elif amount < 0: self.lose_life(amount)
        return locals()
    life = property(**life())

    # For overriding by replacement effects
    def gain_life(self, amount):
        self._life += amount
        self.send(LifeGainedEvent(), amount=amount)
    def lose_life(self, amount):
        self._life += amount  # This seems weird, but amount is negative
        self.send(LifeLostEvent(), amount=amount)

    def __init__(self, name, deck):
        self.name = name
        self._life = 20
        self.poison = 0
        self.land_actions = -1
        self.hand_limit = 7
        self.draw_empty = False
        self.decklist = deck
    def init(self, battlefield, stack, opponents):
        self.opponents = opponents
        self.library = LibraryZone()
        self.hand = HandZone()
        self.graveyard = GraveyardZone()
        self.exile = ExileZone()
        self.battlefield = battlefield.get_view(self)
        self.stack = stack
        self.manapool = ManaPool()

        self.loadDeck()
        self.shuffle()
    def newTurn(self):
        self.land_actions = 1
    def reset(self):
        # return all cards to library
        for from_location in [self.battlefield, self.hand, self.graveyard, self.exile]:
            for card in from_location: card.move_to("library")
    def loadDeck(self):
        for num, name in self.decklist:
            num = int(num)
            for n in range(num):
                self.library.add_new_card(Card.create(name, owner=self))

    # The following functions are part of the card code DSL
    def win(self, msg=''):
        if msg: msg = " %s and"%msg
        self.getIntention(msg="%s%s wins the game!"%(self, msg), notify=True)
        raise GameOverException()
    def lose(self, msg=''):
        if msg: msg = " %s and"%msg
        self.getIntention(msg="%s%s loses the game!"%(self, msg), notify=True)
        raise GameOverException()
    def add_mana(self, *amount):
        if len(amount) > 1:
            amount = self.make_selection([('Add %s'%''.join(['{%s}'%c for c in col]), col) for col in amount], 1, prompt="Choose mana to add")
        else: amount = amount[0]
        # XXX This is a bit hacky - used by, ex Calciform Pools
        # Add X mana in any combination of W and/or U to your manapool
        if "(" in amount:
            amount = self.make_selection([('Add {%s}'%col, col) for col in generate_hybrid_choices(amount)], 1, prompt="Choose mana to add")
        self.manapool.add(amount)
    def shuffle(self):
        self.library.shuffle()
    def you_may(self, msg): return self.getIntention(prompt="You may %s"%msg,msg="Would you like to %s?"%msg)
    def you_may_pay(self, source, cost):
        if isinstance(cost, str): cost = ManaCost(cost)
        intent = self.getIntention(prompt="You may pay %s"%cost, msg="Would you like to pay %s"%cost)
        if intent and cost.precompute(source, self) and cost.compute(source, self):
            cost.pay(source, self)
            return True
        else: return False
    def create_copy(self, card):
        copy = CardCopy.create(name=str(card.name), owner=self)
        copy.clone(card)
        # Make sure it copies when it moves to the stack
        def modifyNewRole(self, new, zone):
            if str(zone) == "stack": new.clone(card)
        override(copy, "modifyNewRole", modifyNewRole)
        return self.exile.add_new_card(copy)
    def create_tokens(self, info, number=1, tag=''):
        return [self.exile.add_new_card(Token.create(info, owner=self)) for _ in range(number)]
    def play_tokens(self, info, number=1, tag=''):
        return [token.move_to("battlefield") for token in self.create_tokens(info, number)]
    def make_selection(self, sellist, number=1, required=True, prompt=''):
        if isinstance(sellist[0], tuple): idx=False
        else: idx=True
        return self.getSelection(sellist, numselections=number, required=required, idx=idx, prompt=prompt)
    # A hack to make cards like Door of Destinies work. -MageKing17
    def choose_creature_type(self):
        import symbols
        subtypes = set()
        creature_types = lambda c: c.types.intersects(set((symbols.Creature, symbols.Tribal)))
        for cards in (getattr(player, zone).get(creature_types) for zone in ["battlefield", "graveyard", "exile", "library", "hand"] for player in Keeper.players):
            for card in cards: subtypes.update(card.subtypes.current)
        return self.make_selection(sorted(subtypes), prompt='Choose a creature type')
    def choose_opponent(self):
        if len(self.opponents) == 1:
            return tuple(self.opponents)[0]
        else:
            return self.getTarget(target_types=OpponentMatch(self), required=True, prompt="Select an opponent")
    def choose_player(self):
        return self.getTarget(target_types=isPlayer, required=True, prompt="Select player")
    def reveal_cards(self, cards, msg=''):
        # XXX I can't do a true asynchronous reveal until i move to the new networking
        # You can only reveal from hand or library
        self.doRevealCard(cards, all=True)
    def look_at(self, cards):
        # You can only look at cards from hand or library
        self.doRevealCard(cards, all=False, prompt="look at %s"%', '.join(map(str, cards)))
    def reveal_hand(self):
        pass
    def choose_from(self, cards, number, cardtype=isCard, required=True, prompt=''):
        if not prompt: prompt = "Choose %d card(s)"%number
        selected = self.getCardSelection(cards, number, cardtype=cardtype, required=required, prompt=prompt)
        if number == 1: return selected[0] if selected else None
        else: return selected
    def choose_from_zone(self, number=1, cardtype=isCard, zone="battlefield", action='', required=True, all=False):
        cards_in_zone = getattr(self, zone).get(cardtype)
        if zone == "library" and not cardtype == isCard: required = False
        if len(cards_in_zone) == 0 and not zone == "library": cards = []
        elif number >= len(cards_in_zone) and required: cards = cards_in_zone
        else:
            cards = []
            if zone == "battlefield" or zone == "hand":
                a = 's' if number > 1 else ''
                total = number
                prompt = "Select %s%s to %s: %d left of %d"%(cardtype, a, action, number, total)
                while number > 0:
                    card = self.getTarget(cardtype, zone=zone, from_player=None if all else "you", required=required, prompt=prompt)
                    if card == False: break
                    if card in cards:
                        prompt = "Card already selected - select again"
                        self.send(InvalidTargetEvent(), target=card)
                    else:
                        cards.append(card)
                        number -= 1
                        prompt = "Select %s%s to %s: %d left of %d"%(cardtype, a, action, number, total)
            else:
                selection = list(getattr(self, zone))
                if number >= -1:
                    if number == 1: a = 'a'
                    elif number == -1: a = 'any number of'
                    else: a = str(number)
                    cards = self.getCardSelection(selection, number=number, cardtype=cardtype, required=required, prompt="Search your %s for %s %s to %s."%(zone, a, cardtype, action))
                    if zone == "library": self.shuffle()
        return cards
    def draw(self, number=1):
        return sum([self.draw_single() for i in range(number)])
    def draw_single(self):
        card = self.library.top()
        num = 0
        if card == None: self.draw_empty = True
        else:
            num = 1
            card.move_to("hand")
            self.send(DrawCardEvent())
        return num
    def discard(self, card):
        if str(card.zone) == "hand":
            card = card.move_to("graveyard")
            self.send(DiscardCardEvent(), card=card)
            return card
        else: return None
    def force_discard(self, number=1, cardtype=isCard):
        if number == -1: number = len(self.hand)
        cards = self.choose_from_zone(number, cardtype, "hand", "discard")
        return [self.discard(card) for card in cards]
    def discard_down(self):
        number = len(self.hand) - self.hand_limit
        if number > 0: return self.force_discard(number)
        else: return []
    def sacrifice(self, perm):
        if perm.controller == self and str(perm.zone) == "battlefield":
            card = perm.move_to("graveyard")
            self.send(PermanentSacrificedEvent(), card=perm)
            return card
        else: return None
    def force_sacrifice(self, number=1, cardtype=isPermanent):
        perms = self.choose_from_zone(number, cardtype, "battlefield", "sacrifice")
        for perm in perms: self.sacrifice(perm)
        return len(perms)
    def skip_next_turn(self, msg):
        def condition(keeper):
            if keeper.player_cycler.peek() == self:
                keeper.nextActivePlayer()
                return True
            else: return False
        def skipTurn(keeper):
            keeper.newTurn()
            skipTurn.expire()
        return replace(Keeper, "newTurn", skipTurn, condition=condition, msg=msg)
    def take_extra_turn(self):
        Keeper.player_cycler.insert(self)
    def setup_special_action(self, action):
        #408.2i. Some effects allow a player to take an action at a later time, usually to end a continuous effect or to stop a delayed triggered ability. This is a special action. A player can stop a delayed triggered ability from triggering or end a continuous effect only if the ability or effect allows it and only when he or she has priority. The player who took the action gets priority after this special action.
        #408.2j. Some effects from static abilities allow a player to take an action to ignore the effect from that ability for a duration. This is a special action. A player can take an action to ignore an effect only when he or she has priority. The player who took the action gets priority after this special action. (Only 3 cards use this: Damping Engine, Lost in Thought, Volrath's Curse)
        raise NotImplementedError()

    # Rule engine functions
    def mulligan(self, number):
        self.send(LogEvent(), msg="%s mulligans"%self)
        for card in self.hand: card.move_to(self.library)
        self.shuffle()
        self.draw(number)
    def checkUntapStep(self, cards): return True
    def untapStep(self):
        permanents = untapCards = set([card for card in self.battlefield if card.canUntapDuringUntapStep()])
        prompt = "Select cards to untap"
        valid_untap = self.checkUntapStep(permanents)
        while not valid_untap:
            permanents = set()
            done_selecting = False
            perm = self.getPermanentOnBattlefield(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.getPermanentOnBattlefield(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()
    def assignDamage(self, amt, source, combat=False):
        # amt is greater than 0
        self.life -= amt
        source.send(DealsDamageToEvent(), to=self, amount=amt, combat=combat)
        return amt
    def canBeTargetedBy(self, targetter):
        # For protection spells - XXX these should be stackable
        return True
    def isTargetedBy(self, targeter):
        self.send(TargetedByEvent(), targeter=targeter)
    def manaBurn(self):
        manaburn = self.manapool.manaBurn()
        if manaburn > 0:
            #take_burn = self.getIntention("Take mana burn?", "Take mana burn?")
            #if not take_burn: return False
            self.manapool.clear()
            self.life -= manaburn
        return True
    def declareDefendingPlayer(self):
        return self.choose_opponent()
    def attackingIntention(self):
        # First check to make sure you have cards on the battlefield
        # XXX although if you have creatures with Flash this might not work since you can play it anytime
        has_creature = False
        for creature in self.battlefield.get(isCreature):
            if creature.canAttack():
                has_creature = True
        if not has_creature: return False
        else: return True #self.getIntention("Declare intention to attack", msg="...attack this turn?")
    def declareAttackers(self, opponents):
        multiple_opponents = len(opponents) > 1
        all_on_attacking_side = self.battlefield.get(isCreature)
        invalid_attack = True
        prompt = "Declare attackers (Enter to accept, Escape to reset)"
        while invalid_attack:
            attackers = set()
            done_selecting = False
            creature = self.getCombatCreature(mine=True, prompt=prompt)
            while not done_selecting:
                if creature == True:
                    done_selecting = True
                    break
                elif creature == False:
                    self.send(AttackersResetEvent())
                    break
                else:
                    if not creature in attackers and creature.canAttack():
                        attackers.add(creature)
                        self.send(AttackerSelectedEvent(), attacker=creature)

                        # Now select who to attack
                        if multiple_opponents:
                            while True:
                                target = self.getTarget(target_types=(OpponentMatch(self), isPlaneswalker), zone="battlefield", prompt="Select opponent to attack")
                                if target in opponents:
                                    creature.setOpponent(target)
                                    break
                                else: prompt = "Can't attack %s. Select again"%target
                        else: creature.setOpponent(opponents[0])
                        prompt = "%s selected - select another"%creature
                    elif creature in attackers:
                        self.send(InvalidTargetEvent(), target=creature)
                        prompt = "%s already in combat - select another"%creature
                    else:
                        self.send(InvalidTargetEvent(), target=creature)
                        prompt = "%s cannot attack - select another"%creature
                creature = self.getCombatCreature(mine=True, prompt=prompt)

            if done_selecting:
                not_attacking = set(all_on_attacking_side)-attackers
                invalid_attackers = [creature for creature in all_on_attacking_side if not creature.checkAttack(attackers, not_attacking)] + [creature for creature in attackers if not creature.computeAttackCost()]
                invalid_attack = len(invalid_attackers) > 0
                if not invalid_attack:
                    for creature in attackers:
                        creature.payAttackCost()
                        creature.setAttacking()
                else:
                    prompt = "Invalid attack - choose another"
                    for creature in invalid_attackers: self.send(InvalidTargetEvent(), target=creature)
            else: prompt = "Declare attackers (Enter to accept, Escape to reset)"
        return list(attackers)
    def reorderBlockers(self, combat_assignment):
        blocker_sets = {}
        do_reorder = False
        for attacker, blockers in combat_assignment.items():
            if len(blockers) > 1:
                for blocker in blockers: blocker_sets[blocker] = (attacker, blockers)
                do_reorder = True
        if do_reorder:
            prompt = "Order blockers (enter to accept)"
            # Select blocker
            while True:
                blocker = self.getCombatCreature(mine=False, prompt=prompt)
                if blocker == True: # Done reordering
                    break
                elif blocker == False: pass
                elif blocker in blocker_sets:
                    attacker, blockers = blocker_sets[blocker]
                    i = blockers.index(blocker)
                    blockers[:] = [blocker] + blockers[:i] + blockers[i+1:]
                    self.send(BlockersReorderedEvent(), attacker=attacker, blockers=blockers)
                else:
                    self.send(InvalidTargetEvent(), target=blocker)
                    prompt = "Invalid creature. Select blocker"
        return combat_assignment

    def declareBlockers(self, attackers):
        combat_assignment = dict([(attacker, []) for attacker in attackers])
        # Make sure you have creatures to block
        all_on_blocking_side = self.battlefield.get(isCreature)
        if len(all_on_blocking_side) == 0: return combat_assignment

        invalid_block = True
        blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"
        while invalid_block:
            total_blockers = set()
            done_selecting = False
            while not done_selecting:
                blocker = self.getCombatCreature(mine=True, prompt=blocker_prompt)
                if blocker == True:
                    done_selecting = True
                    break
                elif blocker == False:
                    # Reset the block
                    self.send(BlockersResetEvent())
                    combat_assignment = dict([(attacker, []) for attacker in attackers])
                    break
                else:
                    if blocker in total_blockers or not blocker.canBlock():
                        if blocker in total_blockers: reason = "already blocking"
                        elif not blocker.canBlock(): reason = "can't block"
                        self.send(InvalidTargetEvent(), target=blocker)
                        blocker_prompt = "%s %s - select another blocker"%(blocker, reason)
                    else:
                        # Select attacker
                        valid_attacker = False
                        attacker_prompt = "Select attacker to block"
                        while not valid_attacker:
                            attacker = self.getCombatCreature(mine=False, prompt=attacker_prompt)
                            if attacker == True: pass # XXX What does enter mean here?
                            elif attacker == False: break # Pick a new blocker
                            elif attacker.attacking and attacker.canBeBlocked() and blocker.canBlockAttacker(attacker) and attacker.canBeBlockedBy(blocker):
                                valid_attacker = True
                                total_blockers.add(blocker)
                                combat_assignment[attacker].append(blocker)
                                self.send(BlockerSelectedEvent(), attacker=attacker, blocker=blocker)
                                attacker_prompt = "Select attacker to block"
                                blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"
                            else:
                                if not attacker.attacking: reason = "cannot block non attacking %s"%attacker
                                else: reason = "cannot block %s"%attacker
                                self.send(InvalidTargetEvent(), target=blocker)
                                self.send(InvalidTargetEvent(), target=attacker)
                                attacker_prompt = "%s %s - select a new attacker"%(blocker,reason)

            if done_selecting:
                nonblockers = set(all_on_blocking_side)-total_blockers
                invalid_blockers = [creature for creature in attackers+all_on_blocking_side if not creature.checkBlock(combat_assignment, nonblockers)] + [creature for creature in total_blockers if not creature.computeBlockCost()]
                invalid_block = len(invalid_blockers) > 0
                if not invalid_block:
                    for attacker, blockers in combat_assignment.items():
                        if blockers:
                            attacker.setBlocked(blockers)
                            for blocker in blockers:
                                blocker.payBlockCost()
                                blocker.setBlocking(attacker)
                else:
                    blocker_prompt = "Invalid defense - choose another"
                    for creature in invalid_blockers: self.send(InvalidTargetEvent(), target=creature)
            else: blocker_prompt = "Declare blockers (Enter to accept, Escape to reset)"

        return combat_assignment

    def doNonInstantAction(self):
        return self.getAction(prompt="Play Spells, Activated Abilities, or Pass Priority")
    def doInstantAction(self):
        return self.getAction(prompt="Play Instants, Activated Abilities or Pass Priority")
    def chooseAndDoAbility(self, card, abilities):
        numabilities = len(abilities)
        if numabilities == 0: return False
        elif numabilities == 1: ability = abilities[0]
        else:
            ability = self.make_selection(abilities, 1, required=False, prompt="%s: Select ability"%card)
            if ability == False: return False
        return ability.play(self)

    def __str__(self): return self.name
    def __repr__(self): return "%s at %s"%(self.name, id(self))

    # The following functions interface with the GUI of the game, and as a result they are kind
    # of convoluted. All interaction with the GUI is carried out through the input function (which
    # is set to dirty_input from the Incantus app) with a context object which indicates the action to perform
    # as well as a filtering function (process) that can convert user actions into game actions (or even
    # discard improper actions (see dirty_input).
    def input(self, context, prompt):
        self.send(GameFocusEvent())
        return self.dirty_input(context, prompt)
    def getMoreMana(self, required): # if necessary when paying a cost
        def convert_gui_action(action):
            if isinstance(action, PassPriority): return False
            elif isinstance(action, CancelAction): return action
            sel = action.selection
            if not isPlayer(sel) and sel.controller == self: return action
            else: return False
        
        context = {"get_ability": True, "process": convert_gui_action}
        prompt = "Need %s - play mana abilities (Esc to cancel)"%"".join(['{%s}'%r for r in required])
        # This loop seems really ugly - is there a way to structure it better?
        cancel = False    # This is returned - it's a way to back out of playing an ability
        while True:
            action = self.input(context, "%s: %s"%(self.name, prompt))
            if isinstance(action, CancelAction):
                cancel = True
                break
            card = action.selection
            if self.chooseAndDoAbility(card, card.abilities.mana_abilities()): break
        return not cancel
    def getAction(self, prompt=''):
        def convert_gui_action(action):
            if isinstance(action, PassPriority) or isinstance(action, OKAction) : return action
            elif isinstance(action, CancelAction): return False
            sel = action.selection
            if not isPlayer(sel) and sel.controller == self: return action
            else: return False

        #context = {"get_ability": True, "process": convert_gui_action}
        context = {"get_choice": True, 'msg': prompt, 'notify': True, 'options': 'Pass Priority', 'process': convert_gui_action}
        # This loop seems really ugly - is there a way to structure it better?
        passed = False
        while True:
            action = self.input(context, "%s: %s"%(self.name,prompt))
            if isinstance(action, PassPriority) or isinstance(action, OKAction):
                passed = True
                break
            card = action.selection
            abilities = [(str(ability), ability) for ability in card.abilities.activated()]
            # Include the playing ability if not on the battlefield
            if not str(card.zone) == "battlefield" and card.playable():
                abilities.append(("Play %s"%card, card))
            num = len(abilities)
            if num == 0: continue
            elif num == 1:
                ability = abilities[0][1]
            else:
                ability = self.make_selection(abilities, 1, required=False, prompt="%s: Select"%card)
                if ability == False: continue
            if ability.play(self): break
        return not passed

    def getIntention(self, prompt='', msg="", options=("Yes", "No"), notify=False):
        def filter(action):
            if not (isinstance(action, OKAction) or isinstance(action, CancelAction)): return False
            else: return action
        if not msg: msg = prompt
        context = {'get_choice': True, 'msg': msg, 'notify': notify, 'options': options, 'process': filter}
        #if not prompt: prompt = "Declare intention"
        result = self.input(context, "%s: %s"%(self.name,prompt))
        return isinstance(result, OKAction)
    def getSelection(self, sellist, numselections=1, required=True, idx=True, msg='', prompt=''):
        def filter(action):
            if isinstance(action, CancelAction) and not required: return action
            if not isinstance(action, PassPriority): return action.selection
            return False
        if msg == '': msg = prompt
        if idx == True: idx_sellist = [(val, i) for i, val in enumerate(sellist)]
        else:
            idx_sellist = [(val[0], i) for i, val in enumerate(sellist)]
            sellist = [val[1] for val in sellist]
        context = {'get_selection': True, 'list':idx_sellist, 'numselections': numselections, 'required': required, 'msg': msg, 'process': filter}
        # get_selection only returns indices into the given list
        sel = self.input(context,"%s: %s"%(self.name,prompt))
        if isinstance(sel, CancelAction): return False
        if numselections == 1: return sellist[sel]
        elif numselections == -1: return [sellist[i] for i in sel]
        else: return [sellist[i] for i in sel][:numselections]
    def getCardSelection(self, selection, number, cardtype=isCard, zone=None, player=None, required=True, prompt=''):
        def filter(action):
            if isinstance(action, CancelAction):
                if not required: return action
                else: return False
            if not isinstance(action, PassPriority): return action.selection
            else: return False
        if not isiterable(cardtype): cardtype = (cardtype,)
        def check_card(card):
            valid = True
            for ctype in cardtype:
                if ctype(card): break
            else: valid = False
            return valid
        if number > len(selection): number = len(selection)
        if not zone: zone = str(selection[0].zone)
        if not player: player = selection[0].controller
        context = {'get_cards': True, 'list':selection, 'numselections': number, 'required': required, 'process': filter, 'from_zone': zone, 'from_player': player, 'check_card': check_card}
        sel = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(sel, CancelAction): return []
        else: return sel
    def getPermanentOnBattlefield(self, prompt='Select permanent'):
        def filter(action):
            if isinstance(action, CancelAction) or isinstance(action, PassPriority):
                return action

            card = action.selection
            if isPermanent(card) and (card.controller == self):
                return card
            else:
                self.send(InvalidTargetEvent(), target=card)
                return False
        context = {'get_target': True, 'process': filter}
        card = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(card, PassPriority): return True
        elif isinstance(card, CancelAction): return False
        else: return card
    def getCombatCreature(self, mine=True, prompt='Select target'):
        def filter(action):
            if isinstance(action, CancelAction) or isinstance(action, PassPriority):
                return action

            target = action.selection
            if isCreature(target) and ((mine and target.controller == self) or
                (not mine and target.controller in self.opponents)): return target
            else:
                self.send(InvalidTargetEvent(), target=target)
                return False
        context = {'get_target': True, 'process': filter}
        target = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(target, PassPriority): return True
        elif isinstance(target, CancelAction): return False
        else: return target
    def getTarget(self, target_types, zone=None, from_player=None, required=True, prompt='Select target'):
        # If required is True (default) then you can't cancel the request for a target
        if not isiterable(target_types): target_types = (target_types,)
        def filter(action):
            # This function is really convoluted
            # If I return False here then the input function will keep cycling until valid input
            if isinstance(action, CancelAction):
                if not required: return action
                else: return False
            elif isinstance(action, PassPriority): return False

            target = action.selection
            if isPlayer(target) or ((not zone or str(target.zone) == zone) and (not from_player or (from_player == "you" and target.controller == self) or (from_player == "opponent" and target.controller in self.opponents))):
                for target_type in target_types:
                    if target_type(target): return target
            # Invalid target
            self.send(InvalidTargetEvent(), target=target)
            return False
        context = {'get_target': True, 'process': filter}
        target = self.input(context, "%s: %s"%(self.name,prompt))

        if isinstance(target, CancelAction) or isinstance(target, PassPriority): return False
        return target
    def getManaChoice(self, manapool_str, total_required, prompt="Select mana to spend"):
        def filter(action):
            if isinstance(action, CancelAction): return action
            else: return action.mana
        context = {'get_mana_choice': True, 'manapool': manapool_str, 'required': total_required, "process": filter, "from_player": self}
        result = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(result, CancelAction): return False
        else: return result
    def getX(self, prompt="Select amount for X"):
        def filter(action):
            if isinstance(action, CancelAction): return action
            if isinstance(action, PassPriority): return False
            else: return action.amount
        context = {'get_X': True, "process": filter, "from_player": self}
        result = self.input(context, "%s: %s"%(self.name,prompt))
        if isinstance(result, CancelAction): return -1
        else: return result
    def getDistribution(self, amount, targets, prompt=''):
        if not prompt: prompt = "Distribute %d to target permanents"%amount
        def filter(action):
            if isinstance(action, CancelAction): return False
            return action.assignment
        context = {'get_distribution': True, 'targets': targets, 'amount': amount, 'process': filter}
        return self.input(context, "%s: %s"%(self.name,prompt))
    def getDamageAssignment(self, blocking_list, prompt="Assign damage to blocking creatures", trample=False, deathtouch=False):
        def filter(action):
            if isinstance(action, CancelAction): return False
            else:
                assn = action.assignment
                # Check damage assignment
                for attacker, blockers in blocking_list:
                    total = attacker.combatDamage()
                    valid_lethal = True
                    for blocker, dmg in assn:
                        total -= dmg
                        if (dmg < blocker.lethalDamage() and total > 0):
                            valid_lethal = False
                if not ((deathtouch or valid_lethal) and
                        ((trample and valid_lethal and total > 0) or (total == 0))):
                    return False
                else:
                    return action.assignment
        context = {'get_damage_assign': True, 'blocking_list': blocking_list, 'trample': trample, 'deathtouch': deathtouch, 'process': filter}
        return dict(self.input(context, "%s: %s"%(self.name,prompt)))
    def doRevealCard(self, cards, all=True, prompt=''):
        import operator
        if not operator.isSequenceType(cards): cards = [cards]
        if not prompt: prompt = "reveals card(s) "+', '.join(map(str,cards))
        zone = str(cards[0].zone)
        player = cards[0].controller
        context = {'reveal_card': True, 'cards': cards, 'from_zone': zone, 'from_player': player, 'all': all, 'process': lambda action: True} #isinstance(action, PassPriority)}
        return self.input(context, "%s: %s"%(self.name, prompt))