Compare commits

2 Commits
Author SHA1 Message Date
vladimir 6a66a02071 feat: wip SessionState, SessionView 2026-08-12 04:30:17 +03:00
vladimir da169b49fc refactor(ui): wip session_view.py 2026-08-12 01:21:54 +03:00
+274 -45
View File
@@ -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,34 +340,36 @@ 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)
setter_title = wx.StaticText(
self, label=f"Загадывает: {self._setter_name}"
setter_sizer = self._create_sequence_section(
title=f"Загадывает: {self._setter_name}",
sequence=self._setter_sequence,
sequence_length=self._sequence_length,
initial_card_state=CardState.SETTER_NOT_SET,
)
setter_sizer = wx.BoxSizer(wx.HORIZONTAL)
for _ in range(self._sequence_length):
card = Card(self, CardState.SETTER_NOT_SET)
setter_sizer.Add(card, 0, wx.ALL, 4)
self._setter_sequence.append(card)
guesser_title = wx.StaticText(
self, label=f"Отгадывает: {self._guesser_name}"
guesser_sizer = self._create_sequence_section(
title=f"Угадывает: {self._guesser_name}",
sequence=self._guesser_sequence,
sequence_length=self._sequence_length,
initial_card_state=CardState.GUESSER_DISABLED,
)
guesser_sizer = wx.BoxSizer(wx.HORIZONTAL)
for _ in range(self._sequence_length):
card = Card(self, CardState.GUESSER_DISABLED)
guesser_sizer.Add(card, 0, wx.ALL, 4)
self._guesser_sequence.append(card)
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_title, 0, wx.LEFT | wx.TOP, 9)
secondary_sizer.Add(setter_sizer, 0, wx.ALL, 5)
secondary_sizer.Add(guesser_title, 0, wx.LEFT | wx.TOP, 9)
secondary_sizer.Add(guesser_sizer, 0, wx.ALL, 5)
secondary_sizer.Add(setter_sizer, 0, wx.EXPAND)
secondary_sizer.Add(guesser_sizer, 0, wx.EXPAND)
secondary_sizer.Add(
controls_sizer, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 25
)
@@ -213,7 +378,32 @@ class SessionPanel(wx.Panel):
self.SetSizer(main_sizer)
def _create_controls_sizer(self) -> wx.Sizer:
def _create_sequence_section(
self,
title: str,
sequence: list[Card],
sequence_length: int,
initial_card_state: CardState,
) -> wx.Sizer:
section_sizer = wx.BoxSizer(wx.VERTICAL)
title_label = wx.StaticText(self, label=title)
cards_sizer = wx.BoxSizer(wx.HORIZONTAL)
for _ in range(sequence_length):
card = Card(self, initial_card_state)
cards_sizer.Add(card, 0, wx.ALL, 4)
sequence.append(card)
section_sizer.Add(title_label, 0, wx.LEFT | wx.TOP, 9)
section_sizer.Add(cards_sizer, 0, wx.ALL, 5)
return section_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))
@@ -223,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)
@@ -261,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,
@@ -279,14 +507,14 @@ class SessionPanel(wx.Panel):
def update_statusbar(
self,
statusbar_text: Optional[str],
message: Optional[str],
stats: SessionStats,
):
if not self._statusbar:
return
if statusbar_text is not None:
self._statusbar.SetStatusText(statusbar_text, 0)
if message is not None:
self._statusbar.SetStatusText(message, 0)
progress = f" Прогресс {stats.setted}/{stats.total} "
self._statusbar.SetStatusText(progress, 1)
@@ -307,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()