Repository navigation
Expand file tree
/
Copy pathdnd_print.py
More file actions
89 lines (74 loc) · 3.7 KB
/
Copy pathdnd_print.py
File metadata and controls
89 lines (74 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
"""Print a random D&D character on the S01 thermal printer.
Rolls a level-1 hero: race, class, alignment, ability scores (4d6 drop lowest)
with modifiers, derived HP/AC, and a personality quirk. Seed prints on the card.
Examples:
python dnd_print.py # random character
python dnd_print.py --seed 1234 # reproducible
python dnd_print.py --no-print
"""
from __future__ import annotations
import argparse
from pathlib import Path
import random
from print_common import ROOT, Card
RACES = ["Human", "Elf", "Dwarf", "Halfling", "Half-Orc", "Tiefling", "Dragonborn",
"Gnome", "Half-Elf", "Goliath", "Tabaxi", "Aasimar"]
CLASSES = {"Barbarian": 12, "Fighter": 10, "Paladin": 10, "Ranger": 10, "Bard": 8,
"Cleric": 8, "Druid": 8, "Monk": 8, "Rogue": 8, "Warlock": 8,
"Wizard": 6, "Sorcerer": 6}
ALIGN = ["Lawful Good", "Neutral Good", "Chaotic Good", "Lawful Neutral", "True Neutral",
"Chaotic Neutral", "Lawful Evil", "Neutral Evil", "Chaotic Evil"]
QUIRKS = ["speaks only in rhyme when nervous", "collects unusual buttons",
"deeply distrusts boats", "names every weapon they own",
"claims to be royalty from a faraway land", "afraid of chickens",
"always pays debts, eventually", "quotes a god no one has heard of",
"never removes their gloves", "keeps a detailed grudge ledger",
"convinced the moon is watching", "hums during combat"]
ABILITIES = ["STR", "DEX", "CON", "INT", "WIS", "CHA"]
SYL1 = ["Bel", "Tho", "Kael", "Mir", "Dra", "Gor", "Syl", "Vor", "Ael", "Bram", "Zar", "Fen"]
SYL2 = ["a", "an", "or", "wyn", "eth", "ric", "us", "ara", "iel", "oth", "im", "ael"]
SYL3 = ["dor", "wen", "thas", "rin", "mir", "gar", "lyn", "vald", "ius", "ra", "wick", "born"]
def roll_4d6() -> int:
dice = sorted(random.randint(1, 6) for _ in range(4))
return sum(dice[1:]) # drop the lowest
def mod(score: int) -> int:
return (score - 10) // 2
def main() -> int:
parser = argparse.ArgumentParser(description="Print a random D&D character on the S01 thermal printer.")
parser.add_argument("--seed", type=int, default=None, help="Seed for a reproducible character.")
parser.add_argument("--out", type=Path, default=ROOT / "dnd.png")
parser.add_argument("--darkness", type=int, choices=range(1, 6), default=3)
parser.add_argument("--bottom-feed", type=int, default=24)
parser.add_argument("--no-print", action="store_true")
args = parser.parse_args()
seed = args.seed if args.seed is not None else random.randrange(1, 100000)
random.seed(seed)
name = random.choice(SYL1) + random.choice(SYL2) + random.choice(SYL3)
race = random.choice(RACES)
cls = random.choice(list(CLASSES))
align = random.choice(ALIGN)
quirk = random.choice(QUIRKS)
scores = {a: roll_4d6() for a in ABILITIES}
hp = CLASSES[cls] + mod(scores["CON"])
ac = 10 + mod(scores["DEX"])
print(f"{name} - {race} {cls} ({align})")
card = Card()
card.title("CHARACTER")
card.heading(name, size=22)
card.line(f"{race} {cls} - Level 1", size=14, bold=True, center=True)
card.line(align, size=12, bold=False, center=True)
card.gap(2).divider()
for a in ABILITIES:
m = mod(scores[a])
card.kv(a, f"{scores[a]:>2} ({m:+d})")
card.gap(2).divider()
card.kv("Hit Points", str(hp))
card.kv("Armor Class", str(ac))
card.kv("Proficiency", "+2")
card.gap(4).line("Quirk:", size=13, bold=True)
card.para(quirk.capitalize() + ".", size=13)
card.footer("roll for initiative", f"seed {seed}")
return card.finish(args.out, args.bottom_feed, args.darkness, do_print=not args.no_print)
if __name__ == "__main__":
raise SystemExit(main())