682 lines
21 KiB
JavaScript
682 lines
21 KiB
JavaScript
|
/*
|
||
|
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) {}
|