feat(mystery): add opening and main-floor content
This commit is contained in:
@@ -3,6 +3,14 @@ import type { GameState, ParsedCommand, DispatchResult, ItemInstance, Transcript
|
||||
import { SCHEMA_VERSION, TRANSCRIPT_CAP } from './types'
|
||||
import { applyVerbToEncounter, maybeTriggerEncounter } from './encounters'
|
||||
|
||||
const HALFSTREET_ASCII = String.raw`
|
||||
_ _ _ __ ____ _ _
|
||||
| | | | __ _| |/ _| / ___|| |_ _ __ ___ ___| |_
|
||||
| |_| |/ _\` | | |_ \___ \| __| '__/ _ \/ _ \ __|
|
||||
| _ | (_| | | _| ___) | |_| | | __/ __/ |_
|
||||
|_| |_|\__,_|_|_| |____/ \__|_| \___|\___|\__|
|
||||
`.trim()
|
||||
|
||||
export function initialStateFor(world: World): GameState {
|
||||
const startingRoom = world.rooms[world.startingRoom]
|
||||
if (!startingRoom) throw new Error(`World has invalid startingRoom: ${world.startingRoom}`)
|
||||
@@ -14,6 +22,7 @@ export function initialStateFor(world: World): GameState {
|
||||
})
|
||||
|
||||
const opening: TranscriptLine[] = [
|
||||
{ kind: 'system', text: HALFSTREET_ASCII },
|
||||
{ kind: 'system', text: startingRoom.title },
|
||||
{ kind: 'narration', text: startingRoom.descriptions.firstVisit },
|
||||
]
|
||||
@@ -134,6 +143,10 @@ export function dispatch(state: GameState, command: ParsedCommand, world: World)
|
||||
}
|
||||
|
||||
if (command.kind === 'verb-only') {
|
||||
const encResult = applyVerbToEncounter(state, command, world)
|
||||
if (encResult?.consumed) {
|
||||
return withEndingCheck({ state: encResult.state, appended: encResult.lines }, world)
|
||||
}
|
||||
if (command.verb === 'look') return withEndingCheck(handleLook(state, world), world)
|
||||
if (command.verb === 'inventory') return withEndingCheck(handleInventory(state, world), world)
|
||||
if (command.verb === 'wait') return withEndingCheck(narrate(state, [{ kind: 'narration', text: 'Time passes.' }]), world)
|
||||
|
||||
@@ -76,6 +76,30 @@ describe('parser — unknown input', () => {
|
||||
})
|
||||
|
||||
describe('parser — verb + target', () => {
|
||||
it('recognizes slice-two encounter verbs', () => {
|
||||
const ctx: ParserContext = {
|
||||
knownItems: [],
|
||||
knownEncounters: ['piano-echo', 'covered-cage'],
|
||||
visibleNouns: [
|
||||
{ id: 'piano-echo', aliases: ['piano', 'note'] },
|
||||
{ id: 'covered-cage', aliases: ['cage'] },
|
||||
],
|
||||
inventoryItemIds: [],
|
||||
lastNoun: null,
|
||||
awaitingDisambiguation: null,
|
||||
}
|
||||
expect(parse('play note', ctx)).toEqual({
|
||||
kind: 'verb-target',
|
||||
verb: 'play',
|
||||
target: { canonical: 'piano-echo', raw: 'note' },
|
||||
})
|
||||
expect(parse('uncover cage', ctx)).toEqual({
|
||||
kind: 'verb-target',
|
||||
verb: 'open',
|
||||
target: { canonical: 'covered-cage', raw: 'cage' },
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves a single visible noun', () => {
|
||||
const ctx: ParserContext = {
|
||||
knownItems: ['torch'],
|
||||
|
||||
@@ -32,6 +32,9 @@ const VERB_SYNONYMS: Record<string, Verb> = {
|
||||
hold: 'hold', show: 'hold',
|
||||
push: 'push', press: 'push',
|
||||
pull: 'pull',
|
||||
cut: 'cut', trim: 'cut',
|
||||
play: 'play',
|
||||
uncover: 'open',
|
||||
wait: 'wait', z: 'wait',
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@ function ctxFor(state: GameState): ParserContext {
|
||||
if (item) visibleNouns.push({ id: inst.id, aliases: item.names })
|
||||
}
|
||||
if (room?.encounter) {
|
||||
visibleNouns.push({ id: room.encounter, aliases: [room.encounter] })
|
||||
const encounter = world.encounters[room.encounter]
|
||||
visibleNouns.push({
|
||||
id: room.encounter,
|
||||
aliases: [room.encounter, room.encounter.replace(/-/g, ' '), ...(encounter?.aliases ?? [])],
|
||||
})
|
||||
}
|
||||
return {
|
||||
knownItems: Object.keys(world.items),
|
||||
@@ -41,8 +45,8 @@ function play(commands: string[]): GameState {
|
||||
describe('playthrough — sample world', () => {
|
||||
it('reaches the rat-gone flag via the canonical command sequence', () => {
|
||||
const state = play([
|
||||
'take letter',
|
||||
'read letter', // verb is recognized but encounter takes priority elsewhere; here it's a no-op
|
||||
'n', // gate → foyer
|
||||
'n', // foyer → hallway
|
||||
'take lamp',
|
||||
'e', // hallway → cellar-stair (triggers rat encounter)
|
||||
@@ -55,11 +59,52 @@ describe('playthrough — sample world', () => {
|
||||
|
||||
it('handles invalid moves gracefully', () => {
|
||||
const state = play([
|
||||
'go up', // foyer has no up exit
|
||||
'go up', // gate has no up exit
|
||||
'n',
|
||||
's',
|
||||
'flibbertigibbet', // unknown verb
|
||||
])
|
||||
expect(state.location).toBe('foyer')
|
||||
expect(state.location).toBe('outside-gate')
|
||||
})
|
||||
|
||||
it('plays through the main-floor slice encounters', () => {
|
||||
const state = play([
|
||||
'n', // gate → foyer
|
||||
'n', // foyer → hallway
|
||||
'n', // hallway → dining-room
|
||||
'close curtains',
|
||||
'take candlestick',
|
||||
'n', // dining-room → conservatory
|
||||
'take shears',
|
||||
'cut vines with shears',
|
||||
's',
|
||||
'w', // dining-room → hallway
|
||||
'w', // hallway → smoking-room
|
||||
'take lighter',
|
||||
'uncover cage',
|
||||
'e',
|
||||
'd', // hallway → music-room
|
||||
'play note',
|
||||
'take tiny key',
|
||||
'n', // music-room → servants-passage
|
||||
'wait',
|
||||
'e', // servants-passage → laundry
|
||||
'wait',
|
||||
'take damp sheet',
|
||||
])
|
||||
|
||||
expect(state.flags['window-guest.resolved']).toBe(true)
|
||||
expect(state.flags['ivy-figure.resolved']).toBe(true)
|
||||
expect(state.flags['covered-cage.resolved']).toBe(true)
|
||||
expect(state.flags['piano-echo.resolved']).toBe(true)
|
||||
expect(state.flags['breathing-wall.resolved']).toBe(true)
|
||||
expect(state.flags['linen-shape.resolved']).toBe(true)
|
||||
expect(state.inventory.map((i) => i.id)).toEqual(expect.arrayContaining([
|
||||
'candlestick',
|
||||
'pruning-shears',
|
||||
'silver-lighter',
|
||||
'music-box-key',
|
||||
'damp-sheet',
|
||||
]))
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ export type Direction = 'n' | 's' | 'e' | 'w' | 'u' | 'd'
|
||||
export type Verb =
|
||||
| 'go' | 'look' | 'examine' | 'take' | 'drop' | 'use' | 'open' | 'close'
|
||||
| 'read' | 'light' | 'extinguish' | 'attack' | 'inventory' | 'wait'
|
||||
| 'hold' | 'push' | 'pull'
|
||||
| 'hold' | 'push' | 'pull' | 'cut' | 'play'
|
||||
|
||||
export type MetaVerb = 'restart' | 'undo' | 'hint' | 'save' | 'quit' | 'theme'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user