Add buying, object conditionals, move command
... and sleep is now fractional seconds.
+import { Token } from 'nearley';
+
+export enum Direction {
+ North,
+ East,
+ South,
+ West,
+}
+
+export function directionFromToken(t: Token): Direction {
+ const s = typeof t == 'string' ? t : t.value;
+ switch(s.slice(1)) {
+ case 'n':
+ case 'north':
+ return Direction.North;
+ break;
+ case 'e':
+ case 'east':
+ return Direction.East;
+ break;
+ case 's':
+ case 'south':
+ return Direction.South;
+ break;
+ case 'w':
+ case 'west':
+ return Direction.West;
+ break;
+ default:
+ throw new Error('Invalid direction: ' + s);
+ }
+}
+
+export function directionToDelta(d: Direction): [number, number] {
+ switch(d) {
+ case Direction.North:
+ return [0, -1];
+ break;
+ case Direction.East:
+ return [1, 0];
+ break;
+ case Direction.South:
+ return [0, 1];
+ break;
+ case Direction.West:
+ return [-1, 0];
+ break;
+ }
+}
condition,
};
case 'buy':
+ const obj = gs.objects.get(o[3][1].value);
+ if (!obj) {
+ throw new Error('Object with id ' + o[3][1].value + ' doesn\'t exist in this world.');
+ }
+
+ let label;
+ if (o[5].value == 0) {
+ label = 'Take ' + obj.name;
+ } else {
+ label = 'Buy ' + obj.name + ' ($' + o[5].value + ')';
+ }
+
return <MenuBuy>{
kind: MenuOptionType.Buy,
- label: o[3][1].value, // TODO: fetch object name from object database
+ label,
object: o[3][1].value,
value: o[5].value,
condition,
return new Promise( (resolve, reject) => {
gs.actions.clear();
for (let o of this.options) {
+ if (o.condition) {
+ switch(o.condition.type) {
+ case MenuConditionType.Flag:
+ // TODO flags
+ break;
+ case MenuConditionType.Object:
+ let status = gs.playerInventory.has(o.condition.name) && gs.playerInventory.get(o.condition.name) > 0;
+ if (o.condition.negate) {
+ status = !status;
+ }
+ console.log(o.condition, status);
+ if (!status) continue;
+ break;
+ }
+ }
gs.actions.addAction(o.label, () => {
gs.actions.clear();
switch(o.kind) {
break;
case MenuOptionType.Buy:
console.log('Buy', o.object, 'for', o.value);
+ gs.addInventory(o.object);
+ const objectName = gs.objects.get(o.object).name;
+ if (o.value == 0) {
+ gs.print("You have taken the", objectName);
+ } else {
+ gs.print("You have bought a", objectName, "for", '$' + o.value);
+ }
resolve({ status: InstructionStatus.OK });
break;
case MenuOptionType.Exit:
+import { ScriptInstruction, InstructionStatus, InstructionPromise } from './';
+
+import gs from '../gamestate';
+import { Direction, directionToDelta } from '../direction';
+
+export class MoveInstruction implements ScriptInstruction {
+ direction?: Direction;
+ newPosition?: [number, number];
+
+ constructor(d: Direction | number, e?: number) {
+ if (typeof e == 'undefined') {
+ this.direction = d;
+ } else {
+ this.newPosition = [d, e];
+ }
+ }
+
+ execute(): InstructionPromise {
+ if (typeof this.direction != 'undefined') {
+ const [dx, dy] = directionToDelta(this.direction);
+ gs.movePlayerRel(dx, dy);
+ } else {
+ gs.movePlayer(this.newPosition[0], this.newPosition[1]);
+ }
+
+ return Promise.resolve({ status: InstructionStatus.OK });
+ }
+}
import { ClearScreenInstruction } from './clear';
import { SleepInstruction } from './sleep';
import { PauseInstruction } from './pause';
+import { MoveInstruction } from './move';
+
+import { Direction, directionFromToken } from '../direction';
const lexer = moo.compile({
- ws: { match: /[ \t\r\n]+/, lineBreaks: true },
- number: /[0-9]+(?:\.[0-9]+)?/,
- keyword: ['say', 'menu', 'item', 'end', 'exit', 'goto', 'if',
- 'finish', 'clear', 'buy', 'transact', 'setflag'],
+ ws: { match: /[ \t\r\n]+/, lineBreaks: true },
+ number: /[0-9]+(?:\.[0-9]+)?/,
+ keyword: ['say', 'menu', 'item', 'end', 'exit', 'goto', 'if', 'finish', 'clear', 'buy', 'transact', 'setflag', 'move'],
+ direction: /\+(?:n|e|s|w|north|east|south|west)/,
label: /[a-zA-Z_][a-zA-Z0-9_]*:/,
word: /[a-zA-Z_][a-zA-Z0-9_]*/,
dqstring: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: s => s.slice(1, -1) },
bang: '!',
flag_mark: '$',
obj_mark: '@',
+ comma: ',',
});
%}
statement -> "goto" __ %word __ {% data => new GotoInstruction(data[2].value) %}
statement -> "finish" __ {% data => new FinishInstruction() %}
statement -> "clear" __ {% data => new ClearScreenInstruction() %}
-statement -> "sleep" __ %number __ {% data => new SleepInstruction(data[2]) %}
+statement -> "sleep" __ number __ {% data => new SleepInstruction(data[2]) %}
statement -> "pause" __ string __ {% data => new PauseInstruction(data[2]) %}
+statement -> "move" __ direction __ {% data => new MoveInstruction(data[2]) %}
+statement -> "move" __ number _ %comma _ number __ {% data => new MoveInstruction(data[2], data[6]) %}
menuitem -> if:? "item" __ string __ program "end" __
-menuitem -> if:? "buy" __ obj_identifier __ %number __
+menuitem -> if:? "buy" __ obj_identifier __ number __
menuitem -> if:? "exit" __ string __
if -> "if" __ %bang:? flag_identifier __
flag_identifier -> %flag_mark %word
obj_identifier -> %obj_mark %word
-number -> %number {% data => parseInt(data[0]) %}
+number -> %number {% data => parseFloat(data[0]) %}
string -> %dqstring {% data => data[0].value %}
string -> %sqstring {% data => data[0].value %}
string -> %mlstring {% data => data[0].value %}
+direction -> %direction {% data => directionFromToken(data[0]) %}
+
__ -> %ws {% _ => null %}
_ -> null | %ws {% _ => null %}
import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
export class SleepInstruction implements ScriptInstruction {
- duration: number
+ milliseconds: number
- constructor(duration: number) {
- this.duration = duration;
+ constructor(seconds: number) {
+ this.milliseconds = seconds * 1000;
}
execute(): InstructionPromise {
return new Promise( (resolve, reject) => {
- setTimeout(resolve, this.duration, { status: InstructionStatus.OK });
+ setTimeout(resolve, this.milliseconds, { status: InstructionStatus.OK });
});
}
}