Commit Graph
133 Commits
Author SHA1 Message Date
Dash 804b0d8b38 ui: import and export .pqw files from the menus
The basic interface gets two main menu entries that ask for a path.
The curses roster gets [I] Import and [E] Export, with a new text input
view for the path, prefilled with NAME.pqw on export, and a message
view for the outcome and any warnings. The README explains how to move
characters to and from the web version.

Resolves #21
2026-10-17 15:56:03 +02:00
Dash fd4a7d151d misc: replace Any with precise types
Curses screens, windows and pads are typed as curses.window, and the
widgets that may get no parent window take curses.window | None.

- EventHandler is generic over a ParamSpec, so each handler declares
  the arguments it passes, and MenuView is generic over the item its
  on_confirm hands out. Choice callbacks take no arguments.
- Scrollable is generic over its items, which DataTable and ListBox
  fix to tuple[str, str] and str.
- random.choice() and choice_low() are generic over the sequence, so
  the casts at their call sites are gone. BasicUserInterface.menu()
  and util.first() are generic too.
- Signal callbacks and their payloads are Callable[..., object] and
  object, since emit() only forwards them.
- JSON values in game_data are object and narrowed by isinstance,
  and data files are Traversable.
- Signal handlers take an int and a FrameType | None, and
  list_players() takes a TextIO.
- The focus_standout() context managers return
  Generator[None, None, None].

Any remains in three places: Player.__setstate__, whose pickled
__dict__ holds values of every type; Focusable.__init__, a mixin that
passes *args and **kwargs through to the next class; and the values
load_game_data() collects, whose element type depends on the field
each one fills.
2026-10-17 15:55:59 +02:00
Dash 9155ef418b 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.
2026-10-17 15:55:58 +02:00
Dash 0a4358e2b8 misc: drop typing as T, use builtin generics
Python 3.10 is the minimum, so annotations use `X | None`, `list[X]`
and friends, enforced by ruff's pyupgrade rules. The remaining typing
names are imported directly, with abstract types coming from
collections.abc. first() is now generic over the item and default.
2026-10-17 15:55:55 +02:00
Dash 4d620bc384 build: replace black and isort with ruff
ruff format keeps black's style at the same line length, and ruff
check covers import sorting plus pycodestyle and pyflakes. Three
comments and a test regex are wrapped to fit 79 columns.
2026-10-17 15:55:51 +02:00
Dash 9c44d69633 random: port Alea, one generator per character
pqcli used Python's global Mersenne Twister, seeded once from the clock.
Upstream uses Johannes Baagøe's Alea, with Random(n) = uint32() % n, and
keeps the generator state with each character. The port matches the JS
output bit for bit, including UTF-16 seeding of non-BMP characters.

Each Player now owns an Alea that is pickled with the save, and
Simulation.tick draws from it. Saves without one get a fresh generator.
As upstream does, a new character's game continues from the state the
kept stat roll was made from.

