Add applyEffect

This commit is contained in:
2021-05-25 18:47:09 -07:00
parent ccd4974266
commit cc306dbf87
9 changed files with 169 additions and 45 deletions

View File

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

View File

@ -10,10 +10,6 @@ export default class Player extends Vessel<VesselProperties> {
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<VesselProperties> {
if (!this._properties.items) this._properties.items = [];
this._properties.items.push(item);
}
get inventory(): Item[] | null {
return this._properties.items || null;
}
}

View File

@ -2,12 +2,9 @@ import { GameData, Item, SceneProperties } from "./types.ts";
import Vessel from "./Vessel.ts";
export default class Scene extends Vessel<SceneProperties> {
#isViewed: boolean;
constructor(gameData: GameData<SceneProperties>) {
// 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<SceneProperties> {
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;
}

View File

@ -19,24 +19,8 @@ export default class Vessel<T extends VesselProperties> {
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<T extends VesselProperties> {
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);
}
}
}

View File

@ -3,17 +3,32 @@ import { GameData, SceneProperties } from "../types.ts";
export const hall: GameData<SceneProperties> = {
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",
},

View File

@ -28,7 +28,7 @@ export interface Term {
canPrecedeVariable: boolean;
}
export interface Action extends Term {
export interface ActionTerm extends Term {
action: ActionFn;
category: "action";
}

View File

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

View File

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

View File

@ -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<T> {
@ -16,7 +41,6 @@ export interface VesselProperties {
export interface SceneProperties extends VesselProperties {
description?: string;
shortDescription?: string;
}
export interface Conditions<T> {