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 { FitAddon } from '@xterm/addon-fit';
|
||||
import { SerializeAddon } from '@xterm/addon-serialize';
|
||||
import JSZM from "../../lib/zork";
|
||||
var co = require("../../lib/co/index.js");
|
||||
|
||||
export class DebugWidget implements FarpatchWidget {
|
||||
name: string;
|
||||
@ -12,6 +14,10 @@ export class DebugWidget implements FarpatchWidget {
|
||||
view: HTMLElement;
|
||||
navItem: HTMLElement;
|
||||
|
||||
zork: Generator | undefined = undefined;
|
||||
zorkCallback: (value: string | PromiseLike<string>) => void = () => { };
|
||||
terminalLine: string = "";
|
||||
|
||||
terminal: Terminal;
|
||||
fitAddon: FitAddon;
|
||||
serializeAddon: SerializeAddon;
|
||||
@ -35,25 +41,61 @@ export class DebugWidget implements FarpatchWidget {
|
||||
onInit(): void {
|
||||
console.log("Initialized Debug Widget");
|
||||
}
|
||||
|
||||
readLine(): Promise<string> {
|
||||
return new Promise((resolve, reject) => { });
|
||||
}
|
||||
|
||||
onFocus(element: HTMLElement): void {
|
||||
console.log("Displaying Debug Widget");
|
||||
if (!this.initialized) {
|
||||
// Ensure the parent frame doesn't get any scrollbars, since we're taking up the whole view
|
||||
element.style.overflow = "hidden";
|
||||
console.log("Initializing xterm.js");
|
||||
// var terminalContainer = document.createElement("div");
|
||||
// this.view.appendChild(terminalContainer);
|
||||
this.terminal.loadAddon(this.fitAddon);
|
||||
this.terminal.loadAddon(this.serializeAddon);
|
||||
this.terminal.onKey((e) => {
|
||||
console.log("Key pressed: " + 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') {
|
||||
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.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;
|
||||
}
|
||||
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": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "ES5",
|
||||
"target": "ES2015",
|
||||
"module": "CommonJS",
|
||||
"outDir": "./build",
|
||||
"strict": true
|
||||
|
Loading…
Reference in New Issue
Block a user