Source code for opend6_tools.character.workbook

"""
OpenD6 Character and Creature Definition DSL.
Some handy workbook functions.

..  autofunction:: workbook_characters

..  autofunction:: workbook_groupBy

..  autofunction:: display

..  autofunction:: debug


"""

from collections import defaultdict
from collections.abc import Callable
from dataclasses import fields
import fnmatch
from pprint import pformat
from typing import Any

from .features import Character, CharacterBudget, Creature, CharacterDict


[docs] def workbook_characters(context: dict[str, Any]) -> dict[str, Character]: """ Emit sequence of Characters in a Workbook. :param context: Usually ``globals()`` for a Notebook :return: dict mapping from ``Character`` name to ``Character`` instances """ return { value.name: value for name, value in context.items() if isinstance(value, Character) }
[docs] def workbook_groupBy( context: dict[str, Any], group_rule: Callable[[Character], str] = lambda c: "" ) -> dict[str, list[Character]]: r"""Transform a dict[name: str, Character] of spells into a dictionary: dict[some_attr: str, list[Character]]. This is often used to partition by realm, but any other string attribute is possible. :param context: Usually ``globals()`` for a Notebook :return: dict mapping from rank number to lists of ``Spell`` instances """ grouped: defaultdict[str, list[Character]] = defaultdict(list) for name, char in workbook_characters(context).items(): group = group_rule(char) grouped[group].append(char) return grouped
[docs] def display( character: Character, check: CharacterBudget | bool = CharacterBudget.NORMAL ) -> str: """ Creates a display a character in plain text to help designers. """ dict_value = { field.name: getattr(character, field.name) for field in fields(character) if not (field.name.startswith("_")) } if check != CharacterBudget.NO_BUDGET: assert isinstance(check, CharacterBudget), ( "Change from True to CharacterBudget.NORMAL" ) dict_value |= {"Check": character.budget_check(check)} return pformat(dict_value, sort_dicts=False)
[docs] def debug( characters: list[Character | Creature] | CharacterDict, ident: int | str | None | list[str] = None, ) -> None: """ Prints details of a Character to STDOUT. Uses :py:func:`display`. >>> from opend6_tools.character import * >>> human = Character( ... occupation="Default", race="Human" ... ) >>> book = [human] >>> debug(book, 0) # doctest: +NORMALIZE_WHITESPACE ## {'name': '', 'occupation': 'Default', 'race': 'Human', 'gender': '', 'age': '', 'height': '', 'weight': '', 'physical_description': '', 'agility': Agility(3*D, {'acrobatics': 0*D, 'climbing': 0*D, 'contortion': 0*D, 'dodge': 0*D, 'fighting': 0*D, 'flying': 0*D, 'jumping': 0*D, 'melee combat': 0*D, 'combat': 0*D, 'riding': 0*D, 'stealth': 0*D}), 'intellect': Intellect(3*D, {'cultures': 0*D, 'devices': 0*D, 'healing': 0*D, 'navigation': 0*D, 'reading/writing': 0*D, 'scholar': 0*D, 'speaking': 0*D, 'trading': 0*D, 'traps': 0*D}), 'coordination': Coordination(3*D, {'charioteering': 0*D, 'lockpicking': 0*D, 'marksmanship': 0*D, 'pilotry': 0*D, 'sleight of hand': 0*D, 'throwing': 0*D}), 'acumen': Acumen(3*D, {'artist': 0*D, 'crafting': 0*D, 'disguise': 0*D, 'gambling': 0*D, 'hide': 0*D, 'investigation': 0*D, 'know-how': 0*D, 'search': 0*D, 'streetwise': 0*D, 'survival': 0*D, 'tracking': 0*D}), 'physique': Physique(3*D, {'lifting': 0*D, 'running': 0*D, 'stamina': 0*D, 'swimming': 0*D}), 'charisma': Charisma(3*D, {'animal handling': 0*D, 'bluff': 0*D, 'charm': 0*D, 'command': 0*D, 'intimidation': 0*D, 'mettle': 0*D, 'persuasion': 0*D}), 'extranormal': Magic(0*D, {'alteration': 0*D, 'apportation': 0*D, 'conjuration': 0*D, 'divination': 0*D}), 'advantages': OptionList(), 'disadvantages': OptionList(), 'special_abilities': OptionList(), 'description': '', 'realm': 'Human realm', 'move': 10, 'strength_damage': 2*D, 'body': 31, 'wounds': {'Mortal': '1-2', 'Incapacitated': '3-5', 'Severe': '6-11', 'Wounded': '12-18', 'Stunned': '19-24'}, 'funds': 3*D, 'silver': 180, 'fate_points': 1, 'character_points': 5, 'equipment': NoteList(), 'armor': NoteList(), 'weapons': NoteList(), 'spells': NoteList(), 'personality': '', 'objectives': '', 'native_language': '', 'other_notes': '', 'Check': {'Attributes': '18D out of 18D', 'Skills': 'Nothing out of 7D', 'Options': 'Nothing'}} <BLANKLINE> :param spells: Spell Book :param ident: Identifier for a spell, a number, or a name, or a list of names. Shell-style wild-cards are used to match names. """ match characters: case list(): char_map = {c.name: c for c in characters} case dict(): char_map = characters case _: # pragma: no cover raise ValueError(f"invalid type for {characters=!r}") keys: list[str] match ident: case None: keys = list(char_map.keys()) case str(): try: keys = [list(char_map.keys())[int(ident)]] except (ValueError, TypeError): keys = [ident] case int() as index: keys = [list(char_map.keys())[index]] case list() as ident_list: keys = [ n for key_pat in ident_list for n in char_map.keys() if fnmatch.fnmatch(n.lower(), key_pat.lower()) ] case _: # pragma: no cover raise ValueError("unknown identifier {ident!r}") for name in keys: character = char_map[name] print("##", character.name) print(display(character)) print()