From 08ee3d1b5de2a2f484f00774941fb7563cfef553 Mon Sep 17 00:00:00 2001 From: nolwn Date: Tue, 1 Jun 2021 16:06:18 -0700 Subject: [PATCH] add ability to move from room to room. --- Vessel.ts => Entity.ts | 22 +++++++++--- Player.ts | 10 +++--- Scene.ts | 78 +++++++++++++++++++++++++++++------------- data/rooms.ts | 24 +++++++++++-- main.ts | 2 +- terms/actions.ts | 31 ++++++++++++++++- terms/terms.ts | 10 ++++++ types.ts | 18 +++++----- 8 files changed, 150 insertions(+), 45 deletions(-) rename Vessel.ts => Entity.ts (83%) diff --git a/Vessel.ts b/Entity.ts similarity index 83% rename from Vessel.ts rename to Entity.ts index 349195d..c5b46bc 100644 --- a/Vessel.ts +++ b/Entity.ts @@ -1,11 +1,11 @@ /** - * Vessel.ts contains the Vessel class which is a base class that handles effects and + * Entity.ts contains the Entity class which is a base class that handles effects and * containing items. It is the base class for Player and Scene. */ -import type { Conditions, Effect, Item, VesselProperties } from "./types.ts"; +import type { Conditions, Item, EntityProperties } from "./types.ts"; -export default class Vessel { +export default class Entity { protected _conditions: Conditions; protected _properties: T; protected _activeEffects: string[]; @@ -24,6 +24,18 @@ export default class Vessel { return this._activeEffects; } + set conditions(conditions: Conditions) { + this._conditions = conditions; + } + + get properties(): T { + return this._properties; + } + + set properties(properties: T) { + this._properties = properties; + } + addEffect(effect: string): void { this.activeEffects.push(effect); } @@ -74,12 +86,12 @@ export default class Vessel { return description; } - // Checks if a given effect exists as an active effect on this vessel + // Checks if a given effect exists as an active effect on this entity hasActiveEffect(effect: string): boolean { return this._activeEffects.includes(effect); } - // Removes an effect if it is active on this vessel + // Removes an effect if it is active on this entity removeEffect(effect: string): void { const idx = this._activeEffects.findIndex((e) => e === effect); diff --git a/Player.ts b/Player.ts index 2c446e0..ecfbe8f 100644 --- a/Player.ts +++ b/Player.ts @@ -3,13 +3,13 @@ * goes on. */ -import { Effect, Item, VesselProperties } from "./types.ts"; -import Vessel from "./Vessel.ts"; +import { Effect, Item, EntityProperties } from "./types.ts"; +import Entity from "./Entity.ts"; -// The Player is a type of "Vessel" which is a generic object that can have effects +// The Player is a type of "entity" which is a generic object that can have effects // and items. -export default class Player extends Vessel { - constructor(items: Item[] = [], _effects: Effect[] = []) { +export default class Player extends Entity { + constructor(items: Item[] = [], _effects: Effect[] = []) { super({ items }, { effects: [] }); } diff --git a/Scene.ts b/Scene.ts index 75740cc..7a9ac12 100644 --- a/Scene.ts +++ b/Scene.ts @@ -3,14 +3,55 @@ */ import type { Exit, StoryScene, Item, SceneProperties } from "./types.ts"; -import Vessel from "./Vessel.ts"; +import Entity from "./Entity.ts"; -// The Scene is a type of "Vessel" which is a generic object that can have effects +// The Scene is a type of "Entity" which is a generic object that can have effects // and items. -export default class Scene extends Vessel { - constructor(gameData: StoryScene) { - // TODO: [] is a placeholder for scene effects. - super(gameData.properties, gameData.conditions); +export default class Scene extends Entity { + protected _map: StoryScene[]; + protected _identifier: string; + + constructor(identifier: string, map: StoryScene[]) { + const scene = Scene.getScene(identifier, map); + + if (!scene) { + throw new Error("cannot find scene!"); + } + + const { properties, conditions } = scene; + + super(properties, conditions); + + this._identifier = identifier; + this._map = map; + } + + changeScene(identifier: string) { + const scene = Scene.getScene(identifier, this._map); + + if (scene) { + const { conditions, properties } = scene; + + this.properties = properties; + this.conditions = conditions; + } + } + + // get removes an items from the scene and returns it + get(target: string): Item | null { + const { items = [] } = this._properties; + const idx = items.findIndex(({ name }) => name === target); + + // if we found an index for the given item + if (idx >= 0) { + const item = items[idx]; + items.splice(idx, 1); + + return item; + } + + // if an item wasn't found and returned, return null + return null; } // look returns a string that describes the scene and the items in it @@ -41,23 +82,6 @@ export default class Scene extends Vessel { return fullDescription; } - // get removes an items from the scene and returns it - get(target: string): Item | null { - const { items = [] } = this._properties; - const idx = items.findIndex(({ name }) => name === target); - - // if we found an index for the given item - if (idx >= 0) { - const item = items[idx]; - items.splice(idx, 1); - - return item; - } - - // if an item wasn't found and returned, return null - return null; - } - // Turn exits into a string description with an "and" separating the last two items private static describeExits(exits: Exit[]) { const exitDescriptions = exits.map(({ description }) => description); @@ -74,4 +98,12 @@ export default class Scene extends Vessel { return `${restExits.join(", ")} and ${lastExit}`; } } + + // This needs to be static so that we can use it in the constructor + private static getScene( + identifier: string, + map: StoryScene[] + ): StoryScene | null { + return map.find((scene) => scene.identifier === identifier) || null; + } } diff --git a/data/rooms.ts b/data/rooms.ts index fdad51c..da65b7f 100644 --- a/data/rooms.ts +++ b/data/rooms.ts @@ -12,8 +12,12 @@ import { StoryScene, SceneProperties } from "../types.ts"; const game: { map: StoryScene[] } = { map: [ { + identifier: "hall", // properties represent the base, default state for this room. properties: { + // The room is called "Hall" + name: "Hall", + // This is what the user will see if the look while in the room. description: "It's very dark", // This array lists all the items that are in the room. @@ -63,12 +67,12 @@ const game: { map: StoryScene[] } = { { description: "a closet to the north", direction: "north", - scene: "Hall Closet", + scene: "hall closet", }, { description: "a dining room to the south", direction: "south", - scene: "Dining Room", + scene: "dining room", }, ], }, @@ -101,6 +105,22 @@ const game: { map: StoryScene[] } = { ], }, }, + { + identifier: "hall closet", + properties: { + name: "Hall Closet", + description: + "It's a closet. You feel a little silly just standing in it.", + items: [ + { + name: "raincoat", + }, + ], + }, + conditions: { + effects: [], + }, + }, ], }; diff --git a/main.ts b/main.ts index d2d78a2..2fbc7ce 100644 --- a/main.ts +++ b/main.ts @@ -6,7 +6,7 @@ import game from "./data/rooms.ts"; async function main() { const user = new User(); // for communication with the user - const scene = new Scene(game.map[0]); // the room that player is in. + const scene = new Scene("hall", game.map); // the room that player is in. const player = new Player(); // the players current state let running = true; // running flag for the game loop. Stops on false. let statement = ""; // holds a statement for the user. diff --git a/terms/actions.ts b/terms/actions.ts index c168cd7..6f9a324 100644 --- a/terms/actions.ts +++ b/terms/actions.ts @@ -15,6 +15,7 @@ import type { ApplyEffectArgs, ItemAction } from "../types.ts"; const ITEM_MISSING = "I can't find that."; const ITEM_UNUSABLE = "I don't know how to use that."; const ITEM_ALREADY_USED = "I already did that."; +const DIRECTION_INACCESSIBLE = "I can't go that way."; // look is what happens when a player uses the "look" verb. It gets a description of the // scene. @@ -52,6 +53,34 @@ export function checkInventory(player: Player): string { return player.look(); } +// move changes the scene around the player +export function move(_player: Player, scene: Scene, target?: string) { + if (!target) { + return "Where do you want me to go?"; + } + + const { exits } = scene.properties; + + if (!exits) { + return DIRECTION_INACCESSIBLE; + } + + const exit = exits.find( + ({ direction }) => direction === target.toLocaleLowerCase() + ); + + if (!exit) { + return DIRECTION_INACCESSIBLE; + } + + const { scene: identifier } = exit; + scene.changeScene(identifier); + + const { description, name } = scene.properties; + + return `${name}\n${description}`; +} + // use is what happens when a player uses the "use" verb. Different items do different // things when they are used, so the function needs to figure out what kind of usage an // item is written for, and then execute that logic with whatever arguments the @@ -75,7 +104,7 @@ export function use(player: Player, scene: Scene, target?: string): string { return ITEM_MISSING; } - const itemAction = item?.actions["use"]; + const itemAction = item?.actions?.["use"]; // The item doesn't have action defined for "use", so it cannot be used. if (itemAction === undefined) { diff --git a/terms/terms.ts b/terms/terms.ts index 0fa068e..608b8f2 100644 --- a/terms/terms.ts +++ b/terms/terms.ts @@ -24,6 +24,15 @@ const look: ActionTerm = { precedesConstants: [], }; +const move: ActionTerm = { + action: actions.move, + canPrecedeVariable: true, + category: "action", + constant: "go", + precedesCategories: [], + precedesConstants: [], +}; + const take: ActionTerm = { action: actions.pickUpItem, canPrecedeVariable: true, @@ -45,6 +54,7 @@ const use: ActionTerm = { export const terms = { [inventory.constant]: inventory, [look.constant]: look, + [move.constant]: move, [take.constant]: take, [use.constant]: use, }; diff --git a/types.ts b/types.ts index cd43111..68fc910 100644 --- a/types.ts +++ b/types.ts @@ -23,7 +23,7 @@ export interface Args { // Conditions contains story elements that can be conditionally applied to the scene or // player -export interface Conditions { +export interface Conditions { effects: Effect[]; } @@ -41,7 +41,7 @@ export type Direction = | "down"; // Effect represents some effect that may be applied to a scene or player -export interface Effect { +export interface Effect { name: string; properties: T; source: "player" | "scene"; // where the effect is applied @@ -57,7 +57,7 @@ export interface Exit { // Item represents some item either in the scene or in the player's inventory export interface Item { name: string; - actions: { [name: string]: ItemAction }; + actions?: { [name: string]: ItemAction }; } // ItemActionType is a union of all the types of action that can be used @@ -69,20 +69,22 @@ export interface ItemAction { args: ActionArgs; } -// SceneProperties are the properties (in addition to the VesselProperties) that are +// SceneProperties are the properties (in addition to the EntityProperties) that are // needed by the scene -export interface SceneProperties extends VesselProperties { +export interface SceneProperties extends EntityProperties { description?: string; exits?: Exit[]; + name?: string; } // StoryScene holds both the conditions and properties for a scene. -export interface StoryScene { +export interface StoryScene { + identifier: string; conditions: Conditions; properties: SceneProperties; } -// VesselProperties is a base interface for the properties that a user or scene might have -export interface VesselProperties { +// EntityProperties is a base interface for the properties that a user or scene might have +export interface EntityProperties { items?: Item[]; }