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:
@@ -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
|
||||
@@ -0,0 +1,124 @@
|
||||
"""Tests for the DARK dream dispatch room."""
|
||||
|
||||
from tests.conftest import send
|
||||
|
||||
|
||||
def test_dark_room_exists(game):
|
||||
"""Verify the DARK room was registered."""
|
||||
state, _, _ = game
|
||||
dark_room = state.world.get_room("DARK")
|
||||
assert dark_room is not None
|
||||
assert dark_room.id == "DARK"
|
||||
|
||||
|
||||
def test_dark_entry_clears_inventory(game):
|
||||
"""Entering the DARK room should strip all inventory items."""
|
||||
state, _, _ = game
|
||||
# Give player an item
|
||||
gown = state.world.get("GOWN")
|
||||
gown.move_to(state.protagonist)
|
||||
assert len(state.protagonist.children) > 0
|
||||
|
||||
# Enter dark room
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
state.output.flush()
|
||||
|
||||
# Inventory should be cleared (items moved to local_globals)
|
||||
assert len(state.protagonist.children) == 0
|
||||
|
||||
|
||||
def test_dark_entry_sets_dreaming(game):
|
||||
"""Entering DARK should set the dreaming flag."""
|
||||
state, _, _ = game
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
state.output.flush()
|
||||
|
||||
assert state.dreaming is True
|
||||
|
||||
|
||||
def test_dark_entry_produces_text(game):
|
||||
"""Entering DARK should display flavor text."""
|
||||
state, _, _ = game
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
text = state.output.flush()
|
||||
|
||||
assert len(text) > 0
|
||||
|
||||
|
||||
def test_dark_dispatch_heart_prob(game):
|
||||
"""With 100% heart_prob and 0% for others, dispatch should go to ENTRY-BAY."""
|
||||
state, _, _ = game
|
||||
state.flags["heart_prob"] = 100
|
||||
state.flags["vogon_prob"] = 0
|
||||
state.flags["traal_prob"] = 0
|
||||
state.flags["fleet_prob"] = 0
|
||||
|
||||
# Enter dark room
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
state.lying_down = False
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
state.output.flush()
|
||||
|
||||
# Set ready and trigger dispatch
|
||||
state.flags["dark_exit_ready"] = True
|
||||
state.flags["dark_hint_given"] = True
|
||||
text = send(game, "south")
|
||||
|
||||
# Should be dispatched to ENTRY-BAY (Heart of Gold)
|
||||
assert state.here.id == "ENTRY-BAY"
|
||||
|
||||
|
||||
def test_dark_dispatch_vogon_prob(game):
|
||||
"""With 100% vogon_prob and 0% for others, dispatch should go to HOLD."""
|
||||
state, _, _ = game
|
||||
state.flags["heart_prob"] = 0
|
||||
state.flags["vogon_prob"] = 100
|
||||
state.flags["traal_prob"] = 0
|
||||
state.flags["fleet_prob"] = 0
|
||||
|
||||
# Enter dark room
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
state.lying_down = False
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
state.output.flush()
|
||||
|
||||
# Set ready and trigger dispatch
|
||||
state.flags["dark_exit_ready"] = True
|
||||
state.flags["dark_hint_given"] = True
|
||||
text = send(game, "south")
|
||||
|
||||
assert state.here.id == "HOLD"
|
||||
|
||||
|
||||
def test_dark_look_shows_pitch_dark(game):
|
||||
"""Looking in DARK should describe pitch darkness."""
|
||||
state, _, _ = game
|
||||
dark_room = state.world.get_room("DARK")
|
||||
state.here = dark_room
|
||||
state.protagonist.move_to(dark_room)
|
||||
if dark_room.action:
|
||||
dark_room.action(state, "M-ENTER")
|
||||
state.output.flush()
|
||||
|
||||
# Trigger M-LOOK
|
||||
dark_room.action(state, "M-LOOK")
|
||||
text = state.output.flush()
|
||||
assert "pitch dark" in text.lower()
|
||||
@@ -0,0 +1,228 @@
|
||||
"""Tests for the Earth section of the game."""
|
||||
|
||||
from tests.conftest import send
|
||||
|
||||
|
||||
def test_start_in_dark(game):
|
||||
state, _, _ = game
|
||||
assert not state.lit
|
||||
text = send(game, "look")
|
||||
assert "pitch black" in text.lower()
|
||||
|
||||
|
||||
def test_turn_on_light(game):
|
||||
text = send(game, "turn on light")
|
||||
assert "Good start" in text
|
||||
assert "light is now on" in text
|
||||
state, _, _ = game
|
||||
assert state.lit
|
||||
|
||||
|
||||
def test_turn_off_light(game):
|
||||
send(game, "turn on light")
|
||||
text = send(game, "turn off light")
|
||||
assert "pitch dark" in text
|
||||
state, _, _ = game
|
||||
assert not state.lit
|
||||
|
||||
|
||||
def test_get_out_of_bed(game):
|
||||
send(game, "turn on light")
|
||||
text = send(game, "get out of bed")
|
||||
assert "manage it" in text.lower()
|
||||
state, _, _ = game
|
||||
assert not state.lying_down
|
||||
|
||||
|
||||
def test_cant_walk_while_lying_down(game):
|
||||
send(game, "turn on light")
|
||||
text = send(game, "south")
|
||||
# Player starts in bed, so the message references getting out of bed
|
||||
assert "get out" in text.lower() or "get up" in text.lower()
|
||||
|
||||
|
||||
def test_take_gown_with_headache(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "get gown")
|
||||
assert "large enough" in text.lower()
|
||||
assert "pocket" in text.lower()
|
||||
|
||||
|
||||
def test_wear_gown(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
text = send(game, "wear gown")
|
||||
assert "wearing" in text.lower()
|
||||
|
||||
|
||||
def test_look_in_pocket(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
text = send(game, "look in pocket")
|
||||
# Gown should open and reveal contents
|
||||
assert "thing" in text.lower() or "fluff" in text.lower() or "analgesic" in text.lower()
|
||||
|
||||
|
||||
def test_eat_tablet(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket") # opens gown
|
||||
text = send(game, "eat tablet")
|
||||
assert "headache goes" in text.lower()
|
||||
state, _, _ = game
|
||||
assert not state.flags.get("headache")
|
||||
assert state.score == 10
|
||||
|
||||
|
||||
def test_cant_leave_bedroom_with_headache(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "open door")
|
||||
text = send(game, "south")
|
||||
assert "eighteen inches" in text.lower() or "jostles" in text.lower()
|
||||
|
||||
|
||||
def test_leave_bedroom_after_tablet(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket")
|
||||
send(game, "eat tablet")
|
||||
text = send(game, "south")
|
||||
# Should successfully leave bedroom
|
||||
state, _, _ = game
|
||||
assert state.here.id == "FRONT-PORCH"
|
||||
|
||||
|
||||
def test_cant_leave_house_without_gown(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket")
|
||||
send(game, "eat tablet")
|
||||
send(game, "south")
|
||||
# Now at front porch, take off gown
|
||||
send(game, "take off gown")
|
||||
text = send(game, "south")
|
||||
assert "indecency" in text.lower()
|
||||
|
||||
|
||||
def test_leave_house_with_gown(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket")
|
||||
send(game, "eat tablet")
|
||||
send(game, "south")
|
||||
text = send(game, "south")
|
||||
state, _, _ = game
|
||||
assert state.here.id == "FRONT-OF-HOUSE"
|
||||
|
||||
|
||||
def test_navigate_to_pub(game):
|
||||
# Speed through to pub
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket")
|
||||
send(game, "eat tablet")
|
||||
send(game, "south") # front porch
|
||||
send(game, "south") # front of house
|
||||
send(game, "south") # country lane
|
||||
text = send(game, "south") # pub
|
||||
state, _, _ = game
|
||||
assert state.here.id == "PUB"
|
||||
# Ford should have arrived
|
||||
assert "Ford" in text or state.flags.get("ford_arrived")
|
||||
|
||||
|
||||
def test_drink_three_beers(game):
|
||||
# Speed through to pub
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
send(game, "get gown")
|
||||
send(game, "wear gown")
|
||||
send(game, "look in pocket")
|
||||
send(game, "eat tablet")
|
||||
send(game, "south")
|
||||
send(game, "south")
|
||||
send(game, "south")
|
||||
send(game, "south") # pub
|
||||
|
||||
text1 = send(game, "drink beer")
|
||||
assert "2 pint" in text1.lower()
|
||||
text2 = send(game, "drink beer")
|
||||
assert "1 pint" in text2.lower()
|
||||
text3 = send(game, "drink beer")
|
||||
assert "wriggly" in text3.lower() or "ear" in text3.lower()
|
||||
|
||||
state, _, _ = game
|
||||
assert state.score == 25 # 10 (tablet) + 15 (beers)
|
||||
|
||||
|
||||
def test_score_command(game):
|
||||
text = send(game, "score")
|
||||
assert "0 of a possible 400" in text
|
||||
|
||||
|
||||
def test_inventory_shows_headache(game):
|
||||
text = send(game, "inventory")
|
||||
assert "splitting headache" in text.lower()
|
||||
|
||||
|
||||
def test_inventory_shows_no_tea(game):
|
||||
text = send(game, "inventory")
|
||||
assert "no tea" in text.lower()
|
||||
|
||||
|
||||
def test_examine_bed(game):
|
||||
send(game, "turn on light")
|
||||
text = send(game, "examine bed")
|
||||
assert "bed" in text.lower()
|
||||
|
||||
|
||||
def test_look_under_bed(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "look under bed")
|
||||
assert "handkerchiefs" in text.lower() or "nothing" in text.lower()
|
||||
|
||||
|
||||
def test_open_curtains_reveals_bulldozer(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "open curtains")
|
||||
assert "bulldozer" in text.lower()
|
||||
|
||||
|
||||
def test_screwdriver_headache(game):
|
||||
"""Can't take screwdriver while having a headache."""
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "take screwdriver")
|
||||
assert "dances" in text.lower() or "possessed" in text.lower()
|
||||
|
||||
|
||||
def test_toothbrush_headache(game):
|
||||
"""Can't take toothbrush while having a headache."""
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "take toothbrush")
|
||||
assert "lunge" in text.lower() or "spins" in text.lower()
|
||||
|
||||
|
||||
def test_phone_action(game):
|
||||
send(game, "turn on light")
|
||||
send(game, "get out of bed")
|
||||
text = send(game, "answer phone")
|
||||
assert "receiver" in text.lower() or "dialling" in text.lower()
|
||||
@@ -0,0 +1,197 @@
|
||||
"""Tests for core engine mechanics: containment, flags, clock, articles."""
|
||||
|
||||
from h2g2.engine.game_object import GameObject, Room, Flag
|
||||
from h2g2.engine.clock import Clock
|
||||
|
||||
|
||||
class TestObjectContainment:
|
||||
def test_move_to_sets_parent(self):
|
||||
room = Room("R1", desc="room")
|
||||
obj = GameObject("O1", desc="thing")
|
||||
obj.move_to(room)
|
||||
assert obj.parent is room
|
||||
assert obj in room.children
|
||||
|
||||
def test_move_to_removes_from_old_parent(self):
|
||||
room1 = Room("R1", desc="room1")
|
||||
room2 = Room("R2", desc="room2")
|
||||
obj = GameObject("O1", desc="thing")
|
||||
obj.move_to(room1)
|
||||
obj.move_to(room2)
|
||||
assert obj not in room1.children
|
||||
assert obj in room2.children
|
||||
assert obj.parent is room2
|
||||
|
||||
def test_contains(self):
|
||||
room = Room("R1", desc="room")
|
||||
obj = GameObject("O1", desc="thing")
|
||||
obj.move_to(room)
|
||||
assert room.contains(obj)
|
||||
|
||||
def test_is_held_by_direct(self):
|
||||
holder = GameObject("H", desc="holder")
|
||||
obj = GameObject("O", desc="thing")
|
||||
obj.move_to(holder)
|
||||
assert obj.is_held_by(holder)
|
||||
|
||||
def test_is_held_by_nested(self):
|
||||
holder = GameObject("H", desc="holder")
|
||||
container = GameObject("C", desc="container")
|
||||
obj = GameObject("O", desc="thing")
|
||||
container.move_to(holder)
|
||||
obj.move_to(container)
|
||||
assert obj.is_held_by(holder)
|
||||
|
||||
def test_is_held_by_false(self):
|
||||
holder = GameObject("H", desc="holder")
|
||||
obj = GameObject("O", desc="thing")
|
||||
assert not obj.is_held_by(holder)
|
||||
|
||||
def test_contents_string_excludes_invisible(self):
|
||||
room = Room("R1", desc="room")
|
||||
visible = GameObject("V", desc="visible")
|
||||
invisible = GameObject("I", desc="invisible", flags={Flag.INVISIBLE})
|
||||
visible.move_to(room)
|
||||
invisible.move_to(room)
|
||||
result = room.contents_string()
|
||||
assert visible in result
|
||||
assert invisible not in result
|
||||
|
||||
def test_contents_string_excludes_ndescbit(self):
|
||||
room = Room("R1", desc="room")
|
||||
visible = GameObject("V", desc="visible")
|
||||
hidden = GameObject("H", desc="hidden", flags={Flag.NDESCBIT})
|
||||
visible.move_to(room)
|
||||
hidden.move_to(room)
|
||||
result = room.contents_string()
|
||||
assert visible in result
|
||||
assert hidden not in result
|
||||
|
||||
|
||||
class TestFlagOperations:
|
||||
def test_fset_and_fset_q(self):
|
||||
obj = GameObject("O", desc="thing")
|
||||
assert not obj.fset_q(Flag.TAKEBIT)
|
||||
obj.fset(Flag.TAKEBIT)
|
||||
assert obj.fset_q(Flag.TAKEBIT)
|
||||
|
||||
def test_fclear(self):
|
||||
obj = GameObject("O", desc="thing", flags={Flag.TAKEBIT})
|
||||
assert obj.fset_q(Flag.TAKEBIT)
|
||||
obj.fclear(Flag.TAKEBIT)
|
||||
assert not obj.fset_q(Flag.TAKEBIT)
|
||||
|
||||
def test_fclear_nonexistent_flag_no_error(self):
|
||||
obj = GameObject("O", desc="thing")
|
||||
obj.fclear(Flag.TAKEBIT) # should not raise
|
||||
|
||||
def test_multiple_flags(self):
|
||||
obj = GameObject("O", desc="thing", flags={Flag.TAKEBIT, Flag.OPENBIT})
|
||||
assert obj.fset_q(Flag.TAKEBIT)
|
||||
assert obj.fset_q(Flag.OPENBIT)
|
||||
assert not obj.fset_q(Flag.WEARBIT)
|
||||
|
||||
|
||||
class TestObjectArticle:
|
||||
def test_default_article(self):
|
||||
obj = GameObject("O", desc="ball")
|
||||
assert obj.article() == "a "
|
||||
assert obj.a_desc() == "a ball"
|
||||
assert obj.the_desc() == "the ball"
|
||||
|
||||
def test_vowel_article(self):
|
||||
obj = GameObject("O", desc="egg", flags={Flag.VOWELBIT})
|
||||
assert obj.article() == "an "
|
||||
assert obj.a_desc() == "an egg"
|
||||
|
||||
def test_no_article(self):
|
||||
obj = GameObject("O", desc="Ford Prefect", flags={Flag.NARTICLEBIT})
|
||||
assert obj.article() == ""
|
||||
assert obj.a_desc() == "Ford Prefect"
|
||||
assert obj.the_desc() == "Ford Prefect"
|
||||
|
||||
|
||||
class TestClock:
|
||||
def test_tick_and_fire(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
clock.queue(handler, 3, name="test")
|
||||
clock.tick_all(None) # tick 1: countdown 3->2
|
||||
assert len(fired) == 0
|
||||
clock.tick_all(None) # tick 2: countdown 2->1
|
||||
assert len(fired) == 0
|
||||
clock.tick_all(None) # tick 3: countdown 1->0, fires
|
||||
assert len(fired) == 1
|
||||
|
||||
def test_one_shot_does_not_refire(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
clock.queue(handler, 1, name="test")
|
||||
clock.tick_all(None) # fires
|
||||
clock.tick_all(None) # should not fire again
|
||||
assert len(fired) == 1
|
||||
|
||||
def test_disable_prevents_fire(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
entry = clock.queue(handler, 2, name="test")
|
||||
clock.tick_all(None) # tick 1
|
||||
clock.disable(entry)
|
||||
clock.tick_all(None) # tick 2 — disabled, should not fire
|
||||
clock.tick_all(None) # tick 3
|
||||
assert len(fired) == 0
|
||||
|
||||
def test_negative_tick_fires_every_turn(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
clock.queue(handler, -1, name="repeating")
|
||||
clock.tick_all(None)
|
||||
clock.tick_all(None)
|
||||
clock.tick_all(None)
|
||||
assert len(fired) == 3
|
||||
|
||||
def test_queue_updates_existing_entry(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
clock.queue(handler, 5, name="test")
|
||||
clock.queue(handler, 1, name="test") # reset to 1
|
||||
clock.tick_all(None) # should fire now (1->0)
|
||||
assert len(fired) == 1
|
||||
|
||||
def test_remove_entry(self):
|
||||
clock = Clock()
|
||||
fired = []
|
||||
|
||||
def handler(state):
|
||||
fired.append(True)
|
||||
return True
|
||||
|
||||
entry = clock.queue(handler, 1, name="test")
|
||||
clock.remove(entry)
|
||||
clock.tick_all(None)
|
||||
assert len(fired) == 0
|
||||
@@ -0,0 +1,177 @@
|
||||
"""Tests for the input parser."""
|
||||
|
||||
from tests.conftest import send
|
||||
|
||||
|
||||
def test_direction_shortcut_n(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("n", state)
|
||||
assert result is not None
|
||||
assert result.verb == "walk"
|
||||
assert result.direction == "NORTH"
|
||||
|
||||
|
||||
def test_direction_shortcut_s(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("s", state)
|
||||
assert result is not None
|
||||
assert result.verb == "walk"
|
||||
assert result.direction == "SOUTH"
|
||||
|
||||
|
||||
def test_direction_shortcut_e(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("e", state)
|
||||
assert result is not None
|
||||
assert result.verb == "walk"
|
||||
assert result.direction == "EAST"
|
||||
|
||||
|
||||
def test_direction_go_north(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("go north", state)
|
||||
assert result is not None
|
||||
assert result.verb == "walk"
|
||||
assert result.direction == "NORTH"
|
||||
|
||||
|
||||
def test_compound_verb_get_out(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("get out of bed", state)
|
||||
assert result is not None
|
||||
assert result.verb == "get out"
|
||||
|
||||
|
||||
def test_compound_verb_turn_on(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("turn on light", state)
|
||||
assert result is not None
|
||||
assert result.verb == "turn on"
|
||||
|
||||
|
||||
def test_compound_verb_turn_off(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("turn off light", state)
|
||||
assert result is not None
|
||||
assert result.verb == "turn off"
|
||||
|
||||
|
||||
def test_compound_verb_look_in(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("look in pocket", state)
|
||||
assert result is not None
|
||||
assert result.verb == "look in"
|
||||
|
||||
|
||||
def test_compound_verb_look_at(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("look at bed", state)
|
||||
assert result is not None
|
||||
assert result.verb == "examine"
|
||||
|
||||
|
||||
def test_compound_verb_pick_up(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("pick up gown", state)
|
||||
assert result is not None
|
||||
assert result.verb == "take"
|
||||
|
||||
|
||||
def test_compound_verb_stand_up(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("stand up", state)
|
||||
assert result is not None
|
||||
assert result.verb == "stand up"
|
||||
|
||||
|
||||
def test_compound_verb_lie_down(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("lie down", state)
|
||||
assert result is not None
|
||||
assert result.verb == "lie down"
|
||||
|
||||
|
||||
def test_compound_verb_put_on(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("put on gown", state)
|
||||
assert result is not None
|
||||
assert result.verb == "put on"
|
||||
|
||||
|
||||
def test_compound_verb_take_off(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("take off gown", state)
|
||||
assert result is not None
|
||||
assert result.verb == "take off"
|
||||
|
||||
|
||||
def test_preposition_splitting(game):
|
||||
state, parser, _ = game
|
||||
# Need objects in scope for "put X in Y" — use gown (in bedroom) and bed
|
||||
result = parser.parse("put gown in bed", state)
|
||||
assert result is not None
|
||||
assert result.prep == "in"
|
||||
|
||||
|
||||
def test_again_command(game):
|
||||
state, parser, _ = game
|
||||
# First command
|
||||
result1 = parser.parse("look", state)
|
||||
assert result1 is not None
|
||||
# Again via "g"
|
||||
result2 = parser.parse("g", state)
|
||||
assert result2 is result1
|
||||
# Again via "again"
|
||||
result3 = parser.parse("again", state)
|
||||
assert result3 is result1
|
||||
|
||||
|
||||
def test_buzz_word_stripping(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("take the gown", state)
|
||||
assert result is not None
|
||||
assert result.verb == "take"
|
||||
# "the" should be stripped, object should resolve to GOWN
|
||||
assert result.direct_obj is not None
|
||||
assert result.direct_obj.id == "GOWN"
|
||||
|
||||
|
||||
def test_verb_synonym_grab(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("grab gown", state)
|
||||
assert result is not None
|
||||
assert result.verb == "take"
|
||||
|
||||
|
||||
def test_verb_synonym_x(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("x bed", state)
|
||||
assert result is not None
|
||||
assert result.verb == "examine"
|
||||
|
||||
|
||||
def test_verb_synonym_i(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("i", state)
|
||||
assert result is not None
|
||||
assert result.verb == "inventory"
|
||||
|
||||
|
||||
def test_negation(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("don't panic", state)
|
||||
assert result is not None
|
||||
assert result.is_negated is True
|
||||
assert result.verb == "panic"
|
||||
|
||||
|
||||
def test_empty_input_returns_none(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("", state)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_only_buzz_words_returns_none(game):
|
||||
state, parser, _ = game
|
||||
result = parser.parse("the a an", state)
|
||||
assert result is None
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Tests for the Vogon ship section."""
|
||||
|
||||
from tests.conftest import send
|
||||
|
||||
|
||||
def test_hold_exists(game):
|
||||
"""Verify the Hold room was registered."""
|
||||
state, _, _ = game
|
||||
hold = state.world.get_room("HOLD")
|
||||
assert hold is not None
|
||||
assert hold.id == "HOLD"
|
||||
|
||||
|
||||
def test_hold_first_visit(game):
|
||||
"""Test that the Hold's first-visit M-END sequence gives peanuts and score."""
|
||||
state, _, loop = game
|
||||
hold = state.world.get_room("HOLD")
|
||||
state.here = hold
|
||||
state.protagonist.move_to(hold)
|
||||
state.lying_down = False
|
||||
# The first-visit logic is in M-END, not M-ENTER
|
||||
if hold.action:
|
||||
hold.action(state, "M-END")
|
||||
text = state.output.flush()
|
||||
# Should mention waking up and give peanuts
|
||||
assert len(text) > 0
|
||||
assert state.score >= 8
|
||||
peanuts = state.world.get("PEANUTS")
|
||||
assert peanuts.parent is state.protagonist
|
||||
|
||||
|
||||
def test_airlock_room_exists(game):
|
||||
"""Verify the Airlock room was registered."""
|
||||
state, _, _ = game
|
||||
airlock = state.world.get_room("AIRLOCK")
|
||||
assert airlock is not None
|
||||
assert airlock.id == "AIRLOCK"
|
||||
|
||||
|
||||
def test_captains_quarters_exists(game):
|
||||
"""Verify the Captain's Quarters room was registered."""
|
||||
state, _, _ = game
|
||||
quarters = state.world.get_room("CAPTAINS-QUARTERS")
|
||||
assert quarters is not None
|
||||
Reference in New Issue
Block a user