This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/adventure/world.go
Torjus Håkestad aa569aac16 feat: add text adventure shell (PLAN.md 3.4)
Zork-style dungeon crawler set in an abandoned data center / medieval dungeon.
11 rooms, 6 items, 3 puzzles (dark room, locked door, maintenance panel),
standard text adventure parser with verb aliases and direction shortcuts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 05:13:03 +01:00

212 lines
7.6 KiB
Go

package adventure
// room represents a location in the game world.
type room struct {
name string
description string
darkDesc string // shown when room is dark (empty = not a dark room)
exits map[string]string // direction → room ID
items []string // item IDs present in the room
locked map[string]string // direction → flag required to unlock
}
// item represents an object that can be picked up and used.
type item struct {
name string
description string
takeable bool
}
func newWorld() (map[string]*room, map[string]*item) {
rooms := map[string]*room{
"oubliette": {
name: "The Oubliette",
description: `You are in a narrow stone chamber. The walls are damp and slick with condensation.
Far above, an iron grate lets in a faint green glow — not daylight, but the steady
pulse of status LEDs. A frayed ethernet cable hangs from the grate like a vine.
A passage leads east into darkness. Stone steps spiral downward.`,
exits: map[string]string{
"east": "corridor",
"down": "pit",
},
},
"corridor": {
name: "Stone Corridor",
description: `A long corridor carved from living rock. The walls transition from rough-hewn
stone to poured concrete as you look east. Fluorescent tubes flicker overhead,
half of them dead. Cable trays run along the ceiling, sagging under the weight
of bundled Cat5. A draft comes from a ventilation shaft above.`,
exits: map[string]string{
"west": "oubliette",
"east": "server_room",
"south": "cable_crypt",
"up": "ventilation",
},
items: []string{"flashlight"},
},
"ventilation": {
name: "Ventilation Shaft",
description: `You've squeezed into a narrow ventilation shaft. The aluminum walls vibrate with
the hum of distant fans. It's barely wide enough to turn around in. Dust and
cobwebs coat everything. Someone has scratched tally marks into the metal — you
stop counting at forty-seven.`,
exits: map[string]string{
"down": "corridor",
},
items: []string{"note"},
},
"server_room": {
name: "Server Room",
description: `Rows of black server racks stretch into the gloom, their LEDs blinking in
patterns that almost seem deliberate. The air is frigid and filled with the
white noise of a thousand fans. A yellowed label on the nearest rack reads:
"PRODUCTION - DO NOT TOUCH". Someone has added in marker: "OR ELSE".
A laminated keycard sits on top of a powered-down blade server.`,
exits: map[string]string{
"west": "corridor",
"south": "cold_storage",
},
items: []string{"keycard"},
},
"pit": {
name: "The Pit",
darkDesc: `You are in absolute darkness. You can hear water dripping somewhere far below
and the faint hum of electronics. The air smells of rust and ozone. You can't
see a thing without a light source. The stairs lead back up.`,
description: `Your flashlight reveals a deep shaft — part medieval oubliette, part cable run.
Rusty chains hang from iron rings set into the walls, intertwined with bundles
of fiber optic cable that glow faintly orange. At the bottom, a skeleton in a
lab coat slumps against the wall, still wearing an ID badge. A rusty key glints
on a hook near the skeleton's hand.`,
exits: map[string]string{
"up": "oubliette",
},
items: []string{"rusty_key"},
},
"cable_crypt": {
name: "Cable Crypt",
description: `This vaulted chamber was clearly something else before the cables arrived.
Stone sarcophagi line the walls, but their lids have been removed and they're
now stuffed full of tangled ethernet runs and power strips. A faded sign reads
"STRUCTURED CABLING" — someone has crossed out "STRUCTURED" and written
"CHAOTIC" above it. The air smells of old stone and warm plastic.`,
exits: map[string]string{
"north": "corridor",
"south": "archive",
},
items: []string{"ethernet_cable"},
},
"cold_storage": {
name: "Cold Storage",
description: `A cavernous room kept at near-freezing temperatures. Frost coats the walls.
Rows of old tape drives and disk platters are stacked on industrial shelving,
their labels faded beyond reading. A humming cryo-unit in the corner has a
blinking amber light. A hand-written sign says "BACKUP STORAGE - CRITICAL".`,
exits: map[string]string{
"north": "server_room",
"east": "generator",
},
items: []string{"floppy_disk"},
},
"archive": {
name: "The Archive",
description: `Floor-to-ceiling shelves hold thousands of manila folders, binders, and
three-ring notebooks. The organization system, if there ever was one, has
long since collapsed into entropy. A thick layer of dust covers everything.
A heavy steel door to the east has an electronic keycard reader with a
steady red light. The cable crypt lies back to the north.`,
exits: map[string]string{
"north": "cable_crypt",
"east": "control_room",
},
locked: map[string]string{
"east": "archive_unlocked",
},
},
"generator": {
name: "Generator Room",
description: `Two massive diesel generators squat in this room like sleeping beasts. One is
clearly dead — corrosion has eaten through the fuel lines. The other hums at
a low idle, keeping the facility on life support. A maintenance panel on the
wall is secured with an old-fashioned keyhole.`,
exits: map[string]string{
"west": "cold_storage",
},
},
"control_room": {
name: "Control Room",
description: `Banks of CRT monitors cast a blue glow across the room. Most show static, but
one displays a facility map with pulsing dots labeled "ACTIVE SESSIONS". You
count the dots — there are more than there should be. A desk covered in coffee
rings holds a battered keyboard. The main display reads:
FACILITY STATUS: NOMINAL
CONTAINMENT: ACTIVE
SUBJECTS: ███
A heavy blast door leads east. Above it, a faded exit sign flickers.`,
exits: map[string]string{
"west": "archive",
"east": "exit",
},
},
"exit": {
name: "The Exit",
description: `The blast door groans open to reveal... a corridor. Not the freedom you
expected, but another corridor — identical to the one you started in. The
same flickering fluorescents. The same sagging cable trays. The same damp
stone walls.
As you step through, the door slams shut behind you. A speaker crackles:
"THANK YOU FOR PARTICIPATING IN TODAY'S SECURITY AUDIT.
YOUR SESSION HAS BEEN LOGGED.
HAVE A NICE DAY."
The lights go out.`,
},
}
items := map[string]*item{
"flashlight": {
name: "flashlight",
description: "A heavy-duty flashlight. The batteries are low but it still works.",
takeable: true,
},
"keycard": {
name: "keycard",
description: "A laminated keycard with a faded photo. The name reads 'J. DUNWICH, LEVEL 3'.",
takeable: true,
},
"rusty_key": {
name: "rusty key",
description: "An old iron key, spotted with rust. It looks like it fits a maintenance panel.",
takeable: true,
},
"note": {
name: "note",
description: `A crumpled note in shaky handwriting:
"If you're reading this, GET OUT. The facility is automated now.
The sessions never end. I thought I was running tests but the
tests were running me. Don't trust the prompts. Don't trust
the exits. Don't trust
[the writing trails off into an illegible scrawl]"`,
takeable: true,
},
"ethernet_cable": {
name: "ethernet cable",
description: "A tangled Cat5e cable, about 3 meters long. Both ends have been chewed by something.",
takeable: true,
},
"floppy_disk": {
name: "floppy disk",
description: `A 3.5" floppy disk labeled "BACKUP - CRITICAL - DO NOT FORMAT". The label is dated 1997.`,
takeable: true,
},
}
return rooms, items
}