The parity traces are re-recorded, since every random number changes.
2026-10-17 15:55:50 +02:00
Dash c0c6c8aeba mechanic: move word lists to data files
The word lists (monsters, spells, equipment, races, classes, titles and
so on) now live in pqcli/data/*.json instead of pqcli/config.py. A
GameData object loaded at startup is passed to the simulation and UIs.

Users can replace any list by putting NAME.json in
$XDG_CONFIG_HOME/pqcli/data/ or a directory given with --data-dir.
--dump-data DIR copies the built-in files out for editing. Malformed
files are reported and pqcli exits before starting the UI.

Combat task descriptions come from the new combat_verbs list, with
{target} standing for the enemy. The built-in list holds only "Executing
{target}", and a single verb consumes no random roll, so stock gameplay
is unchanged. Recorded traces in tests/fixtures pin this down.

Monster, Race and Class stay in pqcli.config so existing saves still
load.

Resolves #3
2026-10-17 15:55:41 +02:00
Jakub StastkaandJakub Stastka 696fad78b3 build: add Docker builds
Co-authored-by: Jakub Stastka <jakub@stastka.org>
2026-04-19 09:58:04 +09:00
Dash 9609e9a3f2 misc: fix style issues 2025-12-11 14:58:55 +01:00
Owen Cochell 7790e52a6d misc: fix crash on Linux due to zero length addnstr call 2025-04-10 08:20:21 +02:00
Owen Cochell e6d18352c5 misc: fix empty character sheet on Linux 2025-04-04 20:26:08 +02:00
Marcin Kurczewski 07cae1a4e8 misc: use xdg-base-dirs
Resolves #26
2024-05-04 12:18:07 +02:00
Marcin Kurczewski 9eaf4397df misc: fix syntax warning 2024-04-26 01:27:33 +02:00
txtsd 77391e7888 misc: Remove unused imports
Signed-off-by: txtsd <code@ihavea.quest>
2022-11-11 08:02:27 +01:00
txtsd 04e0611d45 misc: Fix typos
Signed-off-by: txtsd <code@ihavea.quest>
2022-11-11 08:02:27 +01:00
Marcin Kurczewski bbe3842a2f ui: allow loading characters directly from cli (#16) 2020-09-30 20:20:29 +02:00
rr- 29207c1d4c misc: remove unused imports 2020-08-17 09:12:56 +02:00
rr- fc4dcff9e3 ui/curses: fix division by zero 2020-05-07 15:04:58 +02:00
rr- 82c8d21395 ui/curses: fix division by zero 2019-04-10 20:09:22 +02:00
rr- 555f386b24 ui/curses: highlight changed and added items 2019-01-14 16:10:26 +01:00
rr- 5f34a85c80 ui/curses: fix new quests and acts not appearing 2019-01-14 14:52:24 +01:00
rr- 97552a711a misc: don't use relative imports 2019-01-14 14:42:29 +01:00
rr- 0bd4583c73 ui/curses: add swords to logo 2019-01-13 22:20:59 +01:00
rr- adc3c7914d ui/curses: use title case in menus 2019-01-13 21:44:26 +01:00
rr- d2c06b08bb ui/curses: cycle widgets on tab 2019-01-13 21:41:24 +01:00
rr- 4dc674816b ui/curses: add focus to character stats view 2019-01-13 21:41:24 +01:00
rr- 8c6b1a2504 ui/curses: add focus to character name view 2019-01-13 21:41:21 +01:00
rr- 8d385523ad ui/curses: split into files 2019-01-13 21:41:00 +01:00
rr- 05c9bcd0e6 ui/curses: improve margins 2019-01-13 20:53:18 +01:00
rr- 2c3da6484b ui/curses: align button hints to left 2019-01-13 20:50:05 +01:00
rr- d4f9326759 ui/curses: make escape work in game view 2019-01-13 20:48:33 +01:00
rr- e85a8d5826 ui/curses: improve character choice 2019-01-13 19:48:54 +01:00
rr- 2da46a44a4 ui/curses: implement cheats 2019-01-13 18:34:09 +01:00
rr- 0ecfc2c1fd ui/urwid: remove urwid ui
It was too similar and inferior to curses.
2019-01-13 18:26:12 +01:00
rr- ffe5734433 ui: default to curses user interface 2019-01-13 18:06:27 +01:00
rr- ae99f1f1a4 ui/urwid: respect --no-colors 2019-01-13 17:46:07 +01:00
rr- a47d6233be ui/urwid: colorize logo 2019-01-13 17:33:19 +01:00
rr- 1980049c6f ui/urwid: drop customizing color palettes 2019-01-13 17:33:11 +01:00
rr- 9fc078ad94 ui/curses: add colors 2019-01-13 17:33:11 +01:00
rr- 7a835c615e ui/urwid: split to files 2019-01-13 16:33:07 +01:00
rr- 021d7eb602 misc: improve mypy health 2019-01-04 20:13:52 +01:00
rr- 3d8034b285 ui/curses: cancel char creation one step at a time 2019-01-04 20:07:43 +01:00
rr- bd3f196f84 mechanic: fix stat unrolling 2019-01-04 19:49:50 +01:00
rr- 9a90b7b7b4 ui/curses: add character creation wizard 2019-01-04 19:49:50 +01:00
rr- e3ffe51979 ui/curses: let user scroll game view windows 2019-01-04 16:59:29 +01:00
rr- 0133802570 ui/curses: estimate time in progress bars 2019-01-04 16:40:30 +01:00
rr- b20c4b4b34 ui/curses: fix arrows not working in game view 2019-01-04 16:25:58 +01:00
rr- 5bdfc2ed25 ui/curses: fix scrollable visual glitches 2019-01-04 15:44:51 +01:00
rr- 8b38eeecac ui/curses: remove flickering 2019-01-04 00:56:55 +01:00
rr- 9db61f7191 ui/curses: implement game view widget focusing 2019-01-04 00:22:29 +01:00