Compare commits
	
		
			10 Commits
		
	
	
		
			fbf841b551
			...
			08ee3d1b5d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 08ee3d1b5d | |||
| 8f6d7e4db4 | |||
| b134f1849a | |||
| e26747cc7c | |||
| cc306dbf87 | |||
| ccd4974266 | |||
| b3058097b4 | |||
| 03eddfcd74 | |||
| 09e10e94b5 | |||
| 8d854945e1 | 
							
								
								
									
										102
									
								
								Entity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Entity.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,102 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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, Item, EntityProperties } from "./types.ts";
 | 
			
		||||
 | 
			
		||||
export default class Entity<T extends EntityProperties> {
 | 
			
		||||
	protected _conditions: Conditions<T>;
 | 
			
		||||
	protected _properties: T;
 | 
			
		||||
	protected _activeEffects: string[];
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		properties: T,
 | 
			
		||||
		conditions: Conditions<T>,
 | 
			
		||||
		activeEffects: string[] = []
 | 
			
		||||
	) {
 | 
			
		||||
		this._conditions = conditions; // Conditional properties
 | 
			
		||||
		this._properties = properties; // Base properties
 | 
			
		||||
		this._activeEffects = activeEffects;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get activeEffects(): string[] {
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Player effects should be applied first, then scene effects should be applied.
 | 
			
		||||
	// This will mean that scene effects will take precedence over player effects.
 | 
			
		||||
	// Returns base properties with conditional properties applied.
 | 
			
		||||
	applyEffects(
 | 
			
		||||
		activePlayerEffects: string[],
 | 
			
		||||
		activeSceneEffects: string[]
 | 
			
		||||
	): T {
 | 
			
		||||
		const playerSet = new Set(activePlayerEffects);
 | 
			
		||||
		const sceneSet = new Set(activeSceneEffects);
 | 
			
		||||
 | 
			
		||||
		const effects = this._conditions.effects.filter(({ name, source }) => {
 | 
			
		||||
			const set = source === "player" ? playerSet : sceneSet;
 | 
			
		||||
			return set.has(name);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const appliedProperties = { ...this._properties };
 | 
			
		||||
 | 
			
		||||
		// for each effect, apply changed properties to the
 | 
			
		||||
		for (const effect of effects) {
 | 
			
		||||
			Object.assign(appliedProperties, effect.properties);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return appliedProperties;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Generate a description of the contained 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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 entity
 | 
			
		||||
	removeEffect(effect: string): void {
 | 
			
		||||
		const idx = this._activeEffects.findIndex((e) => e === effect);
 | 
			
		||||
 | 
			
		||||
		if (idx >= 0) {
 | 
			
		||||
			this._activeEffects.splice(idx, 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								Interpreter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								Interpreter.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,136 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Interpreter.ts contains the Interpreter class which takes the command that the user
 | 
			
		||||
 * made as well as the player and scene state. By checking each word of the user command,
 | 
			
		||||
 * the interpreter figures out what action the user wants to take and then execute that
 | 
			
		||||
 * command.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import type Player from "./Player.ts";
 | 
			
		||||
import type Scene from "./Scene.ts";
 | 
			
		||||
import { terms } from "./terms/terms.ts";
 | 
			
		||||
import type {
 | 
			
		||||
	ActionTerm,
 | 
			
		||||
	ActionFn,
 | 
			
		||||
	Category,
 | 
			
		||||
	Constant,
 | 
			
		||||
	Term,
 | 
			
		||||
} from "./terms/Term.ts";
 | 
			
		||||
 | 
			
		||||
const DEFAULT_ISSUE = "I don't understand that.";
 | 
			
		||||
 | 
			
		||||
export default class Interpreter {
 | 
			
		||||
	action: ActionFn | null; // The final command to run
 | 
			
		||||
	expectedCategories: Category[]; // command categories that might follow
 | 
			
		||||
	expectedConstants: Constant[]; // constants that might follow
 | 
			
		||||
	isExpectingVariable: boolean; // a variable might follow
 | 
			
		||||
	isValid: boolean; // signals whether the command is understood
 | 
			
		||||
	issue: string | null; // a statement for a command that isn't clear
 | 
			
		||||
	player: Player; // represents the player state
 | 
			
		||||
	scene: Scene; // represents the scene state
 | 
			
		||||
	tokens: string[]; // user command split by spaces (tokens)
 | 
			
		||||
	vars: string[]; // variables taken from commandf
 | 
			
		||||
 | 
			
		||||
	constructor(player: Player, scene: Scene, command: string) {
 | 
			
		||||
		this.action = null;
 | 
			
		||||
		this.expectedCategories = ["action"];
 | 
			
		||||
		this.expectedConstants = [];
 | 
			
		||||
		this.isExpectingVariable = false;
 | 
			
		||||
		this.isValid = true;
 | 
			
		||||
		this.issue = null;
 | 
			
		||||
		this.player = player;
 | 
			
		||||
		this.scene = scene;
 | 
			
		||||
		this.tokens = command.split(" ");
 | 
			
		||||
		this.vars = [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the intepreter can figure out what the user meant, execute runs the action
 | 
			
		||||
	// function it found with whatever variables it found. If the interpreter could not
 | 
			
		||||
	// interpret the user command, then it will respond with whatever the issue was.
 | 
			
		||||
	execute(): string {
 | 
			
		||||
		let response = DEFAULT_ISSUE;
 | 
			
		||||
 | 
			
		||||
		if (this.isValid && this.action) {
 | 
			
		||||
			response = this.action(this.player, this.scene, ...this.vars);
 | 
			
		||||
		} else if (this.issue) {
 | 
			
		||||
			response = this.issue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return response;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// interpret tries to figure out what the user wants. If it can, it sets as
 | 
			
		||||
	// action function for this object and whatever variables it finds. If it
 | 
			
		||||
	// cannot, then it sets the issue.
 | 
			
		||||
	interpret() {
 | 
			
		||||
		for (const token of this.tokens) {
 | 
			
		||||
			if (!this.isValid) break;
 | 
			
		||||
 | 
			
		||||
			this.interpretToken(token);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private interpretToken(token: string) {
 | 
			
		||||
		const term = Interpreter.lookupTerm(token); // is the token a term?
 | 
			
		||||
 | 
			
		||||
		if (!term) {
 | 
			
		||||
			// if not...
 | 
			
		||||
			if (this.isExpectingVariable) {
 | 
			
		||||
				// ...are we expecting a variable?
 | 
			
		||||
				this.processVariable(token); // add variable
 | 
			
		||||
			} else {
 | 
			
		||||
				// ...or...
 | 
			
		||||
				this.stopParsing(); // this isn't a valid command.
 | 
			
		||||
			}
 | 
			
		||||
		} else if (
 | 
			
		||||
			this.expectedConstants.find(({ constant }) => constant === token)
 | 
			
		||||
		) {
 | 
			
		||||
			// if this matches an expected constant string...
 | 
			
		||||
			this.processTerm(term);
 | 
			
		||||
		} else if (this.expectedCategories.includes(term.category)) {
 | 
			
		||||
			// ...or category
 | 
			
		||||
			this.processTerm(term);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.stopParsing(); // ...this is not a valid command
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private processTerm(term: ActionTerm | Term): void {
 | 
			
		||||
		if ("action" in term) {
 | 
			
		||||
			this.addAction(term);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.expectedCategories = term.precedesCategories;
 | 
			
		||||
		this.expectedConstants = term.precedesConstants;
 | 
			
		||||
		this.isExpectingVariable = term.canPrecedeVariable;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private processVariable(token: string) {
 | 
			
		||||
		if (this.vars.length >= 2) {
 | 
			
		||||
			this.stopParsing();
 | 
			
		||||
		} else {
 | 
			
		||||
			this.vars.unshift(token.toLocaleLowerCase());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
		} else {
 | 
			
		||||
			this.stopParsing();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private stopParsing(issue = DEFAULT_ISSUE) {
 | 
			
		||||
		this.issue = issue;
 | 
			
		||||
		this.isValid = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static lookupTerm(token: string): Term | null {
 | 
			
		||||
		if (token in terms) {
 | 
			
		||||
			return terms[token];
 | 
			
		||||
		} else {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								Player.ts
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								Player.ts
									
									
									
									
									
								
							@ -1,33 +1,37 @@
 | 
			
		||||
import { Item } from "./data/data.ts";
 | 
			
		||||
import User from "./User.ts";
 | 
			
		||||
/**
 | 
			
		||||
 * Player.ts contains the Player class. It represents the player's state as the game
 | 
			
		||||
 * goes on.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export default class Player {
 | 
			
		||||
	#items: Item[];
 | 
			
		||||
	#user: User;
 | 
			
		||||
import { Effect, Item, EntityProperties } from "./types.ts";
 | 
			
		||||
import Entity from "./Entity.ts";
 | 
			
		||||
 | 
			
		||||
	constructor(items?: Item[]) {
 | 
			
		||||
		this.#items = items || [];
 | 
			
		||||
		this.#user = new User();
 | 
			
		||||
// The Player is a type of "entity" which is a generic object that can have effects
 | 
			
		||||
// and items.
 | 
			
		||||
export default class Player extends Entity<EntityProperties> {
 | 
			
		||||
	constructor(items: Item[] = [], _effects: Effect<EntityProperties>[] = []) {
 | 
			
		||||
		super({ items }, { effects: [] });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	drop(item: Item) {
 | 
			
		||||
		this.#items.push(item);
 | 
			
		||||
	// inventory is a getter that returns the items in the user's inventory
 | 
			
		||||
	get inventory(): Item[] | null {
 | 
			
		||||
		return this._properties.items || null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async inventory() {
 | 
			
		||||
		const vowels = ["a", "e", "i", "o", "u"];
 | 
			
		||||
		const description = this.#items
 | 
			
		||||
			.map(({ name }, i) => {
 | 
			
		||||
				let anItem = `${vowels.includes(name[0]) ? "an" : "a"} ${name}`;
 | 
			
		||||
	// look returns a string that describes the players inventory
 | 
			
		||||
	look(): string {
 | 
			
		||||
		const description = super.description(this._properties.items || []);
 | 
			
		||||
 | 
			
		||||
				if (i + 1 === this.#items.length) {
 | 
			
		||||
					anItem = `and ${anItem}`;
 | 
			
		||||
		if (description) {
 | 
			
		||||
			return `You have ${description}`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return "You have nothing.";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
				return anItem;
 | 
			
		||||
			})
 | 
			
		||||
			.join(", ");
 | 
			
		||||
 | 
			
		||||
		await this.#user.tell(`You have ${description}.`);
 | 
			
		||||
	// put takes an item and puts it into the players inventory
 | 
			
		||||
	put(item: Item) {
 | 
			
		||||
		if (!this._properties.items) this._properties.items = [];
 | 
			
		||||
		this._properties.items.push(item);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										112
									
								
								Scene.ts
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								Scene.ts
									
									
									
									
									
								
							@ -1,35 +1,109 @@
 | 
			
		||||
import { GameData, Item } from "./data/data.ts";
 | 
			
		||||
import User from "./User.ts";
 | 
			
		||||
/**
 | 
			
		||||
 * Scene.ts contains the Scene class. It represents the state of the current scene.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export default class Scene {
 | 
			
		||||
	#items: Item[];
 | 
			
		||||
	#user: User;
 | 
			
		||||
import type { Exit, StoryScene, Item, SceneProperties } from "./types.ts";
 | 
			
		||||
import Entity from "./Entity.ts";
 | 
			
		||||
 | 
			
		||||
	constructor(gameData: GameData) {
 | 
			
		||||
		this.#items = gameData.items;
 | 
			
		||||
		this.#user = new User();
 | 
			
		||||
// The Scene is a type of "Entity" which is a generic object that can have effects
 | 
			
		||||
// and items.
 | 
			
		||||
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!");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	async look() {
 | 
			
		||||
		const description = this.#items
 | 
			
		||||
			.map(({ description }) => description)
 | 
			
		||||
			.join(" ");
 | 
			
		||||
		const { properties, conditions } = scene;
 | 
			
		||||
 | 
			
		||||
		await this.#user.tell(description);
 | 
			
		||||
		super(properties, conditions);
 | 
			
		||||
 | 
			
		||||
		this._identifier = identifier;
 | 
			
		||||
		this._map = map;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async take(target: string): Promise<Item | null> {
 | 
			
		||||
		const idx = this.#items.findIndex(({ name }) => name === target);
 | 
			
		||||
	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 = this.#items[idx];
 | 
			
		||||
			this.#items.splice(idx, 1);
 | 
			
		||||
 | 
			
		||||
			await this.#user.tell("Taken.");
 | 
			
		||||
			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
 | 
			
		||||
	look(activePlayerEffects: string[], activeSceneEffects: string[]): string {
 | 
			
		||||
		// the properties of the scene after effects have been examined an applied
 | 
			
		||||
		const properties = this.applyEffects(
 | 
			
		||||
			activePlayerEffects,
 | 
			
		||||
			activeSceneEffects
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		// description of the items in the scene
 | 
			
		||||
		const itemsDescription = super.description(properties.items || []);
 | 
			
		||||
		const { description, exits = [] } = properties; // description of the room
 | 
			
		||||
 | 
			
		||||
		// will be a string that includes both the room and item descriptions
 | 
			
		||||
		let fullDescription = description || "Nothing to see here...";
 | 
			
		||||
 | 
			
		||||
		const exitsDescription = Scene.describeExits(exits);
 | 
			
		||||
 | 
			
		||||
		fullDescription +=
 | 
			
		||||
			exitsDescription && `\n\nThere is ${exitsDescription}.`;
 | 
			
		||||
 | 
			
		||||
		// if there is a description of the items...
 | 
			
		||||
		if (itemsDescription) {
 | 
			
		||||
			fullDescription += `\n\nThere is ${itemsDescription}`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return fullDescription;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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);
 | 
			
		||||
		if (exits.length === 0) {
 | 
			
		||||
			return "";
 | 
			
		||||
		} else if (exits.length === 1) {
 | 
			
		||||
			return exitDescriptions[0];
 | 
			
		||||
		} else {
 | 
			
		||||
			const lastExit = exitDescriptions[exitDescriptions.length - 1];
 | 
			
		||||
			const restExits = exitDescriptions.slice(
 | 
			
		||||
				0,
 | 
			
		||||
				exitDescriptions.length - 1
 | 
			
		||||
			);
 | 
			
		||||
			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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								User.ts
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								User.ts
									
									
									
									
									
								
							@ -1,15 +1,14 @@
 | 
			
		||||
/**
 | 
			
		||||
 * User.ts export the User class that provides methods for communicating with the player
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const DEFAULT_PROMPT = ">";
 | 
			
		||||
 | 
			
		||||
export default class User {
 | 
			
		||||
	#prompt: string;
 | 
			
		||||
	#out: (text: string) => Promise<void>;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.#prompt = DEFAULT_PROMPT;
 | 
			
		||||
		this.#out = async (text: string) => {
 | 
			
		||||
			const data = new TextEncoder().encode(text);
 | 
			
		||||
			await Deno.stdout.write(data);
 | 
			
		||||
		};
 | 
			
		||||
	constructor(prompt: string = DEFAULT_PROMPT) {
 | 
			
		||||
		this.#prompt = prompt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ask(question: string): string {
 | 
			
		||||
@ -23,6 +22,11 @@ export default class User {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async tell(statement: string): Promise<void> {
 | 
			
		||||
		await this.#out(`\n${statement}\n`);
 | 
			
		||||
		await this.out(`\n${statement}\n`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private async out(text: string): Promise<void> {
 | 
			
		||||
		const data = new TextEncoder().encode(text);
 | 
			
		||||
		await Deno.stdout.write(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								data/data.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								data/data.ts
									
									
									
									
									
								
							@ -1,10 +0,0 @@
 | 
			
		||||
export * from "./rooms.ts";
 | 
			
		||||
 | 
			
		||||
export interface Item {
 | 
			
		||||
	name: string;
 | 
			
		||||
	description: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GameData {
 | 
			
		||||
	items: Item[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								data/rooms.ts
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								data/rooms.ts
									
									
									
									
									
								
							@ -1,3 +1,127 @@
 | 
			
		||||
export const hall = {
 | 
			
		||||
	items: [{ name: "flashlight", description: "There is a flashlight." }],
 | 
			
		||||
/**
 | 
			
		||||
 * rooms.ts represents the game data that drives the story. It should contain all of the
 | 
			
		||||
 * elements that are needed to tell the story. Although it is currently written in
 | 
			
		||||
 * TypeScript, it should only contain objects which could just as easily be JSON. The
 | 
			
		||||
 * reason to make this file TypeScript for now is that it lets the TypeScript compiler
 | 
			
		||||
 * tell us if we have made errors. Later, we should add a file parser that can figure out
 | 
			
		||||
 * if there are errors in a JSON file and load it if there are not.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
				items: [
 | 
			
		||||
					// flashlight is an item that can be used. When it is, it applies an effect
 | 
			
		||||
					// to the room that it's being used in.
 | 
			
		||||
					{
 | 
			
		||||
						name: "flashlight",
 | 
			
		||||
						actions: {
 | 
			
		||||
							// Use is the name of the ActionTerm that will be used here. If a
 | 
			
		||||
							// player types "use flashlight" then this describes what should
 | 
			
		||||
							// happen.
 | 
			
		||||
							use: {
 | 
			
		||||
								// These are the pieces of information that the engine needs to
 | 
			
		||||
								// exectue a use action of type "applyEffect."
 | 
			
		||||
								args: {
 | 
			
		||||
									// This is the name of the effect. It is defined in this file
 | 
			
		||||
									// and should correspond to some condition on the player or
 | 
			
		||||
									// scene.
 | 
			
		||||
									effect: "flashlight-on",
 | 
			
		||||
 | 
			
		||||
									// can be "player" or "scene"
 | 
			
		||||
									applyTo: "player",
 | 
			
		||||
 | 
			
		||||
									// When it's used, this is what should be reported back to
 | 
			
		||||
									// the user.
 | 
			
		||||
									result: "The flashlight is on.",
 | 
			
		||||
 | 
			
		||||
									// If the effect is reversed—unapplied—this is what should be
 | 
			
		||||
									// reported back to the user.
 | 
			
		||||
									reverseResult: "The flashlight is off.",
 | 
			
		||||
 | 
			
		||||
									// This indicates that the effect can be unapplied after it
 | 
			
		||||
									// is applied. This should be done by calling use again after
 | 
			
		||||
									// it has already been called and the effect has been applied.
 | 
			
		||||
									reversible: true,
 | 
			
		||||
								},
 | 
			
		||||
								// applyEffect means that when this item is used, it will apply
 | 
			
		||||
								// an effect to either the player or the scene.
 | 
			
		||||
								type: "applyEffect",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				],
 | 
			
		||||
				// exit to the north leads to the Hall Closet
 | 
			
		||||
				exits: [
 | 
			
		||||
					{
 | 
			
		||||
						description: "a closet to the north",
 | 
			
		||||
						direction: "north",
 | 
			
		||||
						scene: "hall closet",
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						description: "a dining room to the south",
 | 
			
		||||
						direction: "south",
 | 
			
		||||
						scene: "dining room",
 | 
			
		||||
					},
 | 
			
		||||
				],
 | 
			
		||||
			},
 | 
			
		||||
			// conditions are a set of conditions that, when met, will make alterations to the
 | 
			
		||||
			// player or the scene.
 | 
			
		||||
			conditions: {
 | 
			
		||||
				// effects are conditions that revolve around effects that have been applied to
 | 
			
		||||
				// either the player or the scene.
 | 
			
		||||
				effects: [
 | 
			
		||||
					{
 | 
			
		||||
						// This is the name of the effect that, if present, will make alterations
 | 
			
		||||
						// to the scene.
 | 
			
		||||
						name: "flashlight-on",
 | 
			
		||||
 | 
			
		||||
						// properties describes the properties on the scene that will be altered
 | 
			
		||||
						// by the presents of this effect.
 | 
			
		||||
						properties: {
 | 
			
		||||
							// description means that, when this effect is applied, i.e. when
 | 
			
		||||
							// the flashlight is on, the description will change to the one
 | 
			
		||||
							// written here.
 | 
			
		||||
							description:
 | 
			
		||||
								"You are standing in a big hall. There's lots of nooks, " +
 | 
			
		||||
								"crannies, and room for general testing. Aw yeah... sweet testing!",
 | 
			
		||||
						},
 | 
			
		||||
 | 
			
		||||
						// source indicates where the effect should be applied. In this case, the
 | 
			
		||||
						// effect should be applied to the player.
 | 
			
		||||
						source: "player",
 | 
			
		||||
					},
 | 
			
		||||
				],
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			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: [],
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default game;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										76
									
								
								main.ts
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								main.ts
									
									
									
									
									
								
							@ -1,66 +1,64 @@
 | 
			
		||||
import User from "./User.ts";
 | 
			
		||||
import Scene from "./Scene.ts";
 | 
			
		||||
import Interpreter from "./Interpreter.ts";
 | 
			
		||||
import Player from "./Player.ts";
 | 
			
		||||
import { hall } from "./data/data.ts";
 | 
			
		||||
import parseCommand from "./parseCommand.ts";
 | 
			
		||||
import Scene from "./Scene.ts";
 | 
			
		||||
import User from "./User.ts";
 | 
			
		||||
import game from "./data/rooms.ts";
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
	const user = new User();
 | 
			
		||||
	const scene = new Scene(hall);
 | 
			
		||||
	const player = new Player();
 | 
			
		||||
	const question = "";
 | 
			
		||||
	let running = true;
 | 
			
		||||
	let statement = "";
 | 
			
		||||
	const user = new User(); // for communication with the user
 | 
			
		||||
	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.
 | 
			
		||||
 | 
			
		||||
	while (running) {
 | 
			
		||||
		const prompts = `${statement}${question}`;
 | 
			
		||||
		const prompts = `${statement}\n`;
 | 
			
		||||
		const answer = await user.ask(prompts);
 | 
			
		||||
 | 
			
		||||
		const { action, target } = parseCommand(answer);
 | 
			
		||||
 | 
			
		||||
		statement = "";
 | 
			
		||||
 | 
			
		||||
		switch (action) {
 | 
			
		||||
		// User commands that are not player moves, but are about running the
 | 
			
		||||
		// game itself (e.g. loading, saving, quitting) may need to get handled
 | 
			
		||||
		// here.
 | 
			
		||||
		switch (answer) {
 | 
			
		||||
			// kills the game loop.
 | 
			
		||||
			case "quit":
 | 
			
		||||
				running = quit(user);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case "look":
 | 
			
		||||
				await scene.look();
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case "take":
 | 
			
		||||
				await pickUpItem(scene, player, target);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case "inventory":
 | 
			
		||||
				await player.inventory();
 | 
			
		||||
				statement = "";
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				statement = "I didn't understand that.\n";
 | 
			
		||||
 | 
			
		||||
				// Game moves require more complicated commands, this switch is
 | 
			
		||||
				// not adequate to handle them. Here we will pass them off to
 | 
			
		||||
				// other functions to parse.
 | 
			
		||||
				statement = interpretUserCommand(player, scene, answer);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Takes player and scene state and a command. Tries to figure out what the
 | 
			
		||||
// user is trying to do and, if it can, it executes the command.
 | 
			
		||||
function interpretUserCommand(
 | 
			
		||||
	player: Player,
 | 
			
		||||
	scene: Scene,
 | 
			
		||||
	command: string
 | 
			
		||||
): string {
 | 
			
		||||
	const interpreter = new Interpreter(player, scene, command);
 | 
			
		||||
	interpreter.interpret();
 | 
			
		||||
	return interpreter.execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// When it returns false, the loop should be stopped. When it returns true,
 | 
			
		||||
// the loop should continue.
 | 
			
		||||
function quit(user: User): boolean {
 | 
			
		||||
	const confirmQuit = user.ask("Are you sure you want to quit?\n");
 | 
			
		||||
 | 
			
		||||
	if (confirmQuit[0] === "y") {
 | 
			
		||||
	// Anything that starts with a "y" or a "Y" is considered an affirmative
 | 
			
		||||
	// response.
 | 
			
		||||
	if (confirmQuit[0].toLocaleLowerCase() === "y") {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function pickUpItem(scene: Scene, player: Player, target: string) {
 | 
			
		||||
	const item = await scene.take(target);
 | 
			
		||||
 | 
			
		||||
	if (item !== null) {
 | 
			
		||||
		player.drop(item);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
export default function parseCommand(command: string) {
 | 
			
		||||
	const [action, target] = command.toLocaleLowerCase().split(" ");
 | 
			
		||||
 | 
			
		||||
	return { action, target };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								terms/Term.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								terms/Term.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Term.ts contains the type information for parsing user sentences.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Player from "../Player.ts";
 | 
			
		||||
import Scene from "../Scene.ts";
 | 
			
		||||
 | 
			
		||||
// ActionFn is the signature for functions that contain the logic associated with verbs
 | 
			
		||||
// the player uses.
 | 
			
		||||
export type ActionFn = (
 | 
			
		||||
	player: Player,
 | 
			
		||||
	scene: Scene,
 | 
			
		||||
	target?: string, // the target is the target of the action.
 | 
			
		||||
	object?: string // the object is an item that is used with the action on the target.
 | 
			
		||||
) => string; // should return a description of the result for the player.
 | 
			
		||||
 | 
			
		||||
// Constant is a specific word that the engine knows about. If the player uses a word
 | 
			
		||||
// that is not a Constant, then it should be a variable that represents something that
 | 
			
		||||
// is defined in the writer's story files.
 | 
			
		||||
export interface Constant {
 | 
			
		||||
	constant: string;
 | 
			
		||||
	category: Category;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Category is a union of the different kinds of Constants.
 | 
			
		||||
export type Category = "action" | "direction" | "position" | "interaction";
 | 
			
		||||
 | 
			
		||||
// Term describes a known word that the player can use. It tells the parse what kinds of
 | 
			
		||||
// words can come after it and what it means.
 | 
			
		||||
export interface Term {
 | 
			
		||||
	precedesCategories: Category[]; // categories of Constant that can follow this Term
 | 
			
		||||
	precedesConstants: Constant[]; // Constants that can follow this Term
 | 
			
		||||
	category: Category; // the category of this Terms Constant is part of
 | 
			
		||||
	constant: string; // the name of the Constant that this Term describes
 | 
			
		||||
	canPrecedeVariable: boolean; // indicates that what follows might be a variable
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActionTerm extends Term, adding the function needed to execute a verb. It also
 | 
			
		||||
// guarentees that the category is "action".
 | 
			
		||||
export interface ActionTerm extends Term {
 | 
			
		||||
	action: ActionFn;
 | 
			
		||||
	category: "action";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								terms/actions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								terms/actions.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
/**
 | 
			
		||||
 * actions.ts contains the logic for various ActionTerms. Exported should match the
 | 
			
		||||
 * signature for "ActionFn," which is:
 | 
			
		||||
 *
 | 
			
		||||
 * (player: Player, scene: Scene, target?: string, object?: string) => string
 | 
			
		||||
 *
 | 
			
		||||
 * The returned string should be a message for the user that describes the result of the
 | 
			
		||||
 * action.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import type Scene from "../Scene.ts";
 | 
			
		||||
import type Player from "../Player.ts";
 | 
			
		||||
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.
 | 
			
		||||
export function look(player: Player, scene: Scene): string {
 | 
			
		||||
	return scene.look(player.activeEffects, scene.activeEffects);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pickUpItem is what happens when a player uses the "take" verb. It finds an item in the
 | 
			
		||||
// scene and, if it's there, moves it into the players inventory.
 | 
			
		||||
export function pickUpItem(
 | 
			
		||||
	player: Player,
 | 
			
		||||
	scene: Scene,
 | 
			
		||||
	target?: string
 | 
			
		||||
): string {
 | 
			
		||||
	if (!target) {
 | 
			
		||||
		return "What do you want me to get?";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove the item from the scene.
 | 
			
		||||
	const item = scene.get(target);
 | 
			
		||||
 | 
			
		||||
	// The item was found in the scene...
 | 
			
		||||
	if (item !== null) {
 | 
			
		||||
		player.put(item); // put it in the players inventory.
 | 
			
		||||
 | 
			
		||||
		return "Taken.";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ITEM_MISSING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkInventory is what happens when a player uses the "inventory" verb. It gets the
 | 
			
		||||
// description for what's in the player's inventory.
 | 
			
		||||
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
 | 
			
		||||
// storywriter has provided for that item.
 | 
			
		||||
export function use(player: Player, scene: Scene, target?: string): string {
 | 
			
		||||
	if (!target) {
 | 
			
		||||
		return "What do you want to use?";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const { inventory } = player;
 | 
			
		||||
 | 
			
		||||
	// if there is no inventory...
 | 
			
		||||
	if (inventory === null) {
 | 
			
		||||
		return ITEM_MISSING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// search the inventory for the named item
 | 
			
		||||
	const item = inventory.find((i) => i.name === target);
 | 
			
		||||
 | 
			
		||||
	if (item === undefined) {
 | 
			
		||||
		return ITEM_MISSING;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const itemAction = item?.actions?.["use"];
 | 
			
		||||
 | 
			
		||||
	// The item doesn't have action defined for "use", so it cannot be used.
 | 
			
		||||
	if (itemAction === undefined) {
 | 
			
		||||
		return ITEM_UNUSABLE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return executeItemAction(player, scene, itemAction);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// executeItemAction figures out what kind of action an item supports and then executes
 | 
			
		||||
// the logic for it.
 | 
			
		||||
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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// applyEffect contains the logic for applying an effect to the player or the scene.
 | 
			
		||||
function applyEffect(
 | 
			
		||||
	player: Player,
 | 
			
		||||
	scene: Scene,
 | 
			
		||||
	{ applyTo, effect, reverseResult, reversible, result }: ApplyEffectArgs
 | 
			
		||||
): string {
 | 
			
		||||
	const target = applyTo === "player" ? player : scene;
 | 
			
		||||
 | 
			
		||||
	// if true, effect is already present.
 | 
			
		||||
	const isApplied = target.hasActiveEffect(effect);
 | 
			
		||||
 | 
			
		||||
	if (!reversible && isApplied) {
 | 
			
		||||
		// effect is present already and can't be reversed
 | 
			
		||||
		return ITEM_ALREADY_USED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (isApplied) {
 | 
			
		||||
		// effect is present...
 | 
			
		||||
		target.removeEffect(effect);
 | 
			
		||||
		return reverseResult || result;
 | 
			
		||||
	} else {
 | 
			
		||||
		// effect is not present...
 | 
			
		||||
		target.addEffect(effect);
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								terms/terms.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								terms/terms.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
/**
 | 
			
		||||
 * terms.ts contains the Terms that describe the Constants that the player can use.
 | 
			
		||||
 * See Terms.ts for more information about what these terms can be.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import * as actions from "./actions.ts";
 | 
			
		||||
import { ActionTerm } from "./Term.ts";
 | 
			
		||||
 | 
			
		||||
const inventory: ActionTerm = {
 | 
			
		||||
	action: actions.checkInventory,
 | 
			
		||||
	canPrecedeVariable: false,
 | 
			
		||||
	category: "action",
 | 
			
		||||
	constant: "inventory",
 | 
			
		||||
	precedesCategories: [],
 | 
			
		||||
	precedesConstants: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const look: ActionTerm = {
 | 
			
		||||
	action: actions.look,
 | 
			
		||||
	canPrecedeVariable: false,
 | 
			
		||||
	category: "action",
 | 
			
		||||
	constant: "look",
 | 
			
		||||
	precedesCategories: [],
 | 
			
		||||
	precedesConstants: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const move: ActionTerm = {
 | 
			
		||||
	action: actions.move,
 | 
			
		||||
	canPrecedeVariable: true,
 | 
			
		||||
	category: "action",
 | 
			
		||||
	constant: "go",
 | 
			
		||||
	precedesCategories: [],
 | 
			
		||||
	precedesConstants: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const take: ActionTerm = {
 | 
			
		||||
	action: actions.pickUpItem,
 | 
			
		||||
	canPrecedeVariable: true,
 | 
			
		||||
	category: "action",
 | 
			
		||||
	constant: "take",
 | 
			
		||||
	precedesCategories: [],
 | 
			
		||||
	precedesConstants: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const use: ActionTerm = {
 | 
			
		||||
	action: actions.use,
 | 
			
		||||
	canPrecedeVariable: true,
 | 
			
		||||
	category: "action",
 | 
			
		||||
	constant: "use",
 | 
			
		||||
	precedesCategories: [],
 | 
			
		||||
	precedesConstants: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const terms = {
 | 
			
		||||
	[inventory.constant]: inventory,
 | 
			
		||||
	[look.constant]: look,
 | 
			
		||||
	[move.constant]: move,
 | 
			
		||||
	[take.constant]: take,
 | 
			
		||||
	[use.constant]: use,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										90
									
								
								types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
// ActionArgs is a union of all the different argument types that can be used with an
 | 
			
		||||
// action
 | 
			
		||||
export type ActionArgs = ApplyEffectArgs;
 | 
			
		||||
 | 
			
		||||
// ApplyEffectArgs are the arguments required to apply effects to a player or scene
 | 
			
		||||
export interface ApplyEffectArgs extends Args {
 | 
			
		||||
	effect: string;
 | 
			
		||||
	applyTo: "player" | "scene";
 | 
			
		||||
	reversible: boolean;
 | 
			
		||||
	reverseResult?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ApplyEffectItemAction describes the apply effect action
 | 
			
		||||
export interface ApplyEffectItemAction extends ItemAction {
 | 
			
		||||
	type: "applyEffect";
 | 
			
		||||
	args: ApplyEffectArgs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Args is a base interface for the various argument interfaces
 | 
			
		||||
export interface Args {
 | 
			
		||||
	result: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Conditions contains story elements that can be conditionally applied to the scene or
 | 
			
		||||
// player
 | 
			
		||||
export interface Conditions<T extends EntityProperties> {
 | 
			
		||||
	effects: Effect<T>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Direction is union that contains the different directions a player can go
 | 
			
		||||
export type Direction =
 | 
			
		||||
	| "north"
 | 
			
		||||
	| "northeast"
 | 
			
		||||
	| "east"
 | 
			
		||||
	| "southest"
 | 
			
		||||
	| "south"
 | 
			
		||||
	| "southwest"
 | 
			
		||||
	| "west"
 | 
			
		||||
	| "northwest"
 | 
			
		||||
	| "up"
 | 
			
		||||
	| "down";
 | 
			
		||||
 | 
			
		||||
// Effect represents some effect that may be applied to a scene or player
 | 
			
		||||
export interface Effect<T extends EntityProperties> {
 | 
			
		||||
	name: string;
 | 
			
		||||
	properties: T;
 | 
			
		||||
	source: "player" | "scene"; // where the effect is applied
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exit describes the exits that the user can take to go to another scene
 | 
			
		||||
export interface Exit {
 | 
			
		||||
	description: string;
 | 
			
		||||
	direction: Direction;
 | 
			
		||||
	scene: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Item represents some item either in the scene or in the player's inventory
 | 
			
		||||
export interface Item {
 | 
			
		||||
	name: string;
 | 
			
		||||
	actions?: { [name: string]: ItemAction };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ItemActionType is a union of all the types of action that can be used
 | 
			
		||||
export type ItemActionType = "applyEffect";
 | 
			
		||||
 | 
			
		||||
// ItemAction represents an action that can be taken by an item
 | 
			
		||||
export interface ItemAction {
 | 
			
		||||
	type: ItemActionType;
 | 
			
		||||
	args: ActionArgs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SceneProperties are the properties (in addition to the EntityProperties) that are
 | 
			
		||||
// needed by the scene
 | 
			
		||||
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 EntityProperties> {
 | 
			
		||||
	identifier: string;
 | 
			
		||||
	conditions: Conditions<T>;
 | 
			
		||||
	properties: SceneProperties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EntityProperties is a base interface for the properties that a user or scene might have
 | 
			
		||||
export interface EntityProperties {
 | 
			
		||||
	items?: Item[];
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user