Session management, blank-filling, and scoring for the scenario learning game. Extracted from Crossing::Game::Engine, made operation-agnostic.
The game engine doesn't know what it's testing. It knows:
MENU → BROWSE → PLAY → RESULT
↑ |
└───────────────────────┘
engine = Wanderland::Game::Engine.new("spec/scenarios/")
# Menu auto-generated from registered boundaries
engine.menu_items
# [{ key: :all, label: "All Scenarios", desc: "Play through everything" },
# { key: :route_match, label: "Route match", desc: "7 scenarios" },
# { key: :middleware_walk, label: "Middleware walk", desc: "4 scenarios" }]
engine.select_operation! # picks current menu item
engine.play! # enter blank-filling mode
# Fill blanks
engine.current_blank # "slug_value"
engine.answer_blank!("sprout-api")
engine.current_blank # "layout"
engine.answer_blank!("node-view")
# Auto-checks when all blanks filled
engine.result
# { results: [...], score: { correct: 2, total: 2, percentage: 100.0 } }
engine.phase # :result
# Navigate
engine.next_scenario!
engine.play!
The board is the scenario YAML with blank answers replaced by <____:key> markers:
engine.board
# ---
# name: parameter extraction from path
# description: |
# The route pattern /node/:slug has one dynamic segment...
# expected:
# matched: true
# params:
# slug: "<____:slug_value>"
# route:
# layout: "<____:layout>"
Emacs renders this in a yaml-mode buffer. The player navigates between <____> markers and fills them in.
engine.skip!
# Shows correct answers without scoring
# { skipped: true, results: [{ blank: "slug_value", expected: "sprout-api", actual: "sprout-api" }] }
Every completed scenario (pass or fail) is logged to ~/.local/share/wanderland/games/ as YAML:
20260401-212617_route_match_a1b2c3d4.yaml
Contains timestamp, session ID, scenario name, player answers, scoring, engine output.
The game engine is the backend for:
SPC W g keybinds, yaml-mode buffersAll three talk to the same engine instance. The API is just JSON wrappers around engine.play!, engine.answer_blank!, engine.board, etc.