Repository navigation
x.4 Example: D&D style Character
Julio Toboso edited this page Sep 30, 2026
·
1 revision
"""A character sheet the D&D way: species, class, background and feats are
independent Tags; the sheet is what they compose.
Run: PYTHONPATH=. python3 examples/dnd_character.py
"""
from __future__ import annotations
from TopKit import Action
from TopKit import Contract
from TopKit import Imprint
from TopKit import Outline
from TopKit import Post
from TopKit import Pre
from TopKit import Record
from TopKit import Report
from TopKit import Tag
from TopKit import Underlay
class Character:
"""The host: inherent structure only."""
def __init__(
character,
name: str,
level: int,
) -> None:
character.name = name
character.level = level
# --- Species ------------------------------------------------------
class Species(Tag):
@Record
def speed(agent) -> int:
return 30
class Elf(Species):
@Record
def spells(agent, stored) -> list[str]:
# Piling up: whatever is stored already, plus the Elf's gift.
return (stored or []) + ["Light"]
@Record
def senses(agent) -> list[str]:
return ["Darkvision"]
class Dwarf(Species):
@Record
def speed(agent) -> int:
return 25
@Record
def hit_points(agent, stored) -> int:
return (stored or 0) + agent.level # +1 per level
# --- Class --------------------------------------------------------
class Class(Tag):
@Report
def hit_die(tag) -> int:
return 8
@Record
def hit_points(agent, stored) -> int:
return (stored or 0) + agent.level * Class.hit_die
class Wizard(Class):
@Report
def hit_die(tag) -> int:
return 6
@Pre
def Can_Study(agent):
return agent.level >= 1
@Record
def hit_points(agent, stored) -> int:
return (stored or 0) + agent.level * Wizard.hit_die
@Record
def spells(agent, stored) -> list[str]:
return (stored or []) + ["Magic Missile", "Shield"]
@Post
def Knows_Spells(agent):
return len(agent.spells) > 0
@Action
def Attack(agent) -> str:
return f"{agent.name} casts {agent.spells[-1]}"
class Fighter(Class):
@Report
def hit_die(tag) -> int:
return 10
@Record
def hit_points(agent, stored) -> int:
return (stored or 0) + agent.level * Fighter.hit_die
@Action
def Attack(agent) -> str:
return f"{agent.name} swings a sword"
# --- Background ---------------------------------------------------
class Sage(Tag):
@Record
def spells(agent, stored) -> list[str]:
return (stored or []) + ["Identify"]
@Record
def languages(agent, stored) -> list[str]:
return (stored or ["Common"]) + ["Draconic"]
# --- Feats: synergy through Preconditions --------------------------
class War_Caster(Tag):
"""Only a spellcaster who can also hold the line may take War Caster."""
@Pre
def Is_A_Caster(agent):
return agent in Wizard
@Action
@Underlay
def Attack(agent, underlay) -> str:
return underlay() + " while holding a shield"
class Tough(Tag):
@Record
def hit_points(agent, stored) -> int:
return (stored or 0) + 2 * agent.level
@Imprint
def note(agent) -> None:
print(f" {agent.name} took Tough: hit points now {agent.hit_points}")
def main() -> None:
ari = Character("Ari", level=3)
Elf(ari)
Wizard(ari)
Sage(ari)
War_Caster(ari)
Tough(ari)
print(Outline(ari))
print()
print("spells :", ari.spells) # Elf + Wizard + Sage
print("hit points :", ari.hit_points) # Wizard 3*6 + Tough 2*3
print("speed :", ari.speed)
print("languages :", ari.languages)
print("attack :", ari.Attack())
print("as a plain Wizard:", ari.Wizard.Attack())
print()
print(Contract.Display(ari))
print()
bruk = Character("Bruk", level=2)
Dwarf(bruk)
Fighter(bruk)
try:
War_Caster(bruk)
except Exception as error:
print("Bruk cannot take War Caster:", type(error).__name__)
print("Bruk hit points:", bruk.hit_points, "(Fighter 2*10 + Dwarf 2)")
print("Bruk attack :", bruk.Attack())
if __name__ == "__main__":
main()