Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix choice mulitimes bug #497

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 106 additions & 97 deletions fireplace/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def __init__(self, *args, **kwargs):
self.callback = ()
self.times = 1
self.event_queue = []
self.choice_callback = []

def __repr__(self):
args = ["%s=%r" % (k, v) for k, v in zip(self.ARGS, self._args)]
Expand Down Expand Up @@ -184,6 +185,12 @@ def matches(self, source, args):
return False
return True

def trigger_choice_callback(self):
callbacks = self.choice_callback
self.choice_callback = []
for callback in callbacks:
callback()


class GameAction(Action):
def trigger(self, source):
Expand Down Expand Up @@ -296,7 +303,7 @@ def do(self, source, target):
log.info("Processing Death for %r", target)
self.broadcast(source, EventListener.ON, target)
if target.deathrattles:
source.game.queue_actions(source, [Deathrattle(target)])
source.game.queue_actions(target.controller, [Deathrattle(target)])


class EndTurn(GameAction):
Expand Down Expand Up @@ -335,73 +342,6 @@ def do(self, source, challenger, defender):
source.game.joust(source, challenger, defender, self.callback)


class Choice(GameAction):
PLAYER = ActionArg()
CARDS = ActionArg()
CARD = ActionArg()

def get_args(self, source):
player = self._args[0]
if isinstance(player, Selector):
player = player.eval(source.game.players, source)
assert len(player) == 1
player = player[0]
cards = self._args[1]
if isinstance(cards, Selector):
cards = cards.eval(source.game, source)
elif isinstance(cards, LazyValue):
cards = cards.evaluate(source)
elif isinstance(cards, list):
eval_cards = []
for card in cards:
if isinstance(card, LazyValue):
eval_cards.append(card.evaluate(source)[0])
elif isinstance(card, str):
eval_cards.append(source.controller.card(card, source))
else:
eval_cards.append(card)
cards = eval_cards

return player, cards

def do(self, source, player, cards):
if len(cards) == 0:
return
log.info("%r choice from %r", player, cards)
self._callback = self.callback
self.callback = ()
self.next_choice = player.choice
player.choice = self
self.source = source
self.player = player
self.cards = cards
self.min_count = 1
self.max_count = 1

def choose(self, card):
if card not in self.cards:
raise InvalidAction("%r is not a valid choice (one of %r)" % (card, self.cards))
for action in self._callback:
self.source.game.trigger(
self.source, [action], [self.player, self.cards, card])
self.player.choice = self.next_choice


class GenericChoice(Choice):
def choose(self, card):
super().choose(card)
for _card in self.cards:
if _card is card:
if card.type == CardType.HERO_POWER:
_card.zone = Zone.PLAY
elif len(self.player.hand) < self.player.max_hand_size:
_card.zone = Zone.HAND
else:
_card.discard()
else:
_card.discard()


class MulliganChoice(GameAction):
PLAYER = ActionArg()

Expand All @@ -422,11 +362,12 @@ def do(self, source, player):
self.max_count = len(player.hand)

def choose(self, *cards):
self.player.draw(len(cards))
for card in cards:
assert card in self.cards
card.zone = Zone.DECK
self.player.choice = None
self.player.draw(len(cards))
for card in cards:
card.zone = Zone.DECK
self.player.shuffle_deck()
self.player.mulligan_state = Mulligan.DONE

Expand Down Expand Up @@ -623,24 +564,34 @@ def trigger(self, source):
times = times.trigger(source)[0]

for i in range(times):
self.trigger_index = i
args = self.get_args(source)
targets = self.get_targets(source, args[0])
args = args[1:]
log.info("%r triggering %r targeting %r", source, self, targets)
for target in targets:
target_args = self.get_target_args(source, target)
ret.append(self.do(source, target, *target_args))
source.game.manager.targeted_action(self, source, target, *target_args)

for action in self.callback:
log.info("%r queues up callback %r", self, action)
ret += source.game.queue_actions(source, [action], event_args=[target] + target_args)
ret += self._trigger(i, source)

self.resolve_broadcasts()

return ret

def _trigger(self, i, source):
if source.controller.choice:
self.choice_callback.append(
lambda: self._trigger(i, source)
)
return []
ret = []
self.trigger_index = i
args = self.get_args(source)
targets = self.get_targets(source, args[0])
args = args[1:]
log.info("%r triggering %r targeting %r", source, self, targets)
for target in targets:
target_args = self.get_target_args(source, target)
ret.append(self.do(source, target, *target_args))
source.game.manager.targeted_action(self, source, target, *target_args)

for action in self.callback:
log.info("%r queues up callback %r", self, action)
ret += source.game.queue_actions(source, [action], event_args=[target] + target_args)
return ret


