misc: fix all ty diagnostics

ty reported 20 diagnostics, and 0.0.89 adds a few more; this clears
them all without suppressions.

- The quest monster is held in a local before being stored on the
  quest book, so its name no longer reads from an Optional.
- PgUp/PgDn narrow the focused window with isinstance against a
  runtime-checkable PageScrollable protocol instead of hasattr.
- The ^t cheat used to monkeypatch SignalMixin.emit. SignalMixin now
  has a muted() context manager that emit() checks, and it restores
  emission even if a tick raises.
- GameView keeps its children as GameWindow, a protocol declaring
  stop() and sync(), so the cheat's resync type-checks.
- curses.ascii is imported explicitly where it is used.
- ProgressBarWindow is now Focusable itself, instead of calling
  Focusable.focus_standout() on a class that is not Focusable. Its
  four subclasses drop the duplicate Focusable base; the MRO and
  initialisation order are unchanged.
- MainMenu narrows the Optional pad before colouring the logo.
- The tests that record tasks assert player.task is set.
- Context managers are annotated as Generator rather than Iterator,
  which ty flags as deprecated for @contextmanager.
- The redundant cast() around getch() is gone.
- The frozen GameData test assigns through setattr(), since the
  direct assignment is an error by design.

Bug fixes:

- ListBox.select(None) crashed on `None < 0`; it now clears the
  selection, as DataTable.select(None) already does.
- ListBox.get() caught ValueError, which indexing never raises; it
  now catches IndexError, so an out-of-range index returns None as
  intended.
This commit is contained in:
Dash committed 2026-10-17 15:55:58 +02:00
1 parent 0c1d5185a4
commit 9155ef418b
14 files changed
+80 -51

No files matched your search

