From e3f58283345cd0faab5bd15cac3ade8f07be8631 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 14 Aug 2026 02:42:18 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=B2=20session=5Fview.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mind_reader/domain.py | 65 +++++ src/mind_reader/state/__init__.py | 3 + src/mind_reader/state/session_state.py | 194 +++++++++++++ src/mind_reader/ui/views/__init__.py | 7 +- src/mind_reader/ui/views/session_view.py | 332 ++--------------------- 5 files changed, 290 insertions(+), 311 deletions(-) create mode 100644 src/mind_reader/domain.py create mode 100644 src/mind_reader/state/__init__.py create mode 100644 src/mind_reader/state/session_state.py diff --git a/src/mind_reader/domain.py b/src/mind_reader/domain.py new file mode 100644 index 0000000..4be2c48 --- /dev/null +++ b/src/mind_reader/domain.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + + +class CardState(Enum): + SETTER_NOT_SET = auto() + SETTER_HIDDEN = auto() + SETTER_REVEALED = auto() + GUESSER_DISABLED = auto() + GUESSER_WAITING = auto() + GUESSER_CORRECT = auto() + GUESSER_WRONG = auto() + + +class ArrowDirection(Enum): + LEFT = auto() + RIGHT = auto() + + +@dataclass +class SessionStats: + total: int + setted: int + correct: int + wrong: int + taken_time: float + + @property + def accuracy(self) -> float: + accuracy: float + if self.total == 0: + accuracy = 0.0 + accuracy = (self.correct / self.total) * 100 + 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 CardData: + direction: Optional[ArrowDirection] + state: CardState + + +@dataclass(frozen=True) +class StepData: + index: int + setter: CardData + guesser: CardData + + +@dataclass +class Player: + name: str + role: PlayerRole diff --git a/src/mind_reader/state/__init__.py b/src/mind_reader/state/__init__.py new file mode 100644 index 0000000..931f345 --- /dev/null +++ b/src/mind_reader/state/__init__.py @@ -0,0 +1,3 @@ +from .session_state import SessionState + +__all__ = ["SessionState"] diff --git a/src/mind_reader/state/session_state.py b/src/mind_reader/state/session_state.py new file mode 100644 index 0000000..c5a9ffe --- /dev/null +++ b/src/mind_reader/state/session_state.py @@ -0,0 +1,194 @@ +from wx.lib.pubsub import pub + +from mind_reader.domain import ( + ArrowDirection, + CardData, + CardState, + Player, + PlayerRole, + StepData, + StepState, +) + + +class SessionState: + def __init__( + self, + local_player: Player, + remote_player: Player, + sequence_length: int = 20, + ): + self._local_player = local_player + self._remote_player = remote_player + + self._sequence_length = sequence_length + self._setter_moves: list[ArrowDirection] = [] + self._guesser_moves: list[ArrowDirection] = [] + + self._step_state: StepState = StepState.WAITING_FOR_SETTER + + @property + def _completed_move_count(self): + return len(self._guesser_moves) + + def _get_setter_card_state(self, index: int) -> CardState: + current_index = len(self._setter_moves) + + if index > current_index: + return CardState.SETTER_NOT_SET + + elif index == current_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, index: int) -> CardState: + completed_move_count = self._completed_move_count + + if index > completed_move_count: + return CardState.GUESSER_DISABLED + + elif index == completed_move_count: + if self._step_state == StepState.WAITING_FOR_GUESSER: + return CardState.GUESSER_WAITING + else: + return CardState.GUESSER_DISABLED + + else: + setter_move = self._setter_moves[index] + guesser_move = self._guesser_moves[index] + if setter_move == guesser_move: + return CardState.GUESSER_CORRECT + else: + return CardState.GUESSER_WRONG + + def start_session(self) -> None: + index = 0 + pub.sendMessage( + "session.session_started", + step=StepData( + index=index, + setter=CardData( + direction=None, + state=self._get_setter_card_state(index), + ), + guesser=CardData( + direction=None, + state=self._get_guesser_card_state(index), + ), + ), + ) + + 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 + + index = len(self._setter_moves) - 1 + + pub.sendMessage( + "session.setter_moved", + step=StepData( + index=index, + setter=CardData( + direction=direction, + state=self._get_setter_card_state(index), + ), + guesser=CardData( + direction=direction, + state=self._get_guesser_card_state(index), + ), + ), + ) + return True + + def guesser_move(self, direction: ArrowDirection) -> bool: + if self._step_state != StepState.WAITING_FOR_GUESSER: + return False + + self._guesser_moves.append(direction) + + if self._completed_move_count < self._sequence_length: + self._step_state = StepState.WAITING_FOR_SETTER + else: + self._step_state = StepState.SESSION_FINISHED + + index = len(self._guesser_moves) - 1 + + pub.sendMessage( + "session.guesser_moved", + step=StepData( + index=index, + setter=CardData( + direction=self._setter_moves[index], + state=self._get_setter_card_state(index), + ), + guesser=CardData( + direction=self._guesser_moves[index], + state=self._get_guesser_card_state(index), + ), + ), + ) + + next = index + 1 + + pub.sendMessage( + "session.step_closed", + step=StepData( + index=next, + setter=CardData( + direction=None, + state=self._get_setter_card_state(next), + ), + guesser=CardData( + direction=None, + state=self._get_guesser_card_state(next), + ), + ), + ) + + if 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 + + @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_length(self) -> int: + return self._sequence_length diff --git a/src/mind_reader/ui/views/__init__.py b/src/mind_reader/ui/views/__init__.py index 3afe467..8bb7e3d 100644 --- a/src/mind_reader/ui/views/__init__.py +++ b/src/mind_reader/ui/views/__init__.py @@ -1,5 +1,4 @@ -from mind_reader.ui.views.lobby_view import LobbyPanel +from .lobby_view import LobbyPanel +from .session_view import SessionPanel -__all__ = [ - "LobbyPanel", -] +__all__ = ["LobbyPanel", "SessionPanel"] diff --git a/src/mind_reader/ui/views/session_view.py b/src/mind_reader/ui/views/session_view.py index 65b90f5..001fc5c 100644 --- a/src/mind_reader/ui/views/session_view.py +++ b/src/mind_reader/ui/views/session_view.py @@ -1,280 +1,12 @@ import sys from dataclasses import dataclass -from enum import Enum, auto from typing import Callable, Optional import wx from wx.lib.pubsub import pub - -def enable_high_dpi_support(): - if sys.platform == "win32": - try: - import ctypes - - ctypes.windll.shcore.SetProcessDpiAwareness(2) - except Exception: - try: - import ctypes - - ctypes.windll.user32.SetProcessDPIAware() - except Exception: - pass - - -class CardState(Enum): - SETTER_NOT_SET = auto() - SETTER_HIDDEN = auto() - SETTER_REVEALED = auto() - - GUESSER_DISABLED = auto() - GUESSER_WAITING = auto() - GUESSER_CORRECT = auto() - GUESSER_WRONG = auto() - - -class ArrowDirection(Enum): - LEFT = auto() - RIGHT = auto() - - -@dataclass -class SessionStats: - total: int - setted: int - correct: int - wrong: int - taken_time: float - - @property - def accuracy(self) -> float: - accuracy: float - if self.total == 0: - accuracy = 0.0 - accuracy = (self.correct / self.total) * 100 - 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: ArrowDirection - setter_card_state: CardState - guesser_direction: Optional[ArrowDirection] = None - guesser_card_state: Optional[CardState] = None - - -@dataclass(frozen=True) -class CardData: - direction: Optional[ArrowDirection] - state: CardState - - -@dataclass(frozen=True) -class StepData: - index: int - setter: CardData - guesser: CardData - - -@dataclass -class Player: - name: str - role: PlayerRole - - -class SessionState: - def __init__( - self, - local_player: Player, - remote_player: Player, - sequence_length: int = 20, - ): - self._local_player = local_player - self._remote_player = remote_player - - self._sequence_length = sequence_length - self._setter_moves: list[ArrowDirection] = [] - self._guesser_moves: list[ArrowDirection] = [] - - self._step_state: StepState = StepState.WAITING_FOR_SETTER - - @property - def _completed_move_count(self): - return len(self._guesser_moves) - - def _get_setter_card_state(self, index: int) -> CardState: - current_index = len(self._setter_moves) - - if index > current_index: - return CardState.SETTER_NOT_SET - - elif index == current_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, index: int) -> CardState: - completed_move_count = self._completed_move_count - - if index > completed_move_count: - return CardState.GUESSER_DISABLED - - elif index == completed_move_count: - if self._step_state == StepState.WAITING_FOR_GUESSER: - return CardState.GUESSER_WAITING - else: - return CardState.GUESSER_DISABLED - - else: - setter_move = self._setter_moves[index] - guesser_move = self._guesser_moves[index] - if setter_move == guesser_move: - return CardState.GUESSER_CORRECT - else: - return CardState.GUESSER_WRONG - - def start_session(self) -> None: - index = 0 - pub.sendMessage( - "session.session_started", - step=StepData( - index=index, - setter=CardData( - direction=None, - state=self._get_setter_card_state(index), - ), - guesser=CardData( - direction=None, - state=self._get_guesser_card_state(index), - ), - ), - ) - - 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 - - index = len(self._setter_moves) - 1 - - pub.sendMessage( - "session.setter_moved", - step=StepData( - index=index, - setter=CardData( - direction=direction, - state=self._get_setter_card_state(index), - ), - guesser=CardData( - direction=direction, - state=self._get_guesser_card_state(index), - ), - ), - ) - return True - - def guesser_move(self, direction: ArrowDirection) -> bool: - if self._step_state != StepState.WAITING_FOR_GUESSER: - return False - - self._guesser_moves.append(direction) - - if self._completed_move_count < self._sequence_length: - self._step_state = StepState.WAITING_FOR_SETTER - else: - self._step_state = StepState.SESSION_FINISHED - - index = len(self._guesser_moves) - 1 - - pub.sendMessage( - "session.guesser_moved", - step=StepData( - index=index, - setter=CardData( - direction=self._setter_moves[index], - state=self._get_setter_card_state(index), - ), - guesser=CardData( - direction=self._guesser_moves[index], - state=self._get_guesser_card_state(index), - ), - ), - ) - - next = index + 1 - - pub.sendMessage( - "session.step_closed", - step=StepData( - index=next, - setter=CardData( - direction=None, - state=self._get_setter_card_state(next), - ), - guesser=CardData( - direction=None, - state=self._get_guesser_card_state(next), - ), - ), - ) - - if 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 - - @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_length(self) -> int: - return self._sequence_length +from mind_reader.domain import ArrowDirection, CardState, SessionStats, StepData +from mind_reader.state import SessionState @dataclass(frozen=True) @@ -379,19 +111,20 @@ class Card(wx.Panel): class SessionPanel(wx.Panel): - def __init__(self, parent, state: SessionState): + def __init__( + self, parent, setter_name: str, guesser_name: str, sequence_length: int + ): super().__init__(parent) - self._state = state - - self._setter_name = self._state.setter_name - self._guesser_name = self._state.guesser_name - self._sequence_length = self._state.sequence_length + self._setter_name = setter_name + self._guesser_name = guesser_name + self._sequence_length = sequence_length self._setter_sequence: list[Card] = [] self._guesser_sequence: list[Card] = [] self._statusbar = self._get_statusbar() + self._setup_statusbar() self._setup() self._fit_parent_frame() @@ -491,6 +224,22 @@ class SessionPanel(wx.Panel): return None + def _setup_statusbar(self) -> None: + if not self._statusbar: + return + + message = " Ожидание начала игры... " + progress = f" Прогресс 0/{self._sequence_length} " + accuracy = " Точность 0.0% (0/0) " + taken_time = " Время 00:00 " + + self._statusbar.SetStatusText(message, 0) + self._statusbar.SetStatusText(progress, 1) + self._statusbar.SetStatusText(accuracy, 2) + self._statusbar.SetStatusText(taken_time, 3) + + self._autofit_statusbar([progress, accuracy, message]) + def _autofit_statusbar(self, texts: list[str]) -> None: if not self._statusbar: return @@ -616,34 +365,3 @@ class SessionPanel(wx.Panel): self._statusbar.SetStatusText(taken_time, 3) self._autofit_statusbar([progress, accuracy, taken_time]) - - -class TestFrame(wx.Frame): - def __init__(self): - super().__init__(None, title="TestFrame") - - local = Player("Бывалый", PlayerRole.SETTER) - remote = Player("Балбес", PlayerRole.GUESSER) - 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) - - s = SessionStats(20, 1, 0, 1, 5.1) - main_panel.update_statusbar(None, s) - - session.start_session() - session.setter_move(ArrowDirection.LEFT) - session.guesser_move(ArrowDirection.LEFT) - session.setter_move(ArrowDirection.LEFT) - session.guesser_move(ArrowDirection.RIGHT) - - -if __name__ == "__main__": - enable_high_dpi_support() - app = wx.App() - frame = TestFrame() - frame.Show() - app.MainLoop()