class Buff(TargetedAction):
"""
Expand Down Expand Up @@ -698,6 +649,65 @@ def do(self, source, target):
target.zone = Zone.HAND


class Choice(TargetedAction):
CARDS = ActionArg()
CARD = ActionArg()

def get_target_args(self, source, target):
cards = self._args[1]
if isinstance(cards, Selector):
cards = cards.eval(source.game, source)
elif isinstance(cards, LazyValue):
cards = cards.evaluate(source)
elif isinstance(cards, list):
eval_cards = []
for card in cards:
if isinstance(card, LazyValue):
eval_cards.append(card.evaluate(source)[0])
elif isinstance(card, str):
eval_cards.append(source.controller.card(card, source))
else:
eval_cards.append(card)
cards = eval_cards

return [cards]

def do(self, source, player, cards):
if len(cards) == 0:
return
log.info("%r choice from %r", player, cards)
player.choice = self
self.source = source
self.player = player
self.cards = cards
self.min_count = 1
self.max_count = 1

def choose(self, card):
if card not in self.cards:
raise InvalidAction("%r is not a valid choice (one of %r)" % (card, self.cards))
self.player.choice = None
for action in self.callback:
self.source.game.trigger(
self.source, [action], [self.cards, card])
self.trigger_choice_callback()


class GenericChoice(Choice):
def choose(self, card):
super().choose(card)
for _card in self.cards:
if _card is card:
if card.type == CardType.HERO_POWER:
_card.zone = Zone.PLAY
elif len(self.player.hand) < self.player.max_hand_size:
_card.zone = Zone.HAND
else:
_card.discard()
else:
_card.discard()


class CopyDeathrattles(TargetedAction):
"""
Copy the deathrattles from a card onto the target
Expand Down Expand Up @@ -899,11 +909,8 @@ def get_target_args(self, source, target):

def do(self, source, target, cards):
log.info("%r discovers %r for %s", source, cards, target)
self._callback = self.callback
self.callback = ()
self.cards = cards
player = source.controller
self.next_choice = player.choice
player.choice = self
self.player = player
self.source = source
Expand All @@ -915,10 +922,11 @@ def do(self, source, target, cards):
def choose(self, card):
if card not in self.cards:
raise InvalidAction("%r is not a valid choice (one of %r)" % (card, self.cards))
self.player.choice = self.next_choice
for action in self._callback:
self.player.choice = None
for action in self.callback:
self.source.game.trigger(
self.source, [action], [self.target, self.cards, card])
self.trigger_choice_callback()


class Draw(TargetedAction):
Expand Down Expand Up @@ -1595,7 +1603,6 @@ def init(self):
def do(self, source, player):
self.init()
self.player = player
self.next_choice = self.player.choice
self.source = source
self.min_count = 1
self.max_count = 1
Expand Down Expand Up @@ -1642,8 +1649,9 @@ def choose(self, card):
elif len(self.choosed_cards) == 2:
self.do_step3()
elif len(self.choosed_cards) == 3:
self.player.choice = None
self.done()
self.player.choice = self.next_choice
self.trigger_choice_callback()


class Upgrade(TargetedAction):
Expand Down Expand Up @@ -1704,7 +1712,6 @@ def do(self, source, target, cards):
log.info("%r adapts %r for %s", source, cards, target)
self.cards = cards
player = source.controller
self.next_choice = player.choice
player.choice = self
self.player = player
self.source = source
Expand All @@ -1716,8 +1723,9 @@ def do(self, source, target, cards):
def choose(self, card):
if card not in self.cards:
raise InvalidAction("%r is not a valid choice (one of %r)" % (card, self.cards))
self.player.choice = None
self.source.game.trigger(self.source, (Battlecry(card, self.target), ), None)
self.player.choice = self.next_choice
self.trigger_choice_callback()


class AddProgress(TargetedAction):
Expand All @@ -1729,7 +1737,8 @@ def do(self, source, target, card):
target.add_progress(card)
if target.progress >= target.progress_total:
source.game.trigger(target, target.get_actions("reward"), event_args=None)
target.zone = Zone.GRAVEYARD
if target.data.quest:
target.zone = Zone.GRAVEYARD


class ClearProgress(TargetedAction):
Expand All @@ -1741,7 +1750,6 @@ def do(self, source, target):
class GlimmerrootAction(TargetedAction):
def do(self, source, player):
self.player = player
self.next_choice = self.player.choice
self.source = source
self.min_count = 1
self.max_count = 1
Expand Down Expand Up @@ -1780,7 +1788,8 @@ def choose(self, card):
card.zone = Zone.HAND
else:
log.info("Choose incorrectly, corrent choice is %r", self.starting_card)
self.player.choice = self.next_choice
self.player.choice = None
self.trigger_choice_callback()


class CreateZombeast(TargetedAction):
Expand All @@ -1803,7 +1812,6 @@ def init(self, source):
def do(self, source, player):
self.init(source)
self.player = player
self.next_choice = self.player.choice
self.source = source
self.min_count = 1
self.max_count = 1
Expand Down Expand Up @@ -1849,8 +1857,9 @@ def choose(self, card):
if len(self.choosed_cards) == 1:
self.do_step2()
elif len(self.choosed_cards) == 2:
self.player.choice = None
self.done()
self.player.choice = self.next_choice
self.trigger_choice_callback()


class LosesDivineShield(TargetedAction):
Expand Down
2 changes: 1 addition & 1 deletion fireplace/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def must_choose_one(self):
Returns True if the card has active choices
"""
if self.controller.choose_both and self.has_choose_one:
self.choose_cards = []
return False
return bool(self.choose_cards)

@property
Expand Down
13 changes: 13 additions & 0 deletions tests/test_ungoro.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,16 @@ def test_molten_reflection():
wisp = game.player1.give(WISP).play()
game.player1.give("UNG_948").play(target=wisp)
assert len(game.player1.field) == 2


def test_volcanosaur():
game = prepare_game()
game.player1.give("LOE_077").play()
volcanosaur = game.player1.give("UNG_002").play()
for _ in range(4):
choice = game.player1.choice
assert choice
choice.choose(choice.cards[0])
choice = game.player1.choice
assert not choice
assert len(volcanosaur.buffs) == 4
2 changes: 2 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
"AT_130", # Sea Reaver
"KAR_096", # Prince Malchezaar
"CFM_637", # Patches the Pirate
"KAR_205", # Silverware Golem
"UNG_836", # Clutchmother Zavas
)

_draftcache = {}
Expand Down