mirror of
https://github.com/rr-/pq-cli.git
synced 2026-11-12 22:40:36 -07:00
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.
This commit is contained in:
40 files changed
+248
-236
No files matched your search
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import typing as T
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from xdg_base_dirs import xdg_config_home
|
from xdg_base_dirs import xdg_config_home
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def list_players(roster: Roster, file: T.Optional[T.Any] = None) -> None:
|
def list_players(roster: Roster, file: Any | None = None) -> None:
|
||||||
for i, player in enumerate(roster.players, start=1):
|
for i, player in enumerate(roster.players, start=1):
|
||||||
print(f"{i}. {player.name}", file=file)
|
print(f"{i}. {player.name}", file=file)
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ def main() -> None:
|
|||||||
list_players(roster)
|
list_players(roster)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
player: T.Optional[Player] = None
|
player: Player | None = None
|
||||||
if args.load_save:
|
if args.load_save:
|
||||||
try:
|
try:
|
||||||
player = list(roster.players)[args.load_save - 1]
|
player = list(roster.players)[args.load_save - 1]
|
||||||
|
|||||||
+3
-4
@@ -1,5 +1,4 @@
|
|||||||
import enum
|
import enum
|
||||||
import typing as T
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@@ -60,16 +59,16 @@ class EquipmentPreset:
|
|||||||
class Monster:
|
class Monster:
|
||||||
name: str
|
name: str
|
||||||
level: int
|
level: int
|
||||||
item: T.Optional[str]
|
item: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Race:
|
class Race:
|
||||||
name: str
|
name: str
|
||||||
attr: T.List[StatType]
|
attr: list[StatType]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Class:
|
class Class:
|
||||||
name: str
|
name: str
|
||||||
attr: T.List[StatType]
|
attr: list[StatType]
|
||||||
+43
-42
@@ -1,9 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import typing as T
|
from collections.abc import Callable, Sequence
|
||||||
from dataclasses import dataclass, fields
|
from dataclasses import dataclass, fields
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
from pqcli.config import (
|
from pqcli.config import (
|
||||||
Class,
|
Class,
|
||||||
@@ -23,24 +24,24 @@ class DataError(Exception):
|
|||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GameData:
|
class GameData:
|
||||||
spells: T.Tuple[str, ...]
|
spells: tuple[str, ...]
|
||||||
offense_attrib: T.Tuple[Modifier, ...]
|
offense_attrib: tuple[Modifier, ...]
|
||||||
defense_attrib: T.Tuple[Modifier, ...]
|
defense_attrib: tuple[Modifier, ...]
|
||||||
offense_bad: T.Tuple[Modifier, ...]
|
offense_bad: tuple[Modifier, ...]
|
||||||
defense_bad: T.Tuple[Modifier, ...]
|
defense_bad: tuple[Modifier, ...]
|
||||||
shields: T.Tuple[EquipmentPreset, ...]
|
shields: tuple[EquipmentPreset, ...]
|
||||||
armors: T.Tuple[EquipmentPreset, ...]
|
armors: tuple[EquipmentPreset, ...]
|
||||||
weapons: T.Tuple[EquipmentPreset, ...]
|
weapons: tuple[EquipmentPreset, ...]
|
||||||
specials: T.Tuple[str, ...]
|
specials: tuple[str, ...]
|
||||||
item_attrib: T.Tuple[str, ...]
|
item_attrib: tuple[str, ...]
|
||||||
item_ofs: T.Tuple[str, ...]
|
item_ofs: tuple[str, ...]
|
||||||
boring_items: T.Tuple[str, ...]
|
boring_items: tuple[str, ...]
|
||||||
monsters: T.Tuple[Monster, ...]
|
monsters: tuple[Monster, ...]
|
||||||
races: T.Tuple[Race, ...]
|
races: tuple[Race, ...]
|
||||||
classes: T.Tuple[Class, ...]
|
classes: tuple[Class, ...]
|
||||||
titles: T.Tuple[str, ...]
|
titles: tuple[str, ...]
|
||||||
impressive_titles: T.Tuple[str, ...]
|
impressive_titles: tuple[str, ...]
|
||||||
combat_verbs: T.Tuple[str, ...]
|
combat_verbs: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
DATA_FILES = tuple(field.name for field in fields(GameData))
|
DATA_FILES = tuple(field.name for field in fields(GameData))
|
||||||
@@ -55,8 +56,8 @@ class _Context:
|
|||||||
|
|
||||||
|
|
||||||
def _check_keys(
|
def _check_keys(
|
||||||
ctx: _Context, where: str, obj: T.Any, keys: T.Sequence[str]
|
ctx: _Context, where: str, obj: Any, keys: Sequence[str]
|
||||||
) -> T.Dict[str, T.Any]:
|
) -> dict[str, Any]:
|
||||||
if not isinstance(obj, dict):
|
if not isinstance(obj, dict):
|
||||||
raise ctx.error(f"{where} must be an object")
|
raise ctx.error(f"{where} must be an object")
|
||||||
missing = [key for key in keys if key not in obj]
|
missing = [key for key in keys if key not in obj]
|
||||||
@@ -68,19 +69,19 @@ def _check_keys(
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def _str(ctx: _Context, where: str, value: T.Any) -> str:
|
def _str(ctx: _Context, where: str, value: Any) -> str:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise ctx.error(f"{where} must be a string")
|
raise ctx.error(f"{where} must be a string")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _int(ctx: _Context, where: str, value: T.Any) -> int:
|
def _int(ctx: _Context, where: str, value: Any) -> int:
|
||||||
if isinstance(value, bool) or not isinstance(value, int):
|
if isinstance(value, bool) or not isinstance(value, int):
|
||||||
raise ctx.error(f"{where} must be an integer")
|
raise ctx.error(f"{where} must be an integer")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _stat(ctx: _Context, where: str, value: T.Any) -> StatType:
|
def _stat(ctx: _Context, where: str, value: Any) -> StatType:
|
||||||
_str(ctx, where, value)
|
_str(ctx, where, value)
|
||||||
for stat in StatType:
|
for stat in StatType:
|
||||||
if value.lower() in (stat.value.lower(), stat.name):
|
if value.lower() in (stat.value.lower(), stat.name):
|
||||||
@@ -89,18 +90,18 @@ def _stat(ctx: _Context, where: str, value: T.Any) -> StatType:
|
|||||||
raise ctx.error(f"{where}: unknown stat {value!r} (use one of {allowed})")
|
raise ctx.error(f"{where}: unknown stat {value!r} (use one of {allowed})")
|
||||||
|
|
||||||
|
|
||||||
def _parse_str(ctx: _Context, where: str, item: T.Any) -> str:
|
def _parse_str(ctx: _Context, where: str, item: Any) -> str:
|
||||||
return _str(ctx, where, item)
|
return _str(ctx, where, item)
|
||||||
|
|
||||||
|
|
||||||
def _parse_verb(ctx: _Context, where: str, item: T.Any) -> str:
|
def _parse_verb(ctx: _Context, where: str, item: Any) -> str:
|
||||||
verb = _str(ctx, where, item)
|
verb = _str(ctx, where, item)
|
||||||
if TARGET_PLACEHOLDER not in verb:
|
if TARGET_PLACEHOLDER not in verb:
|
||||||
raise ctx.error(f"{where} must contain {TARGET_PLACEHOLDER}")
|
raise ctx.error(f"{where} must contain {TARGET_PLACEHOLDER}")
|
||||||
return verb
|
return verb
|
||||||
|
|
||||||
|
|
||||||
def _parse_modifier(ctx: _Context, where: str, item: T.Any) -> Modifier:
|
def _parse_modifier(ctx: _Context, where: str, item: Any) -> Modifier:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
||||||
return Modifier(
|
return Modifier(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -108,7 +109,7 @@ def _parse_modifier(ctx: _Context, where: str, item: T.Any) -> Modifier:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_preset(ctx: _Context, where: str, item: T.Any) -> EquipmentPreset:
|
def _parse_preset(ctx: _Context, where: str, item: Any) -> EquipmentPreset:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
obj = _check_keys(ctx, where, item, ["name", "quality"])
|
||||||
return EquipmentPreset(
|
return EquipmentPreset(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -116,7 +117,7 @@ def _parse_preset(ctx: _Context, where: str, item: T.Any) -> EquipmentPreset:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_monster(ctx: _Context, where: str, item: T.Any) -> Monster:
|
def _parse_monster(ctx: _Context, where: str, item: Any) -> Monster:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "level", "item"])
|
obj = _check_keys(ctx, where, item, ["name", "level", "item"])
|
||||||
loot = obj["item"]
|
loot = obj["item"]
|
||||||
return Monster(
|
return Monster(
|
||||||
@@ -126,13 +127,13 @@ def _parse_monster(ctx: _Context, where: str, item: T.Any) -> Monster:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_attrs(ctx: _Context, where: str, value: T.Any) -> T.List[StatType]:
|
def _parse_attrs(ctx: _Context, where: str, value: Any) -> list[StatType]:
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ctx.error(f"{where} must be a list of stats")
|
raise ctx.error(f"{where} must be a list of stats")
|
||||||
return [_stat(ctx, f"{where}[{i}]", stat) for i, stat in enumerate(value)]
|
return [_stat(ctx, f"{where}[{i}]", stat) for i, stat in enumerate(value)]
|
||||||
|
|
||||||
|
|
||||||
def _parse_race(ctx: _Context, where: str, item: T.Any) -> Race:
|
def _parse_race(ctx: _Context, where: str, item: Any) -> Race:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
||||||
return Race(
|
return Race(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -140,7 +141,7 @@ def _parse_race(ctx: _Context, where: str, item: T.Any) -> Race:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_class(ctx: _Context, where: str, item: T.Any) -> Class:
|
def _parse_class(ctx: _Context, where: str, item: Any) -> Class:
|
||||||
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
obj = _check_keys(ctx, where, item, ["name", "attr"])
|
||||||
return Class(
|
return Class(
|
||||||
_str(ctx, f"{where}.name", obj["name"]),
|
_str(ctx, f"{where}.name", obj["name"]),
|
||||||
@@ -148,7 +149,7 @@ def _parse_class(ctx: _Context, where: str, item: T.Any) -> Class:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_PARSERS: T.Dict[str, T.Callable[[_Context, str, T.Any], T.Any]] = {
|
_PARSERS: dict[str, Callable[[_Context, str, Any], Any]] = {
|
||||||
"spells": _parse_str,
|
"spells": _parse_str,
|
||||||
"offense_attrib": _parse_modifier,
|
"offense_attrib": _parse_modifier,
|
||||||
"defense_attrib": _parse_modifier,
|
"defense_attrib": _parse_modifier,
|
||||||
@@ -171,7 +172,7 @@ _PARSERS: T.Dict[str, T.Callable[[_Context, str, T.Any], T.Any]] = {
|
|||||||
assert set(_PARSERS) == set(DATA_FILES)
|
assert set(_PARSERS) == set(DATA_FILES)
|
||||||
|
|
||||||
|
|
||||||
def _parse(name: str, path: str, text: str) -> T.Tuple[T.Any, ...]:
|
def _parse(name: str, path: str, text: str) -> tuple[Any, ...]:
|
||||||
ctx = _Context(path)
|
ctx = _Context(path)
|
||||||
try:
|
try:
|
||||||
value = json.loads(text)
|
value = json.loads(text)
|
||||||
@@ -185,26 +186,26 @@ def _parse(name: str, path: str, text: str) -> T.Tuple[T.Any, ...]:
|
|||||||
return tuple(parser(ctx, f"[{i}]", item) for i, item in enumerate(value))
|
return tuple(parser(ctx, f"[{i}]", item) for i, item in enumerate(value))
|
||||||
|
|
||||||
|
|
||||||
def _builtin_dir() -> T.Any:
|
def _builtin_dir() -> Any:
|
||||||
return resources.files("pqcli") / "data"
|
return resources.files("pqcli") / "data"
|
||||||
|
|
||||||
|
|
||||||
def _read_text(path: T.Any, display: str) -> str:
|
def _read_text(path: Any, display: str) -> str:
|
||||||
try:
|
try:
|
||||||
return T.cast(str, path.read_text(encoding="utf-8"))
|
return cast(str, path.read_text(encoding="utf-8"))
|
||||||
except UnicodeDecodeError as ex:
|
except UnicodeDecodeError as ex:
|
||||||
raise DataError(f"{display}: not valid UTF-8: {ex}") from ex
|
raise DataError(f"{display}: not valid UTF-8: {ex}") from ex
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
raise DataError(f"{display}: cannot read: {ex}") from ex
|
raise DataError(f"{display}: cannot read: {ex}") from ex
|
||||||
|
|
||||||
|
|
||||||
def load_game_data(override_dir: T.Optional[Path] = None) -> GameData:
|
def load_game_data(override_dir: Path | None = None) -> GameData:
|
||||||
"""Load the built-in word lists, replacing any that override_dir has.
|
"""Load the built-in word lists, replacing any that override_dir has.
|
||||||
|
|
||||||
A missing override_dir is fine. Each <name>.json in it replaces the
|
A missing override_dir is fine. Each <name>.json in it replaces the
|
||||||
whole built-in list of that name.
|
whole built-in list of that name.
|
||||||
"""
|
"""
|
||||||
overrides: T.Set[str] = set()
|
overrides: set[str] = set()
|
||||||
if override_dir is not None and override_dir.exists():
|
if override_dir is not None and override_dir.exists():
|
||||||
if not override_dir.is_dir():
|
if not override_dir.is_dir():
|
||||||
raise DataError(f"{override_dir}: not a directory")
|
raise DataError(f"{override_dir}: not a directory")
|
||||||
@@ -218,11 +219,11 @@ def load_game_data(override_dir: T.Optional[Path] = None) -> GameData:
|
|||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
values: T.Dict[str, T.Tuple[T.Any, ...]] = {}
|
values: dict[str, tuple[Any, ...]] = {}
|
||||||
for name in DATA_FILES:
|
for name in DATA_FILES:
|
||||||
if name in overrides:
|
if name in overrides:
|
||||||
assert override_dir is not None
|
assert override_dir is not None
|
||||||
source: T.Any = override_dir / f"{name}.json"
|
source: Any = override_dir / f"{name}.json"
|
||||||
display = str(source)
|
display = str(source)
|
||||||
else:
|
else:
|
||||||
source = _builtin_dir() / f"{name}.json"
|
source = _builtin_dir() / f"{name}.json"
|
||||||
@@ -231,7 +232,7 @@ def load_game_data(override_dir: T.Optional[Path] = None) -> GameData:
|
|||||||
return GameData(**values)
|
return GameData(**values)
|
||||||
|
|
||||||
|
|
||||||
def dump_builtin_data(target_dir: Path) -> T.List[Path]:
|
def dump_builtin_data(target_dir: Path) -> list[Path]:
|
||||||
"""Copy the built-in data files to target_dir, never overwriting."""
|
"""Copy the built-in data files to target_dir, never overwriting."""
|
||||||
targets = [target_dir / f"{name}.json" for name in DATA_FILES]
|
targets = [target_dir / f"{name}.json" for name in DATA_FILES]
|
||||||
existing = [path for path in targets if path.exists()]
|
existing = [path for path in targets if path.exists()]
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import typing as T
|
|
||||||
|
|
||||||
from pqcli import random
|
from pqcli import random
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ def definite(subject: str, qty: int) -> str:
|
|||||||
return "the " + subject
|
return "the " + subject
|
||||||
|
|
||||||
|
|
||||||
def prefix(a: T.List[str], m: int, subject: str, sep: str = " ") -> str:
|
def prefix(a: list[str], m: int, subject: str, sep: str = " ") -> str:
|
||||||
m = abs(m)
|
m = abs(m)
|
||||||
if m < 1 or m > len(a):
|
if m < 1 or m > len(a):
|
||||||
return subject
|
return subject
|
||||||
|
|||||||
+53
-54
@@ -3,9 +3,10 @@ import functools
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import typing as T
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Callable, Iterator, Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
from pqcli import random
|
from pqcli import random
|
||||||
from pqcli.config import (
|
from pqcli.config import (
|
||||||
@@ -35,23 +36,21 @@ from pqcli.random import Alea, RngState
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_SIGNALS: T.Dict[
|
_SIGNALS: dict[tuple["SignalMixin", str], list[Callable[..., Any]]] = (
|
||||||
T.Tuple["SignalMixin", str], T.List[T.Callable[..., T.Any]]
|
defaultdict(list)
|
||||||
] = defaultdict(list)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SignalMixin:
|
class SignalMixin:
|
||||||
def emit(self, signal_name: str, *user_data: T.Any) -> None:
|
def emit(self, signal_name: str, *user_data: Any) -> None:
|
||||||
for callback in _SIGNALS[self, signal_name]:
|
for callback in _SIGNALS[self, signal_name]:
|
||||||
callback(*user_data)
|
callback(*user_data)
|
||||||
|
|
||||||
def connect(
|
def connect(self, signal_name: str, callback: Callable[..., Any]) -> None:
|
||||||
self, signal_name: str, callback: T.Callable[..., T.Any]
|
|
||||||
) -> None:
|
|
||||||
_SIGNALS[self, signal_name].append(callback)
|
_SIGNALS[self, signal_name].append(callback)
|
||||||
|
|
||||||
def disconnect(
|
def disconnect(
|
||||||
self, signal_name: str, callback: T.Callable[..., T.Any]
|
self, signal_name: str, callback: Callable[..., Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
idx = _SIGNALS[self, signal_name].index(callback)
|
idx = _SIGNALS[self, signal_name].index(callback)
|
||||||
@@ -108,10 +107,10 @@ class Bar(SignalMixin):
|
|||||||
class Stats(SignalMixin):
|
class Stats(SignalMixin):
|
||||||
signals = ["change"]
|
signals = ["change"]
|
||||||
|
|
||||||
def __init__(self, values: T.Dict[StatType, int]) -> None:
|
def __init__(self, values: dict[StatType, int]) -> None:
|
||||||
self._values = values
|
self._values = values
|
||||||
|
|
||||||
def __iter__(self) -> T.Iterator[T.Tuple[StatType, int]]:
|
def __iter__(self) -> Iterator[tuple[StatType, int]]:
|
||||||
return iter(self._values.items())
|
return iter(self._values.items())
|
||||||
|
|
||||||
def __getitem__(self, stat: StatType) -> int:
|
def __getitem__(self, stat: StatType) -> int:
|
||||||
@@ -137,22 +136,22 @@ class QuestBook(SignalMixin):
|
|||||||
signals = ["start_act", "start_quest"]
|
signals = ["start_act", "start_quest"]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._quests: T.List[str] = []
|
self._quests: list[str] = []
|
||||||
self._act = 0
|
self._act = 0
|
||||||
self.plot_bar = Bar(max_=1)
|
self.plot_bar = Bar(max_=1)
|
||||||
self.quest_bar = Bar(max_=1)
|
self.quest_bar = Bar(max_=1)
|
||||||
self.monster: T.Optional[Monster] = None
|
self.monster: Monster | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def act(self) -> int:
|
def act(self) -> int:
|
||||||
return self._act
|
return self._act
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def quests(self) -> T.List[str]:
|
def quests(self) -> list[str]:
|
||||||
return self._quests
|
return self._quests
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_quest(self) -> T.Optional[str]:
|
def current_quest(self) -> str | None:
|
||||||
if self._quests:
|
if self._quests:
|
||||||
return self._quests[-1]
|
return self._quests[-1]
|
||||||
return None
|
return None
|
||||||
@@ -178,7 +177,7 @@ class Inventory(SignalMixin):
|
|||||||
|
|
||||||
def __init__(self, capacity: int = 0) -> None:
|
def __init__(self, capacity: int = 0) -> None:
|
||||||
self._gold = 0
|
self._gold = 0
|
||||||
self._items: T.List[InventoryItem] = []
|
self._items: list[InventoryItem] = []
|
||||||
self.encum_bar = Bar(max_=capacity)
|
self.encum_bar = Bar(max_=capacity)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -188,7 +187,7 @@ class Inventory(SignalMixin):
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self._items)
|
return len(self._items)
|
||||||
|
|
||||||
def __iter__(self) -> T.Iterator[InventoryItem]:
|
def __iter__(self) -> Iterator[InventoryItem]:
|
||||||
return iter(self._items)
|
return iter(self._items)
|
||||||
|
|
||||||
def __getitem__(self, idx: int) -> InventoryItem:
|
def __getitem__(self, idx: int) -> InventoryItem:
|
||||||
@@ -234,16 +233,16 @@ class Equipment(SignalMixin):
|
|||||||
signals = ["change"]
|
signals = ["change"]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._items: T.Dict[EquipmentType, str] = {
|
self._items: dict[EquipmentType, str] = {
|
||||||
EquipmentType.weapon: "Sharp Rock",
|
EquipmentType.weapon: "Sharp Rock",
|
||||||
EquipmentType.hauberk: "-3 Burlap",
|
EquipmentType.hauberk: "-3 Burlap",
|
||||||
}
|
}
|
||||||
self.best = self[EquipmentType.weapon]
|
self.best = self[EquipmentType.weapon]
|
||||||
|
|
||||||
def __iter__(self) -> T.Iterator[T.Tuple[EquipmentType, str]]:
|
def __iter__(self) -> Iterator[tuple[EquipmentType, str]]:
|
||||||
return iter(self._items.items())
|
return iter(self._items.items())
|
||||||
|
|
||||||
def __getitem__(self, equipment_type: EquipmentType) -> T.Optional[str]:
|
def __getitem__(self, equipment_type: EquipmentType) -> str | None:
|
||||||
return self._items.get(equipment_type, None)
|
return self._items.get(equipment_type, None)
|
||||||
|
|
||||||
def put(self, equipment_type: EquipmentType, item_name: str) -> None:
|
def put(self, equipment_type: EquipmentType, item_name: str) -> None:
|
||||||
@@ -267,9 +266,9 @@ class SpellBook(SignalMixin):
|
|||||||
signals = ["add", "change"]
|
signals = ["add", "change"]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._spells: T.List[Spell] = []
|
self._spells: list[Spell] = []
|
||||||
|
|
||||||
def __iter__(self) -> T.Iterator[Spell]:
|
def __iter__(self) -> Iterator[Spell]:
|
||||||
return iter(self._spells)
|
return iter(self._spells)
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
@@ -292,7 +291,7 @@ class SpellBook(SignalMixin):
|
|||||||
self.emit("add", spell)
|
self.emit("add", spell)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def best(self) -> T.Optional[Spell]:
|
def best(self) -> Spell | None:
|
||||||
if not self._spells:
|
if not self._spells:
|
||||||
return None
|
return None
|
||||||
return max(self._spells, key=lambda spell: spell.level)
|
return max(self._spells, key=lambda spell: spell.level)
|
||||||
@@ -306,7 +305,7 @@ class BaseTask:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class KillTask(BaseTask):
|
class KillTask(BaseTask):
|
||||||
monster: T.Optional[Monster] = None
|
monster: Monster | None = None
|
||||||
|
|
||||||
|
|
||||||
class BuyTask(BaseTask):
|
class BuyTask(BaseTask):
|
||||||
@@ -347,7 +346,7 @@ class Player(SignalMixin):
|
|||||||
race: Race,
|
race: Race,
|
||||||
class_: Class,
|
class_: Class,
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
rng: T.Optional[Alea] = None,
|
rng: Alea | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.birthday: datetime.datetime = birthday
|
self.birthday: datetime.datetime = birthday
|
||||||
@@ -366,10 +365,10 @@ class Player(SignalMixin):
|
|||||||
self.spell_book = SpellBook()
|
self.spell_book = SpellBook()
|
||||||
|
|
||||||
self.task_bar = Bar(max_=1)
|
self.task_bar = Bar(max_=1)
|
||||||
self.task: T.Optional[BaseTask] = None
|
self.task: BaseTask | None = None
|
||||||
self.queue: T.List[BaseTask] = []
|
self.queue: list[BaseTask] = []
|
||||||
|
|
||||||
def __setstate__(self, obj: T.Any) -> None:
|
def __setstate__(self, obj: Any) -> None:
|
||||||
self.__dict__.update(obj)
|
self.__dict__.update(obj)
|
||||||
self.elapsed = obj.get("elapsed", 0.0)
|
self.elapsed = obj.get("elapsed", 0.0)
|
||||||
if "rng" not in obj:
|
if "rng" not in obj:
|
||||||
@@ -402,7 +401,7 @@ class Player(SignalMixin):
|
|||||||
self.emit("level_up")
|
self.emit("level_up")
|
||||||
|
|
||||||
def win_stat(self) -> bool:
|
def win_stat(self) -> bool:
|
||||||
chosen_stat: T.Optional[StatType] = None
|
chosen_stat: StatType | None = None
|
||||||
|
|
||||||
if random.odds(1, 2):
|
if random.odds(1, 2):
|
||||||
chosen_stat = random.choice(list(StatType))
|
chosen_stat = random.choice(list(StatType))
|
||||||
@@ -437,9 +436,9 @@ class Player(SignalMixin):
|
|||||||
def win_equipment(self, data: GameData) -> None:
|
def win_equipment(self, data: GameData) -> None:
|
||||||
choice = random.choice(list(EquipmentType))
|
choice = random.choice(list(EquipmentType))
|
||||||
|
|
||||||
stuff: T.Sequence[EquipmentPreset]
|
stuff: Sequence[EquipmentPreset]
|
||||||
better: T.Sequence[Modifier]
|
better: Sequence[Modifier]
|
||||||
worse: T.Sequence[Modifier]
|
worse: Sequence[Modifier]
|
||||||
|
|
||||||
if choice == EquipmentType.weapon:
|
if choice == EquipmentType.weapon:
|
||||||
stuff = data.weapons
|
stuff = data.weapons
|
||||||
@@ -566,7 +565,7 @@ class Simulation:
|
|||||||
self.player.task_bar.max_ / 1000
|
self.player.task_bar.max_ / 1000
|
||||||
)
|
)
|
||||||
elif self.player.quest_book.act == 0 and not isinstance(
|
elif self.player.quest_book.act == 0 and not isinstance(
|
||||||
self.player.task, (LoadingTask, PlotTask)
|
self.player.task, LoadingTask | PlotTask
|
||||||
):
|
):
|
||||||
# the Prologue story tasks fill the plot bar
|
# the Prologue story tasks fill the plot bar
|
||||||
self.player.quest_book.plot_bar.increment(
|
self.player.quest_book.plot_bar.increment(
|
||||||
@@ -599,7 +598,7 @@ class Simulation:
|
|||||||
self.player.inventory.add_gold(-self.player.equip_price())
|
self.player.inventory.add_gold(-self.player.equip_price())
|
||||||
self.player.win_equipment(self.data)
|
self.player.win_equipment(self.data)
|
||||||
|
|
||||||
elif isinstance(self.player.task, (HeadingToMarketTask, SellTask)):
|
elif isinstance(self.player.task, HeadingToMarketTask | SellTask):
|
||||||
if isinstance(self.player.task, SellTask):
|
if isinstance(self.player.task, SellTask):
|
||||||
item = self.player.inventory[0]
|
item = self.player.inventory[0]
|
||||||
amount = item.quantity * self.player.level
|
amount = item.quantity * self.player.level
|
||||||
@@ -629,7 +628,7 @@ class Simulation:
|
|||||||
self.player.set_task(
|
self.player.set_task(
|
||||||
HeadingToMarketTask("Heading to market to sell loot", 4000)
|
HeadingToMarketTask("Heading to market to sell loot", 4000)
|
||||||
)
|
)
|
||||||
elif not isinstance(old, (KillTask, HeadingToKillingFieldsTask)):
|
elif not isinstance(old, KillTask | HeadingToKillingFieldsTask):
|
||||||
if self.player.inventory.gold > self.player.equip_price():
|
if self.player.inventory.gold > self.player.equip_price():
|
||||||
self.player.set_task(
|
self.player.set_task(
|
||||||
BuyTask(
|
BuyTask(
|
||||||
@@ -838,35 +837,35 @@ def special_item(data: GameData) -> str:
|
|||||||
return (
|
return (
|
||||||
interesting_item(data)
|
interesting_item(data)
|
||||||
+ " of "
|
+ " of "
|
||||||
+ T.cast(str, random.choice(data.item_ofs))
|
+ cast(str, random.choice(data.item_ofs))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def interesting_item(data: GameData) -> str:
|
def interesting_item(data: GameData) -> str:
|
||||||
return (
|
return (
|
||||||
T.cast(str, random.choice(data.item_attrib))
|
cast(str, random.choice(data.item_attrib))
|
||||||
+ " "
|
+ " "
|
||||||
+ T.cast(str, random.choice(data.specials))
|
+ cast(str, random.choice(data.specials))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def boring_item(data: GameData) -> str:
|
def boring_item(data: GameData) -> str:
|
||||||
return T.cast(str, random.choice(data.boring_items))
|
return cast(str, random.choice(data.boring_items))
|
||||||
|
|
||||||
|
|
||||||
def impressive_guy(data: GameData) -> str:
|
def impressive_guy(data: GameData) -> str:
|
||||||
if random.below(2):
|
if random.below(2):
|
||||||
title = T.cast(str, random.choice(data.impressive_titles))
|
title = cast(str, random.choice(data.impressive_titles))
|
||||||
race = T.cast(Race, random.choice(data.races))
|
race = cast(Race, random.choice(data.races))
|
||||||
return f"the {title} of the {plural(race.name)}"
|
return f"the {title} of the {plural(race.name)}"
|
||||||
title = T.cast(str, random.choice(data.impressive_titles))
|
title = cast(str, random.choice(data.impressive_titles))
|
||||||
return f"{title} {generate_name()} of {generate_name()}"
|
return f"{title} {generate_name()} of {generate_name()}"
|
||||||
|
|
||||||
|
|
||||||
def unnamed_monster(data: GameData, level: int, iterations: int) -> Monster:
|
def unnamed_monster(data: GameData, level: int, iterations: int) -> Monster:
|
||||||
result = T.cast(Monster, random.choice(data.monsters))
|
result = cast(Monster, random.choice(data.monsters))
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
alternative = T.cast(Monster, random.choice(data.monsters))
|
alternative = cast(Monster, random.choice(data.monsters))
|
||||||
if abs(level - alternative.level) < abs(level - result.level):
|
if abs(level - alternative.level) < abs(level - result.level):
|
||||||
result = alternative
|
result = alternative
|
||||||
return result
|
return result
|
||||||
@@ -878,18 +877,18 @@ def named_monster(data: GameData, level: int) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def pick_equipment(
|
def pick_equipment(
|
||||||
source: T.Sequence[EquipmentPreset], goal: int
|
source: Sequence[EquipmentPreset], goal: int
|
||||||
) -> EquipmentPreset:
|
) -> EquipmentPreset:
|
||||||
result = T.cast(EquipmentPreset, random.choice(source))
|
result = cast(EquipmentPreset, random.choice(source))
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
alternative = T.cast(EquipmentPreset, random.choice(source))
|
alternative = cast(EquipmentPreset, random.choice(source))
|
||||||
if abs(goal - alternative.quality) < abs(goal - result.quality):
|
if abs(goal - alternative.quality) < abs(goal - result.quality):
|
||||||
result = alternative
|
result = alternative
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def monster_task(
|
def monster_task(
|
||||||
data: GameData, player_level: int, quest_monster: T.Optional[Monster]
|
data: GameData, player_level: int, quest_monster: Monster | None
|
||||||
) -> KillTask:
|
) -> KillTask:
|
||||||
level = player_level
|
level = player_level
|
||||||
for _ in range(level):
|
for _ in range(level):
|
||||||
@@ -899,7 +898,7 @@ def monster_task(
|
|||||||
level = 1
|
level = 1
|
||||||
|
|
||||||
is_definite = False
|
is_definite = False
|
||||||
monster: T.Optional[Monster] = None
|
monster: Monster | None = None
|
||||||
if random.odds(1, 25):
|
if random.odds(1, 25):
|
||||||
# use an NPC every once in a while
|
# use an NPC every once in a while
|
||||||
race = random.choice(data.races)
|
race = random.choice(data.races)
|
||||||
@@ -968,7 +967,7 @@ def monster_task(
|
|||||||
verb = (
|
verb = (
|
||||||
data.combat_verbs[0]
|
data.combat_verbs[0]
|
||||||
if len(data.combat_verbs) == 1
|
if len(data.combat_verbs) == 1
|
||||||
else T.cast(str, random.choice(data.combat_verbs))
|
else cast(str, random.choice(data.combat_verbs))
|
||||||
)
|
)
|
||||||
description = verb.replace(TARGET_PLACEHOLDER, result)
|
description = verb.replace(TARGET_PLACEHOLDER, result)
|
||||||
return KillTask(description, duration, monster=monster)
|
return KillTask(description, duration, monster=monster)
|
||||||
@@ -982,9 +981,9 @@ class _Roll:
|
|||||||
|
|
||||||
|
|
||||||
class StatsBuilder:
|
class StatsBuilder:
|
||||||
def __init__(self, rng: T.Optional[Alea] = None) -> None:
|
def __init__(self, rng: Alea | None = None) -> None:
|
||||||
self._rng = rng or Alea()
|
self._rng = rng or Alea()
|
||||||
self.history: T.List[_Roll] = []
|
self.history: list[_Roll] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def seed(self) -> RngState:
|
def seed(self) -> RngState:
|
||||||
@@ -998,7 +997,7 @@ class StatsBuilder:
|
|||||||
def roll(self) -> Stats:
|
def roll(self) -> Stats:
|
||||||
seed = self._rng.state()
|
seed = self._rng.state()
|
||||||
with random.using(self._rng):
|
with random.using(self._rng):
|
||||||
values: T.Dict[StatType, int] = {
|
values: dict[StatType, int] = {
|
||||||
stat: 3 + random.below(6) + random.below(6) + random.below(6)
|
stat: 3 + random.below(6) + random.below(6) + random.below(6)
|
||||||
for stat in PRIME_STATS
|
for stat in PRIME_STATS
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1023,7 @@ def create_player(
|
|||||||
race: Race,
|
race: Race,
|
||||||
class_: Class,
|
class_: Class,
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
seed: T.Optional[RngState] = None,
|
seed: RngState | None = None,
|
||||||
) -> Player:
|
) -> Player:
|
||||||
player = Player(
|
player = Player(
|
||||||
birthday=datetime.datetime.now(),
|
birthday=datetime.datetime.now(),
|
||||||
|
|||||||
+7
-6
@@ -1,8 +1,9 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import time
|
import time
|
||||||
import typing as T
|
from collections.abc import Callable, Iterator, Sequence
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
RngState = T.Tuple[float, float, float, int]
|
RngState = tuple[float, float, float, int]
|
||||||
|
|
||||||
_2_POW_32 = 0x100000000
|
_2_POW_32 = 0x100000000
|
||||||
_2_POW_MINUS_32 = 2.3283064365386963e-10
|
_2_POW_MINUS_32 = 2.3283064365386963e-10
|
||||||
@@ -13,7 +14,7 @@ def _to_uint32(value: float) -> float:
|
|||||||
return float(int(value) % _2_POW_32)
|
return float(int(value) % _2_POW_32)
|
||||||
|
|
||||||
|
|
||||||
def _mash() -> T.Callable[[str], float]:
|
def _mash() -> Callable[[str], float]:
|
||||||
n = float(0xEFC8249D)
|
n = float(0xEFC8249D)
|
||||||
|
|
||||||
def mash(data: str) -> float:
|
def mash(data: str) -> float:
|
||||||
@@ -90,7 +91,7 @@ def seed(source: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def using(rng: Alea) -> T.Iterator[None]:
|
def using(rng: Alea) -> Iterator[None]:
|
||||||
"""Draw from rng instead of the shared generator inside the block."""
|
"""Draw from rng instead of the shared generator inside the block."""
|
||||||
global _current
|
global _current
|
||||||
previous = _current
|
previous = _current
|
||||||
@@ -101,11 +102,11 @@ def using(rng: Alea) -> T.Iterator[None]:
|
|||||||
_current = previous
|
_current = previous
|
||||||
|
|
||||||
|
|
||||||
def choice(source: T.Sequence[T.Any]) -> T.Any:
|
def choice(source: Sequence[Any]) -> Any:
|
||||||
return source[below(len(source))]
|
return source[below(len(source))]
|
||||||
|
|
||||||
|
|
||||||
def choice_low(source: T.Sequence[T.Any]) -> T.Any:
|
def choice_low(source: Sequence[Any]) -> Any:
|
||||||
return source[below_low(len(source))]
|
return source[below_low(len(source))]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import pickle
|
import pickle
|
||||||
import typing as T
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -8,13 +7,13 @@ from pqcli.mechanic import Player
|
|||||||
|
|
||||||
|
|
||||||
class Roster:
|
class Roster:
|
||||||
def __init__(self, path: Path, players: T.List[Player]) -> None:
|
def __init__(self, path: Path, players: list[Player]) -> None:
|
||||||
self.path = path
|
self.path = path
|
||||||
self.players = players
|
self.players = players
|
||||||
self._last_save = datetime.now()
|
self._last_save = datetime.now()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(path: T.Union[str, Path]) -> "Roster":
|
def load(path: str | Path) -> "Roster":
|
||||||
real_path = Path(path)
|
real_path = Path(path)
|
||||||
if real_path.exists():
|
if real_path.exists():
|
||||||
return Roster(
|
return Roster(
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
import abc
|
import abc
|
||||||
import argparse
|
import argparse
|
||||||
import typing as T
|
|
||||||
|
|
||||||
from pqcli.game_data import GameData
|
from pqcli.game_data import GameData
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
@@ -11,7 +10,7 @@ class BaseUserInterface(abc.ABC):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
roster: Roster,
|
roster: Roster,
|
||||||
player: T.Optional[Player],
|
player: Player | None,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
data: GameData,
|
data: GameData,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import typing as T
|
from collections.abc import Sequence
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pqcli import lingo
|
from pqcli import lingo
|
||||||
from pqcli.config import PRIME_STATS
|
from pqcli.config import PRIME_STATS
|
||||||
@@ -36,13 +37,13 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
roster: Roster,
|
roster: Roster,
|
||||||
player: T.Optional[Player],
|
player: Player | None,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
data: GameData,
|
data: GameData,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(roster, player, args, data)
|
super().__init__(roster, player, args, data)
|
||||||
|
|
||||||
def signal_handler(sig: T.Any, frame: T.Any) -> None:
|
def signal_handler(sig: Any, frame: Any) -> None:
|
||||||
logging.info("Quitting")
|
logging.info("Quitting")
|
||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
@@ -103,7 +104,7 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
self.quit()
|
self.quit()
|
||||||
break
|
break
|
||||||
|
|
||||||
def create_player(self, *, auto_play: bool = True) -> T.Optional[Player]:
|
def create_player(self, *, auto_play: bool = True) -> Player | None:
|
||||||
name = input("Name your new character: ")
|
name = input("Name your new character: ")
|
||||||
if not name:
|
if not name:
|
||||||
print("Cancelled.")
|
print("Cancelled.")
|
||||||
@@ -196,7 +197,7 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
|
|
||||||
def choose_player(self) -> T.Optional[Player]:
|
def choose_player(self) -> Player | None:
|
||||||
if not self.roster.players:
|
if not self.roster.players:
|
||||||
print("No characters to choose from!")
|
print("No characters to choose from!")
|
||||||
return None
|
return None
|
||||||
@@ -216,9 +217,9 @@ class BasicUserInterface(BaseUserInterface):
|
|||||||
|
|
||||||
def menu(
|
def menu(
|
||||||
self,
|
self,
|
||||||
options: T.Sequence[T.Tuple[T.Any, str]],
|
options: Sequence[tuple[Any, str]],
|
||||||
title: T.Optional[str] = None,
|
title: str | None = None,
|
||||||
) -> T.Any:
|
) -> Any:
|
||||||
print()
|
print()
|
||||||
if title:
|
if title:
|
||||||
print(f"{title}:")
|
print(f"{title}:")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import curses
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
import typing as T
|
from typing import Any, cast
|
||||||
|
|
||||||
from pqcli import lingo
|
from pqcli import lingo
|
||||||
from pqcli.config import Class, Race
|
from pqcli.config import Class, Race
|
||||||
@@ -68,7 +68,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
curses.init_pair(COLOR_PROGRESSBAR, 0, 9)
|
curses.init_pair(COLOR_PROGRESSBAR, 0, 9)
|
||||||
curses.init_pair(COLOR_HIGHLIGHT, 3, -1)
|
curses.init_pair(COLOR_HIGHLIGHT, 3, -1)
|
||||||
|
|
||||||
def signal_handler(sig: T.Any, frame: T.Any) -> None:
|
def signal_handler(sig: Any, frame: Any) -> None:
|
||||||
if self.args.use_saves:
|
if self.args.use_saves:
|
||||||
self.roster.save()
|
self.roster.save()
|
||||||
exit(0)
|
exit(0)
|
||||||
@@ -91,7 +91,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
self._switch_to_roster_view()
|
self._switch_to_roster_view()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
key = T.cast(int, self._screen.getch())
|
key = cast(int, self._screen.getch())
|
||||||
|
|
||||||
if key == curses.ERR:
|
if key == curses.ERR:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@@ -115,7 +115,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
self._switch_view(self._roster_view)
|
self._switch_view(self._roster_view)
|
||||||
|
|
||||||
def _switch_to_create_char_name_view(
|
def _switch_to_create_char_name_view(
|
||||||
self, player_name: T.Optional[str] = None
|
self, player_name: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
view = ChooseCharacterNameView(self._screen, player_name)
|
view = ChooseCharacterNameView(self._screen, player_name)
|
||||||
view.on_cancel += self._switch_to_roster_view
|
view.on_cancel += self._switch_to_roster_view
|
||||||
@@ -123,7 +123,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
self._switch_view(view)
|
self._switch_view(view)
|
||||||
|
|
||||||
def _switch_to_create_char_race_view(
|
def _switch_to_create_char_race_view(
|
||||||
self, player_name: str, race: T.Optional[Race] = None
|
self, player_name: str, race: Race | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
view = ChooseCharacterRaceView(self._screen, self.data.races, race)
|
view = ChooseCharacterRaceView(self._screen, self.data.races, race)
|
||||||
view.on_cancel += lambda: self._switch_to_create_char_name_view(
|
view.on_cancel += lambda: self._switch_to_create_char_name_view(
|
||||||
@@ -135,7 +135,7 @@ class CursesUserInterface(BaseUserInterface):
|
|||||||
self._switch_view(view)
|
self._switch_view(view)
|
||||||
|
|
||||||
def _switch_to_create_char_class_view(
|
def _switch_to_create_char_class_view(
|
||||||
self, player_name: str, race: Race, class_: T.Optional[Class] = None
|
self, player_name: str, race: Race, class_: Class | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
view = ChooseCharacterClassView(
|
view = ChooseCharacterClassView(
|
||||||
self._screen, self.data.classes, class_
|
self._screen, self.data.classes, class_
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import typing as T
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class EventHandler:
|
class EventHandler:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.callbacks: T.List[T.Callable[..., T.Any]] = []
|
self.callbacks: list[Callable[..., Any]] = []
|
||||||
|
|
||||||
def __call__(self, *args: T.Any, **kwargs: T.Any) -> None:
|
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
for callback in self.callbacks:
|
for callback in self.callbacks:
|
||||||
callback(*args, **kwargs)
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
def __iadd__(self, callback: T.Callable[..., T.Any]) -> "EventHandler":
|
def __iadd__(self, callback: Callable[..., Any]) -> "EventHandler":
|
||||||
self.callbacks.append(callback)
|
self.callbacks.append(callback)
|
||||||
return self
|
return self
|
||||||
+17
-4
@@ -1,7 +1,8 @@
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
import typing as T
|
from collections.abc import Callable, Iterable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, TypeVar, overload
|
||||||
|
|
||||||
KEYS_CYCLE = {curses.ascii.TAB}
|
KEYS_CYCLE = {curses.ascii.TAB}
|
||||||
KEYS_DOWN = set(map(ord, "jJ")) | {curses.KEY_DOWN}
|
KEYS_DOWN = set(map(ord, "jJ")) | {curses.KEY_DOWN}
|
||||||
@@ -13,10 +14,22 @@ KEYS_CANCEL = set(map(ord, "qQ")) | {curses.ascii.ESC}
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Choice:
|
class Choice:
|
||||||
keys: T.List[int]
|
keys: list[int]
|
||||||
desc: str
|
desc: str
|
||||||
callback: T.Callable[..., T.Any]
|
callback: Callable[..., Any]
|
||||||
|
|
||||||
|
|
||||||
def first(source: T.Iterable[T.Any], default: T.Any = None) -> T.Any:
|
ItemT = TypeVar("ItemT")
|
||||||
|
DefaultT = TypeVar("DefaultT")
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def first(source: Iterable[ItemT]) -> ItemT | None: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def first(source: Iterable[ItemT], default: DefaultT) -> ItemT | DefaultT: ...
|
||||||
|
|
||||||
|
|
||||||
|
def first(source: Iterable[Any], default: Any = None) -> Any:
|
||||||
return next(iter(source), default)
|
return next(iter(source), default)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class BaseView:
|
class BaseView:
|
||||||
def __init__(self, screen: T.Any) -> None:
|
def __init__(self, screen: Any) -> None:
|
||||||
self.screen = screen
|
self.screen = screen
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import functools
|
import functools
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.lingo import act_name, to_roman
|
from pqcli.lingo import act_name, to_roman
|
||||||
from pqcli.roster import Roster
|
from pqcli.roster import Roster
|
||||||
@@ -8,11 +8,11 @@ from pqcli.ui.curses.views.menu_view import MenuView
|
|||||||
|
|
||||||
|
|
||||||
class ChooseCharacterView(MenuView):
|
class ChooseCharacterView(MenuView):
|
||||||
def __init__(self, screen: T.Any, roster: Roster, title: str) -> None:
|
def __init__(self, screen: Any, roster: Roster, title: str) -> None:
|
||||||
super().__init__(screen, title)
|
super().__init__(screen, title)
|
||||||
|
|
||||||
for y, player in enumerate(roster.players, 1):
|
for y, player in enumerate(roster.players, 1):
|
||||||
key: T.Optional[str] = str(y % 10) if y <= 10 else None
|
key: str | None = str(y % 10) if y <= 10 else None
|
||||||
|
|
||||||
best_equip = player.equipment.best
|
best_equip = player.equipment.best
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from pqcli.ui.curses.util import Choice
|
from pqcli.ui.curses.util import Choice
|
||||||
@@ -8,14 +8,14 @@ from .base_view import BaseView
|
|||||||
|
|
||||||
|
|
||||||
class ConfirmView(BaseView):
|
class ConfirmView(BaseView):
|
||||||
def __init__(self, screen: T.Any, title: str) -> None:
|
def __init__(self, screen: Any, title: str) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.title = f"{title}:"
|
self.title = f"{title}:"
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler()
|
||||||
|
|
||||||
self.choices: T.List[Choice] = [
|
self.choices: list[Choice] = [
|
||||||
Choice(
|
Choice(
|
||||||
keys=list(map(ord, "yY")),
|
keys=list(map(ord, "yY")),
|
||||||
desc="[Y] Yes",
|
desc="[Y] Yes",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
import typing as T
|
from collections.abc import Sequence
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.config import Class
|
from pqcli.config import Class
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
||||||
@@ -9,9 +10,9 @@ from pqcli.ui.curses.views.menu_view import MenuView
|
|||||||
class ChooseCharacterClassView(MenuView):
|
class ChooseCharacterClassView(MenuView):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: T.Any,
|
screen: Any,
|
||||||
classes: T.Sequence[Class],
|
classes: Sequence[Class],
|
||||||
class_: T.Optional[Class] = None,
|
class_: Class | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
screen,
|
screen,
|
||||||
@@ -20,7 +21,7 @@ class ChooseCharacterClassView(MenuView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for y, class_ in enumerate(classes, 1):
|
for y, class_ in enumerate(classes, 1):
|
||||||
key: T.Optional[str] = str(y % 10) if y <= 10 else None
|
key: str | None = str(y % 10) if y <= 10 else None
|
||||||
self._choices.append(
|
self._choices.append(
|
||||||
Choice(
|
Choice(
|
||||||
keys=[ord(key)] if key is not None else [],
|
keys=[ord(key)] if key is not None else [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import generate_name
|
from pqcli.mechanic import generate_name
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
@@ -10,17 +10,15 @@ from pqcli.ui.curses.widgets.focusable import focus_standout
|
|||||||
|
|
||||||
|
|
||||||
class ChooseCharacterNameView(BaseView):
|
class ChooseCharacterNameView(BaseView):
|
||||||
def __init__(
|
def __init__(self, screen: Any, character_name: str | None = None) -> None:
|
||||||
self, screen: T.Any, character_name: T.Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler()
|
||||||
|
|
||||||
self._active_widget = 0
|
self._active_widget = 0
|
||||||
self._win: T.Optional[T.Any] = None
|
self._win: Any | None = None
|
||||||
self._text_win: T.Optional[T.Any] = None
|
self._text_win: Any | None = None
|
||||||
self._text = character_name or generate_name()
|
self._text = character_name or generate_name()
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
import typing as T
|
from collections.abc import Sequence
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.config import Race
|
from pqcli.config import Race
|
||||||
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
from pqcli.ui.curses.util import KEYS_CANCEL, Choice
|
||||||
@@ -9,9 +10,9 @@ from pqcli.ui.curses.views.menu_view import MenuView
|
|||||||
class ChooseCharacterRaceView(MenuView):
|
class ChooseCharacterRaceView(MenuView):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: T.Any,
|
screen: Any,
|
||||||
races: T.Sequence[Race],
|
races: Sequence[Race],
|
||||||
race: T.Optional[Race] = None,
|
race: Race | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
screen,
|
screen,
|
||||||
@@ -20,7 +21,7 @@ class ChooseCharacterRaceView(MenuView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for y, race in enumerate(races, 1):
|
for y, race in enumerate(races, 1):
|
||||||
key: T.Optional[str] = str(y % 10) if y <= 10 else None
|
key: str | None = str(y % 10) if y <= 10 else None
|
||||||
self._choices.append(
|
self._choices.append(
|
||||||
Choice(
|
Choice(
|
||||||
keys=[ord(key)] if key is not None else [],
|
keys=[ord(key)] if key is not None else [],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses.ascii
|
import curses.ascii
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.config import PRIME_STATS
|
from pqcli.config import PRIME_STATS
|
||||||
from pqcli.mechanic import StatsBuilder
|
from pqcli.mechanic import StatsBuilder
|
||||||
@@ -10,15 +10,15 @@ from pqcli.ui.curses.widgets.focusable import focus_standout
|
|||||||
|
|
||||||
|
|
||||||
class ChooseCharacterStatsView(BaseView):
|
class ChooseCharacterStatsView(BaseView):
|
||||||
def __init__(self, screen: T.Any) -> None:
|
def __init__(self, screen: Any) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_cancel = EventHandler()
|
self.on_cancel = EventHandler()
|
||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler()
|
||||||
|
|
||||||
self._active_widget = 0
|
self._active_widget = 0
|
||||||
self._win: T.Optional[T.Any] = None
|
self._win: Any | None = None
|
||||||
self._stats_win: T.Optional[T.Any] = None
|
self._stats_win: Any | None = None
|
||||||
|
|
||||||
self._stats_builder = StatsBuilder()
|
self._stats_builder = StatsBuilder()
|
||||||
self._stats = self._stats_builder.roll()
|
self._stats = self._stats_builder.roll()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import curses
|
import curses
|
||||||
import datetime
|
import datetime
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.game_data import GameData
|
from pqcli.game_data import GameData
|
||||||
from pqcli.mechanic import Player, SignalMixin, Simulation
|
from pqcli.mechanic import Player, SignalMixin, Simulation
|
||||||
@@ -30,7 +30,7 @@ from .task_progress_window import TaskProgressWindow
|
|||||||
class GameView(BaseView):
|
class GameView(BaseView):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
screen: T.Any,
|
screen: Any,
|
||||||
roster: Roster,
|
roster: Roster,
|
||||||
player: Player,
|
player: Player,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
@@ -225,7 +225,7 @@ class GameView(BaseView):
|
|||||||
x=0,
|
x=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._focusable_children: T.List[Focusable] = [
|
self._focusable_children: list[Focusable] = [
|
||||||
self._char_sheet_win,
|
self._char_sheet_win,
|
||||||
self._spell_book_win,
|
self._spell_book_win,
|
||||||
self._equipment_win,
|
self._equipment_win,
|
||||||
@@ -233,7 +233,7 @@ class GameView(BaseView):
|
|||||||
self._plot_win,
|
self._plot_win,
|
||||||
self._quest_book_win,
|
self._quest_book_win,
|
||||||
]
|
]
|
||||||
self._children: T.List[Widget] = self._focusable_children + [
|
self._children: list[Widget] = self._focusable_children + [
|
||||||
self._task_win
|
self._task_win
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import Player, StatType
|
from pqcli.mechanic import Player, StatType
|
||||||
from pqcli.ui.curses.widgets import Focusable
|
from pqcli.ui.curses.widgets import Focusable
|
||||||
@@ -8,7 +8,7 @@ from .progress_bar_window import DataTableProgressBarWindow
|
|||||||
|
|
||||||
class CharacterSheetWindow(Focusable, DataTableProgressBarWindow):
|
class CharacterSheetWindow(Focusable, DataTableProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent,
|
parent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import EquipmentType, Player
|
from pqcli.mechanic import EquipmentType, Player
|
||||||
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
||||||
@@ -6,7 +6,7 @@ from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
|||||||
|
|
||||||
class EquipmentWindow(Focusable, WindowWrapper):
|
class EquipmentWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
@@ -39,7 +39,7 @@ class EquipmentWindow(Focusable, WindowWrapper):
|
|||||||
self._render()
|
self._render()
|
||||||
|
|
||||||
def _sync_equipment_change(
|
def _sync_equipment_change(
|
||||||
self, equipment_type: EquipmentType, item_name: T.Optional[str]
|
self, equipment_type: EquipmentType, item_name: str | None
|
||||||
) -> None:
|
) -> None:
|
||||||
self._data_table.set(equipment_type.value, item_name or "")
|
self._data_table.set(equipment_type.value, item_name or "")
|
||||||
self._data_table.select(equipment_type.value)
|
self._data_table.select(equipment_type.value)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import InventoryItem, Player
|
from pqcli.mechanic import InventoryItem, Player
|
||||||
from pqcli.ui.curses.widgets import Focusable
|
from pqcli.ui.curses.widgets import Focusable
|
||||||
@@ -8,7 +8,7 @@ from .progress_bar_window import DataTableProgressBarWindow
|
|||||||
|
|
||||||
class InventoryWindow(Focusable, DataTableProgressBarWindow):
|
class InventoryWindow(Focusable, DataTableProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent,
|
parent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.lingo import act_name
|
from pqcli.lingo import act_name
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
@@ -11,7 +11,7 @@ class PlotWindow(Focusable, ListBoxProgressBarWindow):
|
|||||||
cutoff = 100
|
cutoff = 100
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent, h, w, y, x, " Plot Development ", show_time=True
|
parent, h, w, y, x, " Plot Development ", show_time=True
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.widgets import (
|
from pqcli.ui.curses.widgets import (
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -13,7 +13,7 @@ from pqcli.ui.curses.widgets import (
|
|||||||
class ProgressBarWindow(WindowWrapper):
|
class ProgressBarWindow(WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: T.Any,
|
parent: Any,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -82,7 +82,7 @@ class ProgressBarWindow(WindowWrapper):
|
|||||||
class DataTableProgressBarWindow(ProgressBarWindow):
|
class DataTableProgressBarWindow(ProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: T.Any,
|
parent: Any,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -123,7 +123,7 @@ class DataTableProgressBarWindow(ProgressBarWindow):
|
|||||||
class ListBoxProgressBarWindow(ProgressBarWindow):
|
class ListBoxProgressBarWindow(ProgressBarWindow):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: T.Any,
|
parent: Any,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import Focusable
|
from pqcli.ui.curses.widgets import Focusable
|
||||||
@@ -10,7 +10,7 @@ class QuestBookWindow(Focusable, ListBoxProgressBarWindow):
|
|||||||
cutoff = 100
|
cutoff = 100
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x, " Quests ", show_time=True)
|
super().__init__(parent, h, w, y, x, " Quests ", show_time=True)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.lingo import to_roman
|
from pqcli.lingo import to_roman
|
||||||
from pqcli.mechanic import Player, Spell
|
from pqcli.mechanic import Player, Spell
|
||||||
@@ -7,7 +7,7 @@ from pqcli.ui.curses.widgets import DataTable, Focusable, WindowWrapper
|
|||||||
|
|
||||||
class SpellBookWindow(Focusable, WindowWrapper):
|
class SpellBookWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._on_focus_change += self._render
|
self._on_focus_change += self._render
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.mechanic import Player
|
from pqcli.mechanic import Player
|
||||||
from pqcli.ui.curses.widgets import (
|
from pqcli.ui.curses.widgets import (
|
||||||
@@ -11,7 +11,7 @@ from pqcli.ui.curses.widgets import (
|
|||||||
|
|
||||||
class TaskProgressWindow(Focusable, WindowWrapper):
|
class TaskProgressWindow(Focusable, WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
from pqcli.ui.curses.util import Choice
|
from pqcli.ui.curses.util import Choice
|
||||||
@@ -8,7 +8,7 @@ from pqcli.ui.curses.widgets import Menu
|
|||||||
|
|
||||||
class MenuView(BaseView):
|
class MenuView(BaseView):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, screen: T.Any, title: str, active_choice: int = 0
|
self, screen: Any, title: str, active_choice: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class MenuView(BaseView):
|
|||||||
self.on_confirm = EventHandler()
|
self.on_confirm = EventHandler()
|
||||||
|
|
||||||
self._title = f"{title}:"
|
self._title = f"{title}:"
|
||||||
self._choices: T.List[Choice] = []
|
self._choices: list[Choice] = []
|
||||||
self._active_choice = active_choice
|
self._active_choice = active_choice
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_LOGO, COLOR_LOGO_ALT, has_colors
|
from pqcli.ui.curses.colors import COLOR_LOGO, COLOR_LOGO_ALT, has_colors
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
@@ -29,7 +29,7 @@ LOGO = """
|
|||||||
|
|
||||||
class MainMenu(Menu):
|
class MainMenu(Menu):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, choices: T.List[Choice], scr_height: int, scr_width: int
|
self, choices: list[Choice], scr_height: int, scr_width: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
header=LOGO,
|
header=LOGO,
|
||||||
@@ -50,7 +50,7 @@ class MainMenu(Menu):
|
|||||||
|
|
||||||
|
|
||||||
class RosterView(BaseView):
|
class RosterView(BaseView):
|
||||||
def __init__(self, screen: T.Any) -> None:
|
def __init__(self, screen: Any) -> None:
|
||||||
super().__init__(screen)
|
super().__init__(screen)
|
||||||
|
|
||||||
self.on_create = EventHandler()
|
self.on_create = EventHandler()
|
||||||
@@ -58,7 +58,7 @@ class RosterView(BaseView):
|
|||||||
self.on_delete = EventHandler()
|
self.on_delete = EventHandler()
|
||||||
self.on_quit = EventHandler()
|
self.on_quit = EventHandler()
|
||||||
|
|
||||||
self.choices: T.List[Choice] = [
|
self.choices: list[Choice] = [
|
||||||
Choice(
|
Choice(
|
||||||
keys=list(map(ord, "cC")),
|
keys=list(map(ord, "cC")),
|
||||||
desc="[C] Create new character",
|
desc="[C] Create new character",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class Widget:
|
class Widget:
|
||||||
@@ -12,10 +12,10 @@ class Widget:
|
|||||||
|
|
||||||
class WindowWrapper(Widget):
|
class WindowWrapper(Widget):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, parent: T.Optional[T.Any], h: int, w: int, y: int, x: int
|
self, parent: Any | None, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._win: T.Optional[T.Any]
|
self._win: Any | None
|
||||||
|
|
||||||
if not parent:
|
if not parent:
|
||||||
self._win = None
|
self._win = None
|
||||||
@@ -30,12 +30,12 @@ class WindowWrapper(Widget):
|
|||||||
del self._win
|
del self._win
|
||||||
self._win = None
|
self._win = None
|
||||||
|
|
||||||
def getbegyx(self) -> T.Tuple[int, int]:
|
def getbegyx(self) -> tuple[int, int]:
|
||||||
if not self._win:
|
if not self._win:
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
return self._win.getbegyx()
|
return self._win.getbegyx()
|
||||||
|
|
||||||
def getmaxyx(self) -> T.Tuple[int, int]:
|
def getmaxyx(self) -> tuple[int, int]:
|
||||||
if not self._win:
|
if not self._win:
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
return self._win.getmaxyx()
|
return self._win.getmaxyx()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ from .scrollable import Scrollable
|
|||||||
class DataTable(Scrollable):
|
class DataTable(Scrollable):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: T.Any,
|
parent: Any,
|
||||||
h: int,
|
h: int,
|
||||||
w: int,
|
w: int,
|
||||||
y: int,
|
y: int,
|
||||||
@@ -19,8 +19,8 @@ class DataTable(Scrollable):
|
|||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
self._align_right = align_right
|
self._align_right = align_right
|
||||||
self._items: T.List[T.Tuple[str, str]]
|
self._items: list[tuple[str, str]]
|
||||||
self._selected: T.Optional[int] = None
|
self._selected: int | None = None
|
||||||
|
|
||||||
def add(self, text: str, value: str) -> None:
|
def add(self, text: str, value: str) -> None:
|
||||||
self._items.append((text, value))
|
self._items.append((text, value))
|
||||||
@@ -40,10 +40,10 @@ class DataTable(Scrollable):
|
|||||||
del self._items[idx]
|
del self._items[idx]
|
||||||
self.scroll_to_item(idx)
|
self.scroll_to_item(idx)
|
||||||
|
|
||||||
def select(self, text: T.Optional[str]) -> None:
|
def select(self, text: str | None) -> None:
|
||||||
self._selected = None if text is None else self.get_idx(text)
|
self._selected = None if text is None else self.get_idx(text)
|
||||||
|
|
||||||
def get_idx(self, text: str) -> T.Optional[int]:
|
def get_idx(self, text: str) -> int | None:
|
||||||
for idx, row in enumerate(self._items):
|
for idx, row in enumerate(self._items):
|
||||||
if row[0].strip() == text.strip():
|
if row[0].strip() == text.strip():
|
||||||
return idx
|
return idx
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import curses
|
import curses
|
||||||
import typing as T
|
from collections.abc import Generator
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_FOCUSED, has_colors
|
from pqcli.ui.curses.colors import COLOR_FOCUSED, has_colors
|
||||||
from pqcli.ui.curses.event_handler import EventHandler
|
from pqcli.ui.curses.event_handler import EventHandler
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def focus_standout(focused: bool, win: T.Any) -> T.Generator:
|
def focus_standout(focused: bool, win: Any) -> Generator:
|
||||||
if focused:
|
if focused:
|
||||||
if has_colors():
|
if has_colors():
|
||||||
win.attron(curses.color_pair(COLOR_FOCUSED))
|
win.attron(curses.color_pair(COLOR_FOCUSED))
|
||||||
@@ -22,7 +23,7 @@ def focus_standout(focused: bool, win: T.Any) -> T.Generator:
|
|||||||
|
|
||||||
|
|
||||||
class Focusable:
|
class Focusable:
|
||||||
def __init__(self, *args: T.Any, **kwargs: T.Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
self._focused = False
|
self._focused = False
|
||||||
self._on_focus_change = EventHandler()
|
self._on_focus_change = EventHandler()
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -37,6 +38,6 @@ class Focusable:
|
|||||||
self._on_focus_change()
|
self._on_focus_change()
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def focus_standout(self, win: T.Any) -> T.Generator:
|
def focus_standout(self, win: Any) -> Generator:
|
||||||
with focus_standout(self._focused, win):
|
with focus_standout(self._focused, win):
|
||||||
yield
|
yield
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
from pqcli.ui.curses.colors import COLOR_HIGHLIGHT, has_colors
|
||||||
|
|
||||||
@@ -7,11 +7,11 @@ from .scrollable import Scrollable
|
|||||||
|
|
||||||
|
|
||||||
class ListBox(Scrollable):
|
class ListBox(Scrollable):
|
||||||
_items: T.List[str]
|
_items: list[str]
|
||||||
|
|
||||||
def __init__(self, parent: T.Any, h: int, w: int, y: int, x: int) -> None:
|
def __init__(self, parent: Any, h: int, w: int, y: int, x: int) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._selected: T.Optional[int] = None
|
self._selected: int | None = None
|
||||||
|
|
||||||
def add(self, text: str) -> None:
|
def add(self, text: str) -> None:
|
||||||
self._items.append(text)
|
self._items.append(text)
|
||||||
@@ -22,7 +22,7 @@ class ListBox(Scrollable):
|
|||||||
idx %= len(self._items)
|
idx %= len(self._items)
|
||||||
self._items[idx] = value
|
self._items[idx] = value
|
||||||
|
|
||||||
def get(self, idx: int) -> T.Optional[str]:
|
def get(self, idx: int) -> str | None:
|
||||||
if not self._items:
|
if not self._items:
|
||||||
return None
|
return None
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
@@ -35,7 +35,7 @@ class ListBox(Scrollable):
|
|||||||
def delete(self, idx: int, count: int = 1) -> None:
|
def delete(self, idx: int, count: int = 1) -> None:
|
||||||
del self._items[idx : idx + count]
|
del self._items[idx : idx + count]
|
||||||
|
|
||||||
def select(self, idx: T.Optional[int]) -> None:
|
def select(self, idx: int | None) -> None:
|
||||||
if idx < 0 and len(self._items):
|
if idx < 0 and len(self._items):
|
||||||
idx %= len(self._items)
|
idx %= len(self._items)
|
||||||
self._selected = idx
|
self._selected = idx
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.util import KEYS_CYCLE, KEYS_DOWN, KEYS_UP, Choice
|
from pqcli.ui.curses.util import KEYS_CYCLE, KEYS_DOWN, KEYS_UP, Choice
|
||||||
from pqcli.ui.curses.widgets.focusable import focus_standout
|
from pqcli.ui.curses.widgets.focusable import focus_standout
|
||||||
@@ -12,7 +12,7 @@ class Menu(Widget):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
header: str,
|
header: str,
|
||||||
choices: T.List[Choice],
|
choices: list[Choice],
|
||||||
active_choice: int,
|
active_choice: int,
|
||||||
scr_height: int,
|
scr_height: int,
|
||||||
scr_width: int,
|
scr_width: int,
|
||||||
@@ -28,7 +28,7 @@ class Menu(Widget):
|
|||||||
|
|
||||||
w = max(map(len, all_lines)) + 1
|
w = max(map(len, all_lines)) + 1
|
||||||
h = len(all_lines) + 1
|
h = len(all_lines) + 1
|
||||||
self._pad: T.Optional[T.Any] = curses.newpad(h, w)
|
self._pad: Any | None = curses.newpad(h, w)
|
||||||
|
|
||||||
for y, line in enumerate(self._header_lines):
|
for y, line in enumerate(self._header_lines):
|
||||||
self._pad.move(y, 0)
|
self._pad.move(y, 0)
|
||||||
@@ -40,7 +40,7 @@ class Menu(Widget):
|
|||||||
del self._pad
|
del self._pad
|
||||||
self._pad = None
|
self._pad = None
|
||||||
|
|
||||||
def getmaxyx(self) -> T.Tuple[int, int]:
|
def getmaxyx(self) -> tuple[int, int]:
|
||||||
if not self._pad:
|
if not self._pad:
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
return self._pad.getmaxyx()
|
return self._pad.getmaxyx()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import curses
|
import curses
|
||||||
import datetime
|
import datetime
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.lingo import format_timespan
|
from pqcli.lingo import format_timespan
|
||||||
from pqcli.ui.curses.colors import COLOR_PROGRESSBAR, has_colors
|
from pqcli.ui.curses.colors import COLOR_PROGRESSBAR, has_colors
|
||||||
@@ -9,7 +9,7 @@ from pqcli.ui.curses.widgets.base import WindowWrapper
|
|||||||
|
|
||||||
class ProgressBar(WindowWrapper):
|
class ProgressBar(WindowWrapper):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, parent: T.Any, h: int, w: int, y: int, x: int, show_time: bool
|
self, parent: Any, h: int, w: int, y: int, x: int, show_time: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@ class ProgressBar(WindowWrapper):
|
|||||||
self._max_pos = 1.0
|
self._max_pos = 1.0
|
||||||
|
|
||||||
self._show_time = show_time
|
self._show_time = show_time
|
||||||
self._last_tick: T.Optional[T.Tuple[datetime.datetime, float]] = None
|
self._last_tick: tuple[datetime.datetime, float] | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_left(self) -> T.Optional[datetime.timedelta]:
|
def time_left(self) -> datetime.timedelta | None:
|
||||||
if self._last_tick is None:
|
if self._last_tick is None:
|
||||||
return None
|
return None
|
||||||
time_then, pos_then = self._last_tick
|
time_then, pos_then = self._last_tick
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import typing as T
|
from typing import Any
|
||||||
|
|
||||||
from pqcli.ui.curses.colors import (
|
from pqcli.ui.curses.colors import (
|
||||||
COLOR_SCROLLBAR_THUMB,
|
COLOR_SCROLLBAR_THUMB,
|
||||||
@@ -11,11 +11,11 @@ from .base import WindowWrapper
|
|||||||
|
|
||||||
|
|
||||||
class Scrollable(WindowWrapper):
|
class Scrollable(WindowWrapper):
|
||||||
def __init__(self, parent: T.Any, h: int, w: int, y: int, x: int) -> None:
|
def __init__(self, parent: Any, h: int, w: int, y: int, x: int) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
self._scroll_pos = 0
|
self._scroll_pos = 0
|
||||||
self._pad: T.Optional[T.Any] = curses.newpad(1, 1)
|
self._pad: Any | None = curses.newpad(1, 1)
|
||||||
self._items: T.List[T.Any] = []
|
self._items: list[Any] = []
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
self._items = []
|
self._items = []
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ dev = ["prek>=0.2", "pytest>=8.0"]
|
|||||||
line-length = 79
|
line-length = 79
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E", "F", "W", "I"]
|
select = ["E", "F", "W", "I", "UP"]
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
[tool.ruff.lint.isort]
|
||||||
known-first-party = ["pqcli"]
|
known-first-party = ["pqcli"]
|
||||||
+2
-3
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import typing as T
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from pqcli import random
|
from pqcli import random
|
||||||
@@ -20,7 +19,7 @@ STATS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def run_trace(seed: str, ticks: int = 3000) -> T.List[str]:
|
def run_trace(seed: str, ticks: int = 3000) -> list[str]:
|
||||||
"""Play a deterministic game and return everything that happened."""
|
"""Play a deterministic game and return everything that happened."""
|
||||||
data = load_game_data()
|
data = load_game_data()
|
||||||
player = Player(
|
player = Player(
|
||||||
@@ -34,7 +33,7 @@ def run_trace(seed: str, ticks: int = 3000) -> T.List[str]:
|
|||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
simulation = Simulation(player, data)
|
simulation = Simulation(player, data)
|
||||||
|
|
||||||
lines: T.List[str] = []
|
lines: list[str] = []
|
||||||
player.connect(
|
player.connect(
|
||||||
"new_task",
|
"new_task",
|
||||||
lambda: lines.append(f"task: {player.task.description}"),
|
lambda: lines.append(f"task: {player.task.description}"),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
import typing as T
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ from pqcli.game_data import (
|
|||||||
from pqcli.mechanic import Player, Stats, monster_task
|
from pqcli.mechanic import Player, Stats, monster_task
|
||||||
|
|
||||||
|
|
||||||
def write(directory: Path, name: str, value: T.Any) -> Path:
|
def write(directory: Path, name: str, value: Any) -> Path:
|
||||||
path = directory / f"{name}.json"
|
path = directory / f"{name}.json"
|
||||||
path.write_text(json.dumps(value), encoding="utf-8")
|
path.write_text(json.dumps(value), encoding="utf-8")
|
||||||
return path
|
return path
|
||||||
@@ -98,7 +98,7 @@ def test_unknown_file_warns(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_invalid_override_raises(
|
def test_invalid_override_raises(
|
||||||
tmp_path: Path, name: str, value: T.Any, message: str
|
tmp_path: Path, name: str, value: Any, message: str
|
||||||
) -> None:
|
) -> None:
|
||||||
write(tmp_path, name, value)
|
write(tmp_path, name, value)
|
||||||
with pytest.raises(DataError) as excinfo:
|
with pytest.raises(DataError) as excinfo:
|
||||||
|
|||||||
Reference in new issue
Block a user