Wire game transitions end-to-end, add Guide lookup, add 79 tests

Transitions:
- Add I-HOUSEWRECK (tick 20) and I-VOGONS (tick 50) timed events to
  earth.py, queued at startup in main.py
- I-VOGONS demolishes Earth and moves player to Vogon Hold
- Fix airlock→Dark transition to call Dark room M-ENTER handler
- Fix dream-restore to support multiple callbacks (list instead of single)
- Add state.finish() call to RAMP for endgame victory

Guide system:
- Add 16-entry lookup database to GUIDE object (space, towel, vogons,
  poetry, beast, babel fish, earth, magrathea, marvin, etc.)
- "consult guide about X" now returns relevant entry text

Tests (79 passing):
- test_engine.py (14): containment, flags, articles, clock mechanics
- test_parser.py (20): directions, compound verbs, prepositions, synonyms
- test_earth.py (21): full opening sequence, puzzles, navigation
- test_vogon.py (4): room existence, Hold first-visit sequence
- test_dark.py (7): inventory clearing, dream dispatch, probabilities
- conftest.py: shared game fixture and send() helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 22:04:22 +02:00
parent a1bb4cbf02
commit 679639df9f
15 changed files with 1042 additions and 9 deletions
+75
View File
@@ -0,0 +1,75 @@
import pytest
from h2g2.engine.world import World
from h2g2.engine.state import GameState
from h2g2.engine.output import Output
from h2g2.engine.clock import Clock
from h2g2.engine.parser import Parser
from h2g2.engine.loop import GameLoop
from h2g2.content import globals_content, earth, vogon, heart, unearth, dark
import h2g2.engine.verbs # noqa: F401 — register handlers
@pytest.fixture
def game():
"""Create a fully initialized game world."""
world = World()
globals_content.register(world)
earth.register(world)
output = Output()
clock = Clock()
state = GameState(world, output, clock)
state.protagonist = world.protagonist
state.here = world.get_room("BEDROOM")
state.winner = world.protagonist
state.lying_down = True
# Initialize flags (same as main.py)
state.flags["headache"] = True
state.flags["groggy"] = False
state.flags["groggy_counter"] = 0
state.flags["house_demolished"] = False
state.flags["earth_demolished"] = False
state.flags["in_front_of_bulldozer"] = False
state.flags["ford_arrived"] = False
state.flags["ford_has_satchel"] = True
state.flags["prosser_in_mud"] = False
state.flags["beer_counter"] = 0
state.flags["babel_fish_in_ear"] = False
state.flags["poem_enjoyed"] = False
state.flags["holding_no_tea"] = True
state.flags["dead_counter"] = 0
state.flags["vogon_prob"] = 100
state.flags["heart_prob"] = 0
state.flags["traal_prob"] = 60
state.flags["fleet_prob"] = 0
state.flags["whale_prob"] = 0
state.inventory_extras.append(
lambda s: "a splitting headache" if s.flags.get("headache") else None
)
state.inventory_extras.append(
lambda s: "no tea" if s.flags.get("holding_no_tea") else None
)
# Register content that needs state
vogon.register(world, state)
heart.register(world, state)
unearth.register(world, state)
dark.register(world, state)
parser = Parser()
loop = GameLoop(state, parser)
return state, parser, loop
def send(game_tuple, command: str) -> str:
"""Send a command to the game and return the output text."""
state, parser, loop = game_tuple
result = parser.parse(command, state)
if result:
loop._execute(result)
text = state.output.flush()
return text