add ability to move from room to room.

This commit is contained in:
2021-06-01 16:06:18 -07:00
parent 8f6d7e4db4
commit 08ee3d1b5d
8 changed files with 150 additions and 45 deletions

View File

@ -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<T extends VesselProperties> {
export default class Entity<T extends EntityProperties> {
protected _conditions: Conditions<T>;
protected _properties: T;
protected _activeEffects: string[];
@ -24,6 +24,18 @@ export default class Vessel<T extends VesselProperties> {
return this._activeEffects;
}
set conditions(conditions: Conditions<T>) {
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<T extends VesselProperties> {
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);

View File

@ -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<VesselProperties> {
constructor(items: Item[] = [], _effects: Effect<VesselProperties>[] = []) {
export default class Player extends Entity<EntityProperties> {
constructor(items: Item[] = [], _effects: Effect<EntityProperties>[] = []) {
super({ items }, { effects: [] });
}

View File

@ -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<SceneProperties> {
constructor(gameData: StoryScene<SceneProperties>) {
// TODO: [] is a placeholder for scene effects.
super(gameData.properties, gameData.conditions);
export default class Scene extends Entity<SceneProperties> {
protected _map: StoryScene<SceneProperties>[];
protected _identifier: string;
constructor(identifier: string, map: StoryScene<SceneProperties>[]) {
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<SceneProperties> {
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<SceneProperties> {
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<SceneProperties>[]
): StoryScene<SceneProperties> | null {
return map.find((scene) => scene.identifier === identifier) || null;
}
}

View File

@ -12,8 +12,12 @@ import { StoryScene, SceneProperties } from "../types.ts";
const game: { map: StoryScene<SceneProperties>[] } = {
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<SceneProperties>[] } = {
{
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<SceneProperties>[] } = {
],
},
},
{
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: [],
},
},
],
};

View File

@ -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.

View File

@ -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) {

View File

@ -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,
};

View File

@ -23,7 +23,7 @@ export interface Args {
// Conditions contains story elements that can be conditionally applied to the scene or
// player
export interface Conditions<T extends VesselProperties> {
export interface Conditions<T extends EntityProperties> {
effects: Effect<T>[];
}
@ -41,7 +41,7 @@ export type Direction =
| "down";
// Effect represents some effect that may be applied to a scene or player
export interface Effect<T extends VesselProperties> {
export interface Effect<T extends EntityProperties> {
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<T extends VesselProperties> {
export interface StoryScene<T extends EntityProperties> {
identifier: string;
conditions: Conditions<T>;
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[];
}