11 Commits
Author SHA1 Message Date
vladimir 4533789432 feat: переработка NetwrokMessage и EventMapper 2026-08-25 01:50:45 +03:00
vladimir b1132715e1 feat: наследование от BaseEvent и метод from_payload для каждого из классов Event 2026-08-25 01:49:12 +03:00
vladimir 878b83dc1c feat: метод from_dict для класса Player 2026-08-25 01:48:02 +03:00
vladimir 28030412d0 feat: базовый класс BaseEvent с универсальным методом to_payload для гарантированного преобразования всех данных в строки 2026-08-25 01:46:58 +03:00
vladimir 6017c2d757 fix: переработка методов NetworkController 2026-08-24 01:43:03 +03:00
vladimir 53be71316c fix: удаление ненужных методов 2026-08-23 21:06:02 +03:00
vladimir 473b03c1bb fix: корректное определение текущего индекса в методах возврата состояния карт 2026-08-23 21:06:02 +03:00
vladimir a07d94bcc4 fix: корректное определение текущего индекса 2026-08-23 21:05:51 +03:00
vladimir 9a4c98fb76 wip: без комментариев 2026-08-23 01:49:02 +03:00
vladimir 5c7966755f wip: возможность получения загаданной стрелки только после попытки отгадки 2026-08-19 04:06:30 +03:00
vladimir 0730d8180b fix: исправлен порядок открытия карт у загадывающего 2026-08-19 01:39:39 +03:00
19 changed files with 645 additions and 118 deletions
+14
View File
@@ -4,7 +4,14 @@ from .session import (
ArrowDirection,
CardData,
CardState,
GuesserMoveEvent,
NextStepRequestedEvent,
PlayerHelloEvent,
RevealSecretEvent,
SessionEvent,
SessionPhase,
SessionStats,
SetterMoveEvent,
StepData,
StepState,
)
@@ -16,7 +23,14 @@ __all__ = [
"ArrowDirection",
"CardData",
"CardState",
"GuesserMoveEvent",
"NextStepRequestedEvent",
"PlayerHelloEvent",
"RevealSecretEvent",
"SessionEvent",
"SessionPhase",
"SessionStats",
"SetterMoveEvent",
"StepData",
"StepState",
]
+1 -1
View File
@@ -7,7 +7,7 @@ class PlayerRole(StrEnum):
GUESSER = auto()
@dataclass
@dataclass(frozen=True)
class Player:
name: str
role: PlayerRole
+51
View File
@@ -2,20 +2,34 @@ from dataclasses import dataclass
from enum import Enum, StrEnum, auto
from typing import Optional
from .common import Player
class ArrowDirection(StrEnum):
LEFT = auto()
RIGHT = auto()
HIDDEN = auto()
class StepState(Enum):
WAITING_FOR_SETTER = auto()
WAITING_FOR_GUESSER = auto()
WAITING_FOR_CLOSURE = auto()
SESSION_FINISHED = auto()
class SessionPhase(Enum):
INITIALIZING = auto()
WAITING_FOR_SETTER = auto()
WAITING_FOR_GUESSER = auto()
WAITING_FOR_REVEAL = auto()
CHECKOUT = auto()
FINISHED = auto()
class CardState(Enum):
SETTER_NOT_SET = auto()
SETTER_WAITING = auto()
SETTER_HIDDEN = auto()
SETTER_REVEALED = auto()
GUESSER_DISABLED = auto()
@@ -52,3 +66,40 @@ class SessionStats:
accuracy = 0.0
accuracy = (self.correct / self.total) * 100
return round(accuracy, 1)
@dataclass(frozen=True)
class PlayerHelloEvent:
player: Player
@dataclass(frozen=True)
class SetterMoveEvent:
player: Player
direction: ArrowDirection
@dataclass(frozen=True)
class GuesserMoveEvent:
player: Player
direction: ArrowDirection
@dataclass(frozen=True)
class RevealSecretEvent:
player: Player
direction: ArrowDirection
@dataclass(frozen=True)
class NextStepRequestedEvent:
player: Player
SessionEvent = (
PlayerHelloEvent
| SetterMoveEvent
| GuesserMoveEvent
| RevealSecretEvent
| NextStepRequestedEvent
)
+46 -4
View File
@@ -1,4 +1,4 @@
from ..domain import ArrowDirection, Player
from ..domain import ArrowDirection, Player, PlayerRole
from ..network import NetworkController, NetworkMessage, NetworkMessageKind
@@ -13,15 +13,57 @@ class SessionInput:
)
def player_move(self, direction: ArrowDirection) -> None:
payload = {
match self._local_player.role:
case PlayerRole.SETTER:
local_message = NetworkMessage(
NetworkMessageKind.SESSION_PLAYER_MOVED,
{
"player": {
"name": self._local_player.name,
"role": self._local_player.role,
},
"arrow_direction": direction,
}
},
)
network_message = NetworkMessage(
NetworkMessageKind.SESSION_PLAYER_MOVED,
{
"player": {
"name": self._local_player.name,
"role": self._local_player.role,
},
"arrow_direction": ArrowDirection.HIDDEN,
},
local_message.id,
)
self._network.send_with_looback(local_message, network_message)
case PlayerRole.GUESSER:
self._network.send(
NetworkMessage(NetworkMessageKind.SESSION_PLAYER_MOVED, payload)
NetworkMessage(
NetworkMessageKind.SESSION_PLAYER_MOVED,
{
"player": {
"name": self._local_player.name,
"role": self._local_player.role,
},
"arrow_direction": direction,
},
)
)
self._network.send(
NetworkMessage(
NetworkMessageKind.SESSION_SECRET_REVEAL_REQUESTED,
{
"player": {
"name": self._local_player.name,
"role": self._local_player.role,
},
},
)
)
def finish_session(self) -> None:
+10 -10
View File
@@ -1,11 +1,11 @@
from .controller import NetworkController
from .enums import NetworkMessageKind
from .handlers import SessionNetworkMessageHandler
from .message import NetworkMessage
# from .controller import NetworkController
# from .enums import NetworkMessageKind
# from .handlers import SessionNetworkMessageHandler
# from .message import NetworkMessage
__all__ = [
"NetworkController",
"NetworkMessageKind",
"SessionNetworkMessageHandler",
"NetworkMessage",
]
# __all__ = [
# "NetworkController",
# "NetworkMessageKind",
# "SessionNetworkMessageHandler",
# "NetworkMessage",
# ]
+84 -32
View File
@@ -1,24 +1,48 @@
from typing import Callable
import logging
from typing import Optional
import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion
from .enums import NetworkMessageKind
from .message import NetworkMessage
from ..state_machines.session.events import SessionEvent
from ..state_machines.session.machine import SessionStateMachine
from .message import EventMapper, NetworkMessage, SessionMessageKind
MessageHandler = Callable[[NetworkMessage], None]
logger = logging.getLogger(__name__)
class NetworkController:
def __init__(self, host: str, port: int, username: str, password: str):
self._handlers: set[MessageHandler] = set()
def __init__(
self,
host: str,
port: int,
username: str,
password: str,
lobby,
session: SessionStateMachine,
):
self._lobby_topic = "mind_reader/lobby"
self._session_topic = "mind_reader/session"
self._lobby = lobby
self._session = session
self._pending_loopbacks: dict[str, NetworkMessage] = {}
self._topic = "mind_reader"
self._client = mqtt.Client(CallbackAPIVersion.VERSION2)
self._client.username_pw_set(username, password)
self._client.on_message = self._on_message
self._client.message_callback_add(
self._lobby_topic, self._on_lobby_message
)
self._client.message_callback_add(
self._session_topic, self._on_session_message
)
self._client.connect(host, port)
self._client.subscribe("mind_reader/#")
self._client.subscribe(self._lobby_topic)
self._client.subscribe(self._session_topic)
def start(self) -> None:
self._client.loop_start()
@@ -27,32 +51,60 @@ class NetworkController:
self._client.loop_stop()
self._client.disconnect()
def add_handler(self, handler: MessageHandler) -> None:
self._handlers.add(handler)
def remove_handler(self, handler: MessageHandler) -> None:
self._handlers.remove(handler)
def send(self, network_message: NetworkMessage) -> None:
kind = network_message.kind
payload = network_message.to_json()
match network_message.kind:
case SessionMessageKind():
self._client.publish(
self._session_topic, network_message.to_json()
)
match kind:
case (
NetworkMessageKind.SESSION_PLAYER_MOVED
| NetworkMessageKind.SESSION_SESSION_FINISHED
| NetworkMessageKind.SESSION_SESSION_STARTED
):
topic = f"{self._topic}/session"
self._client.publish(topic, payload)
def send_with_loopback(
self,
local_message: NetworkMessage,
network_message: NetworkMessage,
) -> None:
if local_message.id != network_message.id:
raise ValueError(
f"Mismatched message IDs in send_with_loopback: "
f"local_message.id='{local_message.id}'"
f"vs network_message.id='{network_message.id}'"
)
def _handle(self, network_message: NetworkMessage) -> None:
for handler in self._handlers:
handler(network_message)
self._pending_loopbacks[local_message.id] = local_message
self.send(network_message)
def _on_message(
def _pop_loopback(self, message_id: str) -> Optional[NetworkMessage]:
return self._pending_loopbacks.pop(message_id, None)
def _parse_incoming_message(
self, message: mqtt.MQTTMessage
) -> Optional[NetworkMessage]:
try:
json_str = message.payload.decode()
network_message = NetworkMessage.from_json(json_str)
except Exception as err:
logger.error("Failed to parse incoming MQTT message: %s", err)
return None
loopback_message = self._pop_loopback(network_message.id)
return loopback_message or network_message
def _on_lobby_message(
self, client: mqtt.Client, userdata: None, message: mqtt.MQTTMessage
) -> None:
json_str = message.payload.decode("utf-8")
network_message = NetworkMessage.from_json(json_str)
self._handle(network_message)
network_message = self._parse_incoming_message(message)
if network_message is None:
return
event = EventMapper.from_message(network_message)
# if isinstance(event, LobbyEvent):
# self._lobby.process(event)
def _on_session_message(
self, client: mqtt.Client, userdata: None, message: mqtt.MQTTMessage
) -> None:
network_message = self._parse_incoming_message(message)
if network_message is None:
return
event = EventMapper.from_message(network_message)
if isinstance(event, SessionEvent):
self._session.process(event)
-13
View File
@@ -1,13 +0,0 @@
from enum import StrEnum
class NetworkMessageKind(StrEnum):
# ----- Lobby -----
# ---- Session ----
SESSION_SESSION_STARTED = "session_started"
SESSION_PLAYER_MOVED = "player_moved"
SESSION_SESSION_FINISHED = "session_finished"
# ---- System -----
SYSTEM_INC_MSG_PA_ERR = "incoming_message_parsing_error"
-27
View File
@@ -1,27 +0,0 @@
from ..domain import ArrowDirection, Player, PlayerRole
from ..state import SessionState
from .enums import NetworkMessageKind
from .message import NetworkMessage
class SessionNetworkMessageHandler:
def __init__(self, state: SessionState):
self._state = state
def __call__(self, message: NetworkMessage):
match message.kind:
case NetworkMessageKind.SESSION_SESSION_STARTED:
self._state.start_session()
case NetworkMessageKind.SESSION_PLAYER_MOVED:
player_role = PlayerRole(message.payload["player"]["role"])
direction = ArrowDirection(message.payload["arrow_direction"])
match player_role:
case PlayerRole.SETTER:
self._state.setter_move(direction)
case PlayerRole.GUESSER:
self._state.guesser_move(direction)
case NetworkMessageKind.SESSION_SESSION_FINISHED:
pass
+86 -12
View File
@@ -1,34 +1,108 @@
import json
import logging
from dataclasses import asdict, dataclass, field
from enum import StrEnum, auto
from typing import Any, Optional
from uuid_extensions import uuid7str
from .enums import NetworkMessageKind
from ..state_machines.lobby.domain import Player, PlayerRole
from ..state_machines.session.domain import ArrowDirection
from ..state_machines.session.events import (
GuesserMoveEvent,
NextStepRequestedEvent,
PlayerHelloEvent,
RevealSecretEvent,
SessionEvent,
SetterMoveEvent,
)
logger = logging.getLogger(__name__)
class SessionMessageKind(StrEnum):
PLAYER_HELLO = auto()
SETTER_MOVED = auto()
GUESSER_MOVED = auto()
SECRET_REVEALED = auto()
NEXT_STEP_REQUESTED = auto()
NetworkMessageKind = SessionMessageKind
Event = SessionEvent
EVENT_TO_KIND_MAP: dict[type[Event], NetworkMessageKind] = {
PlayerHelloEvent: SessionMessageKind.PLAYER_HELLO,
SetterMoveEvent: SessionMessageKind.SETTER_MOVED,
GuesserMoveEvent: SessionMessageKind.GUESSER_MOVED,
RevealSecretEvent: SessionMessageKind.SECRET_REVEALED,
NextStepRequestedEvent: SessionMessageKind.NEXT_STEP_REQUESTED,
}
KIND_TO_EVENT_MAP = {
kind: event_cls for event_cls, kind in EVENT_TO_KIND_MAP.items()
}
def serializer(obj: Any) -> Any:
if isinstance(obj, StrEnum):
return obj.value
raise TypeError(
f"Object of type {type(obj).__name__} is not JSON serializable"
)
@dataclass(frozen=True)
class NetworkMessage:
kind: NetworkMessageKind
payload: dict[str, Any]
payload: dict[str, Any] = field(default_factory=dict)
id: str = field(default_factory=uuid7str)
def to_json(self) -> str:
data = asdict(self)
data["kind"] = self.kind.value
return json.dumps(data)
@classmethod
def from_json(cls, json_str: str) -> "NetworkMessage":
try:
data = json.loads(json_str)
return cls(
id=data["id"],
kind=NetworkMessageKind(data["kind"]),
payload=data.get("payload", {}),
id=data["id"],
)
except (json.JSONDecodeError, KeyError, ValueError) as e:
return cls(
kind=NetworkMessageKind.SYSTEM_INC_MSG_PA_ERR,
payload={"exception": e},
except (KeyError, ValueError, json.JSONDecodeError) as err:
logger.error(
"Failed to deserialize NetworkMessage from JSON: %s", err
)
raise
def to_json(self) -> str:
data = {"kind": self.kind.value, "payload": self.payload, "id": self.id}
return json.dumps(data, ensure_ascii=False, default=serializer)
class EventMapper:
@staticmethod
def to_message(event: Event) -> NetworkMessage:
event_type = type(event)
kind = EVENT_TO_KIND_MAP.get(event_type)
if not kind:
logger.error(
"Attempted to serialize unregistered event: %s", event_type
)
raise ValueError(
f"Event {event_type} is not registered in EVENT_TO_KIND_MAP"
)
return NetworkMessage(kind=kind, payload=event.to_payload())
@staticmethod
def from_message(message: NetworkMessage) -> Event | None:
event_cls = KIND_TO_EVENT_MAP.get(message.kind)
if not event_cls:
return None
try:
return event_cls.from_payload(message.payload)
except (KeyError, ValueError, TypeError):
return None
+24 -7
View File
@@ -32,13 +32,12 @@ class SessionState:
return len(self._guesser_moves)
def _get_setter_card_state(self, index: int) -> CardState:
current_index = len(self._setter_moves)
completed = self._completed_move_count
if index > current_index:
if index > completed:
return CardState.SETTER_NOT_SET
elif index == current_index:
if self._step_state == StepState.WAITING_FOR_GUESSER:
elif index == completed:
if self._local_player.role == PlayerRole.SETTER:
return CardState.SETTER_REVEALED
else:
@@ -47,12 +46,12 @@ class SessionState:
return CardState.SETTER_REVEALED
def _get_guesser_card_state(self, index: int) -> CardState:
completed_move_count = self._completed_move_count
completed = self._completed_move_count
if index > completed_move_count:
if index > completed:
return CardState.GUESSER_DISABLED
elif index == completed_move_count:
elif index == completed:
if self._step_state == StepState.WAITING_FOR_GUESSER:
return CardState.GUESSER_WAITING
else:
@@ -113,12 +112,21 @@ class SessionState:
return False
self._guesser_moves.append(direction)
self._step_state = StepState.WAITING_FOR_CLOSURE
return True
def step_close(self, direction: ArrowDirection) -> bool:
if self._step_state != StepState.WAITING_FOR_CLOSURE:
return False
if self._completed_move_count < self._sequence_length:
self._step_state = StepState.WAITING_FOR_SETTER
else:
self._step_state = StepState.SESSION_FINISHED
if self._local_player.role == PlayerRole.GUESSER:
self._setter_moves[-1] = direction
index = len(self._guesser_moves) - 1
pub.sendMessage(
@@ -158,6 +166,13 @@ class SessionState:
return True
def reveal_setter_move(self) -> ArrowDirection:
if self._local_player.role == PlayerRole.SETTER:
if self._step_state == StepState.WAITING_FOR_CLOSURE:
return self._setter_moves[-1]
return ArrowDirection.HIDDEN
def local_move(self, direction: ArrowDirection) -> bool:
if not self.can_local_move():
return False
@@ -172,6 +187,8 @@ class SessionState:
return self._local_player.role == PlayerRole.SETTER
case StepState.WAITING_FOR_GUESSER:
return self._local_player.role == PlayerRole.GUESSER
case StepState.WAITING_FOR_CLOSURE:
return False
case StepState.SESSION_FINISHED:
return False
@@ -0,0 +1,22 @@
from dataclasses import asdict, dataclass, is_dataclass
from enum import StrEnum
from typing import Any
def _factory(d: list[tuple[str, Any]]) -> dict[str, Any]:
def _convert(obj: Any) -> Any:
if isinstance(obj, StrEnum):
return obj.value
if isinstance(obj, list | tuple):
return [_convert(item) for item in obj]
if isinstance(obj, dict):
return {k: _convert(v) for k, v in obj.items()}
return obj
return {k: _convert(v) for k, v in d}
@dataclass(frozen=True)
class BaseEvent:
def to_payload(self) -> dict[str, Any]:
return asdict(self, dict_factory=_factory)
@@ -0,0 +1,19 @@
from dataclasses import dataclass
from enum import StrEnum, auto
class PlayerRole(StrEnum):
SETTER = auto()
GUESSER = auto()
@dataclass(frozen=True)
class Player:
name: str
role: PlayerRole
@classmethod
def from_dict(cls, d: dict) -> "Player":
return cls(
name=d["player"]["name"], role=PlayerRole(d["player"]["role"])
)
@@ -0,0 +1,27 @@
from enum import Enum, StrEnum, auto
class ArrowDirection(StrEnum):
LEFT = auto()
RIGHT = auto()
HIDDEN = auto()
class SessionPhase(Enum):
INITIALIZING = auto()
WAITING_FOR_SETTER = auto()
WAITING_FOR_GUESSER = auto()
WAITING_FOR_REVEAL = auto()
CHECKOUT = auto()
FINISHED = auto()
class CardState(Enum):
SETTER_NOT_SET = auto()
SETTER_WAITING = auto()
SETTER_HIDDEN = auto()
SETTER_REVEALED = auto()
GUESSER_DISABLED = auto()
GUESSER_WAITING = auto()
GUESSER_CORRECT = auto()
GUESSER_WRONG = auto()
@@ -0,0 +1,71 @@
from dataclasses import dataclass
from ..base_event import BaseEvent
from ..lobby.domain import Player
from ..session.domain import ArrowDirection
@dataclass(frozen=True)
class PlayerHelloEvent(BaseEvent):
player: Player
@classmethod
def from_payload(cls, p: dict) -> "PlayerHelloEvent":
return cls(player=Player.from_dict(p))
@dataclass(frozen=True)
class SetterMoveEvent(BaseEvent):
player: Player
direction: ArrowDirection
@classmethod
def from_payload(cls, p: dict) -> "SetterMoveEvent":
return cls(
player=Player.from_dict(p),
direction=ArrowDirection(p["arrow_direction"]),
)
@dataclass(frozen=True)
class GuesserMoveEvent(BaseEvent):
player: Player
direction: ArrowDirection
@classmethod
def from_payload(cls, p: dict) -> "GuesserMoveEvent":
return cls(
player=Player.from_dict(p),
direction=ArrowDirection(p["arrow_direction"]),
)
@dataclass(frozen=True)
class RevealSecretEvent(BaseEvent):
player: Player
direction: ArrowDirection
@classmethod
def from_payload(cls, p: dict) -> "RevealSecretEvent":
return cls(
player=Player.from_dict(p),
direction=ArrowDirection(p["arrow_direction"]),
)
@dataclass(frozen=True)
class NextStepRequestedEvent(BaseEvent):
player: Player
@classmethod
def from_payload(cls, p: dict) -> "NextStepRequestedEvent":
return cls(player=Player.from_dict(p))
SessionEvent = (
PlayerHelloEvent
| SetterMoveEvent
| GuesserMoveEvent
| RevealSecretEvent
| NextStepRequestedEvent
)
@@ -0,0 +1,174 @@
from ..lobby.domain import Player, PlayerRole
from ..session.domain import ArrowDirection, CardState, SessionPhase
from ..session.events import (
GuesserMoveEvent,
NextStepRequestedEvent,
PlayerHelloEvent,
RevealSecretEvent,
SessionEvent,
SetterMoveEvent,
)
class SessionStateMachine:
def __init__(
self, setter: Player, guessers: set[Player], sequence_length: int = 20
):
self._setter = setter
self._guessers = guessers
self._pending_players: set[Player] = set(guessers)
self._pending_players.add(setter)
self._sequence_length = sequence_length
self._setter_moves: list[ArrowDirection] = []
self._guessers_moves: dict[Player, list[ArrowDirection]] = {
player: [] for player in guessers
}
self._phase: SessionPhase = SessionPhase.INITIALIZING
def process(self, event: SessionEvent) -> bool:
match (self._phase, event):
case (SessionPhase.INITIALIZING, PlayerHelloEvent(player)):
self._pending_players.remove(player)
if not self._pending_players:
self._phase = SessionPhase.WAITING_FOR_SETTER
self._pending_players = {self._setter}
return True
case (
SessionPhase.WAITING_FOR_SETTER,
SetterMoveEvent(player, direction),
):
self._pending_players.remove(player)
if not self._pending_players:
self._setter_moves.append(direction)
self._phase = SessionPhase.WAITING_FOR_GUESSER
self._pending_players = set(self._guessers)
return True
case (
SessionPhase.WAITING_FOR_GUESSER,
GuesserMoveEvent(player, direction),
):
self._guessers_moves[player].append(direction)
self._pending_players.remove(player)
if not self._pending_players:
self._phase = SessionPhase.WAITING_FOR_REVEAL
self._pending_players = {self._setter}
return True
case (
SessionPhase.WAITING_FOR_REVEAL,
RevealSecretEvent(player, direction),
):
self._pending_players.remove(player)
if not self._pending_players:
self._phase = SessionPhase.CHECKOUT
setter_move_index = len(self._setter_moves) - 1
self._setter_moves[setter_move_index] = direction
self._pending_players = set(self._guessers)
return True
case (SessionPhase.CHECKOUT, NextStepRequestedEvent(player)):
self._pending_players.remove(player)
if not self._pending_players:
if len(self._setter_moves) >= self._sequence_length:
self._phase = SessionPhase.FINISHED
self._pending_players.clear()
else:
self._phase = SessionPhase.WAITING_FOR_SETTER
self._pending_players = {self._setter}
return True
case _:
return False
def get_card_state(self, player: Player, index: int) -> CardState:
match player.role:
case PlayerRole.SETTER:
return self._get_setter_card_state(index)
case PlayerRole.GUESSER:
return self._get_guesser_card_state(player, index)
def _get_setter_card_state(self, index: int) -> CardState:
current_index = self.current_index
if index > current_index:
return CardState.SETTER_NOT_SET
elif index == current_index:
match self._phase:
case SessionPhase.INITIALIZING:
return CardState.SETTER_NOT_SET
case SessionPhase.WAITING_FOR_SETTER:
return CardState.SETTER_WAITING
case (
SessionPhase.WAITING_FOR_GUESSER
| SessionPhase.WAITING_FOR_REVEAL
):
if self._setter_moves[-1] == ArrowDirection.HIDDEN:
return CardState.SETTER_HIDDEN
else:
return CardState.SETTER_REVEALED
case SessionPhase.CHECKOUT | SessionPhase.FINISHED:
return CardState.SETTER_REVEALED
else:
return CardState.SETTER_REVEALED
def _get_guesser_card_state(self, player: Player, index: int) -> CardState:
def _check_direction(
self: SessionStateMachine, player: Player, index: int
) -> CardState:
if self._setter_moves[index] == self._guessers_moves[player][index]:
return CardState.GUESSER_CORRECT
else:
return CardState.GUESSER_WRONG
current_index = self.current_index
if index > current_index:
return CardState.GUESSER_DISABLED
elif index == current_index:
match self._phase:
case (
SessionPhase.INITIALIZING | SessionPhase.WAITING_FOR_SETTER
):
return CardState.GUESSER_DISABLED
case (
SessionPhase.WAITING_FOR_GUESSER
| SessionPhase.WAITING_FOR_REVEAL
):
return CardState.GUESSER_WAITING
case SessionPhase.CHECKOUT | SessionPhase.FINISHED:
return _check_direction(self, player, index)
else:
return _check_direction(self, player, index)
def get_setter_move(self, index: int) -> ArrowDirection | None:
try:
return self._setter_moves[index]
except IndexError:
return None
def get_guesser_move(
self, player: Player, index: int
) -> ArrowDirection | None:
try:
return self._guessers_moves[player][index]
except IndexError:
return None
@property
def current_index(self) -> int:
setter_step_number = len(self._setter_moves)
if setter_step_number:
if self._phase == SessionPhase.WAITING_FOR_SETTER:
return setter_step_number
else:
return setter_step_number - 1
return 0
+5 -1
View File
@@ -29,6 +29,7 @@ class GuesserCardColour:
class ArrowSymbol:
LEFT: str = ""
RIGHT: str = ""
HIDDEN: str = "?"
class Card(wx.Panel):
@@ -61,7 +62,7 @@ class Card(wx.Panel):
case CardState.SETTER_HIDDEN:
colour = SetterCardColour.HIDDEN
show_arrow = False
show_arrow = True
case CardState.SETTER_REVEALED:
colour = SetterCardColour.REVEALED
@@ -91,6 +92,9 @@ class Card(wx.Panel):
case ArrowDirection.RIGHT:
symbol = ArrowSymbol.RIGHT
case ArrowDirection.HIDDEN:
symbol = ArrowSymbol.HIDDEN
self.label.SetLabel(symbol)
else: