diff --git a/src/cards/pack1.json b/src/cards/pack1.json new file mode 100644 index 0000000..5e76da4 --- /dev/null +++ b/src/cards/pack1.json @@ -0,0 +1,258 @@ +{ + "effects": { + "gold": {}, + "damage": {}, + "heal": {}, + "prepare": {}, + "stun": {}, + "sacrifice": {}, + "draw": {}, + "draw_and_discard": {}, + "discard": {}, + "restack_discarded_champion": {}, + "restack_discarded_card": {}, + "stack_next_action_bought": {}, + "stack_next_card_bought": {} + }, + "times": { + "per_other_champion": {}, + "per_cards_of_same_faction": {}, + "per_other_guard": {} + }, + "effect_types": { + "champion_action": {}, + "sacrifice": {}, + "faction_combo": {} + }, + "cards": [ + { + "role": "personal", + "card_type": "item", + "faction": "base", + "name": "Ruby", + "amount": 1, + "effects": [ + { + "effect": "gold", + "amount": 2 + } + ] + }, + { + "role": "personal", + "card_type": "item", + "faction": "base", + "name": "Dagger", + "amount": 1, + "effects": [ + { + "effect": "damage", + "amount": 1 + } + ] + }, + { + "role": "personal", + "card_type": "item", + "faction": "base", + "name": "Shortsword", + "amount": 1, + "effects": [ + { + "effect": "damage", + "amount": 2 + } + ] + }, + { + "role": "personal", + "card_type": "item", + "faction": "base", + "name": "Gold", + "amount": 7, + "effects": [ + { + "effect": "gold", + "amount": 1 + } + ] + }, + { + "role": "fire_gem", + "card_type": "item", + "faction": "base", + "name": "Fire Gem", + "cost": 2, + "amount": 16, + "effects": [ + { + "effect": "gold", + "amount": 2 + }, + { + "effect": "damage", + "amount": 3, + "type": "sacrifice" + } + ] + }, + { + "role": "market", + "card_type": "champion", + "faction": "red", + "name": "Lys, the Unseen", + "cost": 6, + "defense": 5, + "guard": true, + "amount": 1, + "effects": [ + { + "effect": "damage", + "amount": 2, + "type": "champion_action" + }, + { + "effect": "sacrifice", + "amount": 1, + "type": "champion_action", + "sub_effects": [ + { + "effect": "damage", + "amount": 2 + } + ] + } + ] + }, + { + "role": "market", + "card_type": "champion", + "faction": "red", + "name": "Ass Priest", + "cost": 3, + "defense": 4, + "guard": false, + "amount": 2, + "effects": [ + { + "effect": "damage", + "amount": 1, + "type": "champion_action", + "mutex": 1 + }, + { + "effect": "gold", + "amount": 1, + "type": "champion_action", + "mutex": 1 + }, + { + "effect": "damage", + "amount": 4, + "type": "faction_combo" + } + ] + }, + { + "role": "market", + "card_type": "champion", + "faction": "yellow", + "name": "Tits Priest", + "cost": 2, + "defense": 3, + "guard": false, + "amount": 2, + "effects": [ + { + "effect": "gold", + "amount": 1, + "type": "champion_action", + "mutex": 1 + }, + { + "effect": "heal", + "amount": 1, + "type": "champion_action", + "mutex": 1, + "times": "per_other_champion" + } + ] + }, + { + "role": "market", + "card_type": "action", + "faction": "yellow", + "name": "Recruit", + "cost": 2, + "amount": 3, + "effects": [ + { + "effect": "gold", + "amount": 2 + }, + { + "effect": "heal", + "amount": 3 + }, + { + "effect": "heal", + "amount": 1, + "times": "per_other_champion" + }, + { + "effect": "gold", + "amount": 1, + "type": "faction_combo" + } + ] + }, + { + "role": "market", + "card_type": "champion", + "faction": "blue", + "name": "Street Thug", + "cost": 3, + "defense": 4, + "guard": false, + "amount": 2, + "effects": [ + { + "effect": "gold", + "amount": 1, + "type": "champion_action", + "mutex": 1 + }, + { + "effect": "damage", + "amount": 2, + "type": "champion_action", + "mutex": 1 + } + ] + }, + { + "role": "market", + "card_type": "champion", + "faction": "yellow", + "name": "Darian , War Mage", + "cost": 4, + "defense": 5, + "guard": false, + "amount": 1, + "effects": [ + { + "effect": "damage", + "amount": 3, + "type": "champion_action", + "mutex": 1 + }, + { + "effect": "heal", + "amount": 4, + "type": "champion_action", + "mutex": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/controller/client.py b/src/controller/client.py index d34f79a..f61147e 100644 --- a/src/controller/client.py +++ b/src/controller/client.py @@ -58,6 +58,15 @@ class ClientController: async def send_error(self, error: str): await self.send(Message(type="error", data_type="error", data=error)) + async def send_announce_dict(self, notification_type: str, data: dict): + await self.send( + Message( + type=notification_type, + data_type="object", + data=data, + ) + ) + @vibe_check(Message) async def game_context(self, message: Message): """Handles messages when the client is logged in""" @@ -90,6 +99,7 @@ class ClientController: return self.player = player + self.player.team.listeners.append(self.send_announce_dict) self.on_message = self.game_context print("user logged in : " + username) diff --git a/src/main.py b/src/main.py index 40dd633..c679e47 100644 --- a/src/main.py +++ b/src/main.py @@ -23,7 +23,7 @@ playerServer.message_callbacks.append(handler) async def game_init(): - for _ in range(10): + for _ in range(15): card = await hero_game.create_card() hero_game.market_stack.append(card) diff --git a/src/model/card.py b/src/model/card.py index c960bb4..6bcd71a 100644 --- a/src/model/card.py +++ b/src/model/card.py @@ -2,6 +2,12 @@ from enum import Enum from model import Serializable +class CardRole(str, Enum): + PERSONAL = "personal" + MARKET = "market" + FIRE_GEM = "fire_gem" + + class CardType(str, Enum): """Playable card types""" @@ -11,11 +17,14 @@ class CardType(str, Enum): WEAPON = "weapon" CHAMPION = "champion" ACTION = "action" + ITEM = "item" class CardFaction(str, Enum): """Playable card factions""" + BASE = "base" + BLUE = "blue" RED = "red" GREEN = "green" @@ -30,6 +39,7 @@ class Card(Serializable): name: str text: str cost: int + role: CardRole card_type: CardType faction: CardFaction diff --git a/src/model/game.py b/src/model/game.py index f820bfb..49f396a 100644 --- a/src/model/game.py +++ b/src/model/game.py @@ -20,8 +20,6 @@ class Game(Serializable): default_health = 50 - listeners: List[Callable[[str, dict], Awaitable[Any]]] - def __init__(self): super().__init__() self.market_stack = [] @@ -31,17 +29,8 @@ class Game(Serializable): self.cards = [] self.teams = [] - self.listeners = [] self.started = False - async def notify_controller( - self, - notification_type: str, - game_object: dict, - ): - for l in self.listeners: - await l(notification_type, game_object) - def start_game(self): pass @@ -59,12 +48,27 @@ class Game(Serializable): return card + def create_base_deck(self) -> List[Card]: + return [ + Card( + card_type=CardType.ACTION, + faction=CardFaction.BLUE, + cost=1, + name="test", + text="testtext", + sprite="testsprite", + ) + for _ in range(10) + ] + async def create_player(self, username: str, team: Team | None = None) -> Player: if team is None: team = await self.create_team() player = Player(username, team) self.players.append(player) + + player.stack_pile = self.create_base_deck() await self.notify_controller("create", player.serialize_guest()) return player @@ -93,8 +97,7 @@ class Game(Serializable): await self.notify_controller("update", self.serialize_guest()) def serialize_guest(self) -> dict: - return self.__dict__ | { - "listeners": None, + return super().serialize_guest() | { "cards": [card.uuid for card in self.cards], "market_stack": [None for card in self.market_stack], "market": [None if card is None else card.uuid for card in self.market], diff --git a/src/model/player.py b/src/model/player.py index 446e7df..2dc62d9 100644 --- a/src/model/player.py +++ b/src/model/player.py @@ -1,4 +1,6 @@ +import random from typing import List + from model import Card, Team, Serializable @@ -26,7 +28,7 @@ class Player(Serializable): def serialize_guest(self) -> dict: """Get data available to the public""" - return self.__dict__ | { + return super().serialize_guest() | { "stack_pile": [None for card in self.stack_pile], "discard_pile": [card.uuid for card in self.discard_pile], "team": self.team.uuid, @@ -44,3 +46,25 @@ class Player(Serializable): return self.serialize_team() | { "stack_pile": [card.uuid for card in self.stack_pile] } + + async def _rebuild_stack(self): + """Call when the player wants to draw but has no cards in stack_pile""" + if len(self.stack_pile) != 0: + raise RuntimeWarning("Cannot rebuild stack if stack is not empty!") + self.stack_pile, self.discard_pile = self.discard_pile, self.stack_pile + random.shuffle(self.stack_pile) + + async def draw(self, amount: int): + """Draw a card from the stack. Handles stack rebuild from discard automatically""" + try: + while amount <= 0: + if len(self.stack_pile) == 0: + if len(self.discard_pile) == 0: + raise RuntimeWarning("No more cards to draw !") + await self._rebuild_stack() + + if len(self.stack_pile) > 0: + self.hand.append(self.stack_pile.pop()) + return self.draw(amount - 1) + finally: + self.team.notify_controller("update", self.serialize_team()) diff --git a/src/model/serializable.py b/src/model/serializable.py index 5d947d4..83f98da 100644 --- a/src/model/serializable.py +++ b/src/model/serializable.py @@ -1,3 +1,4 @@ +from typing import List, Callable, Awaitable, Any from uuid import uuid4, UUID @@ -6,14 +7,25 @@ class Serializable: uuid: UUID + listeners: List[Callable[[str, dict], Awaitable[Any]]] + def __init__(self): self.uuid = uuid4() self.object_type = self.__class__.object_type + self.listeners = [] def serialize(self): """Returns a dict to be saved on the database""" - return self.__dict__ + return self.__dict__ | {"listeners": None} def serialize_guest(self): """Returns a dict with some elements masked for the players""" - return self.serialize() + return self.__dict__ | {"listeners": None} + + async def notify_controller( + self, + notification_type: str, + game_object: dict, + ): + for l in self.listeners: + await l(notification_type, game_object)