Step 17: Putting It All Together

What This Version Does

This is the complete Family Zoo as a multi-file story. Every concept from steps 1 through 16 — rooms, objects, containers, locked doors, light and dark, NPCs, event handlers, custom actions, capability dispatch, timed events, and scoring — comes together in a production-ready structure.

Instead of a single .ts file that grows ever longer, v17 splits the story into focused modules. Each file has a single responsibility, making the code easier to navigate, test, and extend.

Multi-File Organization

The v17 directory looks like this:

tutorials/familyzoo/src/v17/
├── index.ts        # Story class — wires everything together
├── zoo-map.ts      # Rooms, exits, scenery, locked door
├── zoo-items.ts    # Portable objects, containers, supporters
├── characters.ts   # Zookeeper, parrot, pettable animals, NPC behaviors
├── events.ts       # PA announcements, feeding time, after-hours daemons
├── scoring.ts      # Score IDs, point values, victory condition
└── language.ts     # All player-facing text

Why does this matter? When your story has seven rooms it might fit comfortably in one file. But the Family Zoo already has NPCs, timed events, custom actions, and a scoring system. Finding the code for "what happens when the player feeds the goats" means scrolling past room definitions, NPC setup, grammar patterns, and language text.

With one responsibility per file, you can find code by what it does:

The Story Class

index.ts is the hub. It implements the Story interface and imports from the other files, wiring everything together through Sharpee's lifecycle methods:

MethodWhat It Does
initializeWorld()Calls helpers in zoo-map.ts, zoo-items.ts, characters.ts, and scoring.ts to build the world
createPlayer()Creates the player entity and places them at the entrance
onEngineReady()Registers NPC plugin, scheduler plugin, event handlers from events.ts
extendParser()Adds story-specific grammar patterns (feed, photograph, pet)
extendLanguage()Delegates to language.ts to register all message text
getCustomActions()Returns the feed, photograph, and petting actions

The story class itself stays short — it delegates to the specialist modules rather than containing all the logic.

Entity Helpers

By v17, the story uses world.helpers() instead of manual entity creation. The helpers provide a fluent builder pattern that is more readable and less error-prone:

const { room, object, door, container, actor } = world.helpers();

const entrance = room('Zoo Entrance')
  .description('You stand before the wrought-iron gates of the city zoo.')
  .aliases('entrance', 'gates')
  .build();

const brochure = object('zoo brochure')
  .description('A colorful tri-fold brochure.')
  .aliases('brochure', 'pamphlet')
  .readable('Welcome to the Family Zoo! ...')
  .in(entrance)
  .build();

const backpack = container('backpack')
  .description('A sturdy canvas backpack.')
  .aliases('pack', 'bag')
  .capacity(5)
  .openable({ isOpen: false })
  .in(entrance)
  .build();

Compare this to the v01 approach of creating an entity, then adding IdentityTrait, then LocationTrait, then each additional trait one by one. The helpers do the same thing under the hood, but the code reads like a description of the thing you're building.

The Language Layer

All player-facing text lives in language.ts. No English strings appear in the story logic files. This is the same pattern Sharpee uses internally — the engine emits message IDs, and the language layer provides the actual prose.

// language.ts
export function registerMessages(language: LanguageProvider): void {
  // Feeding
  language.addMessage('zoo.feeding.fed_goats',
    'You scatter the feed on the ground. The goats rush over!');
  language.addMessage('zoo.feeding.already_fed',
    'The goats are still happily munching away.');

  // Petting
  language.addMessage('zoo.petting.goats',
    'You reach over the fence and pet a goat. It bleats contentedly.');
  language.addMessage('zoo.petting.parrot',
    'You extend a finger. The parrot bites it. Hard.');

  // Timed events
  language.addMessage('zoo.pa.announcement',
    'A tinny PA speaker crackles: "The zoo closes in one hour."');
  // ...
}

This pays off in several ways: you can review all your game text in one place, a proofreader can edit language.ts without touching game logic, and if you ever want to translate the game, you write a second language file and swap it in.

Build and Play

npx @sharpee/sharpee build
open dist/web/index.html

What You Learned

Over 17 steps, you built a complete interactive fiction game. Here is what each step covered:

StepConcept
1Story interface, entities, traits — a single room
2Room connections and compass directions
3Scenery — non-portable objects that enrich rooms
4Portable objects, inventory, taking and dropping
5Containers and supporters
6Openable containers with hidden contents
7Locked doors and keys
8Light, dark rooms, and switchable light sources
9Readable objects
10Switchable devices
11Non-player characters with behaviors
12Event handlers and chain events
13Custom actions with the four-phase pattern
14Capability dispatch — per-entity verb behaviors
15Timed events with daemons and fuses
16Scoring and endgame conditions
17Multi-file organization and entity helpers

You now have the tools to build your own interactive fiction with Sharpee. Start with a single room, add things one concept at a time, and split into multiple files when the code outgrows a single module. The architecture scales from a tutorial to a full-sized game.

The Code

See src/v17/ for the complete, multi-file source.