"""Stores which effects have been locked or used in a turn Checks the actions of the user and prevent the user to do something illegal """ from typing import TYPE_CHECKING, List, Dict, cast, Optional, Callable from uuid import UUID from model import ( Serializable, CardType, Card, Effect, CardEffects, EffectType, EffectTimes, ) if TYPE_CHECKING: from model import Player class Turn(Serializable): """Stores which effects have been locked or used in a turn Checks the actions of the user and prevent the user to do something illegal""" object_type = "turn" player: "Player" cards_locked: List[Card] effects_availables: List[Effect] champions_health: Dict[UUID, int] discard_markers: int damage_reserve: int heal_reserve: int gold_reserve: int def __init__(self, player: "Player") -> None: super().__init__() self.player = player self.damage_reserve = 0 self.heal_reserve = 0 self.gold_reserve = 0 self.champions_health: Dict[UUID, int] = {} self.effects_availables: List[Effect] = [] self.cards_locked: List[Card] = [] self.discard_markers = 0 async def end_turn(self) -> None: """Resets card champion health and card effects""" if len(self.player.hand) > 0: return print("You must play all your cards before ending a turn!") self.damage_reserve = 0 self.heal_reserve = 0 self.gold_reserve = 0 self.effects_availables = [] self.cards_locked = [] self.discard_markers = 0 await self.player.game.notify_controller("update", self.serialize()) await self.player.end_turn() async def lock_card(self, card_id: str): if self.discard_markers > 0: return print("You cannot lock a card while having discard markers") card = self.player.game.search_card_in_pile(card_id, self.player.board) if card is None: return print("Couldn't find card in player board") if card in self.cards_locked: return print("Card already locked") self.cards_locked.append(card) for effect in card.effects: for _ in range(effect.amount): match effect.times: case EffectTimes.PER_CARD_OF_SAME_FACTION: for c in self.player.board: if c.faction == card.faction: self.effects_availables.append(effect) case EffectTimes.PER_CHAMPION: for c in self.player.board: if c.card_role == CardType.CHAMPION: self.effects_availables.append(effect) case EffectTimes.PER_OTHER_CHAMPION: for c in self.player.board: if c.card_role == CardType.CHAMPION and c != card: self.effects_availables.append(effect) case EffectTimes.PER_OTHER_GUARD: for c in self.player.board: if ( c.card_role == CardType.CHAMPION and c.guard is True and c != card ): self.effects_availables.append(effect) case None: self.effects_availables.append(effect) case _: raise NotImplementedError( "EffectTimes " + effect.times + " not implemented" ) await self.player.game.notify_controller("update", self.serialize()) def discard_card(self, card_id: str): if self.discard_markers <= 0: return print("You don't have any discard markers!") card = self.player.game.search_card_in_pile(card_id, self.cards_locked) if card is not None: return print("Cannot discard: card is locked") return self.player.discard_card(card_id) def sacrifice_card(self, card_id: str): card = self.player.game.search_card_in_pile(card_id, self.cards_locked) if card is not None: return print("Cannot sacrifice : card is locked") return self.player.discard_card(card_id) async def use_effect(self, effect_id: str, target: Optional[str]): if self.discard_markers > 0: return print("You cannot use an effect while having discard markers") effect = next( (e for e in self.effects_availables if str(e.uuid) == effect_id), None, ) if effect is None: return print("Effect not available") card = next( (m for m in self.player.board if effect in m.effects), None, ) if card is None: # TODO: Could break for subEffects # Maybe store a reference to card in effect to fix this return print( "Something went wrong while finding the card parent of an effect" ) match effect.effect_type: case EffectType.SUICIDE: success = False success = await self.player.sacrifice_card(str(card.uuid)) if not success: return print("something went wrong while trying to suicide a card") for e in card.effects: if e != effect: self.effects_availables.remove(e) for se in e.sub_effects: if se != effect: self.effects_availables.remove(se) case EffectType.FACTION_COMBO: if ( len([c for c in self.player.board if c.faction == card.faction]) <= 1 ): return print("effect cannot run : faction combo not reached") case None: pass case _: raise NotImplementedError( "EffectType " + effect.effect_type + " not implemented" ) success = False match effect.effect: case CardEffects.GOLD: self.gold_reserve += 1 success = True case CardEffects.DAMAGE: self.damage_reserve += 1 success = True case CardEffects.HEAL: self.heal_reserve += 1 success = True case CardEffects.PERPARE: success = await self.prepare(target) case CardEffects.STUN: success = await self.player.stun(target) case CardEffects.SACRIFICE: success = await self.sacrifice_card(target) case CardEffects.DRAW: success = await self.player.draw(1) case CardEffects.DRAW_AND_DISCARD: self.discard_markers += 1 success = await self.player.draw(1) case CardEffects.DISCARD: success = await self.player.make_enemy_discard(target) case CardEffects.RESTACK_DISCARDED_CHAMPION: success = await self.restack_discarded_champion(target) case CardEffects.RESTACK_DISCARDED_CARD: success = await self.player.put_on_stack_from_discard(target) case CardEffects.STACK_NEXT_ACTION_BOUGHT: success = await self.buy_card(target, self.player.buy_on_stack) case CardEffects.STACK_NEXT_CARD_BOUGHT: success = await self.buy_card(target, self.stack_next_card_bought_check) case CardEffects.PLAY_NEXT_CARD_BOUGHT: success = await self.buy_card(target, self.player.buy_in_hand) case _: raise NotImplementedError( "CardEffect " + effect.effect + " not implemented" ) return if not success: return print("something went wrong while executing effect") self.effects_availables.remove(effect) for e in effect.sub_effects: self.effects_availables.append(e) await self.player.game.notify_controller("update", self.serialize()) def prepare(self, card_id: str) -> bool: card = next( (e for e in self.cards_locked if str(e.uuid) == card_id), None, ) if card is None: print("Card is not locked") return False for e in card.effects: if e.effect_type != EffectType.CHAMPION_ACTION: continue if e in self.effects_availables: continue self.effects_availables.append(e) return True def start_turn(self) -> None: for c in self.player.board: if c.card_type == CardType.CHAMPION: self.champions_health[c.uuid] = c.defense def serialize(self): data = super().serialize() | { "cards_locked": [card.uuid for card in self.cards_locked], "effects_availables": [effect.uuid for effect in self.effects_availables], } return {k: data[k] for k in data.keys() - ["player"]} async def restack_discarded_champion(self, card_id) -> bool: card = self.player.game.search_card_in_pile(card_id, self.player.discard_pile) if card is None: print("card not found in discard") return False if card.card_type != CardType.CHAMPION: print("card is not a champion") return False else: return await self.player.put_on_stack_from_discard(self, card) async def stack_next_card_bought_check(self, card: Card) -> bool: if self.gold_reserve < card.cost: print("Not enough gold to buy card!") return False return await self.player.buy_on_stack(card) async def buy_card( self, card_id: str, buy_function: Optional[Callable[[Card], bool]] = None ): if buy_function is None: buy_function = self.player.buy_card card = self.player.game.search_card_in_pile(card_id, self.player.game.market) if card is None: card = self.player.game.search_card_in_pile( card_id, self.player.game.gem_stack ) if card is None: print("Card not found in market or gem_stack") return False if self.gold_reserve < card.cost: print("Not enough gold to buy card!") return False if await buy_function(card): self.gold_reserve -= card.cost await self.player.game.notify_controller("update", self.serialize()) return True return False