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):
LEFT = auto()
RIGHT = auto()
HIDDEN = auto()
class StepState(Enum):
WAITING_FOR_SETTER = auto()
WAITING_FOR_GUESSER = auto()
WAITING_FOR_CLOSURE = 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
@@ -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:
+24 -2
View File
@@ -12,6 +12,7 @@ MessageHandler = Callable[[NetworkMessage], None]
class NetworkController:
def __init__(self, host: str, port: int, username: str, password: str):
self._handlers: set[MessageHandler] = set()
self._pending_loopbacks: dict[str, NetworkMessage] = {}
self._topic = "mind_reader"
self._client = mqtt.Client(CallbackAPIVersion.VERSION2)
@@ -40,12 +41,28 @@ class NetworkController:
match kind:
case (
NetworkMessageKind.SESSION_PLAYER_MOVED
| NetworkMessageKind.SESSION_SECRET_REVEAL_REQUESTED
| NetworkMessageKind.SESSION_SECRET_REVEALED
| NetworkMessageKind.SESSION_SESSION_FINISHED
| NetworkMessageKind.SESSION_SESSION_STARTED
):
topic = f"{self._topic}/session"
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:
for handler in self._handlers:
handler(network_message)
@@ -54,5 +71,10 @@ class NetworkController:
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)
incoming_message = NetworkMessage.from_json(json_str)
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_STARTED = "session_started"
SESSION_PLAYER_MOVED = "player_moved"
SESSION_SECRET_REVEAL_REQUESTED = "secret_reveal_requested"
SESSION_SECRET_REVEALED = "secret_revealed"
SESSION_SESSION_FINISHED = "session_finished"
# ---- System -----
+21 -1
View File
@@ -1,11 +1,13 @@
from ..domain import ArrowDirection, Player, PlayerRole
from ..state import SessionState
from .controller import NetworkController
from .enums import NetworkMessageKind
from .message import NetworkMessage
class SessionNetworkMessageHandler:
def __init__(self, state: SessionState):
def __init__(self, network: NetworkController, state: SessionState):
self._network = network
self._state = state
def __call__(self, message: NetworkMessage):
@@ -23,5 +25,23 @@ class SessionNetworkMessageHandler:
case PlayerRole.GUESSER:
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:
pass
+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
+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: