From 6a66a020710684034b3b03a440383eb795eb6a30 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 12 Aug 2026 04:30:17 +0300 Subject: [PATCH] feat: wip SessionState, SessionView --- src/mind_reader/ui/views/session_view.py | 262 ++++++++++++++++++++--- 1 file changed, 238 insertions(+), 24 deletions(-) diff --git a/src/mind_reader/ui/views/session_view.py b/src/mind_reader/ui/views/session_view.py index 43c5c20..80ef40f 100644 --- a/src/mind_reader/ui/views/session_view.py +++ b/src/mind_reader/ui/views/session_view.py @@ -1,11 +1,10 @@ import sys from dataclasses import dataclass from enum import Enum, auto -from typing import Optional +from typing import Callable, Optional import wx - -from mind_reader.core import SessionState +from wx.lib.pubsub import pub def enable_high_dpi_support(): @@ -56,6 +55,172 @@ class SessionStats: return round(accuracy, 1) +class StepState(Enum): + WAITING_FOR_SETTER = auto() + WAITING_FOR_GUESSER = auto() + SESSION_FINISHED = auto() + + +class PlayerRole(Enum): + SETTER = auto() + GUESSER = auto() + + +@dataclass(frozen=True) +class MoveResult: + step_index: int + setter_direction: Optional[ArrowDirection] = None + setter_card_state: Optional[CardState] = None + guesser_direction: Optional[ArrowDirection] = None + guesser_card_state: Optional[CardState] = None + + +@dataclass +class Player: + name: str + role: PlayerRole + + +class SessionState: + def __init__( + self, + local_player: Player, + remote_player: Player, + sequence_length: int = 20, + ): + self._sequence_length = sequence_length + + self._local_player = local_player + self._remote_player = remote_player + + self._completed_step_count: int = 0 + self._step_state: StepState = StepState.WAITING_FOR_SETTER + + self._setter_moves: list[ArrowDirection] = [] + self._guesser_moves: list[ArrowDirection] = [] + + self._stats: Optional[SessionStats] = None + + def setter_move(self, direction: ArrowDirection) -> bool: + if self._step_state != StepState.WAITING_FOR_SETTER: + return False + + self._setter_moves.append(direction) + self._step_state = StepState.WAITING_FOR_GUESSER + + step_index = len(self._setter_moves) - 1 + result = MoveResult( + step_index=step_index, + setter_direction=direction, + setter_card_state=self.get_setter_card_state(step_index), + ) + + pub.sendMessage("session.setter_moved", result=result) + + pub.sendMessage( + "session.step_state_changed", + step_state=self._step_state, + ) + + return True + + def guesser_move(self, direction: ArrowDirection) -> bool: + if self._step_state != StepState.WAITING_FOR_GUESSER: + return False + + self._guesser_moves.append(direction) + self._completed_step_count += 1 + + step_index = len(self._guesser_moves) - 1 + + result = MoveResult( + step_index=step_index, + setter_direction=self._setter_moves[-1], + setter_card_state=self.get_setter_card_state(step_index), + guesser_direction=self._guesser_moves[-1], + guesser_card_state=self.get_guesser_card_state(step_index), + ) + + pub.sendMessage("session.guesser_moved", result=result) + + if self._completed_step_count < self._sequence_length: + self._step_state = StepState.WAITING_FOR_SETTER + pub.sendMessage( + "session.step_state_changed", + step_state=self._step_state, + ) + else: + self._step_state = StepState.SESSION_FINISHED + pub.sendMessage("session.session_finished") + + return True + + def local_move(self, direction: ArrowDirection) -> bool: + if not self.can_local_move(): + return False + if self._local_player.role == PlayerRole.SETTER: + return self.setter_move(direction) + else: + return self.guesser_move(direction) + + def can_local_move(self) -> bool: + match self._step_state: + case StepState.WAITING_FOR_SETTER: + return self._local_player.role == PlayerRole.SETTER + case StepState.WAITING_FOR_GUESSER: + return self._local_player.role == PlayerRole.GUESSER + case StepState.SESSION_FINISHED: + return False + + def get_setter_card_state(self, step_index: int) -> CardState: + setter_step_index = len(self._setter_moves) + 1 + + if step_index > setter_step_index: + return CardState.SETTER_NOT_SET + + if step_index == setter_step_index: + if self._step_state == StepState.WAITING_FOR_GUESSER: + if self._local_player.role == PlayerRole.SETTER: + return CardState.SETTER_REVEALED + else: + return CardState.SETTER_HIDDEN + + return CardState.SETTER_REVEALED + + def get_guesser_card_state(self, step_index: int) -> CardState: + if step_index > self._completed_step_count: + return CardState.GUESSER_DISABLED + + elif step_index == self._completed_step_count: + return CardState.GUESSER_WAITING + + else: + guesser_move = self._guesser_moves[step_index] + setter_move = self._setter_moves[step_index] + if guesser_move == setter_move: + return CardState.GUESSER_CORRECT + else: + return CardState.GUESSER_WRONG + + @property + def setter_name(self) -> str: + if self._local_player.role == PlayerRole.SETTER: + return self._local_player.name + else: + return self._remote_player.name + + @property + def guesser_name(self) -> str: + if self._local_player.role == PlayerRole.GUESSER: + return self._local_player.name + else: + return self._remote_player.name + + @property + def sequence_lenght(self) -> int: + return self._sequence_length + + @dataclass(frozen=True) class SetterCardColour: NOT_SET = wx.Colour(82, 97, 107) @@ -158,17 +323,15 @@ class Card(wx.Panel): class SessionPanel(wx.Panel): - def __init__( - self, - parent, - setter_name: str, - guesser_name: str, - sequence_length: int = 20, - ): + def __init__(self, parent, state: SessionState): super().__init__(parent) - self._setter_name = setter_name - self._guesser_name = guesser_name - self._sequence_length = sequence_length + + self._state = state + + self._setter_name = self._state.setter_name + self._guesser_name = self._state.guesser_name + self._sequence_length = self._state.sequence_lenght + self._setter_sequence: list[Card] = [] self._guesser_sequence: list[Card] = [] @@ -177,6 +340,11 @@ class SessionPanel(wx.Panel): self._setup() self._fit_parent_frame() + pub.subscribe(self._on_setter_moved, "session.setter_moved") + pub.subscribe(self._on_guesser_moved, "session.guesser_moved") + pub.subscribe(self._on_step_state_changed, "session.step_state_changed") + pub.subscribe(self._on_session_finished, "session.session_finished") + def _setup(self) -> None: main_sizer = wx.BoxSizer(wx.VERTICAL) @@ -194,7 +362,10 @@ class SessionPanel(wx.Panel): initial_card_state=CardState.GUESSER_DISABLED, ) - controls_sizer = self._create_controls_sizer() + controls_sizer = self._create_controls_sizer( + on_left=self._on_left_click, + on_right=self._on_right_click, + ) secondary_sizer = wx.BoxSizer(wx.VERTICAL) secondary_sizer.Add(setter_sizer, 0, wx.EXPAND) @@ -228,7 +399,11 @@ class SessionPanel(wx.Panel): return section_sizer - def _create_controls_sizer(self) -> wx.Sizer: + def _create_controls_sizer( + self, + on_left: Callable[[wx.CommandEvent], None], + on_right: Callable[[wx.CommandEvent], None], + ) -> wx.Sizer: left = wx.Button(self, label=ArrowSymbol.LEFT, size=wx.Size(145, 55)) right = wx.Button(self, label=ArrowSymbol.RIGHT, size=wx.Size(145, 55)) @@ -238,6 +413,9 @@ class SessionPanel(wx.Panel): left.SetFont(font) right.SetFont(font) + left.Bind(wx.EVT_BUTTON, on_left) + right.Bind(wx.EVT_BUTTON, on_right) + controls_sizer = wx.BoxSizer(wx.HORIZONTAL) controls_sizer.Add(left, 0, wx.RIGHT, 10) controls_sizer.Add(right, 0, wx.LEFT, 10) @@ -276,6 +454,41 @@ class SessionPanel(wx.Panel): top_frame.SetMinSize(min_size) top_frame.Fit() + def _on_left_click(self, event: wx.CommandEvent) -> None: + self._handle_arrow_input(ArrowDirection.LEFT) + + def _on_right_click(self, event: wx.CommandEvent) -> None: + self._handle_arrow_input(ArrowDirection.RIGHT) + + def _handle_arrow_input(self, direction: ArrowDirection) -> None: + pass + + def _on_setter_moved(self, result: MoveResult): + self.update_setter_card( + index=result.step_index, + state=result.setter_card_state or CardState.SETTER_NOT_SET, + direction=result.setter_direction, + ) + + def _on_guesser_moved(self, result: MoveResult): + self.update_setter_card( + index=result.step_index, + state=result.setter_card_state or CardState.SETTER_NOT_SET, + direction=result.setter_direction, + ) + + self.update_guesser_card( + index=result.step_index, + state=result.guesser_card_state or CardState.GUESSER_DISABLED, + direction=result.guesser_direction, + ) + + def _on_step_state_changed(self, step_state: StepState): + pass + + def _on_session_finished(self): + pass + def update_setter_card( self, index: int, @@ -322,22 +535,23 @@ class SessionPanel(wx.Panel): class TestFrame(wx.Frame): def __init__(self): super().__init__(None, title="TestFrame") - main_panel = SessionPanel(self, "Бывалый", "Балбес") + + local = Player("Бывалый", PlayerRole.GUESSER) + remote = Player("Балбес", PlayerRole.SETTER) + session = SessionState(local, remote) + + main_panel = SessionPanel(self, session) main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(main_panel, 1, wx.EXPAND) self.SetSizer(main_sizer) - main_panel.update_setter_card( - 0, CardState.SETTER_REVEALED, ArrowDirection.LEFT - ) - - main_panel.update_guesser_card( - 0, CardState.GUESSER_WRONG, ArrowDirection.RIGHT - ) - s = SessionStats(20, 1, 0, 1, 5.1) main_panel.update_statusbar(None, s) + session.setter_move(ArrowDirection.LEFT) + session.guesser_move(ArrowDirection.LEFT) + session.setter_move(ArrowDirection.LEFT) + if __name__ == "__main__": enable_high_dpi_support()