Skip to content

Commit

Permalink
Merge pull request #30 from corail-research/light_action
Browse files Browse the repository at this point in the history
Light action
  • Loading branch information
YoannSab authored Jun 5, 2024
2 parents cee4d9b + 2d9ce5d commit 3ea0ae2
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 55 deletions.
47 changes: 9 additions & 38 deletions src/seahorse/game/action.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,30 @@
from __future__ import annotations

from abc import abstractmethod
from typing import TYPE_CHECKING

Check failure on line 4 in src/seahorse/game/action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (F401)

src/seahorse/game/action.py:4:20: F401 `typing.TYPE_CHECKING` imported but unused

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__
81 changes: 70 additions & 11 deletions src/seahorse/game/game_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -95,20 +98,35 @@ 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

Check failure on line 115 in src/seahorse/game/game_state.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/game_state.py:115:1: W293 Blank line contains whitespace
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.
"""
# 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:
"""
Expand All @@ -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()

Check failure on line 175 in src/seahorse/game/game_state.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/game_state.py:175:1: W293 Blank line contains whitespace

@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()

Check failure on line 189 in src/seahorse/game/game_state.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/game_state.py:189:1: W293 Blank line contains whitespace
@abstractmethod
def generate_possible_heavy_actions(self) -> set[HeavyAction]:
"""
Generates a set of all possible actions from this game state.
Expand Down
69 changes: 69 additions & 0 deletions src/seahorse/game/heavy_action.py
Original file line number Diff line number Diff line change
@@ -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

Check failure on line 6 in src/seahorse/game/heavy_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (F401)

src/seahorse/game/heavy_action.py:6:39: F401 `seahorse.utils.serializer.Serializable` imported but unused

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:

Check failure on line 50 in src/seahorse/game/heavy_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (ARG002)

src/seahorse/game/heavy_action.py:50:32: ARG002 Unused method argument: `game_state`
"""
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__
53 changes: 53 additions & 0 deletions src/seahorse/game/light_action.py
Original file line number Diff line number Diff line change
@@ -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.

Check failure on line 25 in src/seahorse/game/light_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/light_action.py:25:1: W293 Blank line contains whitespace
"""
self.data = data


Check failure on line 29 in src/seahorse/game/light_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/light_action.py:29:1: W293 Blank line contains whitespace
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.")

Check failure on line 38 in src/seahorse/game/light_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (EM101)

src/seahorse/game/light_action.py:38:30: EM101 Exception must not use a string literal, assign to variable first

Check failure on line 39 in src/seahorse/game/light_action.py

View workflow job for this annotation

GitHub Actions / Linting (3.9)

Ruff (W293)

src/seahorse/game/light_action.py:39:1: W293 Blank line contains whitespace
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__
13 changes: 10 additions & 3 deletions src/seahorse/game/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -87,7 +89,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()
Expand All @@ -105,6 +108,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()

Expand All @@ -120,6 +124,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()),
Expand All @@ -135,6 +140,7 @@ async def play_game(self) -> list[Player]:
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()
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())))

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()}")
Expand Down Expand Up @@ -180,8 +186,9 @@ async def play_game(self) -> list[Player]:

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()))

print(f"Time taken for the whole game : {time.time()-time_start}")

return self.winner

def record_game(self, listeners:Optional[List[EventSlave]]=None) -> None:
Expand Down
1 change: 1 addition & 0 deletions src/seahorse/game/time_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def timed_function(fun):
Exception: _description_
SeahorseTimeoutError: _description_
Returns:
Callable[...]: wrapper
"""
Expand Down
Loading

0 comments on commit 3ea0ae2

Please sign in to comment.