add zork game
Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
parent
180ad998f3
commit
ee336e05f5
239
lib/co/index.js
Normal file
239
lib/co/index.js
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* slice() reference.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var slice = Array.prototype.slice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `co`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = co['default'] = co.co = co;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the given generator `fn` into a
|
||||||
|
* function that returns a promise.
|
||||||
|
* This is a separate function so that
|
||||||
|
* every `co()` call doesn't create a new,
|
||||||
|
* unnecessary closure.
|
||||||
|
*
|
||||||
|
* @param {GeneratorFunction} fn
|
||||||
|
* @return {Function}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
co.wrap = function (fn) {
|
||||||
|
createPromise.__generatorFunction__ = fn;
|
||||||
|
return createPromise;
|
||||||
|
function createPromise() {
|
||||||
|
return co.call(this, fn.apply(this, arguments));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the generator function or a generator
|
||||||
|
* and return a promise.
|
||||||
|
*
|
||||||
|
* @param {Function} fn
|
||||||
|
* @return {Promise}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function co(gen) {
|
||||||
|
var ctx = this;
|
||||||
|
var args = slice.call(arguments, 1);
|
||||||
|
|
||||||
|
// we wrap everything in a promise to avoid promise chaining,
|
||||||
|
// which leads to memory leak errors.
|
||||||
|
// see https://github.com/tj/co/issues/180
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
if (typeof gen === 'function') gen = gen.apply(ctx, args);
|
||||||
|
if (!gen || typeof gen.next !== 'function') return resolve(gen);
|
||||||
|
|
||||||
|
onFulfilled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Mixed} res
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onFulfilled(res) {
|
||||||
|
var ret;
|
||||||
|
try {
|
||||||
|
ret = gen.next(res);
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e);
|
||||||
|
}
|
||||||
|
next(ret);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Error} err
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onRejected(err) {
|
||||||
|
var ret;
|
||||||
|
try {
|
||||||
|
ret = gen.throw(err);
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e);
|
||||||
|
}
|
||||||
|
next(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next value in the generator,
|
||||||
|
* return a promise.
|
||||||
|
*
|
||||||
|
* @param {Object} ret
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function next(ret) {
|
||||||
|
if (ret.done) return resolve(ret.value);
|
||||||
|
var value = toPromise.call(ctx, ret.value);
|
||||||
|
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
|
||||||
|
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
|
||||||
|
+ 'but the following object was passed: "' + String(ret.value) + '"'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a `yield`ed value into a promise.
|
||||||
|
*
|
||||||
|
* @param {Mixed} obj
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function toPromise(obj) {
|
||||||
|
if (!obj) return obj;
|
||||||
|
if (isPromise(obj)) return obj;
|
||||||
|
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
|
||||||
|
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
|
||||||
|
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
|
||||||
|
if (isObject(obj)) return objectToPromise.call(this, obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a thunk to a promise.
|
||||||
|
*
|
||||||
|
* @param {Function}
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function thunkToPromise(fn) {
|
||||||
|
var ctx = this;
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
fn.call(ctx, function (err, res) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
if (arguments.length > 2) res = slice.call(arguments, 1);
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array of "yieldables" to a promise.
|
||||||
|
* Uses `Promise.all()` internally.
|
||||||
|
*
|
||||||
|
* @param {Array} obj
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function arrayToPromise(obj) {
|
||||||
|
return Promise.all(obj.map(toPromise, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object of "yieldables" to a promise.
|
||||||
|
* Uses `Promise.all()` internally.
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @return {Promise}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function objectToPromise(obj){
|
||||||
|
var results = new obj.constructor();
|
||||||
|
var keys = Object.keys(obj);
|
||||||
|
var promises = [];
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var key = keys[i];
|
||||||
|
var promise = toPromise.call(this, obj[key]);
|
||||||
|
if (promise && isPromise(promise)) defer(promise, key);
|
||||||
|
else results[key] = obj[key];
|
||||||
|
}
|
||||||
|
return Promise.all(promises).then(function () {
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
|
||||||
|
function defer(promise, key) {
|
||||||
|
// predefine the key in the result
|
||||||
|
results[key] = undefined;
|
||||||
|
promises.push(promise.then(function (res) {
|
||||||
|
results[key] = res;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if `obj` is a promise.
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @return {Boolean}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isPromise(obj) {
|
||||||
|
return 'function' == typeof obj.then;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if `obj` is a generator.
|
||||||
|
*
|
||||||
|
* @param {Mixed} obj
|
||||||
|
* @return {Boolean}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isGenerator(obj) {
|
||||||
|
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if `obj` is a generator function.
|
||||||
|
*
|
||||||
|
* @param {Mixed} obj
|
||||||
|
* @return {Boolean}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isGeneratorFunction(obj) {
|
||||||
|
var constructor = obj.constructor;
|
||||||
|
if (!constructor) return false;
|
||||||
|
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
|
||||||
|
return isGenerator(constructor.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for plain object.
|
||||||
|
*
|
||||||
|
* @param {Mixed} val
|
||||||
|
* @return {Boolean}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isObject(val) {
|
||||||
|
return Object == val.constructor;
|
||||||
|
}
|
98
lib/zork/index.d.ts
vendored
Normal file
98
lib/zork/index.d.ts
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
declare class JSZM {
|
||||||
|
constructor(arr: ArrayLike<number>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function you define, which will be called to update the highlighting mode.
|
||||||
|
* fixpitch (if the argument is true) or normal (if argument is false).
|
||||||
|
*/
|
||||||
|
highlight(fixpitch: boolean): void;
|
||||||
|
|
||||||
|
/** Normally false. Set it to true to tell the game that it is a Tandy computer; this affects some games. */
|
||||||
|
isTandy: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function that you must define that will be called to print text. You must implement
|
||||||
|
* wrapping, buffering and scripting yourself. The second argument is true if it should be
|
||||||
|
* copied to the transcript or false if it should not be.
|
||||||
|
*/
|
||||||
|
print(text: string, scripting: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function which you must define which should return a string containing the player's input.
|
||||||
|
* Called when a READ instruction is executed. The argument is the maximum number of characters that are
|
||||||
|
* allowed (if you return a longer string, it will be truncated)
|
||||||
|
*/
|
||||||
|
read(maxlen: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function you can optionally define. When the game starts or if restarted (with the RESTART
|
||||||
|
* instruction), it will be called after memory is initialized but before executing any more.
|
||||||
|
*/
|
||||||
|
restarted(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function you define, which is called when restoring a saved game. Return a Uint8Array with
|
||||||
|
* the same contents passed to save() if successful, or you can return false or null or undefined if it failed.
|
||||||
|
*/
|
||||||
|
restore(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function. Call it to run the program from the beginning, and call the next() method of the
|
||||||
|
* returned object to begin and to continue. This generator may call your own generator functions which
|
||||||
|
* may yield; it doesn't otherwise yield by itself. You must set up the other methods before calling run
|
||||||
|
* so that it can properly set up the contents of the Z-machine mode byte.
|
||||||
|
*
|
||||||
|
* This generator only finishes when a QUIT instruction is executed.
|
||||||
|
*/
|
||||||
|
run(): IterableIterator<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator function you can define yourself, and is called when saving the game. The argument is a Uint8Array,
|
||||||
|
* and you should attempt to save its contents somewhere, and then return true if successful or false if it failed.
|
||||||
|
*/
|
||||||
|
save(buf: Uint8Array): void;
|
||||||
|
|
||||||
|
/** The serial number of the story file, as six ASCII characters. */
|
||||||
|
serial: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally null. You can set it to a generator function which will be called when the SCREEN opcode is executed if
|
||||||
|
* you want to implement split screen.
|
||||||
|
*/
|
||||||
|
screen(window: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally null. You can set it to a generator function which will be called when the SPLIT opcode is executed if
|
||||||
|
* you want to implement split screen.
|
||||||
|
*/
|
||||||
|
split(height: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* False for score/moves and true for hours/minutes. Use this to determine the meaning of arguments to updateStatusLine.
|
||||||
|
*/
|
||||||
|
statusType: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally null, but can be a generator function if you are implementing the status line. It is called when a READ or
|
||||||
|
* USL instruction is executed. See statusType for the meaning of v18 and v17. Return value is unused.
|
||||||
|
*/
|
||||||
|
updateStatusLine(text: string, v18: number, v17: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A normal function. Calling it will attempt to verify the story file, and returns true if successful or false on error.
|
||||||
|
* You can override it with your own verification function if you want to.
|
||||||
|
*/
|
||||||
|
verify(): void;
|
||||||
|
|
||||||
|
/** The ZORKID of the story file. This is what is normally displayed as the release number. */
|
||||||
|
zorkid: number;
|
||||||
|
|
||||||
|
static version: {
|
||||||
|
major: number;
|
||||||
|
minor: number;
|
||||||
|
subminor: number;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export = JSZM;
|
1
lib/zork/index.js
Normal file
1
lib/zork/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./jszm')
|
682
lib/zork/jszm.js
Normal file
682
lib/zork/jszm.js
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
/*
|
||||||
|
JSZM - JavaScript implementation of Z-machine
|
||||||
|
This program is in public domain.
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
The exported function called JSZM is the constructor, which takes a
|
||||||
|
Uint8Array as input. You can also use JSZM.Version for the version
|
||||||
|
number which is object with properties: major, minor, subminor,
|
||||||
|
timestamp. Properties of JSZM instances are:
|
||||||
|
|
||||||
|
.highlight(fixpitch) = A generator function you define, which will be
|
||||||
|
called to update the highlighting mode, which is fixpitch (if the
|
||||||
|
argument is true) or normal (if argument is false). (You don't have to
|
||||||
|
set it if you aren't implementing variable pitch by default.)
|
||||||
|
|
||||||
|
.isTandy = A boolean, normally false. Set it to true to tell the game
|
||||||
|
that it is a Tandy computer; this affects some games.
|
||||||
|
|
||||||
|
.print(text,scripting) = A generator function that you must define, and
|
||||||
|
will be called to print text. You must implement wrapping and buffering
|
||||||
|
and scripting yourself. The second argument is true if it should be
|
||||||
|
copied to the transcript or false if it should not be.
|
||||||
|
|
||||||
|
.read(maxlen) = A generator function which you must define yourself, and
|
||||||
|
which should return a string containing the player's input. Called when
|
||||||
|
a READ instruction is executed; the argument is the maximum number of
|
||||||
|
characters that are allowed (if you return a longer string, it will be
|
||||||
|
truncated).
|
||||||
|
|
||||||
|
.restarted() = A generator function you can optionally define. When the
|
||||||
|
game starts or if restarted (with the RESTART instruction), it will be
|
||||||
|
called after memory is initialized but before executing any more.
|
||||||
|
|
||||||
|
.restore() = A generator function you can define yourself, which is
|
||||||
|
called when restoring a saved game. Return a Uint8Array with the same
|
||||||
|
contents passed to save() if successful, or you can return false or null
|
||||||
|
or undefined if it failed.
|
||||||
|
|
||||||
|
.run() = A generator function. Call it to run the program from the
|
||||||
|
beginning, and call the next() method of the returned object to begin
|
||||||
|
and to continue. This generator may call your own generator functions
|
||||||
|
which may yield; it doesn't otherwise yield by itself. You must set up
|
||||||
|
the other methods before calling run so that it can properly set up the
|
||||||
|
contents of the Z-machine mode byte. This generator only finishes when a
|
||||||
|
QUIT instruction is executed.
|
||||||
|
|
||||||
|
.save(buf) = A generator function you can define yourself, and is called
|
||||||
|
when saving the game. The argument is a Uint8Array, and you should
|
||||||
|
attempt to save its contents somewhere, and then return true if
|
||||||
|
successful or false if it failed.
|
||||||
|
|
||||||
|
.serial = The serial number of the story file, as six ASCII characters.
|
||||||
|
|
||||||
|
.screen(window) = Normally null. You can set it to a generator function
|
||||||
|
which will be called when the SCREEN opcode is executed if you want to
|
||||||
|
implement split screen.
|
||||||
|
|
||||||
|
.split(height) = Normally null. You can set it to a generator function
|
||||||
|
which will be called when the SPLIT opcode is executed if you want to
|
||||||
|
implement split screen.
|
||||||
|
|
||||||
|
.statusType = False for score/moves and true for hours/minutes. Use this
|
||||||
|
to determine the meaning of arguments to updateStatusLine.
|
||||||
|
|
||||||
|
.updateStatusLine(text,v18,v17) = Normally null, but can be a generator
|
||||||
|
function if you are implementing the status line. It is called when a
|
||||||
|
READ or USL instruction is executed. See statusType for the meaning of
|
||||||
|
v18 and v17. Return value is unused.
|
||||||
|
|
||||||
|
.verify() = A normal function. Calling it will attempt to verify the
|
||||||
|
story file, and returns true if successful or false on error. You can
|
||||||
|
override it with your own verification function if you want to.
|
||||||
|
|
||||||
|
.zorkid = The ZORKID of the story file. This is what is normally
|
||||||
|
displayed as the release number.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const JSZM_Version={major:2,minor:0,subminor:2,timestamp:1480624305074};
|
||||||
|
|
||||||
|
function JSZM(arr) {
|
||||||
|
var mem;
|
||||||
|
mem=this.memInit=new Uint8Array(arr);
|
||||||
|
if(mem[0]!=3) throw new Error("Unsupported Z-code version.");
|
||||||
|
this.byteSwapped=!!(mem[1]&1);
|
||||||
|
this.statusType=!!(mem[1]&2);
|
||||||
|
this.serial=String.fromCharCode(...mem.slice(18,24));
|
||||||
|
this.zorkid=(mem[2]<<(this.byteSwapped?0:8))|(mem[3]<<(this.byteSwapped?8:0));
|
||||||
|
}
|
||||||
|
|
||||||
|
JSZM.prototype={
|
||||||
|
byteSwapped: false,
|
||||||
|
constructor: JSZM,
|
||||||
|
deserialize: function(ar) {
|
||||||
|
var e,i,j,ds,cs,pc,vi,purbot;
|
||||||
|
var g8,g16s,g16,g24,g32;
|
||||||
|
g8=()=>ar[e++];
|
||||||
|
g16s=()=>(e+=2,vi.getInt16(e-2));
|
||||||
|
g16=()=>(e+=2,vi.getUint16(e-2));
|
||||||
|
g24=()=>(e+=3,vi.getUint32(e-4)&0xFFFFFF);
|
||||||
|
g32=()=>(e+=4,vi.getUint32(e-4));
|
||||||
|
try {
|
||||||
|
e=purbot=this.getu(14);
|
||||||
|
vi=new DataView(ar.buffer);
|
||||||
|
if(ar[2]!=this.mem[2] || ar[3]!=this.mem[3]) return null; // ZORKID does not match
|
||||||
|
pc=g32();
|
||||||
|
cs=new Array(g16());
|
||||||
|
ds=Array.from({length:g16()},g16s);
|
||||||
|
for(i=0;i<cs.length;i++) {
|
||||||
|
cs[i]={};
|
||||||
|
cs[i].local=new Int16Array(g8());
|
||||||
|
cs[i].pc=g24();
|
||||||
|
cs[i].ds=Array.from({length:g16()},g16s);
|
||||||
|
for(j=0;j<cs[i].local.length;j++) cs[i].local[j]=g16s();
|
||||||
|
}
|
||||||
|
this.mem.set(new Uint8Array(ar.buffer,0,purbot));
|
||||||
|
return [ds,cs,pc];
|
||||||
|
} catch(e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endText: 0,
|
||||||
|
fwords: null,
|
||||||
|
genPrint: function*(text) {
|
||||||
|
var x=this.get(16);
|
||||||
|
if(x!=this.savedFlags) {
|
||||||
|
this.savedFlags=x;
|
||||||
|
yield*this.highlight(!!(x&2));
|
||||||
|
}
|
||||||
|
yield*this.print(text,!!(x&1));
|
||||||
|
},
|
||||||
|
get: function(x) { return this.view.getInt16(x,this.byteSwapped); },
|
||||||
|
getText: function(addr) {
|
||||||
|
var d; // function to parse each Z-character
|
||||||
|
var o=""; // output
|
||||||
|
var ps=0; // permanent shift
|
||||||
|
var ts=0; // temporary shift
|
||||||
|
var w; // read each 16-bits data
|
||||||
|
var y; // auxiliary data for parsing state
|
||||||
|
d=v => {
|
||||||
|
if(ts==3) {
|
||||||
|
y=v<<5;
|
||||||
|
ts=4;
|
||||||
|
} else if(ts==4) {
|
||||||
|
y+=v;
|
||||||
|
if(y==13) o+="\n";
|
||||||
|
else if(y) o+=String.fromCharCode(y);
|
||||||
|
ts=ps;
|
||||||
|
} else if(ts==5) {
|
||||||
|
o+=this.getText(this.getu(this.fwords+(y+v)*2)*2);
|
||||||
|
ts=ps;
|
||||||
|
} else if(v==0) {
|
||||||
|
o+=" ";
|
||||||
|
} else if(v<4) {
|
||||||
|
ts=5;
|
||||||
|
y=(v-1)*32;
|
||||||
|
} else if(v<6) {
|
||||||
|
if(!ts) ts=v-3;
|
||||||
|
else if(ts==v-3) ps=ts;
|
||||||
|
else ps=ts=0;
|
||||||
|
} else if(v==6 && ts==2) {
|
||||||
|
ts=3;
|
||||||
|
} else {
|
||||||
|
o+="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*\n0123456789.,!?_#'\"/\\-:()"[ts*26+v-6];
|
||||||
|
ts=ps;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for(;;) {
|
||||||
|
w=this.getu(addr);
|
||||||
|
addr+=2;
|
||||||
|
d((w>>10)&31);
|
||||||
|
d((w>>5)&31);
|
||||||
|
d(w&31);
|
||||||
|
if(w&32768) break;
|
||||||
|
}
|
||||||
|
this.endText=addr;
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
getu: function(x) { return this.view.getUint16(x,this.byteSwapped); },
|
||||||
|
handleInput: function(str,t1,t2) {
|
||||||
|
var i,br,w;
|
||||||
|
// Put text
|
||||||
|
str=str.toLowerCase().slice(0,this.mem[t1]-1);
|
||||||
|
for(i=0;i<str.length;i++) this.mem[t1+i+1]=str.charCodeAt(i);
|
||||||
|
this.mem[t1+str.length+1]=0;
|
||||||
|
// Lex text
|
||||||
|
w=x=>(i=0,x.split("").filter(y => (i+=/[a-z]/.test(y)?1:/[0-9.,!?_#'"\/\\:\-()]/.test(y)?2:4)<7).join(""));
|
||||||
|
br=JSON.parse("["+str.replace(this.regBreak,(m,o)=>",["+(m.length)+","+(this.vocabulary.get(w(m))||0)+","+(o+1)+"]").slice(1)+"]");
|
||||||
|
i=this.mem[t2+1]=br.length;
|
||||||
|
while(i--) {
|
||||||
|
this.putu(t2+i*4+2,br[i][1]);
|
||||||
|
this.mem[t2+i*4+4]=br[i][0];
|
||||||
|
this.mem[t2+i*4+5]=br[i][2];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highlight: ()=>[],
|
||||||
|
isTandy: false,
|
||||||
|
mem: null,
|
||||||
|
memInit: null,
|
||||||
|
parseVocab: function(s) {
|
||||||
|
this.vocabulary=new Map();
|
||||||
|
|
||||||
|
if (s === 0) { // If the story file does not contain a dictionary..
|
||||||
|
this.regBreak=new RegExp("[^ \\n\\t]+","g"); // use the default word separators
|
||||||
|
return; // and early exit.
|
||||||
|
}
|
||||||
|
|
||||||
|
var e;
|
||||||
|
var n;
|
||||||
|
n=this.mem[s++];
|
||||||
|
e=this.selfInsertingBreaks=String.fromCharCode(...this.mem.slice(s,s+n));
|
||||||
|
e=e.split("").map(x=>(x.toUpperCase()==x.toLowerCase()?"":"\\")+x).join("")+"]";
|
||||||
|
this.regBreak=new RegExp("["+e+"|[^ \\n\\t"+e+"+","g");
|
||||||
|
s+=n;
|
||||||
|
e=this.mem[s++];
|
||||||
|
n=this.get(s);
|
||||||
|
s+=2;
|
||||||
|
while(n--) {
|
||||||
|
this.vocabulary.set(this.getText(s),s);
|
||||||
|
s+=e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
print: ()=>[],
|
||||||
|
put: function(x,y) { return this.view.setInt16(x,y,this.byteSwapped); },
|
||||||
|
putu: function(x,y) { return this.view.setUint16(x,y&65535,this.byteSwapped); },
|
||||||
|
read: ()=>[],
|
||||||
|
regBreak: null,
|
||||||
|
restarted: ()=>[],
|
||||||
|
restore: ()=>[],
|
||||||
|
run: function*() {
|
||||||
|
var mem,pc,cs,ds,op0,op1,op2,op3,opc,inst,x,y,z;
|
||||||
|
var globals,objects,fwords,defprop;
|
||||||
|
var addr,fetch,flagset,init,move,opfetch,pcfetch,pcget,pcgetb,pcgetu,predicate,propfind,ret,store,xfetch,xstore;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
addr=(x) => (x&65535)<<1;
|
||||||
|
fetch=(x) => {
|
||||||
|
if(x==0) return ds.pop();
|
||||||
|
if(x<16) return cs[0].local[x-1];
|
||||||
|
return this.get(globals+2*x);
|
||||||
|
};
|
||||||
|
flagset=() => {
|
||||||
|
op3=1<<(15&~op1);
|
||||||
|
op2=objects+op0*9+(op1&16?2:0);
|
||||||
|
opc=this.get(op2);
|
||||||
|
};
|
||||||
|
const initRng = () => {
|
||||||
|
this.seed = (Math.random() * 0xFFFFFFFF) >>> 0;
|
||||||
|
};
|
||||||
|
init=() => {
|
||||||
|
mem=this.mem=new Uint8Array(this.memInit);
|
||||||
|
this.view=new DataView(mem.buffer);
|
||||||
|
mem[1]&=3;
|
||||||
|
if(this.isTandy) mem[1]|=8;
|
||||||
|
if(!this.updateStatusLine) mem[1]|=16;
|
||||||
|
if(this.screen && this.split) mem[1]|=32;
|
||||||
|
this.put(16,this.savedFlags);
|
||||||
|
if(!this.vocabulary) this.parseVocab(this.getu(8));
|
||||||
|
defprop=this.getu(10)-2;
|
||||||
|
globals=this.getu(12)-32;
|
||||||
|
this.fwords=fwords=this.getu(24);
|
||||||
|
cs=[];
|
||||||
|
ds=[];
|
||||||
|
pc=this.getu(6);
|
||||||
|
objects=defprop+55;
|
||||||
|
initRng();
|
||||||
|
};
|
||||||
|
move=(x,y) => {
|
||||||
|
var w,z;
|
||||||
|
// Remove from old FIRST-NEXT chain
|
||||||
|
if(z=mem[objects+x*9+4]) {
|
||||||
|
if(mem[objects+z*9+6]==x) { // is x.loc.first=x?
|
||||||
|
mem[objects+z*9+6]=mem[objects+x*9+5]; // x.loc.first=x.next
|
||||||
|
} else {
|
||||||
|
z=mem[objects+z*9+6]; // z=x.loc.first
|
||||||
|
while(z!=x) {
|
||||||
|
w=z;
|
||||||
|
z=mem[objects+z*9+5]; // z=z.next
|
||||||
|
}
|
||||||
|
mem[objects+w*9+5]=mem[objects+x*9+5]; // w.next=x.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Insert at beginning of new FIRST-NEXT chain
|
||||||
|
if(mem[objects+x*9+4]=y) { // x.loc=y
|
||||||
|
mem[objects+x*9+5]=mem[objects+y*9+6]; // x.next=y.first
|
||||||
|
mem[objects+y*9+6]=x; // y.first=x
|
||||||
|
} else {
|
||||||
|
mem[objects+x*9+5]=0; // x.next=0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
opfetch=(x,y) => {
|
||||||
|
if((x&=3)==3) return;
|
||||||
|
opc=y;
|
||||||
|
return [pcget,pcgetb,pcfetch][x]();
|
||||||
|
};
|
||||||
|
pcfetch=(x) => fetch(mem[pc++]);
|
||||||
|
pcget=() => {
|
||||||
|
pc+=2;
|
||||||
|
return this.get(pc-2);
|
||||||
|
};
|
||||||
|
pcgetb=() => mem[pc++];
|
||||||
|
pcgetu=() => {
|
||||||
|
pc+=2;
|
||||||
|
return this.getu(pc-2);
|
||||||
|
};
|
||||||
|
predicate=(p) => {
|
||||||
|
var x=pcgetb();
|
||||||
|
if(x&128) p=!p;
|
||||||
|
if(x&64) x&=63; else x=((x&63)<<8)|pcgetb();
|
||||||
|
if(p) return;
|
||||||
|
if(x==0 || x==1) return ret(x);
|
||||||
|
if(x&0x2000) x-=0x4000;
|
||||||
|
pc+=x-2;
|
||||||
|
};
|
||||||
|
propfind=() => {
|
||||||
|
var z=this.getu(objects+op0*9+7);
|
||||||
|
z+=mem[z]*2+1;
|
||||||
|
while(mem[z]) {
|
||||||
|
if((mem[z]&31)==op1) {
|
||||||
|
op3=z+1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
z+=(mem[z]>>5)+2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op3=0;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
ret=(x) => {
|
||||||
|
ds=cs[0].ds;
|
||||||
|
pc=cs[0].pc;
|
||||||
|
cs.shift();
|
||||||
|
store(x);
|
||||||
|
};
|
||||||
|
store=(y) => {
|
||||||
|
var x=pcgetb();
|
||||||
|
if(x==0) ds.push(y);
|
||||||
|
else if(x<16) cs[0].local[x-1]=y;
|
||||||
|
else this.put(globals+2*x,y);
|
||||||
|
};
|
||||||
|
xfetch=(x) => {
|
||||||
|
if(x==0) return ds[ds.length-1];
|
||||||
|
if(x<16) return cs[0].local[x-1];
|
||||||
|
return this.get(globals+2*x);
|
||||||
|
};
|
||||||
|
xstore=(x,y) => {
|
||||||
|
if(x==0) ds[ds.length-1]=y;
|
||||||
|
else if(x<16) cs[0].local[x-1]=y;
|
||||||
|
else this.put(globals+2*x,y);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initializations
|
||||||
|
init();
|
||||||
|
yield*this.restarted();
|
||||||
|
yield*this.highlight(!!(this.savedFlags&2));
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
main: for(;;) {
|
||||||
|
inst=pcgetb();
|
||||||
|
if(inst<128) {
|
||||||
|
// 2OP
|
||||||
|
if(inst&64) op0=pcfetch(); else op0=pcgetb();
|
||||||
|
if(inst&32) op1=pcfetch(); else op1=pcgetb();
|
||||||
|
inst&=31;
|
||||||
|
opc=2;
|
||||||
|
} else if(inst<176) {
|
||||||
|
// 1OP
|
||||||
|
x=(inst>>4)&3;
|
||||||
|
inst&=143;
|
||||||
|
if(x==0) op0=pcget();
|
||||||
|
else if(x==1) op0=pcgetb();
|
||||||
|
else if(x==2) op0=pcfetch();
|
||||||
|
} else if(inst>=192) {
|
||||||
|
// EXT
|
||||||
|
x=pcgetb();
|
||||||
|
op0=opfetch(x>>6,1);
|
||||||
|
op1=opfetch(x>>4,2);
|
||||||
|
op2=opfetch(x>>2,3);
|
||||||
|
op3=opfetch(x>>0,4);
|
||||||
|
if(inst<224) inst&=31;
|
||||||
|
}
|
||||||
|
switch(inst) {
|
||||||
|
case 1: // EQUAL?
|
||||||
|
predicate(op0==op1 || (opc>2 && op0==op2) || (opc==4 && op0==op3));
|
||||||
|
break;
|
||||||
|
case 2: // LESS?
|
||||||
|
predicate(op0<op1);
|
||||||
|
break;
|
||||||
|
case 3: // GRTR?
|
||||||
|
predicate(op0>op1);
|
||||||
|
break;
|
||||||
|
case 4: // DLESS?
|
||||||
|
xstore(op0,x=xfetch(op0)-1);
|
||||||
|
predicate(x<op1);
|
||||||
|
break;
|
||||||
|
case 5: // IGRTR?
|
||||||
|
xstore(op0,x=xfetch(op0)+1);
|
||||||
|
predicate(x>op1);
|
||||||
|
break;
|
||||||
|
case 6: // IN?
|
||||||
|
predicate(mem[objects+op0*9+4]==op1);
|
||||||
|
break;
|
||||||
|
case 7: // BTST
|
||||||
|
predicate((op0&op1)==op1);
|
||||||
|
break;
|
||||||
|
case 8: // BOR
|
||||||
|
store(op0|op1);
|
||||||
|
break;
|
||||||
|
case 9: // BAND
|
||||||
|
store(op0&op1);
|
||||||
|
break;
|
||||||
|
case 10: // FSET?
|
||||||
|
flagset();
|
||||||
|
predicate(opc&op3);
|
||||||
|
break;
|
||||||
|
case 11: // FSET
|
||||||
|
flagset();
|
||||||
|
this.put(op2,opc|op3);
|
||||||
|
break;
|
||||||
|
case 12: // FCLEAR
|
||||||
|
flagset();
|
||||||
|
this.put(op2,opc&~op3);
|
||||||
|
break;
|
||||||
|
case 13: // SET
|
||||||
|
xstore(op0,op1);
|
||||||
|
break;
|
||||||
|
case 14: // MOVE
|
||||||
|
move(op0,op1);
|
||||||
|
break;
|
||||||
|
case 15: // GET
|
||||||
|
store(this.get((op0+op1*2)&65535));
|
||||||
|
break;
|
||||||
|
case 16: // GETB
|
||||||
|
store(mem[(op0+op1)&65535]);
|
||||||
|
break;
|
||||||
|
case 17: // GETP
|
||||||
|
if(propfind()) store(mem[op3-1]&32?this.get(op3):mem[op3]);
|
||||||
|
else store(this.get(defprop+2*op1));
|
||||||
|
break;
|
||||||
|
case 18: // GETPT
|
||||||
|
propfind();
|
||||||
|
store(op3);
|
||||||
|
break;
|
||||||
|
case 19: // NEXTP
|
||||||
|
if(op1) {
|
||||||
|
// Return next property
|
||||||
|
propfind();
|
||||||
|
store(mem[op3+(mem[op3-1]>>5)+1]&31);
|
||||||
|
} else {
|
||||||
|
// Return first property
|
||||||
|
x=this.getu(objects+op0*9+7);
|
||||||
|
store(mem[x+mem[x]*2+1]&31);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 20: // ADD
|
||||||
|
store(op0+op1);
|
||||||
|
break;
|
||||||
|
case 21: // SUB
|
||||||
|
store(op0-op1);
|
||||||
|
break;
|
||||||
|
case 22: // MUL
|
||||||
|
store(Math.imul(op0,op1));
|
||||||
|
break;
|
||||||
|
case 23: // DIV
|
||||||
|
store(Math.trunc(op0/op1));
|
||||||
|
break;
|
||||||
|
case 24: // MOD
|
||||||
|
store(op0%op1);
|
||||||
|
break;
|
||||||
|
case 128: // ZERO?
|
||||||
|
predicate(!op0);
|
||||||
|
break;
|
||||||
|
case 129: // NEXT?
|
||||||
|
store(x=mem[objects+op0*9+5]);
|
||||||
|
predicate(x);
|
||||||
|
break;
|
||||||
|
case 130: // FIRST?
|
||||||
|
store(x=mem[objects+op0*9+6]);
|
||||||
|
predicate(x);
|
||||||
|
break;
|
||||||
|
case 131: // LOC
|
||||||
|
store(mem[objects+op0*9+4]);
|
||||||
|
break;
|
||||||
|
case 132: // PTSIZE
|
||||||
|
store((mem[(op0-1)&65535]>>5)+1);
|
||||||
|
break;
|
||||||
|
case 133: // INC
|
||||||
|
x=xfetch(op0);
|
||||||
|
xstore(op0,x+1);
|
||||||
|
break;
|
||||||
|
case 134: // DEC
|
||||||
|
x=xfetch(op0);
|
||||||
|
xstore(op0,x-1);
|
||||||
|
break;
|
||||||
|
case 135: // PRINTB
|
||||||
|
yield*this.genPrint(this.getText(op0&65535));
|
||||||
|
break;
|
||||||
|
case 137: // REMOVE
|
||||||
|
move(op0,0);
|
||||||
|
break;
|
||||||
|
case 138: // PRINTD
|
||||||
|
yield*this.genPrint(this.getText(this.getu(objects+op0*9+7)+1));
|
||||||
|
break;
|
||||||
|
case 139: // RETURN
|
||||||
|
ret(op0);
|
||||||
|
break;
|
||||||
|
case 140: // JUMP
|
||||||
|
pc+=op0-2;
|
||||||
|
break;
|
||||||
|
case 141: // PRINT
|
||||||
|
yield*this.genPrint(this.getText(addr(op0)));
|
||||||
|
break;
|
||||||
|
case 142: // VALUE
|
||||||
|
store(xfetch(op0));
|
||||||
|
break;
|
||||||
|
case 143: // BCOM
|
||||||
|
store(~op0);
|
||||||
|
break;
|
||||||
|
case 176: // RTRUE
|
||||||
|
ret(1);
|
||||||
|
break;
|
||||||
|
case 177: // RFALSE
|
||||||
|
ret(0);
|
||||||
|
break;
|
||||||
|
case 178: // PRINTI
|
||||||
|
yield*this.genPrint(this.getText(pc));
|
||||||
|
pc=this.endText;
|
||||||
|
break;
|
||||||
|
case 179: // PRINTR
|
||||||
|
yield*this.genPrint(this.getText(pc)+"\n");
|
||||||
|
ret(1);
|
||||||
|
break;
|
||||||
|
case 180: // NOOP
|
||||||
|
break;
|
||||||
|
case 181: // SAVE
|
||||||
|
this.savedFlags=this.get(16);
|
||||||
|
predicate(yield*this.save(this.serialize(ds,cs,pc)));
|
||||||
|
break;
|
||||||
|
case 182: // RESTORE
|
||||||
|
this.savedFlags=this.get(16);
|
||||||
|
if(z=yield*this.restore()) z=this.deserialize(z);
|
||||||
|
this.put(16,this.savedFlags);
|
||||||
|
if(z) ds=z[0],cs=z[1],pc=z[2];
|
||||||
|
predicate(z);
|
||||||
|
break;
|
||||||
|
case 183: // RESTART
|
||||||
|
init();
|
||||||
|
yield*this.restarted();
|
||||||
|
break;
|
||||||
|
case 184: // RSTACK
|
||||||
|
ret(ds[ds.length-1]);
|
||||||
|
break;
|
||||||
|
case 185: // FSTACK
|
||||||
|
ds.pop();
|
||||||
|
break;
|
||||||
|
case 186: // QUIT
|
||||||
|
return;
|
||||||
|
case 187: // CRLF
|
||||||
|
yield*this.genPrint("\n");
|
||||||
|
break;
|
||||||
|
case 188: // USL
|
||||||
|
if(this.updateStatusLine) yield*this.updateStatusLine(this.getText(this.getu(objects+xfetch(16)*9+7)+1),xfetch(18),xfetch(17));
|
||||||
|
break;
|
||||||
|
case 189: // VERIFY
|
||||||
|
predicate(this.verify());
|
||||||
|
break;
|
||||||
|
case 224: // CALL
|
||||||
|
if(op0) {
|
||||||
|
x=mem[op0=addr(op0)];
|
||||||
|
cs.unshift({ds:ds,pc:pc,local:new Int16Array(x)});
|
||||||
|
ds=[];
|
||||||
|
pc=op0+1;
|
||||||
|
for(x=0;x<mem[op0];x++) cs[0].local[x]=pcget();
|
||||||
|
if(opc>1 && mem[op0]>0) cs[0].local[0]=op1;
|
||||||
|
if(opc>2 && mem[op0]>1) cs[0].local[1]=op2;
|
||||||
|
if(opc>3 && mem[op0]>2) cs[0].local[2]=op3;
|
||||||
|
} else {
|
||||||
|
store(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 225: // PUT
|
||||||
|
this.put((op0+op1*2)&65535,op2);
|
||||||
|
break;
|
||||||
|
case 226: // PUTB
|
||||||
|
mem[(op0+op1)&65535]=op2;
|
||||||
|
break;
|
||||||
|
case 227: // PUTP
|
||||||
|
propfind();
|
||||||
|
if(mem[op3-1]&32) this.put(op3,op2);
|
||||||
|
else mem[op3]=op2;
|
||||||
|
break;
|
||||||
|
case 228: // READ
|
||||||
|
yield*this.genPrint("");
|
||||||
|
if(this.updateStatusLine) yield*this.updateStatusLine(this.getText(this.getu(objects+xfetch(16)*9+7)+1),xfetch(18),xfetch(17));
|
||||||
|
this.handleInput(yield*this.read(mem[op0&65535]),op0&65535,op1&65535);
|
||||||
|
break;
|
||||||
|
case 229: // PRINTC
|
||||||
|
yield*this.genPrint(op0==13?"\n":op0?String.fromCharCode(op0):"");
|
||||||
|
break;
|
||||||
|
case 230: // PRINTN
|
||||||
|
yield*this.genPrint(String(op0));
|
||||||
|
break;
|
||||||
|
case 231: // RANDOM
|
||||||
|
if (op0 <= 0) { // If 'op0' is non-positive, reseed the PRNG.
|
||||||
|
if (op0 === 0) {
|
||||||
|
initRng(); // If 0, seed using Math.random().
|
||||||
|
} else {
|
||||||
|
this.seed = (op0 >>> 0); // If negative, seed with the specified value.
|
||||||
|
}
|
||||||
|
store(0); // Reseeding always returns 0.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.seed = (1664525 * this.seed + 1013904223) >>> 0; // Linear congruential generator
|
||||||
|
store(Math.floor((this.seed / 0xFFFFFFFF) * op0) + 1); // Return integer in range [1..op0] (inclusive).
|
||||||
|
break;
|
||||||
|
case 232: // PUSH
|
||||||
|
ds.push(op0);
|
||||||
|
break;
|
||||||
|
case 233: // POP
|
||||||
|
xstore(op0,ds.pop());
|
||||||
|
break;
|
||||||
|
case 234: // SPLIT
|
||||||
|
if(this.split) yield*this.split(op0);
|
||||||
|
break;
|
||||||
|
case 235: // SCREEN
|
||||||
|
if(this.screen) yield*this.screen(op0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("JSZM: Invalid Z-machine opcode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
save: ()=>[],
|
||||||
|
savedFlags: 0,
|
||||||
|
selfInsertingBreaks: null,
|
||||||
|
serial: null,
|
||||||
|
serialize: function(ds,cs,pc) {
|
||||||
|
var i,j,e,ar,vi;
|
||||||
|
e=this.getu(14); // PURBOT
|
||||||
|
i=e+cs.reduce((p,c)=>p+2*(c.ds.length+c.local.length)+6,0)+2*ds.length+8;
|
||||||
|
ar=new Uint8Array(i);
|
||||||
|
ar.set(new Uint8Array(this.mem.buffer,0,e));
|
||||||
|
vi=new DataView(ar.buffer);
|
||||||
|
vi.setUint32(e,pc);
|
||||||
|
vi.setUint16(e+4,cs.length);
|
||||||
|
vi.setUint16(e+6,ds.length);
|
||||||
|
for(i=0;i<ds.length;i++) vi.setInt16(e+i*2+8,ds[i]);
|
||||||
|
e+=ds.length*2+8;
|
||||||
|
for(i=0;i<cs.length;i++) {
|
||||||
|
vi.setUint32(e,cs[i].pc);
|
||||||
|
vi.setUint8(e,cs[i].local.length);
|
||||||
|
vi.setUint16(e+4,cs[i].ds.length);
|
||||||
|
for(j=0;j<cs[i].ds.length;j++) vi.setInt16(e+j*2+6,cs[i].ds[j]);
|
||||||
|
for(j=0;j<cs[i].local.length;j++) vi.setInt16(e+cs[i].ds.length*2+j*2+6,cs[i].local[j]);
|
||||||
|
e+=(cs[i].ds.length+cs[i].local.length)*2+6;
|
||||||
|
}
|
||||||
|
return ar;
|
||||||
|
},
|
||||||
|
screen: null,
|
||||||
|
split: null,
|
||||||
|
statusType: null,
|
||||||
|
updateStatusLine: null,
|
||||||
|
verify: function() {
|
||||||
|
var plenth=this.getu(26);
|
||||||
|
var pchksm=this.getu(28);
|
||||||
|
var i=64;
|
||||||
|
while(i<plenth*2) pchksm=(pchksm-this.memInit[i++])&65535;
|
||||||
|
return !pchksm;
|
||||||
|
},
|
||||||
|
view: null,
|
||||||
|
vocabulary: null,
|
||||||
|
zorkid: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
JSZM.version=JSZM_Version;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(module && module.exports) module.exports=JSZM;
|
||||||
|
} catch(e) {}
|
@ -2,6 +2,8 @@ import { FarpatchWidget, makeNavView } from "../interfaces";
|
|||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { SerializeAddon } from '@xterm/addon-serialize';
|
import { SerializeAddon } from '@xterm/addon-serialize';
|
||||||
|
import JSZM from "../../lib/zork";
|
||||||
|
var co = require("../../lib/co/index.js");
|
||||||
|
|
||||||
export class DebugWidget implements FarpatchWidget {
|
export class DebugWidget implements FarpatchWidget {
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,6 +14,10 @@ export class DebugWidget implements FarpatchWidget {
|
|||||||
view: HTMLElement;
|
view: HTMLElement;
|
||||||
navItem: HTMLElement;
|
navItem: HTMLElement;
|
||||||
|
|
||||||
|
zork: Generator | undefined = undefined;
|
||||||
|
zorkCallback: (value: string | PromiseLike<string>) => void = () => { };
|
||||||
|
terminalLine: string = "";
|
||||||
|
|
||||||
terminal: Terminal;
|
terminal: Terminal;
|
||||||
fitAddon: FitAddon;
|
fitAddon: FitAddon;
|
||||||
serializeAddon: SerializeAddon;
|
serializeAddon: SerializeAddon;
|
||||||
@ -35,25 +41,61 @@ export class DebugWidget implements FarpatchWidget {
|
|||||||
onInit(): void {
|
onInit(): void {
|
||||||
console.log("Initialized Debug Widget");
|
console.log("Initialized Debug Widget");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readLine(): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => { });
|
||||||
|
}
|
||||||
|
|
||||||
onFocus(element: HTMLElement): void {
|
onFocus(element: HTMLElement): void {
|
||||||
console.log("Displaying Debug Widget");
|
console.log("Displaying Debug Widget");
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
// Ensure the parent frame doesn't get any scrollbars, since we're taking up the whole view
|
// Ensure the parent frame doesn't get any scrollbars, since we're taking up the whole view
|
||||||
element.style.overflow = "hidden";
|
element.style.overflow = "hidden";
|
||||||
console.log("Initializing xterm.js");
|
console.log("Initializing xterm.js");
|
||||||
// var terminalContainer = document.createElement("div");
|
|
||||||
// this.view.appendChild(terminalContainer);
|
|
||||||
this.terminal.loadAddon(this.fitAddon);
|
this.terminal.loadAddon(this.fitAddon);
|
||||||
this.terminal.loadAddon(this.serializeAddon);
|
this.terminal.loadAddon(this.serializeAddon);
|
||||||
this.terminal.onKey((e) => {
|
this.terminal.onKey((e) => {
|
||||||
console.log("Key pressed: " + e.key);
|
|
||||||
this.terminal.write(e.key);
|
this.terminal.write(e.key);
|
||||||
|
if (e.key === '\h') {
|
||||||
|
if (this.terminalLine.length > 0) {
|
||||||
|
this.terminalLine = this.terminalLine.substring(0, this.terminalLine.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (e.key === '\r') {
|
if (e.key === '\r') {
|
||||||
this.terminal.write('\n');
|
this.terminal.write('\n');
|
||||||
|
var zcb = this.zorkCallback;
|
||||||
|
var tl = this.terminalLine;
|
||||||
|
this.zorkCallback = () => { };
|
||||||
|
this.terminalLine = "";
|
||||||
|
zcb(tl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.terminalLine += e.key;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.terminal.open(this.view);
|
this.terminal.open(this.view);
|
||||||
this.terminal.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n');
|
|
||||||
|
var zorkTerminal = this.terminal;
|
||||||
|
var debugWidget = this;
|
||||||
|
fetch("/zork1.z3").then((response) => {
|
||||||
|
response.arrayBuffer().then((buffer) => {
|
||||||
|
this.zork = co.co(function* () {
|
||||||
|
var zork = new JSZM(new Uint8Array(buffer));
|
||||||
|
zork.print = function* (str: string) {
|
||||||
|
str = str.replace("\n", "\r\n");
|
||||||
|
zorkTerminal.write(str);
|
||||||
|
};
|
||||||
|
zork.read = function* (maxlen: number): Generator {
|
||||||
|
// console.log("Zork: read " + maxlen);
|
||||||
|
var val = yield new Promise((resolve, reject) => {
|
||||||
|
debugWidget.zorkCallback = resolve;
|
||||||
|
});
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
yield* zork.run();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
element.appendChild(this.view);
|
element.appendChild(this.view);
|
||||||
|
BIN
static/zork1.z3
Normal file
BIN
static/zork1.z3
Normal file
Binary file not shown.
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"target": "ES5",
|
"target": "ES2015",
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"strict": true
|
"strict": true
|
||||||
|
Loading…
Reference in New Issue
Block a user