+27 -13
View File
@@ -1,7 +1,8 @@
import argparse
import curses
import curses.ascii
import datetime
from typing import Any
from typing import Any, Protocol, runtime_checkable
from pqcli.game_data import GameData
from pqcli.mechanic import Player, SignalMixin, Simulation
@@ -16,7 +17,7 @@ from pqcli.ui.curses.util import (
KEYS_UP,
)
from pqcli.ui.curses.views.base_view import BaseView
from pqcli.ui.curses.widgets import Focusable, Widget
from pqcli.ui.curses.widgets import Focusable
from .character_sheet_window import CharacterSheetWindow
from .equipment_window import EquipmentWindow
@@ -27,6 +28,19 @@ from .spell_book_window import SpellBookWindow
from .task_progress_window import TaskProgressWindow
class GameWindow(Protocol):
def stop(self) -> None: ...
def sync(self) -> None: ...
@runtime_checkable
class PageScrollable(Protocol):
def scroll_page_up(self) -> None: ...
def scroll_page_down(self) -> None: ...
class GameView(BaseView):
def __init__(
self,
@@ -53,11 +67,11 @@ class GameView(BaseView):
self.on_exit()
elif key in {curses.KEY_PPAGE}:
if hasattr(focused, "scroll_page_up"):
if isinstance(focused, PageScrollable):
focused.scroll_page_up()
elif key in {curses.KEY_NPAGE}:
if hasattr(focused, "scroll_page_down"):
if isinstance(focused, PageScrollable):
focused.scroll_page_down()
elif key in KEYS_CYCLE:
@@ -118,11 +132,9 @@ class GameView(BaseView):
self._simulation.tick()
elif self._args.cheats and key == curses.ascii.DC4: # ^t
old = SignalMixin.emit
SignalMixin.emit = lambda *_: None
for _ in range(10000):
self._simulation.tick()
SignalMixin.emit = old
with SignalMixin.muted():
for _ in range(10000):
self._simulation.tick()
for child in self._children:
child.sync()
@@ -225,16 +237,18 @@ class GameView(BaseView):
x=0,
)
self._focusable_children: list[Focusable] = [
focusable_children = (
self._char_sheet_win,
self._spell_book_win,
self._equipment_win,
self._inventory_win,
self._plot_win,
self._quest_book_win,
]
self._children: list[Widget] = self._focusable_children + [
self._task_win
)
self._focusable_children: list[Focusable] = list(focusable_children)
self._children: list[GameWindow] = [
*focusable_children,
self._task_win,
]
def stop(self) -> None:
@@ -1,12 +1,11 @@
from typing import Any
from pqcli.mechanic import Player, StatType
from pqcli.ui.curses.widgets import Focusable
from .progress_bar_window import DataTableProgressBarWindow
class CharacterSheetWindow(Focusable, DataTableProgressBarWindow):
class CharacterSheetWindow(DataTableProgressBarWindow):
def __init__(
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
) -> None:
@@ -1,12 +1,11 @@
from typing import Any
from pqcli.mechanic import InventoryItem, Player
from pqcli.ui.curses.widgets import Focusable
from .progress_bar_window import DataTableProgressBarWindow
class InventoryWindow(Focusable, DataTableProgressBarWindow):
class InventoryWindow(DataTableProgressBarWindow):
def __init__(
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
) -> None:
@@ -2,12 +2,11 @@ from typing import Any
from pqcli.lingo import act_name
from pqcli.mechanic import Player
from pqcli.ui.curses.widgets import Focusable
from .progress_bar_window import ListBoxProgressBarWindow
class PlotWindow(Focusable, ListBoxProgressBarWindow):
class PlotWindow(ListBoxProgressBarWindow):
cutoff = 100
def __init__(
@@ -10,7 +10,7 @@ from pqcli.ui.curses.widgets import (
)
class ProgressBarWindow(WindowWrapper):
class ProgressBarWindow(Focusable, WindowWrapper):
def __init__(
self,
parent: Any,
@@ -51,7 +51,7 @@ class ProgressBarWindow(WindowWrapper):
self._progress_bar_win.erase()
with Focusable.focus_standout(self, self._progress_bar_win):
with self.focus_standout(self._progress_bar_win):
self._progress_bar_win.border(
curses.ACS_VLINE,
curses.ACS_VLINE,
@@ -105,7 +105,7 @@ class DataTableProgressBarWindow(ProgressBarWindow):
if not self._win:
return
with Focusable.focus_standout(self, self._win):
with self.focus_standout(self._win):
self._win.box()
x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2)
self._win.addnstr(
@@ -143,7 +143,7 @@ class ListBoxProgressBarWindow(ProgressBarWindow):
if not self._win:
return
with Focusable.focus_standout(self, self._win):
with self.focus_standout(self._win):
self._win.box()
x = max(0, (self.getmaxyx()[1] - len(self._title)) // 2)
self._win.addnstr(
@@ -1,12 +1,11 @@
from typing import Any
from pqcli.mechanic import Player
from pqcli.ui.curses.widgets import Focusable
from .progress_bar_window import ListBoxProgressBarWindow
class QuestBookWindow(Focusable, ListBoxProgressBarWindow):
class QuestBookWindow(ListBoxProgressBarWindow):
cutoff = 100
def __init__(
+9 -7
View File
@@ -38,15 +38,17 @@ class MainMenu(Menu):
scr_height=scr_height,
scr_width=scr_width,
)
pad = self._pad
assert pad is not None
if has_colors():
for y in range(len(self._header_lines)):
self._pad.chgat(y, 0, curses.color_pair(COLOR_LOGO))
self._pad.chgat(3, 29, 5, curses.color_pair(COLOR_LOGO_ALT))
self._pad.chgat(4, 29, 5, curses.color_pair(COLOR_LOGO_ALT))
self._pad.chgat(5, 32, 4, curses.color_pair(COLOR_LOGO_ALT))
self._pad.chgat(5, 27, 4, curses.color_pair(COLOR_LOGO_ALT))
self._pad.chgat(6, 34, 2, curses.color_pair(COLOR_LOGO_ALT))
self._pad.chgat(6, 27, 2, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(y, 0, curses.color_pair(COLOR_LOGO))
pad.chgat(3, 29, 5, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(4, 29, 5, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(5, 32, 4, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(5, 27, 4, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(6, 34, 2, curses.color_pair(COLOR_LOGO_ALT))
pad.chgat(6, 27, 2, curses.color_pair(COLOR_LOGO_ALT))
class RosterView(BaseView):