a1bb4cbf02
Engine refactoring: - Replace hardcoded state attributes with generic state.flags dict - Add death system (jigs_up/finish) with dream-restore callbacks - Add inventory extras hook system (removes hardcoded headache/tea lines) - Add 16 new verb handlers (consult, say, carve, plug, repair, kick, etc.) - Add verb synonyms to parser New content modules (25 rooms, 107 objects total): - vogon.py: Hold, Captain's Quarters, Airlock; babel fish puzzle, poetry scene, airlock ejection sequence with timed events - heart.py: 10 Heart of Gold rooms; Marvin tool quest, tea/no-tea paradox, Nutrimat overload, Infinite Improbability Drive puzzle, victory sequence - unearth.py: Traal (Beast lair with towel/name/carve puzzle), War Chamber, Inside Whale, Maze with random exits and particle - dark.py: Dream dispatch room with probabilistic destination selection, sensory discovery mechanic, dream-restore callback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1154 lines
35 KiB
Python
1154 lines
35 KiB
Python
"""Vogon ship locations -- Hold, Captain's Quarters, Airlock."""
|
|
|
|
import random
|
|
|
|
from h2g2.engine.game_object import (
|
|
GameObject, Room, Flag, Direction,
|
|
DirectExit, ConditionalExit, BlockedExit,
|
|
)
|
|
from h2g2.engine.world import World
|
|
from h2g2.engine.state import GameState
|
|
|
|
|
|
# ---- Gibberish generator ----
|
|
|
|
_VOGON_SYLLABLES = [
|
|
"blurp", "grun", "flib", "morgh", "splut", "vrunt", "glub", "snarg",
|
|
"plod", "quork", "bleg", "frot", "nargh", "sklurb", "thrip", "zunt",
|
|
"drob", "munt", "flob", "grak", "prunt", "vlib", "skrog", "blort",
|
|
]
|
|
|
|
_VOGON_SUFFIXES = [
|
|
"ulous", "ting", "ment", "ish", "wards", "oid", "esque", "ble",
|
|
"ness", "ling", "osity", "ated", "ingly", "ation",
|
|
]
|
|
|
|
|
|
def _gibberish_word() -> str:
|
|
"""Generate a random Vogon-sounding gibberish word."""
|
|
base = random.choice(_VOGON_SYLLABLES)
|
|
if random.random() > 0.5:
|
|
base += random.choice(_VOGON_SYLLABLES)[:3]
|
|
base += random.choice(_VOGON_SUFFIXES)
|
|
return base
|
|
|
|
|
|
def _gibberish_line() -> str:
|
|
"""Generate a full line of Vogon gibberish."""
|
|
words = [_gibberish_word() for _ in range(random.randint(4, 8))]
|
|
line = " ".join(words)
|
|
return f'"{line.capitalize()}!"'
|
|
|
|
|
|
# ---- Poetry text ----
|
|
|
|
POETRY_LINES = [
|
|
'"Oh freddled gruntbuggly, thy nacturations are to me!"',
|
|
'"As plurdled gabbleblotchits on a lurgid bee."',
|
|
'"Groop I implore thee, my foonting turlingdromes."',
|
|
'"And hooptiously drangle me with crinkly bindlewurdles,"',
|
|
'"or I will rend thee in the gobberwarts with my blurglecruncheon, see if I don\'t!"',
|
|
]
|
|
|
|
POETRY_VERSE_2 = [
|
|
'"Fripping lyshus wimbgunts, awhilst moongrovenly kormzibs."',
|
|
'"Gashee morphousite, thou expungiest quoopisk!"',
|
|
'"Bleem miserable venchit! Bleem forever mestinglish asunder frapt."',
|
|
]
|
|
|
|
|
|
# ---- Object action handlers ----
|
|
|
|
def _inner_door_action(state: GameState) -> bool:
|
|
out = state.output
|
|
door = state.world.get("INNER-DOOR")
|
|
|
|
if state.prsa == "open":
|
|
if door.fset_q(Flag.OPENBIT):
|
|
out.tell("The inner airlock door is already open.\n")
|
|
else:
|
|
door.fset(Flag.OPENBIT)
|
|
out.tell("You open the inner airlock door.\n")
|
|
return True
|
|
|
|
if state.prsa == "close":
|
|
if not door.fset_q(Flag.OPENBIT):
|
|
out.tell("It's already closed.\n")
|
|
else:
|
|
door.fclear(Flag.OPENBIT)
|
|
out.tell("You close the inner airlock door.\n")
|
|
return True
|
|
|
|
if state.prsa == "examine":
|
|
status = "open" if door.fset_q(Flag.OPENBIT) else "closed"
|
|
out.tell(f"A heavy inner airlock door. It is {status}.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _airlock_obj_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"The airlock is a standard Vogon design: built to eject "
|
|
"unwanted hitchhikers into the vacuum of space.\n"
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _dispenser_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"The dispenser has a button, and a small sign that reads "
|
|
'"Babel Fish." A small hole at the bottom dispenses the fish.\n'
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _dispenser_button_action(state: GameState) -> bool:
|
|
out = state.output
|
|
|
|
if state.prsa not in ("push", "press"):
|
|
return False
|
|
|
|
fish_counter = state.flags.get("fish_counter", 5)
|
|
|
|
if state.flags.get("babel_fish_in_ear"):
|
|
out.tell("You already have a babel fish in your ear.\n")
|
|
return True
|
|
|
|
if fish_counter <= 0:
|
|
out.tell(
|
|
"The dispenser makes a grinding noise, but nothing comes "
|
|
"out. It appears to be empty.\n"
|
|
)
|
|
return True
|
|
|
|
state.flags["fish_counter"] = fish_counter - 1
|
|
|
|
# Check puzzle conditions
|
|
gown_hung = state.flags.get("gown_hung", False)
|
|
towel_on_drain = state.flags.get("towel_on_drain", False)
|
|
panel_blocker = state.flags.get("panel_blocker")
|
|
item_on_satchel = state.flags.get("item_on_satchel")
|
|
|
|
if not gown_hung:
|
|
out.tell(
|
|
"A small babel fish shoots out of the dispenser, arcs through "
|
|
"the air, and vanishes down a drain in the floor.\n"
|
|
)
|
|
return True
|
|
|
|
if not towel_on_drain:
|
|
out.tell(
|
|
"A small babel fish shoots out of the dispenser, bounces off "
|
|
"the dressing gown hanging on the hook, and vanishes down a "
|
|
"drain in the floor.\n"
|
|
)
|
|
return True
|
|
|
|
if panel_blocker is None:
|
|
out.tell(
|
|
"A small babel fish shoots out of the dispenser, bounces off "
|
|
"the dressing gown, slides across the towel covering the "
|
|
"drain, and is scooped up by a small cleaning robot that "
|
|
"emerges from a panel in the wall. The robot disappears back "
|
|
"into the wall.\n"
|
|
)
|
|
return True
|
|
|
|
if item_on_satchel is None:
|
|
out.tell(
|
|
"A small babel fish shoots out of the dispenser, bounces off "
|
|
"the dressing gown, slides across the towel, hits the satchel "
|
|
"blocking the robot panel, and flies upward -- straight into "
|
|
"the upper reaches of the room and out of sight.\n"
|
|
)
|
|
return True
|
|
|
|
# Success!
|
|
out.tell(
|
|
"A small babel fish shoots out of the dispenser, bounces off "
|
|
"the dressing gown, slides across the towel, hits the satchel "
|
|
"blocking the robot panel, flies upward, bounces off the junk "
|
|
"mail balanced on the satchel, and lands neatly in your ear.\n\n"
|
|
"You can suddenly understand everything around you. The babel "
|
|
"fish is now happily nestled in your ear.\n"
|
|
)
|
|
state.flags["babel_fish_in_ear"] = True
|
|
state.score += 12
|
|
return True
|
|
|
|
|
|
def _hook_action(state: GameState) -> bool:
|
|
out = state.output
|
|
|
|
if state.prsa == "examine":
|
|
if state.flags.get("gown_hung"):
|
|
out.tell("Your gown is hanging on the hook.\n")
|
|
else:
|
|
out.tell("A small hook on the wall opposite the dispenser.\n")
|
|
return True
|
|
|
|
if state.prsa in ("hang", "put"):
|
|
gown = state.world.objects.get("GOWN")
|
|
if state.prso is gown:
|
|
if not gown.is_held_by(state.protagonist):
|
|
out.tell("You're not carrying the gown.\n")
|
|
return True
|
|
state.flags["gown_hung"] = True
|
|
gown.move_to(state.world.local_globals)
|
|
out.tell("You hang the gown on the hook.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _drain_action(state: GameState) -> bool:
|
|
out = state.output
|
|
|
|
if state.prsa == "examine":
|
|
if state.flags.get("towel_on_drain"):
|
|
out.tell("The drain is covered by a towel.\n")
|
|
else:
|
|
out.tell("A small drain grate in the floor.\n")
|
|
return True
|
|
|
|
if state.prsa in ("cover", "put", "block"):
|
|
towel = state.world.objects.get("TOWEL")
|
|
if state.prso is towel:
|
|
if not towel.is_held_by(state.protagonist):
|
|
out.tell("You're not carrying the towel.\n")
|
|
return True
|
|
state.flags["towel_on_drain"] = True
|
|
towel.move_to(state.world.local_globals)
|
|
out.tell("You cover the drain with the towel.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _robot_panel_action(state: GameState) -> bool:
|
|
out = state.output
|
|
|
|
if state.prsa == "examine":
|
|
blocker = state.flags.get("panel_blocker")
|
|
if blocker:
|
|
out.tell(f"The robot panel is blocked by {blocker.the_desc()}.\n")
|
|
else:
|
|
out.tell("A small panel in the wall from which a cleaning robot emerges.\n")
|
|
return True
|
|
|
|
if state.prsa in ("put", "block", "cover"):
|
|
satchel = state.world.objects.get("SATCHEL")
|
|
if state.prso is satchel:
|
|
if not satchel.is_held_by(state.protagonist):
|
|
out.tell("You're not carrying the satchel.\n")
|
|
return True
|
|
state.flags["panel_blocker"] = satchel
|
|
satchel.move_to(state.world.local_globals)
|
|
out.tell("You put the satchel in front of the robot panel.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _babel_fish_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
if state.flags.get("babel_fish_in_ear"):
|
|
out.tell(
|
|
"The babel fish is snugly lodged in your ear, translating "
|
|
"all alien languages into English for you.\n"
|
|
)
|
|
else:
|
|
out.tell("A small, yellow, leech-like fish.\n")
|
|
return True
|
|
if state.prsa == "take":
|
|
if state.flags.get("babel_fish_in_ear"):
|
|
out.tell("It's far too comfortable in your ear to remove.\n")
|
|
return True
|
|
return False
|
|
|
|
|
|
def _vogon_captain_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"The Vogon Captain is a large, slug-like creature with a "
|
|
"foul temper and a passion for bureaucratic paperwork. And "
|
|
"poetry. Especially poetry.\n"
|
|
)
|
|
return True
|
|
if state.prsa in ("tell", "ask", "talk"):
|
|
out.tell(
|
|
'The Captain grunts. "Resistance is useless!" he bellows.\n'
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _poetry_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
if state.flags.get("babel_fish_in_ear"):
|
|
out.tell(
|
|
"It's Vogon poetry. The third worst in the known galaxy.\n"
|
|
)
|
|
else:
|
|
out.tell(
|
|
"It sounds like someone gargling with a throat full of "
|
|
"live weasels.\n"
|
|
)
|
|
return True
|
|
|
|
if state.prsa in ("enjoy", "appreciate", "like", "listen"):
|
|
if not state.flags.get("babel_fish_in_ear"):
|
|
out.tell(
|
|
"You can't even understand it. How could you enjoy it?\n"
|
|
)
|
|
return True
|
|
if state.flags.get("poem_enjoyed"):
|
|
out.tell("You've already expressed your enjoyment.\n")
|
|
return True
|
|
state.flags["poem_enjoyed"] = True
|
|
state.score += 15
|
|
out.tell(
|
|
'You nod your head approvingly. "Oh, very good," you say.\n'
|
|
"The Captain beams with delight and continues reading.\n"
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _chair_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"The Poetry Appreciation Chair is a fiendish Vogon device "
|
|
"that clamps you in place and forces you to listen. Steel "
|
|
"bands hold your arms, legs, and head firmly in position.\n"
|
|
)
|
|
return True
|
|
if state.prsa in ("get out", "leave", "escape", "stand"):
|
|
out.tell(
|
|
"The steel bands hold you firmly in the chair. You're not "
|
|
"going anywhere until the Captain decides otherwise.\n"
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _glass_case_action(state: GameState) -> bool:
|
|
out = state.output
|
|
case = state.world.get("GLASS-CASE")
|
|
|
|
if state.prsa == "examine":
|
|
if case.fset_q(Flag.OPENBIT):
|
|
out.tell("The glass case is open.\n")
|
|
else:
|
|
out.tell(
|
|
"A sealed glass case on the wall. Inside you can see a "
|
|
"device of some sort. There is a keyboard and a small "
|
|
"switch on the front.\n"
|
|
)
|
|
return True
|
|
|
|
if state.prsa == "open":
|
|
if case.fset_q(Flag.OPENBIT):
|
|
out.tell("It's already open.\n")
|
|
else:
|
|
out.tell("It won't open by force. Try the keyboard.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _keyboard_action(state: GameState) -> bool:
|
|
out = state.output
|
|
|
|
if state.prsa in ("type", "use", "push"):
|
|
case = state.world.get("GLASS-CASE")
|
|
if case.fset_q(Flag.OPENBIT):
|
|
out.tell("The case is already open.\n")
|
|
return True
|
|
# Accept any typing action -- the correct answer is typing a
|
|
# word from the poem while you understand it
|
|
if state.flags.get("babel_fish_in_ear") and state.flags.get("poem_enjoyed"):
|
|
case.fset(Flag.OPENBIT)
|
|
state.score += 25
|
|
out.tell(
|
|
"You type a word from the poem on the keyboard. The case "
|
|
"makes a satisfying click and swings open, revealing an "
|
|
"atomic vector plotter.\n"
|
|
)
|
|
return True
|
|
out.tell(
|
|
"You type something on the keyboard. Nothing happens. "
|
|
"Perhaps you need to type the right thing.\n"
|
|
)
|
|
return True
|
|
|
|
if state.prsa == "examine":
|
|
out.tell("A small keyboard attached to the glass case.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _switch_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa in ("push", "press", "flip", "turn on", "turn off"):
|
|
out.tell("Click. Nothing obvious happens.\n")
|
|
return True
|
|
if state.prsa == "examine":
|
|
out.tell("A small switch on the front of the glass case.\n")
|
|
return True
|
|
return False
|
|
|
|
|
|
def _plotter_action(state: GameState) -> bool:
|
|
out = state.output
|
|
case = state.world.get("GLASS-CASE")
|
|
|
|
if state.prsa == "take":
|
|
if not case.fset_q(Flag.OPENBIT):
|
|
out.tell("The plotter is sealed inside the glass case.\n")
|
|
return True
|
|
plotter = state.world.get("PLOTTER")
|
|
plotter.move_to(state.protagonist)
|
|
out.tell("You take the atomic vector plotter.\n")
|
|
return True
|
|
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"It's an atomic vector plotter -- a small, sleek device "
|
|
"of obviously advanced technology.\n"
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _satchel_action(state: GameState) -> bool:
|
|
out = state.output
|
|
satchel = state.world.get("SATCHEL")
|
|
|
|
if state.prsa == "examine":
|
|
out.tell("It's Ford's battered leather satchel.\n")
|
|
return True
|
|
|
|
if state.prsa in ("open", "look in"):
|
|
visible = satchel.contents_string()
|
|
if visible:
|
|
descs = [obj.a_desc() for obj in visible]
|
|
out.tell("The satchel contains " + ", ".join(descs) + ".\n")
|
|
else:
|
|
out.tell("The satchel is empty.\n")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _towel_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
out.tell(
|
|
"It's a large, fluffy towel. About the most massively "
|
|
"useful thing an interstellar hitchhiker can have.\n"
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _guide_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa in ("read", "examine", "consult"):
|
|
out.tell(
|
|
"The cover of the Guide reads, in large friendly letters: "
|
|
'"DON\'T PANIC"\n\n'
|
|
"The Guide has a lot to say on many subjects. It is the "
|
|
"standard repository of all knowledge and wisdom.\n"
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
def _peanuts_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa in ("eat", "open"):
|
|
out.tell(
|
|
"You eat the peanuts. They were rather good, as peanuts go.\n"
|
|
)
|
|
state.world.get("PEANUTS").move_to(state.world.local_globals)
|
|
return True
|
|
if state.prsa == "examine":
|
|
out.tell("A small packet of salted peanuts.\n")
|
|
return True
|
|
return False
|
|
|
|
|
|
def _ford_vogon_action(state: GameState) -> bool:
|
|
out = state.output
|
|
if state.prsa == "examine":
|
|
if state.flags.get("ford_sleeping"):
|
|
out.tell("Ford is slumped against the wall, fast asleep.\n")
|
|
else:
|
|
out.tell(
|
|
"Ford looks somewhat the worse for wear, but his eyes "
|
|
"are sharp and alert.\n"
|
|
)
|
|
return True
|
|
if state.prsa in ("tell", "ask", "talk"):
|
|
if state.flags.get("ford_sleeping"):
|
|
out.tell("Ford is asleep and doesn't respond.\n")
|
|
else:
|
|
out.tell('"Don\'t panic," says Ford.\n')
|
|
return True
|
|
if state.prsa == "wake":
|
|
if state.flags.get("ford_sleeping"):
|
|
out.tell("Ford grunts and rolls over but doesn't wake.\n")
|
|
else:
|
|
out.tell("Ford is already awake.\n")
|
|
return True
|
|
return False
|
|
|
|
|
|
# ---- Timed event handlers ----
|
|
|
|
def _i_groggy(state: GameState) -> bool:
|
|
"""Groggy countdown -- player must act before passing out."""
|
|
out = state.output
|
|
counter = state.flags.get("groggy_counter", 0)
|
|
|
|
if not state.flags.get("groggy"):
|
|
return False
|
|
|
|
if state.here and state.here.id != "HOLD":
|
|
return False
|
|
|
|
counter += 1
|
|
state.flags["groggy_counter"] = counter
|
|
|
|
if counter == 1:
|
|
out.tell(
|
|
"\nYour head is spinning. You feel very groggy and confused.\n"
|
|
)
|
|
return True
|
|
if counter == 2:
|
|
out.tell(
|
|
"\nThe room lurches sickeningly. You really don't feel well.\n"
|
|
)
|
|
return True
|
|
if counter >= 3:
|
|
state.jigs_up(
|
|
"The room spins wildly and you pass out on the filthy floor. "
|
|
"You are woken some time later by a Vogon guard who drags "
|
|
"you off and throws you out of an airlock."
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _i_ford(state: GameState) -> bool:
|
|
"""Ford gives the player the Guide and goes to sleep."""
|
|
out = state.output
|
|
|
|
if state.here and state.here.id != "HOLD":
|
|
return False
|
|
|
|
ford = state.world.get("FORD-VOGON")
|
|
guide = state.world.get("GUIDE")
|
|
satchel = state.world.get("SATCHEL")
|
|
towel = state.world.get("TOWEL")
|
|
|
|
out.tell(
|
|
'\nFord rummages through his satchel. "Here," he says, handing '
|
|
'you a battered book. "The Hitchhiker\'s Guide to the Galaxy. '
|
|
"It's the most remarkable book ever to come out of the great "
|
|
'publishing corporations of Ursa Minor."\n\n'
|
|
"He also hands you a towel.\n\n"
|
|
'"A towel," says Ford, "is about the most massively useful thing '
|
|
'an interstellar hitchhiker can have."\n\n'
|
|
"With that, Ford slumps against the wall and falls asleep.\n"
|
|
)
|
|
guide.move_to(state.protagonist)
|
|
towel.move_to(state.protagonist)
|
|
satchel.move_to(state.here)
|
|
state.flags["ford_sleeping"] = True
|
|
return True
|
|
|
|
|
|
def _i_announcement(state: GameState) -> bool:
|
|
"""Intercom announcement."""
|
|
out = state.output
|
|
|
|
if state.here and state.here.id != "HOLD":
|
|
return False
|
|
|
|
out.tell(
|
|
"\nThere is a crackle from the intercom. A Vogon voice "
|
|
'announces: "Attention. This is your Captain speaking. We will '
|
|
"shortly be arriving at our destination. Passengers are reminded "
|
|
'that the airlock is NOT an emergency exit."\n'
|
|
)
|
|
return True
|
|
|
|
|
|
def _i_guards(state: GameState) -> bool:
|
|
"""Guards arrive to take you to the Captain."""
|
|
out = state.output
|
|
|
|
if state.here and state.here.id != "HOLD":
|
|
return False
|
|
|
|
out.tell(
|
|
"\nThe door bursts open. Two Vogon guards stomp in, grab you "
|
|
"and Ford by your collars, and drag you down the corridor to "
|
|
"the Captain's quarters.\n"
|
|
)
|
|
# Move player and Ford to the Captain's quarters
|
|
captains_quarters = state.world.get_room("CAPTAINS-QUARTERS")
|
|
state.here = captains_quarters
|
|
state.protagonist.move_to(captains_quarters)
|
|
ford = state.world.get("FORD-VOGON")
|
|
ford.move_to(captains_quarters)
|
|
state.flags["ford_sleeping"] = False
|
|
|
|
# Start the captain sequence
|
|
state.flags["captain_counter"] = 0
|
|
state.clock.queue(_i_captain, -1, name="I-CAPTAIN")
|
|
|
|
return True
|
|
|
|
|
|
def _i_captain(state: GameState) -> bool:
|
|
"""Poetry reading sequence in the Captain's quarters."""
|
|
out = state.output
|
|
|
|
if state.here and state.here.id != "CAPTAINS-QUARTERS":
|
|
return False
|
|
|
|
counter = state.flags.get("captain_counter", 0)
|
|
state.flags["captain_counter"] = counter + 1
|
|
has_fish = state.flags.get("babel_fish_in_ear", False)
|
|
|
|
if counter == 0:
|
|
out.tell(
|
|
'\nThe Vogon Captain clears his throat. "I\'m now going to '
|
|
"read you some of my poetry,\" he announces.\n"
|
|
)
|
|
return True
|
|
|
|
# Read poetry lines (counter 1-5)
|
|
if 1 <= counter <= 5:
|
|
line_idx = counter - 1
|
|
if has_fish:
|
|
line = POETRY_LINES[line_idx]
|
|
else:
|
|
line = _gibberish_line()
|
|
out.tell(f"\nThe Captain reads: {line}\n")
|
|
if counter == 5:
|
|
if has_fish:
|
|
out.tell(
|
|
"\nThe Captain pauses and looks at you expectantly.\n"
|
|
)
|
|
else:
|
|
out.tell(
|
|
"\nThe Captain finishes. It sounded like someone "
|
|
"gargling with quicksand.\n"
|
|
)
|
|
return True
|
|
|
|
# After the first verse
|
|
if counter == 6:
|
|
enjoyed = state.flags.get("poem_enjoyed", False)
|
|
if enjoyed and has_fish:
|
|
out.tell(
|
|
'\nThe Captain is delighted. "You liked it! Oh how '
|
|
"wonderful! Let me read you another!\"\n"
|
|
)
|
|
state.flags["captain_counter"] = 100 # jump to second verse
|
|
return True
|
|
else:
|
|
_eject_to_airlock(state)
|
|
return True
|
|
|
|
# Second verse (counters 101-103)
|
|
if 101 <= counter <= 103:
|
|
line_idx = counter - 101
|
|
if line_idx < len(POETRY_VERSE_2):
|
|
line = POETRY_VERSE_2[line_idx]
|
|
out.tell(f"\nThe Captain reads: {line}\n")
|
|
if line_idx == len(POETRY_VERSE_2) - 1:
|
|
# After second verse, eject
|
|
out.tell(
|
|
'\n"Now," says the Captain, "did you enjoy that?"\n'
|
|
"Before you can answer, the guards grab you.\n"
|
|
)
|
|
return True
|
|
|
|
if counter == 104:
|
|
_eject_to_airlock(state)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _eject_to_airlock(state: GameState) -> None:
|
|
"""Guards drag you from the Captain's quarters to the hold, then airlock."""
|
|
out = state.output
|
|
|
|
out.tell(
|
|
"\nThe Vogon guards grab you and Ford, drag you struggling "
|
|
"down the corridor, through the hold, and hurl you into the "
|
|
"airlock.\n"
|
|
)
|
|
|
|
# Disable the captain timer
|
|
state.clock.queue(_i_captain, 0, name="I-CAPTAIN")
|
|
for entry in state.clock.entries:
|
|
if entry.routine is _i_captain:
|
|
entry.enabled = False
|
|
|
|
# Move to airlock
|
|
airlock = state.world.get_room("AIRLOCK")
|
|
state.here = airlock
|
|
state.protagonist.move_to(airlock)
|
|
ford = state.world.get("FORD-VOGON")
|
|
ford.move_to(airlock)
|
|
|
|
state.flags["airlock_counter"] = 0
|
|
state.clock.queue(_i_airlock, -1, name="I-AIRLOCK")
|
|
|
|
|
|
def _i_airlock(state: GameState) -> bool:
|
|
"""Airlock countdown -- Ford's dialogue then ejection into space."""
|
|
out = state.output
|
|
|
|
if state.here and state.here.id != "AIRLOCK":
|
|
return False
|
|
|
|
counter = state.flags.get("airlock_counter", 0)
|
|
counter += 1
|
|
state.flags["airlock_counter"] = counter
|
|
|
|
if counter == 1:
|
|
out.tell(
|
|
'\nFord says: "I wonder if we\'ll be picked up by another '
|
|
"ship. The chances are pretty remote, of course, but it's "
|
|
'our only hope."\n'
|
|
)
|
|
return True
|
|
|
|
if counter == 2:
|
|
out.tell(
|
|
"\nFord is rummaging in his pockets for something. "
|
|
'"I had a Sub-Etha Sens-O-Matic somewhere..." he mutters.\n'
|
|
)
|
|
return True
|
|
|
|
if counter == 3:
|
|
out.tell(
|
|
'\nFord says: "If we hold our breath, we might survive for '
|
|
"about thirty seconds in the vacuum of space. It's not "
|
|
'long, but it might just be enough."\n'
|
|
)
|
|
return True
|
|
|
|
if counter >= 4:
|
|
out.tell(
|
|
"\nWith a great hiss, the outer airlock door opens. The "
|
|
"air rushes out. You and Ford are sucked into the cold "
|
|
"vacuum of space.\n\n"
|
|
"You gasp. Stars wheel around you. The cold is incredible.\n"
|
|
)
|
|
|
|
# Disable airlock timer
|
|
for entry in state.clock.entries:
|
|
if entry.routine is _i_airlock:
|
|
entry.enabled = False
|
|
|
|
# Set heart probability for Heart of Gold sequence
|
|
state.flags["heart_prob"] = 100
|
|
|
|
# Transition to DARK room
|
|
dark = state.world.rooms.get("DARK")
|
|
if dark:
|
|
state.here = dark
|
|
state.protagonist.move_to(dark)
|
|
else:
|
|
# If DARK room doesn't exist yet, end the sequence
|
|
out.tell(
|
|
"\nYou drift in the void of space...\n"
|
|
"Everything goes dark.\n"
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# ---- Room action handlers ----
|
|
|
|
def _hold_action(state: GameState, rarg: str) -> bool:
|
|
out = state.output
|
|
|
|
if rarg == "M-LOOK":
|
|
out.tell(
|
|
"This is a squalid room filled with grubby mattresses, "
|
|
"unwashed cups, and unidentifiable bits of smelly alien "
|
|
"underwear. A door lies to port, and an airlock lies to "
|
|
"starboard.\n"
|
|
)
|
|
return True
|
|
|
|
if rarg == "M-END":
|
|
hold = state.world.get_room("HOLD")
|
|
|
|
if hold.fset_q(Flag.REVISITBIT):
|
|
# Revisit -- guards stun you
|
|
state.jigs_up(
|
|
"A Vogon guard spots you wandering around and stuns you "
|
|
"with a blast from his kill-o-zap gun."
|
|
)
|
|
return True
|
|
|
|
if not hold.fset_q(Flag.TOUCHBIT):
|
|
# First visit
|
|
hold.fset(Flag.TOUCHBIT)
|
|
hold.fset(Flag.REVISITBIT)
|
|
|
|
# Give player peanuts
|
|
peanuts = state.world.get("PEANUTS")
|
|
peanuts.move_to(state.protagonist)
|
|
|
|
# Move Ford here
|
|
ford = state.world.get("FORD-VOGON")
|
|
ford.move_to(hold)
|
|
|
|
# Set groggy state
|
|
state.flags["groggy"] = True
|
|
state.flags["groggy_counter"] = 0
|
|
|
|
# Add score
|
|
state.score += 8
|
|
|
|
out.tell(
|
|
"\nYou wake up. The room is spinning very gently round "
|
|
"your head. Or at least it would be if you could see it, "
|
|
"which you can't.\n\n"
|
|
"You are lying on what feels like a pile of old mattresses "
|
|
"in a room that smells like the inside of a pair of "
|
|
"Vogon-ripper trainers.\n\n"
|
|
'Ford Prefect is here. "It\'s OK," he says, "we\'re safe '
|
|
'now. We\'re on a Vogon ship."\n\n'
|
|
"He hands you a small packet of peanuts.\n"
|
|
)
|
|
|
|
# Queue timed events
|
|
state.clock.queue(_i_groggy, 3, name="I-GROGGY")
|
|
state.clock.queue(_i_ford, 6, name="I-FORD")
|
|
state.clock.queue(_i_announcement, 18, name="I-ANNOUNCEMENT")
|
|
state.clock.queue(_i_guards, 36, name="I-GUARDS")
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _hold_east_exit(state: GameState) -> "Room | None":
|
|
"""Exit from hold east to airlock -- requires inner door open."""
|
|
out = state.output
|
|
door = state.world.get("INNER-DOOR")
|
|
if not door.fset_q(Flag.OPENBIT):
|
|
out.tell("The inner airlock door is closed.\n")
|
|
return None
|
|
return state.world.get_room("AIRLOCK")
|
|
|
|
|
|
def _captains_quarters_action(state: GameState, rarg: str) -> bool:
|
|
out = state.output
|
|
|
|
if rarg == "M-LOOK":
|
|
out.tell(
|
|
"This is the cabin of the Vogon Captain. You and Ford are "
|
|
"strapped into poetry appreciation chairs.\n"
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _airlock_action(state: GameState, rarg: str) -> bool:
|
|
out = state.output
|
|
|
|
if rarg == "M-LOOK":
|
|
out.tell(
|
|
"This airlock has massive doors to port and starboard.\n"
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# ---- Registration ----
|
|
|
|
def register(world: World, state: GameState) -> None:
|
|
"""Create all Vogon ship rooms and objects."""
|
|
|
|
# ---- Initialize state flags ----
|
|
|
|
state.flags.setdefault("groggy", False)
|
|
state.flags.setdefault("groggy_counter", 0)
|
|
state.flags.setdefault("gown_hung", False)
|
|
state.flags.setdefault("panel_blocker", None)
|
|
state.flags.setdefault("item_on_satchel", None)
|
|
state.flags.setdefault("fish_counter", 5)
|
|
state.flags.setdefault("babel_fish_in_ear", False)
|
|
state.flags.setdefault("captain_counter", 0)
|
|
state.flags.setdefault("poem_enjoyed", False)
|
|
state.flags.setdefault("airlock_counter", 0)
|
|
state.flags.setdefault("guards_counter", 0)
|
|
state.flags.setdefault("ford_sleeping", False)
|
|
|
|
# ---- Global objects for the ship ----
|
|
|
|
inner_door = world.register(GameObject(
|
|
"INNER-DOOR", desc="inner door",
|
|
synonyms=["door", "inner"],
|
|
adjectives=["inner", "airlock"],
|
|
flags={Flag.DOORBIT, Flag.NDESCBIT},
|
|
action=_inner_door_action,
|
|
))
|
|
inner_door.move_to(world.local_globals)
|
|
|
|
airlock_obj = world.register(GameObject(
|
|
"AIRLOCK-OBJ", desc="airlock",
|
|
synonyms=["airlock"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_airlock_obj_action,
|
|
))
|
|
airlock_obj.move_to(world.local_globals)
|
|
|
|
# ---- HOLD ----
|
|
|
|
hold = world.register(Room(
|
|
"HOLD", desc="Vogon Hold",
|
|
flags={Flag.RLANDBIT, Flag.ONBIT},
|
|
action=_hold_action,
|
|
global_objects=[inner_door, airlock_obj],
|
|
))
|
|
|
|
# Babel fish dispenser and puzzle objects
|
|
dispenser = world.register(GameObject(
|
|
"DISPENSER", desc="babel fish dispenser",
|
|
synonyms=["dispenser", "machine"],
|
|
adjectives=["babel", "fish"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_dispenser_action,
|
|
))
|
|
dispenser.move_to(hold)
|
|
|
|
dispenser_button = world.register(GameObject(
|
|
"DISPENSER-BUTTON", desc="dispenser button",
|
|
synonyms=["button"],
|
|
adjectives=["dispenser"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_dispenser_button_action,
|
|
))
|
|
dispenser_button.move_to(hold)
|
|
|
|
hook = world.register(GameObject(
|
|
"HOOK", desc="hook",
|
|
synonyms=["hook", "peg"],
|
|
adjectives=["small", "wall"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_hook_action,
|
|
))
|
|
hook.move_to(hold)
|
|
|
|
drain = world.register(GameObject(
|
|
"DRAIN", desc="drain",
|
|
synonyms=["drain", "grate"],
|
|
adjectives=["small", "floor"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_drain_action,
|
|
))
|
|
drain.move_to(hold)
|
|
|
|
robot_panel = world.register(GameObject(
|
|
"ROBOT-PANEL", desc="robot panel",
|
|
synonyms=["panel"],
|
|
adjectives=["robot", "small", "wall"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_robot_panel_action,
|
|
))
|
|
robot_panel.move_to(hold)
|
|
|
|
babel_fish = world.register(GameObject(
|
|
"BABEL-FISH", desc="babel fish",
|
|
synonyms=["fish"],
|
|
adjectives=["babel", "small", "yellow"],
|
|
flags={Flag.NDESCBIT, Flag.INVISIBLE},
|
|
action=_babel_fish_action,
|
|
))
|
|
babel_fish.move_to(world.local_globals)
|
|
|
|
# Glass case with plotter
|
|
glass_case = world.register(GameObject(
|
|
"GLASS-CASE", desc="glass case",
|
|
synonyms=["case"],
|
|
adjectives=["glass", "sealed"],
|
|
flags={Flag.CONTBIT, Flag.NDESCBIT, Flag.SEARCHBIT},
|
|
action=_glass_case_action,
|
|
))
|
|
glass_case.move_to(hold)
|
|
|
|
keyboard = world.register(GameObject(
|
|
"KEYBOARD", desc="keyboard",
|
|
synonyms=["keyboard", "keys"],
|
|
adjectives=["small"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_keyboard_action,
|
|
))
|
|
keyboard.move_to(hold)
|
|
|
|
switch = world.register(GameObject(
|
|
"SWITCH", desc="small switch",
|
|
synonyms=["switch"],
|
|
adjectives=["small"],
|
|
flags={Flag.NDESCBIT},
|
|
action=_switch_action,
|
|
))
|
|
switch.move_to(hold)
|
|
|
|
plotter = world.register(GameObject(
|
|
"PLOTTER", desc="atomic vector plotter",
|
|
synonyms=["plotter", "device"],
|
|
adjectives=["atomic", "vector"],
|
|
flags={Flag.TAKEBIT, Flag.NDESCBIT},
|
|
size=4,
|
|
action=_plotter_action,
|
|
))
|
|
plotter.move_to(glass_case)
|
|
|
|
# Ford (Vogon ship version -- separate from earth Ford)
|
|
ford = world.register(GameObject(
|
|
"FORD-VOGON", desc="Ford Prefect",
|
|
synonyms=["ford", "prefect"],
|
|
adjectives=["ford"],
|
|
flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
|
|
action=_ford_vogon_action,
|
|
))
|
|
|
|
# Items Ford gives or that appear on the ship
|
|
peanuts = world.register(GameObject(
|
|
"PEANUTS", desc="small packet of peanuts",
|
|
synonyms=["peanuts", "packet", "nuts"],
|
|
adjectives=["small"],
|
|
flags={Flag.TAKEBIT},
|
|
size=2,
|
|
action=_peanuts_action,
|
|
))
|
|
|
|
satchel = world.register(GameObject(
|
|
"SATCHEL", desc="satchel",
|
|
synonyms=["satchel", "bag"],
|
|
adjectives=["ford's", "leather", "battered"],
|
|
flags={Flag.TAKEBIT, Flag.CONTBIT, Flag.SEARCHBIT, Flag.OPENBIT},
|
|
size=10, capacity=20,
|
|
action=_satchel_action,
|
|
))
|
|
|
|
towel = world.register(GameObject(
|
|
"TOWEL", desc="towel",
|
|
synonyms=["towel"],
|
|
adjectives=["large", "fluffy"],
|
|
flags={Flag.TAKEBIT},
|
|
size=4,
|
|
action=_towel_action,
|
|
))
|
|
|
|
guide = world.register(GameObject(
|
|
"GUIDE", desc="Hitchhiker's Guide",
|
|
synonyms=["guide", "book"],
|
|
adjectives=["hitchhiker's", "electronic"],
|
|
flags={Flag.TAKEBIT, Flag.READBIT, Flag.NARTICLEBIT},
|
|
size=3,
|
|
text='The cover reads: "DON\'T PANIC"',
|
|
action=_guide_action,
|
|
))
|
|
|
|
# ---- CAPTAIN'S QUARTERS ----
|
|
|
|
captains_quarters = world.register(Room(
|
|
"CAPTAINS-QUARTERS", desc="Captain's Quarters",
|
|
flags={Flag.RLANDBIT, Flag.ONBIT},
|
|
action=_captains_quarters_action,
|
|
))
|
|
|
|
vogon_captain = world.register(GameObject(
|
|
"VOGON-CAPTAIN", desc="Vogon Captain",
|
|
synonyms=["captain", "vogon"],
|
|
adjectives=["vogon"],
|
|
flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
|
|
action=_vogon_captain_action,
|
|
))
|
|
vogon_captain.move_to(captains_quarters)
|
|
|
|
poetry = world.register(GameObject(
|
|
"POETRY", desc="poetry",
|
|
synonyms=["poetry", "poem", "verse"],
|
|
adjectives=["vogon"],
|
|
flags={Flag.NDESCBIT, Flag.NARTICLEBIT},
|
|
action=_poetry_action,
|
|
))
|
|
poetry.move_to(captains_quarters)
|
|
|
|
chair = world.register(GameObject(
|
|
"POETRY-APPRECIATION-CHAIR", desc="poetry appreciation chair",
|
|
synonyms=["chair", "chairs"],
|
|
adjectives=["poetry", "appreciation"],
|
|
flags={Flag.VEHBIT, Flag.NDESCBIT},
|
|
action=_chair_action,
|
|
))
|
|
chair.move_to(captains_quarters)
|
|
|
|
# ---- AIRLOCK ----
|
|
|
|
airlock = world.register(Room(
|
|
"AIRLOCK", desc="Airlock",
|
|
flags={Flag.RLANDBIT, Flag.ONBIT},
|
|
action=_airlock_action,
|
|
global_objects=[inner_door, airlock_obj],
|
|
))
|
|
|
|
# ---- Connect rooms ----
|
|
|
|
hold.exits = {
|
|
Direction.WEST: BlockedExit(
|
|
"The door to the corridor is locked (from the outside)."
|
|
),
|
|
Direction.EAST: ConditionalExit(_hold_east_exit),
|
|
}
|
|
|
|
# Captain's quarters has no exits -- you arrive via guards
|
|
captains_quarters.exits = {}
|
|
|
|
# Airlock exits are blocked
|
|
airlock.exits = {
|
|
Direction.WEST: BlockedExit(
|
|
"The inner door has sealed behind you."
|
|
),
|
|
Direction.EAST: BlockedExit(
|
|
"The outer door is massive and immovable. Not that you'd "
|
|
"want to open it."
|
|
),
|
|
}
|