UNWRITTEN: complete short-form RPG at games/unwritten/
A 30-45 minute turn-based RPG built overnight as an engine showcase: six recruitable characters (party of three), four recurring NPCs, choice-driven dialogue with visible-but-locked skill checks, shops, loot, levels, two bosses with in-battle Talk options, the reverted grey-town beat, and an epilogue assembled from the player's actual choices. 33 dialogue scenes / 171 nodes. Terrain is ColorLayers only; kenney_tinydungeon supplies characters, items, and props. Creative direction, story, dialogue, maps, and systems design by Fable; implementation by four Opus subagents (framework, battle, overworld, integration). Verified by tests/playthrough.py: a scripted headless run of all three acts, 20/20 beats passing, plus per-system unit tests (script integrity, UI kit, 225-battle balance sim, overworld). Run: cd build && ./mcrogueface --exec ../games/unwritten/main.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vxWJ6GY384SmZiMVXgrF4
This commit is contained in:
parent
d94a25706a
commit
80908f771c
31 changed files with 8847 additions and 0 deletions
46
games/unwritten/core/inputstack.py
Normal file
46
games/unwritten/core/inputstack.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""UNWRITTEN - modal input dispatch. Owner: Agent A.
|
||||
|
||||
One InputStack per scene owns scene.on_key and dispatches to a stack of
|
||||
handlers, top-most first. A handler is fn(key, state) -> bool; returning True
|
||||
consumes the event (lower handlers do not see it). Overworld pushes its
|
||||
movement handler; a DialogueBox / menu pushes itself while open and pops on
|
||||
close, freezing everything beneath it.
|
||||
"""
|
||||
|
||||
|
||||
class InputStack:
|
||||
def __init__(self, scene):
|
||||
self.scene = scene
|
||||
self.stack = [] # list of (name, handler)
|
||||
scene.on_key = self._dispatch
|
||||
|
||||
def push(self, handler, name=""):
|
||||
self.stack.append((name, handler))
|
||||
|
||||
def pop(self, name=""):
|
||||
"""Pop the top handler, or the top-most handler matching name."""
|
||||
if name:
|
||||
for i in range(len(self.stack) - 1, -1, -1):
|
||||
if self.stack[i][0] == name:
|
||||
return self.stack.pop(i)
|
||||
return None
|
||||
if self.stack:
|
||||
return self.stack.pop()
|
||||
return None
|
||||
|
||||
def replace(self, handler, name=""):
|
||||
"""Replace the top handler with a new one."""
|
||||
self.pop()
|
||||
self.push(handler, name)
|
||||
|
||||
def clear(self):
|
||||
self.stack = []
|
||||
|
||||
def has(self, name):
|
||||
return any(n == name for n, _ in self.stack)
|
||||
|
||||
def _dispatch(self, key, state):
|
||||
for i in range(len(self.stack) - 1, -1, -1):
|
||||
handler = self.stack[i][1]
|
||||
if handler(key, state):
|
||||
return
|
||||
Loading…
Add table
Add a link
Reference in a new issue