Refactor player and room to support adding effects.
This commit is contained in:
29
Container.ts
29
Container.ts
@ -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
18
Game.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
27
Player.ts
27
Player.ts
@ -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);
|
||||
}
|
||||
}
|
||||
|
46
Scene.ts
46
Scene.ts
@ -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;
|
||||
|
||||
let fullDescription = description || "Nothing to see here...";
|
||||
|
||||
if (this.#isViewed && shortDescription) {
|
||||
fullDescription = shortDescription;
|
||||
} else if (description) {
|
||||
this.#isViewed = true;
|
||||
fullDescription = description;
|
||||
}
|
||||
|
||||
if (itemsDescription) {
|
||||
description += `\n\nThere is ${itemsDescription}`;
|
||||
fullDescription += `\n\nThere is ${itemsDescription}`;
|
||||
}
|
||||
|
||||
return description;
|
||||
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
13
State.ts
Normal 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
65
Vessel.ts
Normal 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;
|
||||
}
|
||||
}
|
10
data/data.ts
10
data/data.ts
@ -1,10 +0,0 @@
|
||||
export * from "./rooms.ts";
|
||||
|
||||
export interface Item {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface GameData {
|
||||
description: string;
|
||||
items: Item[];
|
||||
}
|
@ -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!",
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
2
main.ts
2
main.ts
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
29
types.ts
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user