diff --git a/src/seahorse/game/action.py b/src/seahorse/game/action.py index 9f1cc09..f78617f 100644 --- a/src/seahorse/game/action.py +++ b/src/seahorse/game/action.py @@ -1,59 +1,30 @@ from __future__ import annotations +from abc import abstractmethod from typing import TYPE_CHECKING from seahorse.utils.serializer import Serializable -if TYPE_CHECKING: - from seahorse.game.game_state import GameState - - class Action(Serializable): """ - A class representing an action in the game. + A generic class representing an action in the game. - Attributes: - past_gs (GameState): The past game state. - new_gs (GameState): The new game state. """ - def __init__(self, current_game_state: GameState, next_game_state: GameState) -> None: + def __init__(self) -> None: """ Initializes a new instance of the Action class. - Args: - past_gs (GameState): The past game state. - new_gs (GameState): The new game state. - """ - self.current_game_state = current_game_state - self.next_game_state = next_game_state - - def get_current_game_state(self) -> GameState: - """ - Returns the past game state. - - Returns: - GameState: The past game state. """ - return self.current_game_state + pass - def get_next_game_state(self) -> GameState: + @abstractmethod + def get_heavy_action(self, **kwargs) -> Action: """ - Returns the new game state. + Returns the heavy action. Returns: - GameState: The new game state. + Action: The heavy action. """ - return self.next_game_state - - def __hash__(self) -> int: - return hash((hash(self.get_next_game_state()), hash(self.get_current_game_state()))) - - def __eq__(self, value: object) -> bool: - return hash(self) == hash(value) - - def __str__(self) -> str: - return "From:\n" + self.get_current_game_state().get_rep().__str__() + "\nto:\n" + self.get_next_game_state().get_rep().__str__() + raise NotImplementedError - def to_json(self) -> dict: - return self.__dict__ diff --git a/src/seahorse/game/game_state.py b/src/seahorse/game/game_state.py index f1ec1e0..45909b4 100644 --- a/src/seahorse/game/game_state.py +++ b/src/seahorse/game/game_state.py @@ -3,6 +3,8 @@ from typing import Any from seahorse.game.action import Action +from seahorse.game.heavy_action import HeavyAction +from seahorse.game.light_action import LightAction from seahorse.game.representation import Representation from seahorse.player.player import Player from seahorse.utils.custom_exceptions import MethodNotImplementedError @@ -34,7 +36,8 @@ def __init__(self, scores: dict[int, Any], next_player: Player, players: list[Pl self.next_player = next_player self.players = players self.rep = rep - self._possible_actions = None + self._possible_light_actions = None + self._possible_heavy_actions = None def get_player_score(self, player: Player) -> float: """ @@ -95,10 +98,25 @@ def get_rep(self) -> Representation: """ return self.rep - def get_possible_actions(self) -> frozenset[Action]: + def get_possible_light_actions(self) -> frozenset[LightAction]: """ - Returns a copy of the possible actions from this state. - The first call triggers the `generate_possible_actions` method. + Returns a copy of the possible light actions from this state. + The first call triggers the `generate_possible_light_actions` method. + + Returns: + FrozenSet[LightAction]: The possible actions. + """ + # Lazy loading + if self.is_done(): + return frozenset() + if self._possible_light_actions is None: + self._possible_light_actions = frozenset(self.generate_possible_light_actions()) + return self._possible_light_actions + + def get_possible_heavy_actions(self) -> frozenset[HeavyAction]: + """ + Returns a copy of the possible heavy actions from this state. + The first call triggers the `generate_possible_heavy_actions` method. Returns: FrozenSet[Action]: The possible actions. @@ -106,9 +124,9 @@ def get_possible_actions(self) -> frozenset[Action]: # Lazy loading if self.is_done(): return frozenset() - if self._possible_actions is None: - self._possible_actions = frozenset(self.generate_possible_actions()) - return self._possible_actions + if self._possible_heavy_actions is None: + self._possible_heavy_actions = frozenset(self.generate_possible_heavy_actions()) + return self._possible_heavy_actions def check_action(self, action: Action) -> bool: """ @@ -120,16 +138,57 @@ def check_action(self, action: Action) -> bool: Returns: bool: True if the action is feasible, False otherwise. """ - if action in self.get_possible_actions(): - return True + if isinstance(action, LightAction): + return action in self.get_possible_light_actions() + if isinstance(action, HeavyAction): + return action in self.get_possible_heavy_actions() return False + def convert_gui_data_to_action_data(self, data: dict[str, Any]) -> dict[str, Any]: + """ + Converts GUI data to light action data. + This method can and should be overridden by the user. + + Args: + data (Dict[str, Any]): The GUI data. + + Returns: + Dict[str, Any]: The action data. + """ + return data + @abstractmethod - def convert_light_action_to_action(self,data) -> Action : + def apply_action(self, action: LightAction) -> "GameState": + """ + Applies an action to the game state. + + Args: + action (LightAction): The action to apply. + + Returns: + GameState: The new game state. + + Raises: + MethodNotImplementedError: If the method is not implemented. + """ raise MethodNotImplementedError() + @abstractmethod - def generate_possible_actions(self) -> set[Action]: + def generate_possible_light_actions(self) -> set[LightAction]: + """ + Generates a set of all possible actions from this game state. + + Returns: + Set[Action]: A set of possible actions. + + Raises: + MethodNotImplementedError: If the method is not implemented. + """ + raise MethodNotImplementedError() + + @abstractmethod + def generate_possible_heavy_actions(self) -> set[HeavyAction]: """ Generates a set of all possible actions from this game state. diff --git a/src/seahorse/game/heavy_action.py b/src/seahorse/game/heavy_action.py new file mode 100644 index 0000000..d58f1f2 --- /dev/null +++ b/src/seahorse/game/heavy_action.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from seahorse.game.action import Action +from seahorse.utils.serializer import Serializable + +if TYPE_CHECKING: + from seahorse.game.game_state import GameState + + +class HeavyAction(Action): + """ + A class representing an action in the game. + + Attributes: + past_gs (GameState): The past game state. + new_gs (GameState): The new game state. + """ + + def __init__(self, current_game_state: GameState, next_game_state: GameState) -> None: + """ + Initializes a new instance of the Action class. + + Args: + past_gs (GameState): The past game state. + new_gs (GameState): The new game state. + """ + self.current_game_state = current_game_state + self.next_game_state = next_game_state + + def get_current_game_state(self) -> GameState: + """ + Returns the past game state. + + Returns: + GameState: The past game state. + """ + return self.current_game_state + + def get_next_game_state(self) -> GameState: + """ + Returns the new game state. + + Returns: + GameState: The new game state. + """ + return self.next_game_state + + def get_heavy_action(self, game_state: GameState = None) -> HeavyAction: + """ + Returns the heavy action. + + Returns: + HeavyAction: The heavy action. + """ + return self + + def __hash__(self) -> int: + return hash((hash(self.get_next_game_state()), hash(self.get_current_game_state()))) + + def __eq__(self, value: object) -> bool: + return hash(self) == hash(value) + + def __str__(self) -> str: + return "From:\n" + self.get_current_game_state().get_rep().__str__() + "\nto:\n" + self.get_next_game_state().get_rep().__str__() + + def to_json(self) -> dict: + return self.__dict__ diff --git a/src/seahorse/game/light_action.py b/src/seahorse/game/light_action.py new file mode 100644 index 0000000..21ffabb --- /dev/null +++ b/src/seahorse/game/light_action.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from seahorse.game.action import Action +from seahorse.game.heavy_action import HeavyAction + +if TYPE_CHECKING: + from seahorse.game.game_state import GameState + + +class LightAction(Action): + """ + A class representing an action in the game. + + Attributes: + data (dict): The data of the light action. + """ + + def __init__(self, data: dict) -> None: + """ + Initializes a new instance of the Action class. + + Args: data (dict): The data of the light action. + + """ + self.data = data + + + def get_heavy_action(self, game_state: GameState = None) -> HeavyAction: + """ + Returns the heavy action. + + Returns: + HeavyAction: The heavy action. + """ + if game_state is None: + raise ValueError("Cannot apply a light action without current game state.") + + return HeavyAction(game_state, game_state.apply_action(self)) + + + def __hash__(self) -> int: + return hash(tuple(self.data.items())) + + def __eq__(self, value: object) -> bool: + return hash(self) == hash(value) + + def __str__(self) -> str: + return "LightAction: " + str(self.data) + + def to_json(self) -> dict: + return self.__dict__ diff --git a/src/seahorse/game/master.py b/src/seahorse/game/master.py index d69d5b9..34d06f3 100644 --- a/src/seahorse/game/master.py +++ b/src/seahorse/game/master.py @@ -10,7 +10,9 @@ from loguru import logger from seahorse.game.game_state import GameState +from seahorse.game.heavy_action import HeavyAction from seahorse.game.io_stream import EventMaster, EventSlave +from seahorse.game.light_action import LightAction from seahorse.player.player import Player from seahorse.utils.custom_exceptions import ( ActionNotPermittedError, @@ -90,7 +92,8 @@ async def step(self) -> GameState: GamseState : The new game_state. """ next_player = self.current_game_state.get_next_player() - possible_actions = self.current_game_state.get_possible_actions() + + possible_actions = self.current_game_state.get_possible_heavy_actions() start = time.time() # next_player.start_timer() @@ -111,6 +114,7 @@ async def step(self) -> GameState: # next_player.stop_timer() + action = action.get_heavy_action(self.current_game_state) if action not in possible_actions: raise ActionNotPermittedError() @@ -126,6 +130,7 @@ async def play_game(self) -> list[Player]: Returns: Iterable[Player]: The winner(s) of the game. """ + time_start = time.time() await self.emitter.sio.emit( "play", json.dumps(self.current_game_state.to_json(),default=lambda x:x.to_json()), @@ -140,7 +145,12 @@ async def play_game(self) -> list[Player]: try: logger.info(f"Player now playing : {self.get_game_state().get_next_player().get_name()} - {self.get_game_state().get_next_player().get_id()}") self.current_game_state = await self.step() +<<<<<<< remove_timer # self.recorded_plays.append(self.current_game_state.__class__.from_json(json.dumps(self.current_game_state.to_json(),default=lambda x:x.to_json()))) +======= + self.recorded_plays.append(self.current_game_state.__class__.from_json(json.dumps(self.current_game_state.to_json(),default=lambda x:x.to_json()))) + +>>>>>>> main except (ActionNotPermittedError,SeahorseTimeoutError,StopAndStartError) as e: if isinstance(e,SeahorseTimeoutError): logger.error(f"Time credit expired for player {self.current_game_state.get_next_player()}") @@ -193,7 +203,13 @@ async def play_game(self) -> list[Player]: logger.info(f"Winner - {player.get_name()}") await self.emitter.sio.emit("done",json.dumps(self.get_scores())) +<<<<<<< remove_timer logger.verdict(f"{','.join(w.get_name() for w in self.get_winner())} has won the game") +======= + logger.verdict(f"{verdict_scores[::-1]}") + + print(f"Time taken for the whole game : {time.time()-time_start}") +>>>>>>> main return self.winner diff --git a/src/seahorse/player/proxies.py b/src/seahorse/player/proxies.py index 70d4ff9..3cd1b8c 100644 --- a/src/seahorse/player/proxies.py +++ b/src/seahorse/player/proxies.py @@ -8,6 +8,7 @@ from seahorse.game.game_state import GameState from seahorse.game.io_stream import EventMaster, EventSlave, event_emitting, remote_action +from seahorse.game.light_action import LightAction from seahorse.player.player import Player from seahorse.utils.custom_exceptions import MethodNotImplementedError from seahorse.utils.gui_client import GUIClient @@ -155,14 +156,16 @@ async def play(self, current_state: GameState, **args) -> Action: if self.shared_sid and not self.sid: self.sid=self.shared_sid.sid while True: - data = json.loads(await EventMaster.get_instance().wait_for_event(self.sid,"interact",flush_until=time.time())) + data_gui = json.loads(await EventMaster.get_instance().wait_for_event(self.sid,"interact",flush_until=time.time())) try: - action = current_state.convert_light_action_to_action(data) + data = current_state.convert_gui_data_to_action_data(data_gui) + action = LightAction(data).get_heavy_action(current_state) + except MethodNotImplementedError: #TODO: handle this case action = Action.from_json(data) - if action in current_state.get_possible_actions(): + if action in current_state.get_possible_heavy_actions(): break else: await EventMaster.get_instance().sio.emit("ActionNotPermitted",None)