diff --git a/src/seahorse/game/master.py b/src/seahorse/game/master.py index 4cbef24..34d06f3 100644 --- a/src/seahorse/game/master.py +++ b/src/seahorse/game/master.py @@ -43,7 +43,8 @@ def __init__( players_iterator: Iterable[Player], log_level: str = "INFO", port: int =8080, - hostname: str ="localhost" + hostname: str ="localhost", + time_limit: int = 15*60, ) -> None: """ Initializes a new instance of the GameMaster class. @@ -56,10 +57,12 @@ def __init__( log_level (str): The name of the log file. """ self.timetol = 1e-1 - self.recorded_plays = [] + # self.recorded_plays = [] self.name = name self.current_game_state = initial_game_state self.players = initial_game_state.players + self.remaining_time = {player.get_id(): time_limit for player in self.players} + player_names = [x.name for x in self.players] if len(set(player_names)) GameState: possible_actions = self.current_game_state.get_possible_heavy_actions() start = time.time() - next_player.start_timer() - logger.info(f"time : {next_player.get_remaining_time()}") + # next_player.start_timer() + logger.info(f"time : {self.remaining_time[next_player.get_id()]}") if isinstance(next_player,EventSlave): - action = await next_player.play(self.current_game_state) + action = await next_player.play(self.current_game_state, remaining_time=self.remaining_time[next_player.get_id()]) else: - action = next_player.play(self.current_game_state) - + action = next_player.play(self.current_game_state, remaining_time=self.remaining_time[next_player.get_id()]) tstp = time.time() - if abs((tstp-start)-(tstp-next_player.get_last_timestamp()))>self.timetol: - next_player.stop_timer() - raise StopAndStartError() + self.remaining_time[next_player.get_id()] -= (tstp-start) + if self.remaining_time[next_player.get_id()] < 0: + raise SeahorseTimeoutError() + + # if abs((tstp-start)-(tstp-next_player.get_last_timestamp()))>self.timetol: + # next_player.stop_timer() + # raise StopAndStartError() - next_player.stop_timer() + # next_player.stop_timer() action = action.get_heavy_action(self.current_game_state) if action not in possible_actions: @@ -129,9 +135,9 @@ async def play_game(self) -> list[Player]: "play", 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()))) + # 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()))) id2player={} - verdict_scores=[-1e9,-1e9] + # verdict_scores=[-1e9,-1e9] for player in self.get_game_state().get_players() : id2player[player.get_id()]=player.get_name() logger.info(f"Player : {player.get_name()} - {player.get_id()}") @@ -139,34 +145,45 @@ 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()}") elif isinstance(e,ActionNotPermittedError) : logger.error(f"Action not permitted for player {self.current_game_state.get_next_player()}") - else: - logger.error(f"Player {self.current_game_state.get_next_player()} might have tried tampering with the timer.\n The timedelta difference exceeded the allowed tolerancy in GameMaster.timetol ") + # else: + # logger.error(f"Player {self.current_game_state.get_next_player()} might have tried tampering with the timer.\n The timedelta difference exceeded the allowed tolerancy in GameMaster.timetol ") temp_score = copy.copy(self.current_game_state.get_scores()) id_player_error = self.current_game_state.get_next_player().get_id() - temp_score.pop(id_player_error) - self.winner = self.compute_winner(temp_score) - self.current_game_state.get_scores()[id_player_error] = -3 other_player = next(iter([player.get_id() for player in self.current_game_state.get_players() if player.get_id()!=id_player_error])) - self.current_game_state.get_scores()[other_player] = 0 - scores = self.get_scores() - for key in scores.keys(): - verdict_scores[int(id2player[key].split("_")[-1])-1]=-scores[key] - logger.info(f"{id2player[key]}:{scores[key]}") + temp_score[id_player_error] = -1e9 + temp_score[other_player] = 1e9 + + # temp_score.pop(id_player_error) + self.winner = self.compute_winner(temp_score) + + # self.current_game_state.get_scores()[id_player_error] = -3 + # other_player = next(iter([player.get_id() for player in self.current_game_state.get_players() if player.get_id()!=id_player_error])) + # self.current_game_state.get_scores()[other_player] = 0 + + # scores = self.get_scores() + for key in temp_score.keys(): + # verdict_scores[int(id2player[key].split("_")[-1])-1]=-scores[key] + logger.info(f"{id2player[key]}:{temp_score[key]}") + for player in self.get_winner() : logger.info(f"Winner - {player.get_name()}") await self.emitter.sio.emit("done",json.dumps(self.get_scores())) - logger.verdict(f"{verdict_scores[::-1]}") - with open(self.players[0].name+"_"+self.players[-1].name+"_"+str(time.time())+".json","w+") as f: - f.write(json.dumps(self.recorded_plays),default=lambda x:x.to_json()) + + logger.verdict(f"{self.current_game_state.get_next_player().get_name()} has been disqualified") + return self.winner logger.info(f"Current game state: \n{self.current_game_state.get_rep()}") @@ -179,15 +196,20 @@ async def play_game(self) -> list[Player]: self.winner = self.compute_winner(self.current_game_state.get_scores()) scores = self.get_scores() for key in scores.keys() : - verdict_scores[int(id2player[key].split("_")[-1])-1]=-scores[key] + # verdict_scores[int(id2player[key].split("_")[-1])-1]=-scores[key] logger.info(f"{id2player[key]}:{(scores[key])}") + for player in self.get_winner() : 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/game/time_manager.py b/src/seahorse/game/time_manager.py index a82a4b2..b624854 100644 --- a/src/seahorse/game/time_manager.py +++ b/src/seahorse/game/time_manager.py @@ -1,265 +1,265 @@ -import builtins -import functools -import time -from typing import Any - -from seahorse.utils.custom_exceptions import ( - AlreadyRunningError, - NotRunningError, - SeahorseTimeoutError, - TimerNotInitializedError, -) - +# import builtins +# import functools +# import time +# from typing import Any + +# from seahorse.utils.custom_exceptions import ( +# AlreadyRunningError, +# NotRunningError, +# SeahorseTimeoutError, +# TimerNotInitializedError, +# ) + -class TimeMaster: - __instance = None - - class Timer: - def __init__(self,time_limit:float=1e9): - self._time_limit = time_limit - self._remaining_time = time_limit - self._last_timestamp = None - self._is_running = False - - def start_timer(self) -> float: - """Starts the timer - - Raises: - AlreadyRunningException: when trying to start twice. - """ - if self._is_running: - raise AlreadyRunningError() - - self._last_timestamp = time.time() - - self._is_running = True - - return self._last_timestamp - - def is_running(self) -> bool: - """ - Is the timer running ? - - Returns: - bool: `True` if the timer is running, `False` otherwise - """ - return self._is_running - - def get_time_limit(self): - """ - Get the limit set in `set_time_limit()` - """ - return self._time_limit - - def get_last_timestamp(self): - """ - Get the last timestamp set at start_timer() - """ - return self._last_timestamp - - def get_remaining_time(self) -> float: - """Gets the timer's remaining time - - Returns: - float: the remaining time - """ - if self._is_running: - return self._remaining_time - (time.time() - self._last_timestamp) - else: - return self._remaining_time - - def stop_timer(self) -> float: - """Pauses the timer - - Raises: - NotRunningException: when the timer isn't running - - Returns: - float: remaining time - """ - if not self._is_running: - raise NotRunningError() - - self._remaining_time = self._remaining_time - (time.time() - self._last_timestamp) - - self._is_running = False - return self._remaining_time - - - def is_locked(self) -> bool: - """Is the time credit expired ? - - Returns: - bool: `True` if expired `False` otherwise - """ - #logger.info(f"time : {self.get_remaining_time()}") - return self.get_remaining_time() <= 0 - - @staticmethod - def get_instance()->"TimeMaster": - if TimeMaster.__instance is None: - TimeMaster.__instance=TimeMaster() - return TimeMaster.__instance - - @classmethod - def register_timer(cls: "TimeMaster", linked_instance: Any, time_limit:float=1e9): - pid = linked_instance.__dict__.get("id",builtins.id(linked_instance)) - cls.get_instance().__time_register[pid]=cls.get_instance().__time_register.get(pid,TimeMaster.Timer(time_limit)) - - @classmethod - def get_timer(cls: "TimeMaster", linked_instance: Any)-> Timer: - return cls.get_instance().__time_register.get(linked_instance.__dict__.get("id",builtins.id(linked_instance))) - - - def __init__(self): - if TimeMaster.__instance is not None: - msg = "Trying to initialize multiple instances of TimeMaster, this is forbidden to avoid side-effects.\n Call TimeMaster.get_instance() instead." - raise NotImplementedError(msg) - else: - self.__time_register={} - -class TimeMixin: - """ - When implemented allows any object to keep track of time - - Example usage: - ``` - import time - class MyTimedObject(TimeMixin): - def __init__(self): - self.myattr = 2 - - x = MyTimedObject() - x.set_time_limit(10) - x.start_timer() - time.sleep(11) - x.myattr=5 # raises SeahorseTimeoutException - - ``` - """ - - def init_timer(self, time_limit: int) -> None: - """ - Initializes the time credit of the instance - - Doesn't start the timer yet ! Call `start_timer()`. - - Args: - time_limit (int): max time before locking all methods of the class - """ - TimeMaster.register_timer(self,time_limit) - - def start_timer(self) -> float: - """Starts the timer - - Raises: - AlreadyRunningException: when trying to start twice. - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).start_timer() - - def is_running(self) -> bool: - """ - Is the timer running ? - - Returns: - bool: `True` if the timer is running, `False` otherwise - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).is_running() - - def get_time_limit(self): - """ - Get the limit set in `set_time_limit()` - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).get_time_limit() - - def get_remaining_time(self) -> float: - """Gets the timer's remaining time - - Returns: - float: the remaining time - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).get_remaining_time() - - def get_last_timestamp(self) -> float: - """Gets the timer's last recorded timestamp at which it was started - - Returns: - float: the timestamp - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).get_last_timestamp() - - def stop_timer(self) -> float: - """Pauses the timer - - Raises: - NotRunningException: when the timer isn't running - - Returns: - float: remaining time - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).stop_timer() - - - def is_locked(self) -> bool: - """Is the time credit expired ? - - Returns: - bool: `True` if expired `False` otherwise - """ - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - return TimeMaster.get_timer(self).is_locked() - - def __setattr__(self, __name: str, value: Any) -> None: - """_summary_ - - Args: - Inherited from object - Raises: - TimeoutException: prevents modification after timout - - """ - try: - if TimeMaster.get_timer(self) and self.is_locked(): - raise SeahorseTimeoutError() - else: - self.__dict__[__name] = value - except Exception as e: - raise e - -def timed_function(fun): - """ - Decorator to prevent using a function after object's timeout. - Args: - fun (_type_): wrapped function - - Raises: - TimerNotInitializedError: _description_ - Exception: _description_ - SeahorseTimeoutError: _description_ - - - Returns: - Callable[...]: wrapper - """ - @functools.wraps(fun) - def wrapper(self, *args, **kwargs): - r = fun(self, *args, **kwargs) - if TimeMaster.get_timer(self) is None: - raise TimerNotInitializedError - elif(self.is_locked()): - raise SeahorseTimeoutError() - return r - return wrapper +# class TimeMaster: +# __instance = None + +# class Timer: +# def __init__(self,time_limit:float=1e9): +# self._time_limit = time_limit +# self._remaining_time = time_limit +# self._last_timestamp = None +# self._is_running = False + +# def start_timer(self) -> float: +# """Starts the timer + +# Raises: +# AlreadyRunningException: when trying to start twice. +# """ +# if self._is_running: +# raise AlreadyRunningError() + +# self._last_timestamp = time.time() + +# self._is_running = True + +# return self._last_timestamp + +# def is_running(self) -> bool: +# """ +# Is the timer running ? + +# Returns: +# bool: `True` if the timer is running, `False` otherwise +# """ +# return self._is_running + +# def get_time_limit(self): +# """ +# Get the limit set in `set_time_limit()` +# """ +# return self._time_limit + +# def get_last_timestamp(self): +# """ +# Get the last timestamp set at start_timer() +# """ +# return self._last_timestamp + +# def get_remaining_time(self) -> float: +# """Gets the timer's remaining time + +# Returns: +# float: the remaining time +# """ +# if self._is_running: +# return self._remaining_time - (time.time() - self._last_timestamp) +# else: +# return self._remaining_time + +# def stop_timer(self) -> float: +# """Pauses the timer + +# Raises: +# NotRunningException: when the timer isn't running + +# Returns: +# float: remaining time +# """ +# if not self._is_running: +# raise NotRunningError() + +# self._remaining_time = self._remaining_time - (time.time() - self._last_timestamp) + +# self._is_running = False +# return self._remaining_time + + +# def is_locked(self) -> bool: +# """Is the time credit expired ? + +# Returns: +# bool: `True` if expired `False` otherwise +# """ +# #logger.info(f"time : {self.get_remaining_time()}") +# return self.get_remaining_time() <= 0 + +# @staticmethod +# def get_instance()->"TimeMaster": +# if TimeMaster.__instance is None: +# TimeMaster.__instance=TimeMaster() +# return TimeMaster.__instance + +# @classmethod +# def register_timer(cls: "TimeMaster", linked_instance: Any, time_limit:float=1e9): +# pid = linked_instance.__dict__.get("id",builtins.id(linked_instance)) +# cls.get_instance().__time_register[pid]=cls.get_instance().__time_register.get(pid,TimeMaster.Timer(time_limit)) + +# @classmethod +# def get_timer(cls: "TimeMaster", linked_instance: Any)-> Timer: +# return cls.get_instance().__time_register.get(linked_instance.__dict__.get("id",builtins.id(linked_instance))) + + +# def __init__(self): +# if TimeMaster.__instance is not None: +# msg = "Trying to initialize multiple instances of TimeMaster, this is forbidden to avoid side-effects.\n Call TimeMaster.get_instance() instead." +# raise NotImplementedError(msg) +# else: +# self.__time_register={} + +# class TimeMixin: +# """ +# When implemented allows any object to keep track of time + +# Example usage: +# ``` +# import time +# class MyTimedObject(TimeMixin): +# def __init__(self): +# self.myattr = 2 + +# x = MyTimedObject() +# x.set_time_limit(10) +# x.start_timer() +# time.sleep(11) +# x.myattr=5 # raises SeahorseTimeoutException + +# ``` +# """ + +# def init_timer(self, time_limit: int) -> None: +# """ +# Initializes the time credit of the instance + +# Doesn't start the timer yet ! Call `start_timer()`. + +# Args: +# time_limit (int): max time before locking all methods of the class +# """ +# TimeMaster.register_timer(self,time_limit) + +# def start_timer(self) -> float: +# """Starts the timer + +# Raises: +# AlreadyRunningException: when trying to start twice. +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).start_timer() + +# def is_running(self) -> bool: +# """ +# Is the timer running ? + +# Returns: +# bool: `True` if the timer is running, `False` otherwise +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).is_running() + +# def get_time_limit(self): +# """ +# Get the limit set in `set_time_limit()` +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).get_time_limit() + +# def get_remaining_time(self) -> float: +# """Gets the timer's remaining time + +# Returns: +# float: the remaining time +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).get_remaining_time() + +# def get_last_timestamp(self) -> float: +# """Gets the timer's last recorded timestamp at which it was started + +# Returns: +# float: the timestamp +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).get_last_timestamp() + +# def stop_timer(self) -> float: +# """Pauses the timer + +# Raises: +# NotRunningException: when the timer isn't running + +# Returns: +# float: remaining time +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).stop_timer() + + +# def is_locked(self) -> bool: +# """Is the time credit expired ? + +# Returns: +# bool: `True` if expired `False` otherwise +# """ +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# return TimeMaster.get_timer(self).is_locked() + +# def __setattr__(self, __name: str, value: Any) -> None: +# """_summary_ + +# Args: +# Inherited from object +# Raises: +# TimeoutException: prevents modification after timout + +# """ +# try: +# if TimeMaster.get_timer(self) and self.is_locked(): +# raise SeahorseTimeoutError() +# else: +# self.__dict__[__name] = value +# except Exception as e: +# raise e + +# def timed_function(fun): +# """ +# Decorator to prevent using a function after object's timeout. +# Args: +# fun (_type_): wrapped function + +# Raises: +# TimerNotInitializedError: _description_ +# Exception: _description_ +# SeahorseTimeoutError: _description_ + + +# Returns: +# Callable[...]: wrapper +# """ +# @functools.wraps(fun) +# def wrapper(self, *args, **kwargs): +# r = fun(self, *args, **kwargs) +# if TimeMaster.get_timer(self) is None: +# raise TimerNotInitializedError +# elif(self.is_locked()): +# raise SeahorseTimeoutError() +# return r +# return wrapper diff --git a/src/seahorse/player/player.py b/src/seahorse/player/player.py index 24284c6..bfe4e30 100644 --- a/src/seahorse/player/player.py +++ b/src/seahorse/player/player.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from seahorse.game.action import Action -from seahorse.game.time_manager import TimeMixin, timed_function +# from seahorse.game.time_manager import TimeMixin, timed_function from seahorse.utils.custom_exceptions import MethodNotImplementedError from seahorse.utils.serializer import Serializable @@ -13,7 +13,7 @@ from seahorse.game.game_state import GameState -class Player(Serializable,TimeMixin): +class Player(Serializable):#,TimeMixin): """ A base class representing a player in the game. @@ -22,7 +22,7 @@ class Player(Serializable,TimeMixin): name (str) : the name of the player """ - def __init__(self, name: str = "bob", time_limit: float = 1e6,*,id:int | None = None,**_) -> None: + def __init__(self, name: str = "bob",*,id:int | None = None,**_) -> None: """ Initializes a new instance of the Player class. @@ -36,11 +36,11 @@ def __init__(self, name: str = "bob", time_limit: float = 1e6,*,id:int | None = self.id = builtins.id(self) else: self.id = id - self.init_timer(time_limit) + # self.init_timer(time_limit) - @timed_function - def play(self, current_state: GameState) -> Action: + # @timed_function + def play(self, current_state: GameState, remaining_time: int) -> Action: """ Implements the player's logic and calls compute_action with minimal information. @@ -54,7 +54,7 @@ def play(self, current_state: GameState) -> Action: Action: The resulting action. """ # TODO: check score ???? - return self.compute_action(current_state=current_state) + return self.compute_action(current_state=current_state, remaining_time=remaining_time) @abstractmethod def compute_action(self, **kwargs) -> Action: diff --git a/src/seahorse/player/proxies.py b/src/seahorse/player/proxies.py index cdd7c3c..3cd1b8c 100644 --- a/src/seahorse/player/proxies.py +++ b/src/seahorse/player/proxies.py @@ -39,7 +39,7 @@ def __init__(self, mimics: type[Player], *args, **kwargs) -> None: self.sid = None @remote_action("turn") - def play(self, *,current_state: GameState) -> Action: + def play(self, *,current_state: GameState, remaining_time: int) -> Action: """ Plays a move. @@ -108,7 +108,7 @@ async def update_id(data): self.wrapped_player.id = json.loads(data)["new_id"] @event_emitting("action") - def play(self, current_state: GameState) -> Action: + def play(self, current_state: GameState, remaining_time: int) -> Action: """ Plays a move. @@ -118,7 +118,7 @@ def play(self, current_state: GameState) -> Action: Returns: Action: The action resulting from the move. """ - return self.compute_action(current_state=current_state) + return self.compute_action(current_state=current_state, remaining_time=remaining_time) def __getattr__(self, attr): return getattr(self.wrapped_player, attr) @@ -130,7 +130,7 @@ def __eq__(self, __value: object) -> bool: return hash(self) == hash(__value) def __str__(self) -> str: - return f"Player {self.wrapped_player.get_name()} has ID {self.wrapped_player.get_id()}." + return f"Player {self.wrapped_player.get_name()} (ID: {self.wrapped_player.get_id()})." def to_json(self) -> dict: return self.wrapped_player.to_json() @@ -152,7 +152,7 @@ def __init__(self, mimics: Player, gui_path:Optional[str]=None, *args, **kwargs) self.shared_sid = None self.sid = None - async def play(self, current_state: GameState) -> Action: + 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: