Add applyEffect
This commit is contained in:
@ -2,7 +2,7 @@ import type Player from "./Player.ts";
|
|||||||
import type Scene from "./Scene.ts";
|
import type Scene from "./Scene.ts";
|
||||||
import { terms } from "./terms/terms.ts";
|
import { terms } from "./terms/terms.ts";
|
||||||
import type {
|
import type {
|
||||||
Action,
|
ActionTerm,
|
||||||
ActionFn,
|
ActionFn,
|
||||||
Category,
|
Category,
|
||||||
Constant,
|
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) {
|
if ("action" in term) {
|
||||||
this.addAction(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
|
// make sure we expected an action and process it if it was
|
||||||
if (this.expectedCategories.includes("action")) {
|
if (this.expectedCategories.includes("action")) {
|
||||||
this.action = term.action;
|
this.action = term.action;
|
||||||
|
@ -10,10 +10,6 @@ export default class Player extends Vessel<VesselProperties> {
|
|||||||
this.#effects = effects;
|
this.#effects = effects;
|
||||||
}
|
}
|
||||||
|
|
||||||
get effects() {
|
|
||||||
return this.#effects;
|
|
||||||
}
|
|
||||||
|
|
||||||
look(): string {
|
look(): string {
|
||||||
const description = super.description(this._properties.items || []);
|
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 = [];
|
if (!this._properties.items) this._properties.items = [];
|
||||||
this._properties.items.push(item);
|
this._properties.items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get inventory(): Item[] | null {
|
||||||
|
return this._properties.items || null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
10
Scene.ts
10
Scene.ts
@ -2,12 +2,9 @@ import { GameData, Item, SceneProperties } from "./types.ts";
|
|||||||
import Vessel from "./Vessel.ts";
|
import Vessel from "./Vessel.ts";
|
||||||
|
|
||||||
export default class Scene extends Vessel<SceneProperties> {
|
export default class Scene extends Vessel<SceneProperties> {
|
||||||
#isViewed: boolean;
|
|
||||||
|
|
||||||
constructor(gameData: GameData<SceneProperties>) {
|
constructor(gameData: GameData<SceneProperties>) {
|
||||||
// TODO: [] is a placeholder for scene effects.
|
// TODO: [] is a placeholder for scene effects.
|
||||||
super(gameData.properties, gameData.conditions);
|
super(gameData.properties, gameData.conditions);
|
||||||
this.#isViewed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
look(activePlayerEffects: string[], activeSceneEffects: string[]): string {
|
look(activePlayerEffects: string[], activeSceneEffects: string[]): string {
|
||||||
@ -16,14 +13,11 @@ export default class Scene extends Vessel<SceneProperties> {
|
|||||||
activeSceneEffects
|
activeSceneEffects
|
||||||
);
|
);
|
||||||
const itemsDescription = super.description(properties.items || []);
|
const itemsDescription = super.description(properties.items || []);
|
||||||
const { description, shortDescription } = properties;
|
const { description } = properties;
|
||||||
|
|
||||||
let fullDescription = description || "Nothing to see here...";
|
let fullDescription = description || "Nothing to see here...";
|
||||||
|
|
||||||
if (this.#isViewed && shortDescription) {
|
if (description) {
|
||||||
fullDescription = shortDescription;
|
|
||||||
} else if (description) {
|
|
||||||
this.#isViewed = true;
|
|
||||||
fullDescription = description;
|
fullDescription = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
Vessel.ts
52
Vessel.ts
@ -19,24 +19,8 @@ export default class Vessel<T extends VesselProperties> {
|
|||||||
return this._activeEffects;
|
return this._activeEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
description(items: Item[]): string | null {
|
addEffect(effect: string): void {
|
||||||
if (items.length === 0) return null;
|
this.activeEffects.push(effect);
|
||||||
|
|
||||||
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.
|
// 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;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,32 @@ import { GameData, SceneProperties } from "../types.ts";
|
|||||||
export const hall: GameData<SceneProperties> = {
|
export const hall: GameData<SceneProperties> = {
|
||||||
properties: {
|
properties: {
|
||||||
description: "It's very dark",
|
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: {
|
conditions: {
|
||||||
effects: [
|
effects: [
|
||||||
{
|
{
|
||||||
name: "lights-on",
|
name: "lit-by-flashlight",
|
||||||
properties: {
|
properties: {
|
||||||
description:
|
description:
|
||||||
"You are standing in a big hall. There's lots of nooks, " +
|
"You are standing in a big hall. There's lots of nooks, " +
|
||||||
"crannies, and room for general testing. Aw yeah... sweet testing!",
|
"crannies, and room for general testing. Aw yeah... sweet testing!",
|
||||||
shortDescription: "You are in a big hall.",
|
|
||||||
},
|
},
|
||||||
source: "player",
|
source: "player",
|
||||||
},
|
},
|
||||||
|
@ -28,7 +28,7 @@ export interface Term {
|
|||||||
canPrecedeVariable: boolean;
|
canPrecedeVariable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Action extends Term {
|
export interface ActionTerm extends Term {
|
||||||
action: ActionFn;
|
action: ActionFn;
|
||||||
category: "action";
|
category: "action";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import Scene from "../Scene.ts";
|
import type Scene from "../Scene.ts";
|
||||||
import Player from "../Player.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 {
|
export function look(player: Player, scene: Scene): string {
|
||||||
return scene.look(player.activeEffects, scene.activeEffects);
|
return scene.look(player.activeEffects, scene.activeEffects);
|
||||||
@ -23,6 +28,66 @@ export function pickUpItem(
|
|||||||
return "Taken.";
|
return "Taken.";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkInventory(player: Player) {
|
export function checkInventory(player: Player): string {
|
||||||
return player.look();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as actions from "./actions.ts";
|
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,
|
action: actions.checkInventory,
|
||||||
canPrecedeVariable: false,
|
canPrecedeVariable: false,
|
||||||
category: "action",
|
category: "action",
|
||||||
@ -10,7 +10,7 @@ const inventory: Action = {
|
|||||||
precedesConstants: [],
|
precedesConstants: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const look: Action = {
|
const look: ActionTerm = {
|
||||||
action: actions.look,
|
action: actions.look,
|
||||||
canPrecedeVariable: false,
|
canPrecedeVariable: false,
|
||||||
category: "action",
|
category: "action",
|
||||||
@ -19,7 +19,7 @@ const look: Action = {
|
|||||||
precedesConstants: [],
|
precedesConstants: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const take: Action = {
|
const take: ActionTerm = {
|
||||||
action: actions.pickUpItem,
|
action: actions.pickUpItem,
|
||||||
canPrecedeVariable: true,
|
canPrecedeVariable: true,
|
||||||
category: "action",
|
category: "action",
|
||||||
@ -28,8 +28,18 @@ const take: Action = {
|
|||||||
precedesConstants: [],
|
precedesConstants: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const use: ActionTerm = {
|
||||||
|
action: actions.use,
|
||||||
|
canPrecedeVariable: true,
|
||||||
|
category: "action",
|
||||||
|
constant: "use",
|
||||||
|
precedesCategories: [],
|
||||||
|
precedesConstants: [],
|
||||||
|
};
|
||||||
|
|
||||||
export const terms = {
|
export const terms = {
|
||||||
[inventory.constant]: inventory,
|
[inventory.constant]: inventory,
|
||||||
[look.constant]: look,
|
[look.constant]: look,
|
||||||
[take.constant]: take,
|
[take.constant]: take,
|
||||||
|
[use.constant]: use,
|
||||||
};
|
};
|
||||||
|
26
types.ts
26
types.ts
@ -1,7 +1,32 @@
|
|||||||
export * from "./data/rooms.ts";
|
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 {
|
export interface Item {
|
||||||
name: string;
|
name: string;
|
||||||
|
actions: { [name: string]: ItemAction };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Effect<T> {
|
export interface Effect<T> {
|
||||||
@ -16,7 +41,6 @@ export interface VesselProperties {
|
|||||||
|
|
||||||
export interface SceneProperties extends VesselProperties {
|
export interface SceneProperties extends VesselProperties {
|
||||||
description?: string;
|
description?: string;
|
||||||
shortDescription?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Conditions<T> {
|
export interface Conditions<T> {
|
||||||
|
Reference in New Issue
Block a user