137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|