mirror of
https://github.com/rr-/pq-cli.git
synced 2026-11-12 22:40:36 -07:00
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:
14 files changed
+80
-51
No files matched your search
+19
-5
@@ -1,10 +1,11 @@
|
||||
import contextlib
|
||||
import datetime
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import math
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from collections.abc import Callable, Generator, Iterator, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -42,7 +43,21 @@ _SIGNALS: dict[tuple["SignalMixin", str], list[Callable[..., Any]]] = (
|
||||
|
||||
|
||||
class SignalMixin:
|
||||
_muted = 0
|
||||
|
||||
@staticmethod
|
||||
@contextlib.contextmanager
|
||||
def muted() -> Generator[None, None, None]:
|
||||
"""Suppress all signal emission for the duration of the block."""
|
||||
SignalMixin._muted += 1
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
SignalMixin._muted -= 1
|
||||
|
||||
def emit(self, signal_name: str, *user_data: Any) -> None:
|
||||
if SignalMixin._muted:
|
||||
return
|
||||
for callback in _SIGNALS[self, signal_name]:
|
||||
callback(*user_data)
|
||||
|
||||
@@ -683,12 +698,11 @@ class Simulation:
|
||||
caption = ""
|
||||
choice = random.below(5)
|
||||
if choice == 0:
|
||||
self.player.quest_book.monster = unnamed_monster(
|
||||
monster = unnamed_monster(
|
||||
self.data, self.player.level, iterations=3
|
||||
)
|
||||
caption = "Exterminate " + definite(
|
||||
self.player.quest_book.monster.name, 2
|
||||
)
|
||||
self.player.quest_book.monster = monster
|
||||
caption = "Exterminate " + definite(monster.name, 2)
|
||||
elif choice == 1:
|
||||
caption = "Seek " + definite(interesting_item(self.data), 1)
|
||||
elif choice == 2:
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import contextlib
|
||||
import time
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from collections.abc import Callable, Generator, Sequence
|
||||
from typing import Any
|
||||
|
||||
RngState = tuple[float, float, float, int]
|
||||
@@ -91,7 +91,7 @@ def seed(source: str) -> None:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def using(rng: Alea) -> Iterator[None]:
|
||||
def using(rng: Alea) -> Generator[None, None, None]:
|
||||
"""Draw from rng instead of the shared generator inside the block."""
|
||||
global _current
|
||||
previous = _current
|
||||
|
||||
@@ -3,7 +3,7 @@ import curses
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from pqcli import lingo
|
||||
from pqcli.config import Class, Race
|
||||
@@ -91,7 +91,7 @@ class CursesUserInterface(BaseUserInterface):
|
||||
self._switch_to_roster_view()
|
||||
|
||||
while True:
|
||||
key = cast(int, self._screen.getch())
|
||||
key = self._screen.getch()
|
||||
|
||||
if key == curses.ERR:
|
||||
time.sleep(0.1)
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -29,14 +29,14 @@ class ListBox(Scrollable):
|
||||
idx %= len(self._items)
|
||||
try:
|
||||
return self._items[idx]
|
||||
except ValueError:
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def delete(self, idx: int, count: int = 1) -> None:
|
||||
del self._items[idx : idx + count]
|
||||
|
||||
def select(self, idx: int | None) -> None:
|
||||
if idx < 0 and len(self._items):
|
||||
if idx is not None and idx < 0 and self._items:
|
||||
idx %= len(self._items)
|
||||
self._selected = idx
|
||||
|
||||
|
||||
@@ -28,11 +28,12 @@ class Menu(Widget):
|
||||
|
||||
w = max(map(len, all_lines)) + 1
|
||||
h = len(all_lines) + 1
|
||||
self._pad: Any | None = curses.newpad(h, w)
|
||||
pad = curses.newpad(h, w)
|
||||
self._pad: Any | None = pad
|
||||
|
||||
for y, line in enumerate(self._header_lines):
|
||||
self._pad.move(y, 0)
|
||||
self._pad.addstr(line)
|
||||
pad.move(y, 0)
|
||||
pad.addstr(line)
|
||||
|
||||
self._active_choice = active_choice
|
||||
|
||||
|
||||
+6
-4
@@ -34,10 +34,12 @@ def run_trace(seed: str, ticks: int = 3000) -> list[str]:
|
||||
simulation = Simulation(player, data)
|
||||
|
||||
lines: list[str] = []
|
||||
player.connect(
|
||||
"new_task",
|
||||
lambda: lines.append(f"task: {player.task.description}"),
|
||||
)
|
||||
|
||||
def record_task() -> None:
|
||||
assert player.task is not None
|
||||
lines.append(f"task: {player.task.description}")
|
||||
|
||||
player.connect("new_task", record_task)
|
||||
for _ in range(ticks):
|
||||
simulation.tick(100000.0)
|
||||
|
||||
|
||||
@@ -179,4 +179,4 @@ def test_game_data_is_immutable() -> None:
|
||||
data = load_game_data()
|
||||
assert isinstance(data, GameData)
|
||||
with pytest.raises(AttributeError):
|
||||
data.spells = () # type: ignore[misc]
|
||||
setattr(data, "spells", ())
|
||||
Reference in new issue
Block a user