Files
mind-reader/src/mind_reader/ui/views/session_view.py
T

380 lines
11 KiB
Python

import sys
from dataclasses import dataclass
from typing import Callable, Optional
import wx
from wx.lib.pubsub import pub
from ...domain import ArrowDirection, CardState, SessionStats, StepData
from ...input import SessionInput
from ...state import SessionState
@dataclass(frozen=True)
class SetterCardColour:
NOT_SET = wx.Colour(82, 97, 107)
HIDDEN = wx.Colour(201, 214, 223)
REVEALED = wx.Colour(240, 245, 249)
@dataclass(frozen=True)
class GuesserCardColour:
DISABLED = wx.Colour(82, 97, 107)
WAITING = wx.Colour(201, 214, 223)
CORRECT = wx.Colour(76, 175, 80)
WRONG = wx.Colour(229, 115, 115)
@dataclass(frozen=True)
class ArrowSymbol:
LEFT: str = "←"
RIGHT: str = "→"
HIDDEN: str = "?"
class Card(wx.Panel):
def __init__(self, parent, state: CardState):
super().__init__(parent, style=wx.BORDER_SIMPLE)
self._direction: Optional[ArrowDirection] = None
self._state = state
self._setup()
self._update()
def _setup(self):
self.SetMinSize(wx.Size(45, 75))
self.label = wx.StaticText(self, style=wx.ALIGN_CENTER)
font = self.label.GetFont()
font.SetPointSize(20)
self.label.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.EXPAND)
self.SetSizer(sizer)
def _update(self):
match self._state:
case CardState.SETTER_NOT_SET:
colour = SetterCardColour.NOT_SET
show_arrow = False
case CardState.SETTER_HIDDEN:
colour = SetterCardColour.HIDDEN
show_arrow = True
case CardState.SETTER_REVEALED:
colour = SetterCardColour.REVEALED
show_arrow = True
case CardState.GUESSER_DISABLED:
colour = GuesserCardColour.DISABLED
show_arrow = False
case CardState.GUESSER_WAITING:
colour = GuesserCardColour.WAITING
show_arrow = False
case CardState.GUESSER_CORRECT:
colour = GuesserCardColour.CORRECT
show_arrow = True
case CardState.GUESSER_WRONG:
colour = GuesserCardColour.WRONG
show_arrow = True
if show_arrow and self._direction:
match self._direction:
case ArrowDirection.LEFT:
symbol = ArrowSymbol.LEFT
case ArrowDirection.RIGHT:
symbol = ArrowSymbol.RIGHT
case ArrowDirection.HIDDEN:
symbol = ArrowSymbol.HIDDEN
self.label.SetLabel(symbol)
else:
self.label.SetLabel("")
self.SetBackgroundColour(colour)
self.Layout()
self.Refresh()
def set(
self,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._state = state
self._direction = direction
self._update()
class SessionPanel(wx.Panel):
def __init__(
self,
parent,
input: SessionInput,
setter_name: str,
guesser_name: str,
sequence_length: int,
):
super().__init__(parent)
self._input = input
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()
pub.subscribe(self._on_session_started, "session.session_started")
pub.subscribe(self._on_setter_moved, "session.setter_moved")
pub.subscribe(self._on_guesser_moved, "session.guesser_moved")
pub.subscribe(self._on_step_closed, "session.step_closed")
pub.subscribe(self._on_session_finished, "session.session_finished")
def _setup(self) -> None:
main_sizer = wx.BoxSizer(wx.VERTICAL)
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,
)
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,
)
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)
secondary_sizer.Add(guesser_sizer, 0, wx.EXPAND)
secondary_sizer.Add(
controls_sizer, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 25
)
main_sizer.Add(secondary_sizer, 0, wx.LEFT | wx.ALL, 0)
self.SetSizer(main_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))
font = left.GetFont()
font.SetPointSize(22)
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)
return controls_sizer
def _get_statusbar(self) -> Optional[wx.StatusBar]:
top_frame = self.GetTopLevelParent()
if isinstance(top_frame, wx.Frame):
statusbar = top_frame.GetStatusBar()
if not statusbar:
statusbar = top_frame.CreateStatusBar(1)
statusbar.SetFieldsCount(4)
return statusbar
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
dc = wx.ClientDC(self._statusbar)
width_1 = dc.GetTextExtent(texts[0]).width
width_2 = dc.GetTextExtent(texts[1]).width
width_3 = dc.GetTextExtent(texts[2]).width
self._statusbar.SetStatusWidths([-1, width_1, width_2, width_3])
def _fit_parent_frame(self) -> None:
top_frame = self.GetTopLevelParent()
if isinstance(top_frame, wx.Frame):
self.Layout()
best_size = self.GetBestSize()
min_size = top_frame.ClientToWindowSize(best_size)
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:
self._input.player_move(direction)
def _on_session_started(self, step: StepData) -> None:
self.update_setter_card(
step.index,
step.setter.state,
step.setter.direction,
)
self.update_guesser_card(
step.index,
step.guesser.state,
step.guesser.direction,
)
def _on_setter_moved(self, step: StepData) -> None:
self.update_setter_card(
step.index,
step.setter.state,
step.setter.direction,
)
self.update_guesser_card(
step.index,
step.guesser.state,
step.guesser.direction,
)
def _on_guesser_moved(self, step: StepData):
self.update_setter_card(
step.index,
step.setter.state,
step.setter.direction,
)
self.update_guesser_card(
step.index,
step.guesser.state,
step.guesser.direction,
)
def _on_step_closed(self, step: StepData):
self.update_setter_card(
step.index,
step.setter.state,
step.setter.direction,
)
self.update_guesser_card(
step.index,
step.guesser.state,
step.guesser.direction,
)
def _on_session_finished(self):
pass
def update_setter_card(
self,
index: int,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._setter_sequence[index].set(state, direction)
def update_guesser_card(
self,
index: int,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._guesser_sequence[index].set(state, direction)
def update_statusbar(
self,
message: Optional[str],
stats: SessionStats,
):
if not self._statusbar:
return
if message is not None:
self._statusbar.SetStatusText(message, 0)
progress = f" Прогресс {stats.setted}/{stats.total} "
self._statusbar.SetStatusText(progress, 1)
accuracy = (
f" Точность {stats.accuracy}% ({stats.correct}/{stats.wrong}) "
)
self._statusbar.SetStatusText(accuracy, 2)
minutes = int(stats.taken_time) // 60
seconds = int(stats.taken_time) % 60
taken_time = f" Время {minutes:02d}:{seconds:02d} "
self._statusbar.SetStatusText(taken_time, 3)
self._autofit_statusbar([progress, accuracy, taken_time])