diff --git a/Interpreter.ts b/Interpreter.ts index b56a0da..e5b843e 100644 --- a/Interpreter.ts +++ b/Interpreter.ts @@ -2,7 +2,7 @@ import type Player from "./Player.ts"; import type Scene from "./Scene.ts"; import { terms } from "./terms/terms.ts"; import type { - Action, + ActionTerm, ActionFn, Category, Constant, @@ -87,7 +87,7 @@ export default class Interpreter { } } - private processTerm(term: Action | Term): void { + private processTerm(term: ActionTerm | Term): void { if ("action" in term) { this.addAction(term); } @@ -105,7 +105,7 @@ export default class Interpreter { } } - private addAction(term: Action) { + private addAction(term: ActionTerm) { // make sure we expected an action and process it if it was if (this.expectedCategories.includes("action")) { this.action = term.action; diff --git a/Player.ts b/Player.ts index 827a17e..bf56031 100644 --- a/Player.ts +++ b/Player.ts @@ -10,10 +10,6 @@ export default class Player extends Vessel { this.#effects = effects; } - get effects() { - return this.#effects; - } - look(): string { const description = super.description(this._properties.items || []); @@ -28,4 +24,8 @@ export default class Player extends Vessel { if (!this._properties.items) this._properties.items = []; this._properties.items.push(item); } + + get inventory(): Item[] | null { + return this._properties.items || null; + } } diff --git a/Scene.ts b/Scene.ts index 2d28a48..6582377 100644 --- a/Scene.ts +++ b/Scene.ts @@ -2,12 +2,9 @@ import { GameData, Item, SceneProperties } from "./types.ts"; import Vessel from "./Vessel.ts"; export default class Scene extends Vessel { - #isViewed: boolean; - constructor(gameData: GameData) { // TODO: [] is a placeholder for scene effects. super(gameData.properties, gameData.conditions); - this.#isViewed = false; } look(activePlayerEffects: string[], activeSceneEffects: string[]): string { @@ -16,14 +13,11 @@ export default class Scene extends Vessel { activeSceneEffects ); const itemsDescription = super.description(properties.items || []); - const { description, shortDescription } = properties; + const { description } = properties; let fullDescription = description || "Nothing to see here..."; - if (this.#isViewed && shortDescription) { - fullDescription = shortDescription; - } else if (description) { - this.#isViewed = true; + if (description) { fullDescription = description; } diff --git a/Vessel.ts b/Vessel.ts index d35ad0a..866d3fb 100644 --- a/Vessel.ts +++ b/Vessel.ts @@ -19,24 +19,8 @@ export default class Vessel { return this._activeEffects; } - description(items: Item[]): string | null { - if (items.length === 0) return null; - - const vowels = ["a", "e", "i", "o", "u"]; - const description = items - .map(({ name }, i) => { - let anItem = `${vowels.includes(name[0]) ? "an" : "a"} ${name}`; - - // if we have more than one item, and this is the last item... - if (i > 1 && i + 1 === items.length) { - anItem = `and ${anItem}`; - } - - return anItem; - }) - .join(", "); - - return description; + addEffect(effect: string): void { + this.activeEffects.push(effect); } // Player effects should be applied first, then scene effects should be applied. @@ -62,4 +46,36 @@ export default class Vessel { return appliedProperties; } + + description(items: Item[]): string | null { + if (items.length === 0) return null; + + const vowels = ["a", "e", "i", "o", "u"]; + const description = items + .map(({ name }, i) => { + let anItem = `${vowels.includes(name[0]) ? "an" : "a"} ${name}`; + + // if we have more than one item, and this is the last item... + if (i > 1 && i + 1 === items.length) { + anItem = `and ${anItem}`; + } + + return anItem; + }) + .join(", "); + + return description; + } + + hasActiveEffect(effect: string): boolean { + return this._activeEffects.includes(effect); + } + + removeEffect(effect: string): void { + const idx = this._activeEffects.findIndex((e) => e === effect); + + if (idx >= 0) { + this._activeEffects.splice(idx, 1); + } + } } diff --git a/data/rooms.ts b/data/rooms.ts index eae4140..c42a292 100644 --- a/data/rooms.ts +++ b/data/rooms.ts @@ -3,17 +3,32 @@ import { GameData, SceneProperties } from "../types.ts"; export const hall: GameData = { properties: { description: "It's very dark", - items: [{ name: "flashlight" }], + items: [ + { + name: "flashlight", + actions: { + use: { + args: { + effect: "lit-by-flashlight", + applyTo: "scene", + result: "The flashlight is on.", + reverseResult: "The flashlight is off.", + reversible: true, + }, + type: "applyEffect", + }, + }, + }, + ], }, conditions: { effects: [ { - name: "lights-on", + name: "lit-by-flashlight", properties: { description: "You are standing in a big hall. There's lots of nooks, " + "crannies, and room for general testing. Aw yeah... sweet testing!", - shortDescription: "You are in a big hall.", }, source: "player", }, diff --git a/terms/Term.ts b/terms/Term.ts index 2181023..842fade 100644 --- a/terms/Term.ts +++ b/terms/Term.ts @@ -28,7 +28,7 @@ export interface Term { canPrecedeVariable: boolean; } -export interface Action extends Term { +export interface ActionTerm extends Term { action: ActionFn; category: "action"; } diff --git a/terms/actions.ts b/terms/actions.ts index 2caa9d9..a12a669 100644 --- a/terms/actions.ts +++ b/terms/actions.ts @@ -1,5 +1,10 @@ -import Scene from "../Scene.ts"; -import Player from "../Player.ts"; +import type Scene from "../Scene.ts"; +import type Player from "../Player.ts"; +import type { ApplyEffectArgs, ItemAction } from "../types.ts"; + +const ITEM_MISSING = "You don't have that."; +const ITEM_UNUSABLE = "I don't know how to use that."; +const ITEM_ALREADY_USED = "I already did that."; export function look(player: Player, scene: Scene): string { return scene.look(player.activeEffects, scene.activeEffects); @@ -23,6 +28,66 @@ export function pickUpItem( return "Taken."; } -export function checkInventory(player: Player) { +export function checkInventory(player: Player): string { return player.look(); } + +export function use(player: Player, scene: Scene, target?: string): string { + if (!target) { + return "What do you want to use?"; + } + + const { inventory } = player; + + if (inventory === null) { + return ITEM_MISSING; + } + + const item = inventory.find((i) => i.name === target); + + if (item === undefined) { + return ITEM_MISSING; + } + + const itemAction = item?.actions["use"]; + + if (itemAction === undefined) { + return ITEM_UNUSABLE; + } + + return executeItemAction(player, scene, itemAction); +} + +function executeItemAction( + player: Player, + scene: Scene, + { args, type }: ItemAction +): string { + switch (type) { + case "applyEffect": + return applyEffect(player, scene, args as ApplyEffectArgs); + default: + return ITEM_UNUSABLE; + } +} + +function applyEffect( + player: Player, + scene: Scene, + { applyTo, effect, reverseResult, reversible, result }: ApplyEffectArgs +): string { + const target = applyTo === "player" ? player : scene; + const isApplied = target.hasActiveEffect(effect); + + if (!reversible && isApplied) { + return ITEM_ALREADY_USED; + } + + if (isApplied) { + target.removeEffect(effect); + return reverseResult || result; + } else { + target.addEffect(effect); + return result; + } +} diff --git a/terms/terms.ts b/terms/terms.ts index fef83da..eab388e 100644 --- a/terms/terms.ts +++ b/terms/terms.ts @@ -1,7 +1,7 @@ import * as actions from "./actions.ts"; -import { Action } from "./Term.ts"; +import { ActionTerm } from "./Term.ts"; -const inventory: Action = { +const inventory: ActionTerm = { action: actions.checkInventory, canPrecedeVariable: false, category: "action", @@ -10,7 +10,7 @@ const inventory: Action = { precedesConstants: [], }; -const look: Action = { +const look: ActionTerm = { action: actions.look, canPrecedeVariable: false, category: "action", @@ -19,7 +19,7 @@ const look: Action = { precedesConstants: [], }; -const take: Action = { +const take: ActionTerm = { action: actions.pickUpItem, canPrecedeVariable: true, category: "action", @@ -28,8 +28,18 @@ const take: Action = { precedesConstants: [], }; +const use: ActionTerm = { + action: actions.use, + canPrecedeVariable: true, + category: "action", + constant: "use", + precedesCategories: [], + precedesConstants: [], +}; + export const terms = { [inventory.constant]: inventory, [look.constant]: look, [take.constant]: take, + [use.constant]: use, }; diff --git a/types.ts b/types.ts index a9d095b..b860f67 100644 --- a/types.ts +++ b/types.ts @@ -1,7 +1,32 @@ export * from "./data/rooms.ts"; +export type ActionArgs = ApplyEffectArgs; + +export type ItemActionType = "applyEffect"; + +export interface Args { + result: string; +} + +export interface ItemAction { + type: ItemActionType; + args: ActionArgs; +} + +export interface ApplyEffectArgs extends Args { + effect: string; + applyTo: "player" | "scene"; + reversible: boolean; + reverseResult?: string; +} + +export interface ApplyEffectItemAction extends ItemAction { + type: "applyEffect"; +} + export interface Item { name: string; + actions: { [name: string]: ItemAction }; } export interface Effect { @@ -16,7 +41,6 @@ export interface VesselProperties { export interface SceneProperties extends VesselProperties { description?: string; - shortDescription?: string; } export interface Conditions {