wip: возможность получения загаданной стрелки только после попытки отгадки

This commit is contained in:
2026-08-19 04:06:30 +03:00
parent 0730d8180b
commit 5c7966755f
7 changed files with 125 additions and 15 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()
+53 -11
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,16 +13,58 @@ class SessionInput:
) )
def player_move(self, direction: ArrowDirection) -> None: def player_move(self, direction: ArrowDirection) -> None:
payload = { match self._local_player.role:
"player": { case PlayerRole.SETTER:
"name": self._local_player.name, local_message = NetworkMessage(
"role": self._local_player.role, NetworkMessageKind.SESSION_PLAYER_MOVED,
}, {
"arrow_direction": direction, "player": {
} "name": self._local_player.name,
self._network.send( "role": self._local_player.role,
NetworkMessage(NetworkMessageKind.SESSION_PLAYER_MOVED, payload) },
) "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,
{
"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:
pass pass
+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
+18
View File
@@ -112,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(
@@ -157,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
@@ -171,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: