"""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, Optional, Callable from uuid import UUID from model import ( Serializable, CardType, Card, Effect, CardEffects, EffectType, EffectTimes, Team, ) 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[str, 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_OTHER_CARD_OF_SAME_FACTION: for c in self.player.board: if c.faction == card.faction and c != card: 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()) async 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") self.discard_markers -= 1 await self.player.game.notify_controller("update", self.serialize()) return await 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.sacrifice_card(card_id) async def use_effect_with_check(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 not None: # TODO: Could break for subEffects # Maybe store a reference to card in effect to fix this # Also breaks for Sacrifice effects match effect.effect_type: case EffectType.SUICIDE: for e in card.effects: if e != effect: while e in self.effects_availables: self.effects_availables.remove(e) for se in e.sub_effects: if se != effect: while se in self.effects_availables: self.effects_availables.remove(se) await self.player.sacrifice_card(str(card.uuid)) 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 EffectType.CHAMPION_ACTION: pass case _: raise NotImplementedError( "EffectType " + effect.effect_type + " not implemented" ) else: print( "Something went wrong while finding the card parent of an effect\nAre you using an effect from an already used card ?" ) success = await self.use_effect(effect_id, target) if not success: return print("something went wrong while executing effect") ## Remove effects for OR Conditions if effect.mutex is not None and len(effect.mutex) > 0: for e in effect.mutex: if e != effect: while e in self.effects_availables: self.effects_availables.remove(e) self.effects_availables.remove(effect) for e in effect.sub_effects: for _ in range(e.amount): self.effects_availables.append(e) print([str(e.uuid) for e in self.effects_availables]) await self.player.game.notify_controller("update", self.serialize()) async def use_effect(self, effect_id: str, target: str): effect = next( (e for e in self.effects_availables if str(e.uuid) == effect_id), None, ) if effect is None: print("effect couldn't found!") return False 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 = 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 success 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 async def start_turn(self) -> None: self.champions_health = {} for p in self.player.game.players: for c in p.board: if c.card_type == CardType.CHAMPION: p.turn.champions_health[str(c.uuid)] = c.defense await p.game.notify_controller("update", p.turn.serialize()) 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(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 async def heal(self, amount: int): while amount > 0: if self.heal_reserve <= 0: return print("You cannot heal if your heal_reserve is empty") self.player.team.health += 1 self.heal_reserve -= 1 amount -= 1 await self.player.game.notify_controller("update", self.serialize()) await self.player.game.notify_controller("update", self.player.team.serialize()) def check_for_guard(self, team: "Team"): for p in self.player.game.players: if p.team != team: continue for c in p.board: if c.guard == True: return True return False async def damage_team(self, target: str): team = next( (t for t in self.player.game.teams if str(t.uuid) == target), None, ) if team is None: return print("team not found") if team == self.player.team: return print("KYS!") if self.check_for_guard(team): return print("You cannot attack this team if a guard is present") if self.damage_reserve > 0: self.damage_reserve -= 1 team.health -= 1 await self.player.game.notify_controller("update", self.serialize()) await self.player.game.notify_controller("update", team.serialize()) async def damage_champion(self, target: str): if self.damage_reserve <= 0: print("No damage left to attack") return for p in self.player.game.players: if self.player.team == p.team: continue for c in p.board: if str(c.uuid) != target: continue if c.card_type != CardType.CHAMPION: continue if not c.guard and self.check_for_guard(p.team): print("You cannot attack this champion if a guard is present") return self.damage_reserve -= 1 p.turn.champions_health[str(c.uuid)] -= 1 if p.turn.champions_health[str(c.uuid)] <= 0: await self.player.stun(str(c.uuid)) await self.player.game.notify_controller("update", self.serialize()) await self.player.game.notify_controller("update", p.turn.serialize()) return print("card not found")