Refactor player and room to support adding effects.

This commit is contained in:
2021-05-25 16:02:06 -07:00
parent b3058097b4
commit ccd4974266
13 changed files with 198 additions and 79 deletions

View File

@ -1,29 +0,0 @@
import type { Item } from "./data/data.ts";
export default class Container {
protected items: Item[];
constructor(items: Item[]) {
this.items = items;
}
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;
}
}

18
Game.ts Normal file
View File

@ -0,0 +1,18 @@
import type Player from "./Player.ts";
import type Scene from "./Scene.ts";
export default class Game {
#player: Player;
#scene: Scene;
constructor(player: Player, scene: Scene) {
this.#player = player;
this.#scene = scene;
}
lookAtScene(): string {
const { effects } = this.#player;
return this.#scene.look(effects);
}
}

View File

@ -1,5 +1,5 @@
import Player from "./Player.ts";
import Scene from "./Scene.ts";
import type Player from "./Player.ts";
import type Scene from "./Scene.ts";
import { terms } from "./terms/terms.ts";
import type {
Action,

View File

@ -1,21 +1,21 @@
import { Item } from "./data/data.ts";
import Container from "./Container.ts";
import User from "./User.ts";
import { Effect, Item, VesselProperties } from "./types.ts";
import Vessel from "./Vessel.ts";
export default class Player extends Container {
#user: User;
export default class Player extends Vessel<VesselProperties> {
#effects: Effect<VesselProperties>[];
constructor(items?: Item[]) {
super(items || []);
this.#user = new User();
constructor(items: Item[] = [], effects: Effect<VesselProperties>[] = []) {
super({ items }, { effects: [] });
this.#effects = effects;
}
put(item: Item) {
this.items.push(item);
get effects() {
return this.#effects;
}
look(): string {
const description = super.description(this.items);
const description = super.description(this._properties.items || []);
if (description) {
return `You have ${description}`;
@ -23,4 +23,9 @@ export default class Player extends Container {
return "You have nothing.";
}
}
put(item: Item) {
if (!this._properties.items) this._properties.items = [];
this._properties.items.push(item);
}
}

View File

@ -1,32 +1,46 @@
import { GameData, Item } from "./data/data.ts";
import Container from "./Container.ts";
import { GameData, Item, SceneProperties } from "./types.ts";
import Vessel from "./Vessel.ts";
export default class Scene extends Container {
#roomDescription: string;
export default class Scene extends Vessel<SceneProperties> {
#isViewed: boolean;
constructor(gameData: GameData) {
super(gameData.items);
this.#roomDescription = gameData.description;
constructor(gameData: GameData<SceneProperties>) {
// TODO: [] is a placeholder for scene effects.
super(gameData.properties, gameData.conditions);
this.#isViewed = false;
}
look(): string {
const itemsDescription = super.description(this.items);
let description = this.#roomDescription;
look(activePlayerEffects: string[], activeSceneEffects: string[]): string {
const properties = this.applyEffects(
activePlayerEffects,
activeSceneEffects
);
const itemsDescription = super.description(properties.items || []);
const { description, shortDescription } = properties;
if (itemsDescription) {
description += `\n\nThere is ${itemsDescription}`;
let fullDescription = description || "Nothing to see here...";
if (this.#isViewed && shortDescription) {
fullDescription = shortDescription;
} else if (description) {
this.#isViewed = true;
fullDescription = description;
}
return description;
if (itemsDescription) {
fullDescription += `\n\nThere is ${itemsDescription}`;
}
return fullDescription;
}
get(target: string): Item | null {
const idx = this.items.findIndex(({ name }) => name === target);
const { items = [] } = this._properties;
const idx = items.findIndex(({ name }) => name === target);
if (idx >= 0) {
const item = this.items[idx];
this.items.splice(idx, 1);
const item = items[idx];
items.splice(idx, 1);
return item;
}

13
State.ts Normal file
View File

@ -0,0 +1,13 @@
import { GameData, SceneProperties } from "./types.ts";
import Player from "./Player.ts";
import Scene from "./Scene.ts";
export class State {
#player: Player;
#scene: Scene;
constructor(gameData: GameData<SceneProperties>) {
this.#player = new Player();
this.#scene = new Scene(gameData);
}
}

65
Vessel.ts Normal file
View File

@ -0,0 +1,65 @@
import type { Conditions, Effect, Item, VesselProperties } from "./types.ts";
export default class Vessel<T extends VesselProperties> {
protected _conditions: Conditions<T>;
protected _properties: T;
protected _activeEffects: string[];
constructor(
properties: T,
conditions: Conditions<T>,
activeEffects: string[] = []
) {
this._conditions = conditions;
this._properties = properties;
this._activeEffects = activeEffects;
}
get activeEffects(): string[] {
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;
}
// Player effects should be applied first, then scene effects should be applied.
// This will mean that scene effects will take precedence over player effects.
applyEffects(
activePlayerEffects: string[],
activeSceneEffects: string[]
): T {
const activeEffects = [...activePlayerEffects, ...activeSceneEffects];
const map = this._conditions.effects.reduce(
(map: { [name: string]: Effect<T> }, effect: Effect<T>) => {
map[effect.name] = effect;
return map;
},
{}
);
const effects = activeEffects.map((e) => map[e]);
const appliedProperties = { ...this._properties };
for (const effect of effects) {
Object.assign(appliedProperties, effect.properties);
}
return appliedProperties;
}
}

View File

@ -1,10 +0,0 @@
export * from "./rooms.ts";
export interface Item {
name: string;
}
export interface GameData {
description: string;
items: Item[];
}

View File

@ -1,8 +1,22 @@
import { GameData } from "./data.ts";
import { GameData, SceneProperties } from "../types.ts";
export const hall: GameData = {
description:
"You are standing in a big hall. There's lots of nooks, crannies, and" +
"room for general testing. Aw yeah... sweet testing!",
items: [{ name: "flashlight" }],
export const hall: GameData<SceneProperties> = {
properties: {
description: "It's very dark",
items: [{ name: "flashlight" }],
},
conditions: {
effects: [
{
name: "lights-on",
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",
},
],
},
};

View File

@ -2,7 +2,7 @@ import Interpreter from "./Interpreter.ts";
import Player from "./Player.ts";
import Scene from "./Scene.ts";
import User from "./User.ts";
import { hall } from "./data/data.ts";
import { hall } from "./types.ts";
async function main() {
const user = new User(); // for communication with the user

View File

@ -1,8 +1,8 @@
import Scene from "../Scene.ts";
import Player from "../Player.ts";
export function look(_player: Player, scene: Scene): string {
return scene.look();
export function look(player: Player, scene: Scene): string {
return scene.look(player.activeEffects, scene.activeEffects);
}
export function pickUpItem(

View File

@ -21,8 +21,8 @@ const look: Action = {
const take: Action = {
action: actions.pickUpItem,
category: "action",
canPrecedeVariable: true,
category: "action",
constant: "take",
precedesCategories: [],
precedesConstants: [],

29
types.ts Normal file
View File

@ -0,0 +1,29 @@
export * from "./data/rooms.ts";
export interface Item {
name: string;
}
export interface Effect<T> {
name: string;
properties: T;
source: "player" | "scene";
}
export interface VesselProperties {
items?: Item[];
}
export interface SceneProperties extends VesselProperties {
description?: string;
shortDescription?: string;
}
export interface Conditions<T> {
effects: Effect<T>[];
}
export interface GameData<T> {
conditions: Conditions<T>;
properties: SceneProperties;
}