Add Phase 1 MVP: D&D text-adventure server

FastAPI + Postgres backend with fastapi-users auth, games/characters
CRUD, and a WebSocket chat endpoint where an x.ai Grok DM narrates
play, rolls dice, and maintains character sheets via tool calls.
React + Tailwind frontend covers registration/login, game list and
creation, participation-code join flow, character viewer, and a
real-time chat session view. Docker Compose skeleton (Postgres +
backend) included; Caddy/frontend container wiring is deferred to
the deploy milestone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thorsten
2026-08-31 18:43:08 +02:00
commit f37dc9fa76
75 changed files with 106988 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
import re
from random import SystemRandom
_rng = SystemRandom()
_NOTATION_RE = re.compile(r"^\s*(\d*)d(\d+)\s*(?:([+-])\s*(\d+))?\s*$", re.IGNORECASE)
TOOL_SCHEMA = {
"name": "roll_dice",
"description": (
"Roll dice using standard D&D notation (e.g. '1d20+5', '2d6', 'd8-1'). "
"Always use this tool for any dice roll that affects the game — never invent or guess a result yourself."
),
"parameters": {
"type": "object",
"properties": {
"notation": {
"type": "string",
"description": "Dice notation, e.g. '1d20+5'",
},
"reason": {
"type": "string",
"description": "Short reason for the roll, e.g. 'Geschicklichkeitsrettung, Schwierigkeit 13'",
},
},
"required": ["notation"],
},
}
def roll(notation: str) -> dict:
match = _NOTATION_RE.match(notation)
if not match:
raise ValueError(f"Invalid dice notation: {notation!r}")
count_str, sides_str, sign, modifier_str = match.groups()
count = int(count_str) if count_str else 1
sides = int(sides_str)
if not (1 <= count <= 100) or not (2 <= sides <= 1000):
raise ValueError(f"Dice notation out of allowed range: {notation!r}")
modifier = int(modifier_str) if modifier_str else 0
if sign == "-":
modifier = -modifier
rolls = [_rng.randint(1, sides) for _ in range(count)]
total = sum(rolls) + modifier
return {"notation": notation, "rolls": rolls, "modifier": modifier, "total": total}