diff --git a/src/engine/dispatcher.test.ts b/src/engine/dispatcher.test.ts index 35f11a3..460386a 100644 --- a/src/engine/dispatcher.test.ts +++ b/src/engine/dispatcher.test.ts @@ -212,3 +212,43 @@ describe('ambiguous → disambiguation flow', () => { expect(result.state.inventory.find((i) => i.id === 'iron-key')).toBeDefined() }) }) + +function readWorld(): World { + return { + startingRoom: 'r', + startingInventory: [], + rooms: { r: { id: 'r', title: '[ R ]', descriptions: { firstVisit: '.', revisit: '.', examined: '.' }, exits: {}, items: [] } }, + items: { + letter: { id: 'letter', names: ['letter'], short: 'a letter', long: 'A letter.', initialState: {}, takeable: true, readable: true, readableText: 'You loved Halfstreet, the letter says.' }, + rock: { id: 'rock', names: ['rock'], short: 'a rock', long: 'A rock.', initialState: {}, takeable: true }, + }, + encounters: {}, + endings: { + true: { whenFlags: {}, narration: '' }, + wrong: { whenFlags: {}, narration: '' }, + bad: { whenFlags: {}, narration: '' }, + }, + } +} + +describe('read verb', () => { + it('narrates readableText for a readable item in inventory', () => { + const world = readWorld() + let state = initialStateFor(world) + state = { ...state, inventory: [{ id: 'letter', state: {} }] } + const result = dispatch(state, { + kind: 'verb-target', verb: 'read', target: { canonical: 'letter', raw: 'letter' }, + }, world) + expect(result.appended.at(-1)?.text).toBe('You loved Halfstreet, the letter says.') + }) + + it('errors politely on non-readable items', () => { + const world = readWorld() + let state = initialStateFor(world) + state = { ...state, inventory: [{ id: 'rock', state: {} }] } + const result = dispatch(state, { + kind: 'verb-target', verb: 'read', target: { canonical: 'rock', raw: 'rock' }, + }, world) + expect(result.appended.at(-1)?.text).toBe("There's nothing to read on it.") + }) +}) diff --git a/src/engine/dispatcher.ts b/src/engine/dispatcher.ts index de9a49b..36b0183 100644 --- a/src/engine/dispatcher.ts +++ b/src/engine/dispatcher.ts @@ -116,6 +116,7 @@ export function dispatch(state: GameState, command: ParsedCommand, world: World) if (command.verb === 'take') return handleTake(stateWithNoun, command.target.canonical, world) if (command.verb === 'drop') return handleDrop(stateWithNoun, command.target.canonical, world) if (command.verb === 'examine' || command.verb === 'look') return handleExamine(stateWithNoun, command.target.canonical, world) + if (command.verb === 'read') return handleRead(stateWithNoun, command.target.canonical, world) return narrate(stateWithNoun, [{ kind: 'narration', text: `You're not sure how to ${command.verb} that.` }]) } @@ -262,3 +263,16 @@ function handleExamine(state: GameState, itemId: string, world: World): Dispatch if (!visible) return narrate(state, [{ kind: 'narration', text: 'You don\'t see anything like that.' }]) return narrate(state, [{ kind: 'narration', text: item.long }]) } + +function handleRead(state: GameState, itemId: string, world: World): DispatchResult { + const item = world.items[itemId] + if (!item) return narrate(state, [{ kind: 'narration', text: "You don't see anything like that." }]) + const visible = + state.inventory.find((i) => i.id === itemId) || + getItemsInRoom(state, world, state.location).includes(itemId) + if (!visible) return narrate(state, [{ kind: 'narration', text: "You don't see anything like that." }]) + if (!item.readable || !item.readableText) { + return narrate(state, [{ kind: 'narration', text: "There's nothing to read on it." }]) + } + return narrate(state, [{ kind: 'narration', text: item.readableText }]) +}