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) {} | ||||
		Reference in New Issue
	
	Block a user