Compare commits

2 Commits
7 changed files with 135 additions and 26 deletions
+2
View File
@@ -6,11 +6,13 @@ from typing import Optional
class ArrowDirection(StrEnum): class ArrowDirection(StrEnum):
LEFT = auto() LEFT = auto()
RIGHT = auto() RIGHT = auto()
HIDDEN = auto()
class StepState(Enum): class StepState(Enum):
WAITING_FOR_SETTER = auto() WAITING_FOR_SETTER = auto()
WAITING_FOR_GUESSER = auto() WAITING_FOR_GUESSER = auto()
WAITING_FOR_CLOSURE = auto()
SESSION_FINISHED = auto() SESSION_FINISHED = auto()
+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 from ..network import NetworkController, NetworkMessage, NetworkMessageKind
@@ -13,15 +13,57 @@ class SessionInput:
) )
def player_move(self, direction: ArrowDirection) -> None: def player_move(self, direction: ArrowDirection) -> None:
payload = { match self._local_player.role:
case PlayerRole.SETTER:
local_message = NetworkMessage(
NetworkMessageKind.SESSION_PLAYER_MOVED,
{
"player": { "player": {
"name": self._local_player.name, "name": self._local_player.name,
"role": self._local_player.role, "role": self._local_player.role,
}, },
"arrow_direction": direction, "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( 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: def finish_session(self) -> None:
+24 -2
View File
@@ -12,6 +12,7 @@ MessageHandler = Callable[[NetworkMessage], None]
class NetworkController: class NetworkController:
def __init__(self, host: str, port: int, username: str, password: str): def __init__(self, host: str, port: int, username: str, password: str):
self._handlers: set[MessageHandler] = set() self._handlers: set[MessageHandler] = set()
self._pending_loopbacks: dict[str, NetworkMessage] = {}
self._topic = "mind_reader" self._topic = "mind_reader"
self._client = mqtt.Client(CallbackAPIVersion.VERSION2) self._client = mqtt.Client(CallbackAPIVersion.VERSION2)
@@ -40,12 +41,28 @@ class NetworkController:
match kind: match kind:
case ( case (
NetworkMessageKind.SESSION_PLAYER_MOVED NetworkMessageKind.SESSION_PLAYER_MOVED
| NetworkMessageKind.SESSION_SECRET_REVEAL_REQUESTED
| NetworkMessageKind.SESSION_SECRET_REVEALED
| NetworkMessageKind.SESSION_SESSION_FINISHED | NetworkMessageKind.SESSION_SESSION_FINISHED
| NetworkMessageKind.SESSION_SESSION_STARTED | NetworkMessageKind.SESSION_SESSION_STARTED
): ):
topic = f"{self._topic}/session" topic = f"{self._topic}/session"
self._client.publish(topic, payload) self._client.publish(topic, payload)
def send_with_looback(
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}' vs network_message.id='{network_message.id}'"
)
self._pending_loopbacks[local_message.id] = local_message
self.send(network_message)
def _handle(self, network_message: NetworkMessage) -> None: def _handle(self, network_message: NetworkMessage) -> None:
for handler in self._handlers: for handler in self._handlers:
handler(network_message) handler(network_message)
@@ -54,5 +71,10 @@ class NetworkController:
self, client: mqtt.Client, userdata: None, message: mqtt.MQTTMessage self, client: mqtt.Client, userdata: None, message: mqtt.MQTTMessage
) -> None: ) -> None:
json_str = message.payload.decode("utf-8") json_str = message.payload.decode("utf-8")
network_message = NetworkMessage.from_json(json_str) incoming_message = NetworkMessage.from_json(json_str)
self._handle(network_message)
if incoming_message.id in self._pending_loopbacks:
local_message = self._pending_loopbacks.pop(incoming_message.id)
self._handle(local_message)
else:
self._handle(incoming_message)
+2
View File
@@ -7,6 +7,8 @@ class NetworkMessageKind(StrEnum):
# ---- Session ---- # ---- Session ----
SESSION_SESSION_STARTED = "session_started" SESSION_SESSION_STARTED = "session_started"
SESSION_PLAYER_MOVED = "player_moved" SESSION_PLAYER_MOVED = "player_moved"
SESSION_SECRET_REVEAL_REQUESTED = "secret_reveal_requested"
SESSION_SECRET_REVEALED = "secret_revealed"
SESSION_SESSION_FINISHED = "session_finished" SESSION_SESSION_FINISHED = "session_finished"
# ---- System ----- # ---- System -----
+21 -1
View File
@@ -1,11 +1,13 @@
from ..domain import ArrowDirection, Player, PlayerRole from ..domain import ArrowDirection, Player, PlayerRole
from ..state import SessionState from ..state import SessionState
from .controller import NetworkController
from .enums import NetworkMessageKind from .enums import NetworkMessageKind
from .message import NetworkMessage from .message import NetworkMessage
class SessionNetworkMessageHandler: class SessionNetworkMessageHandler:
def __init__(self, state: SessionState): def __init__(self, network: NetworkController, state: SessionState):
self._network = network
self._state = state self._state = state
def __call__(self, message: NetworkMessage): def __call__(self, message: NetworkMessage):
@@ -23,5 +25,23 @@ class SessionNetworkMessageHandler:
case PlayerRole.GUESSER: case PlayerRole.GUESSER:
self._state.guesser_move(direction) self._state.guesser_move(direction)
case NetworkMessageKind.SESSION_SECRET_REVEAL_REQUESTED:
self._network.send(
NetworkMessage(
NetworkMessageKind.SESSION_SECRET_REVEALED,
{
"player": {
"name": self._state.local_player.name,
"role": self._state.local_player.role,
},
"arrow_direction": self._state.reveal_setter_move(),
},
)
)
case NetworkMessageKind.SESSION_SECRET_REVEALED:
direction = ArrowDirection(message.payload["arrow_direction"])
self._state.step_close(direction)
case NetworkMessageKind.SESSION_SESSION_FINISHED: case NetworkMessageKind.SESSION_SESSION_FINISHED:
pass pass
+24 -7
View File
@@ -32,13 +32,12 @@ class SessionState:
return len(self._guesser_moves) return len(self._guesser_moves)
def _get_setter_card_state(self, index: int) -> CardState: 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 return CardState.SETTER_NOT_SET
elif index == current_index: elif index == completed:
if self._step_state == StepState.WAITING_FOR_GUESSER:
if self._local_player.role == PlayerRole.SETTER: if self._local_player.role == PlayerRole.SETTER:
return CardState.SETTER_REVEALED return CardState.SETTER_REVEALED
else: else:
@@ -47,12 +46,12 @@ class SessionState:
return CardState.SETTER_REVEALED return CardState.SETTER_REVEALED
def _get_guesser_card_state(self, index: int) -> CardState: 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 return CardState.GUESSER_DISABLED
elif index == completed_move_count: elif index == completed:
if self._step_state == StepState.WAITING_FOR_GUESSER: if self._step_state == StepState.WAITING_FOR_GUESSER:
return CardState.GUESSER_WAITING return CardState.GUESSER_WAITING
else: else:
@@ -113,12 +112,21 @@ class SessionState:
return False return False
self._guesser_moves.append(direction) 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: if self._completed_move_count < self._sequence_length:
self._step_state = StepState.WAITING_FOR_SETTER self._step_state = StepState.WAITING_FOR_SETTER
else: else:
self._step_state = StepState.SESSION_FINISHED self._step_state = StepState.SESSION_FINISHED
if self._local_player.role == PlayerRole.GUESSER:
self._setter_moves[-1] = direction
index = len(self._guesser_moves) - 1 index = len(self._guesser_moves) - 1
pub.sendMessage( pub.sendMessage(
@@ -158,6 +166,13 @@ class SessionState:
return True 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: def local_move(self, direction: ArrowDirection) -> bool:
if not self.can_local_move(): if not self.can_local_move():
return False return False
@@ -172,6 +187,8 @@ class SessionState:
return self._local_player.role == PlayerRole.SETTER return self._local_player.role == PlayerRole.SETTER
case StepState.WAITING_FOR_GUESSER: case StepState.WAITING_FOR_GUESSER:
return self._local_player.role == PlayerRole.GUESSER return self._local_player.role == PlayerRole.GUESSER
case StepState.WAITING_FOR_CLOSURE:
return False
case StepState.SESSION_FINISHED: case StepState.SESSION_FINISHED:
return False return False
+5 -1
View File
@@ -29,6 +29,7 @@ class GuesserCardColour:
class ArrowSymbol: class ArrowSymbol:
LEFT: str = "" LEFT: str = ""
RIGHT: str = "" RIGHT: str = ""
HIDDEN: str = "?"
class Card(wx.Panel): class Card(wx.Panel):
@@ -61,7 +62,7 @@ class Card(wx.Panel):
case CardState.SETTER_HIDDEN: case CardState.SETTER_HIDDEN:
colour = SetterCardColour.HIDDEN colour = SetterCardColour.HIDDEN
show_arrow = False show_arrow = True
case CardState.SETTER_REVEALED: case CardState.SETTER_REVEALED:
colour = SetterCardColour.REVEALED colour = SetterCardColour.REVEALED
@@ -91,6 +92,9 @@ class Card(wx.Panel):
case ArrowDirection.RIGHT: case ArrowDirection.RIGHT:
symbol = ArrowSymbol.RIGHT symbol = ArrowSymbol.RIGHT
case ArrowDirection.HIDDEN:
symbol = ArrowSymbol.HIDDEN
self.label.SetLabel(symbol) self.label.SetLabel(symbol)
else: else: