better showcanvas fix
[mminer.git] / miner.js
blob65458ea6da1e983c4d7f6266c74f740a3eb4dbe0
1 window.levelsets = [];
4 if (typeof(opera) == "undefined") {
5   opera = { postError: function () { return; } };
10 function ManicMiner () {
11   var DEBUG_COLDET = false, dbgColDetected = false;
13   var willyJ = [0, -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4];
14   var skylabCoords = [
15     [{x: 8,y:0}, {x: 72,y:0}, {x:136,y:0}, {x:200,y:0}],
16     [{x:40,y:0}, {x:104,y:0}, {x:168,y:0}, {x:232,y:0}],
17     [{x:24,y:0}, {x: 88,y:0}, {x:152,y:0}, {x:216,y:0}]
18   ];
19   var scrollText = ".  .  .  .  .  .  .  .  .  .  . "+
20     "MANIC MINER . . "+
21     "\u00a9 BUG-BYTE Ltd. 1983 . . "+
22     "By Matthew Smith . . . "+
23     "P = Pause . . . "+
24     "Guide Miner Willy through 20 lethal caverns "+
25     ".  .  .  .  .  .  .  .";
28   function unpackRoom (roomS) {
29     var room = {}, pos = 0;
31     function getNum () {
32       var n = roomS.charCodeAt(pos++)-48, len;
33       var len = n>>4; n = n&0x0f;
34       while (len--) n = n*10+roomS.charCodeAt(pos++)-48;
35       return n;
36     }
38     function getIP (arr) {
39       arr.ink = getNum();
40       arr.paper = getNum();
41     }
43     function getGIP (arr) {
44       arr.gfx = getNum();
45       getIP(arr);
46     }
48     // map
49     room.map = [];
50     for (var f = 0; f < 512; ) {
51       var bt = roomS.charCodeAt(pos++)-48;
52       var cnt = roomS.charCodeAt(pos++)-31;
53       while (cnt--) { room.map.push(bt); f++; }
54     }
55     // params and Willy start
56     room.air = getNum();
57     room.willy = { x:getNum(), y:getNum(), sd:getNum() };
58     room.exit = { gfx:getNum(), x:getNum(), y:getNum() };
59     room.border = getNum();
60     getIP(room);
61     // platforms
62     var cnt = getNum();
63     room.platforms = [];
64     while (cnt--) {
65       var t = {}; getGIP(t);
66       room.platforms.push(t);
67     }
68     room.wall = {}; getGIP(room.wall);
69     room.crumb = {}; getGIP(room.crumb);
70     // deadlies
71     var cnt = getNum();
72     room.deadlies = [];
73     while (cnt--) {
74       var t = {}; getGIP(t);
75       room.deadlies.push(t);
76     }
77     // conveyor
78     room.conveyor = { x:getNum(), y:getNum(), d:getNum(), l:getNum() };
79     getGIP(room.conveyor);
80     // keys
81     cnt = getNum();
82     //LogPrint("keys: "+cnt);
83     room.keys = { gfx: getNum(), info:[] };
84     for (var f = 0; f < 5; f++) {
85       var t = {};
86       if (f < cnt) t = { x:getNum(), y:getNum(), s: getNum() };
87       else t = { x:-1, y:-1, s:0 };
88       room.keys.info.push(t);
89     }
90     // switches
91     cnt = getNum();
92     room.switches = [];
93     for (var f = 0; f < 2; f++) {
94       var sw;
95       if (f < cnt) sw = { x: getNum(), y:getNum(), s:getNum() };
96       else sw = { x: 0, y: 0, s: 0 };
97       room.switches.push(sw);
98     }
99     // enemies
100     cnt = getNum();
101     room.enemies = [];
102     for (var f = 0; f < 8; f++) {
103       var e;
104       if (f < cnt) {
105         e = {};
106         e.vert = roomS.charCodeAt(pos++)=="V";
107         getGIP(e);
108         e.x = getNum();
109         e.y = getNum();
110         e.min = getNum();
111         e.max = getNum();
112         e.d = getNum();
113         e.s = getNum();
114         e.flip = getNum();
115         e.anim = getNum();
116       } else {
117         e = { vert: true, gfx: 0, ink: 1, paper: 0, x: -1, y: -1, min: 2, max: 81, d: 0, s: 111, flip: 0, anim: 0 };
118       }
119       room.enemies.push(e);
120     }
121     // title
122     room.title = roomS.substr(pos);
124     return room;
125   }
127   /*
128    * unpacker code derived from JavaScript O Lait library (jsolait)
129    * JavaScript O Lait library(jsolait): Copyright (c) 2003-2006 Jan-Klaas Kollhof, GNU LGPL v2.1+
130    *
131    * modified by Ketmar // Avalon Group
132   */
133   function lzwUnpack (str) {
134     var dict = {};
135     var data = (str+"").split("");
136     var currChar = data[0];
137     var oldPhrase = currChar;
138     var out = [currChar];
139     var code = 256;
140     var phrase;
141     for (var i = 1; i < data.length; i++) {
142       var currCode = data[i].charCodeAt(0);
143       if (currCode < 256) phrase = data[i];
144       else phrase = dict[currCode]?dict[currCode]:(oldPhrase+currChar);
145       out.push(phrase);
146       currChar = phrase.charAt(0);
147       dict[code] = oldPhrase+currChar;
148       code++;
149       oldPhrase = phrase;
150     }
151     var s = out.join("");
152     var res = [];
153     for (var f = 0; f < s.length; f++) res.push(s.charCodeAt(f));
154     return res;
155   }
157   var me = this, canvas, ctx, mainX, mainY, mainPal;
158   this.lsets = [];
159   this.setData = function (data) {
160     this.data = data;
161     var k;
162     for (k in data) {
163       var v = data[k];
164       if (typeof(v) == "string") data[k] = lzwUnpack(v);
165     }
166   };
167   this.addLevelSet = function (lset) {
168     if (lset.packed) {
169       delete lset.packed;
170       var r = lset.rooms;
171       lset.rooms = [];
172       for (var f = 0; f < r.length; f++) lset.rooms.push(unpackRoom(r[f]));
173     }
174     this.lsets.push(lset);
175   };
177   var gameTID;
179   var kbdActions; // will be inited later
180   var kLeft, kRight, kJump, kUp, kDown;
182   var kong, eugene, skylab, switches, spg;
183   var finalSpr, sunSpr, kongSpr, eugeneSpr, skylabSpr, switchesSpr;
184   var holeLen, holeY;
186   var willySpr, willyX, willyY, willyDir, willyJump, willyJumpDir;
187   var willyLastMoveDir, willyFall, willyDead, willyConv, willyStall;
188   var brickCache, convCache, monsterCache, monsterOfsCache, keyCache, exitCache;
189   var curLSet = 0, curRoomNo = 0, curRoom, curMap, curKeys, keysLeft, curMonsters;
190   var conv;
191   var frameNo;
192   var score;
194   var gameRunning;
196   var dbgNF, dbgSingleStep;
199 function removeMessage (force) {
200   var msg = document.getElementById("k8PageMesssage");
201   if (!msg) return;
202   if (msg.k8ClickToRemove && !force) return;
203   msg.style.display = "none";
204   window.removeEventListener("resize", msg.k8FNRS, false);
205   setTimeout(function () {
206     //opera.postError("msg: "+msg);
207     if (!msg || !msg.parentNode) return;
208     msg.parentNode.removeChild(msg);
209   }, 1);
213 function message (msgs, clickToRemove) {
214   while (!msgs) msgs = rndMessages[Math.ceil(Math.random()*rndMessages.length)];
215   var msg = document.getElementById("k8PageMesssage");
216   if (!msg) {
217     var db = document.body;
218     var fc = db.firstChild;
219     var msg = document.createElement("k8_msg_div");
220     msg.id = "k8PageMesssage";
221     msg.setAttribute("style",
222       "z-index:7779;;border:1px solid #fff;padding:3px;background:#222;"+
223       "color:#f"+(clickToRemove?"00":"ff")+";display:block;"+
224       "position:fixed;left:-1000px;top:-1000px;"+
225       "font-size:28px;font-family:Verdana Tahoma;font-weight:normal;font-style:normal;");
226     var img = msg.appendChild(document.createElement("img"));
227     img.width = 27; img.height = 27;
228     img.setAttribute("style", "margin-left:4px;margin-top:5px;");
229     img.src = "data:image/gif;base64,"+ //27x27
230       "R0lGODlhGwAbAKEBAAAAAP///////////yH/C05FVFNDQVBFMi4wAwEAAAAh/iNvcHBvc2l0"+
231       "ZXMgYnkgS2V0bWFyIC8vIEF2YWxvbiBHcm91cAAh+QQJCgACACwAAAAAGwAbAAACbZSPoRvo"+
232       "fxSYFCgIZd22YRNw4uRl41km55oK4cqqMBrNsPfOTG65dreraG67BWUo3DCAOSQJaXxqJDyL"+
233       "0hjsWFnFJbUq7Hp9Tqk4hANez9Qa+9xzv7tx+RxrurcxrzfpA9JX9ASYANZRmFEEWAAAIfkE"+
234       "CQoAAwAsAAAAABsAGwAAAm6cj6Eb6H8UmBQoCGXdtmETcOLkZeNZJme3VOkQokxLquOMW9HK"+
235       "sL0Xu2l8kkZQWMSFhkhli9l0dlYU5/PIs05FWOsSS2J6dWDLRgukhpU6EBVti87aO7ML/vh6"+
236       "n5/YvvYB45fTEZhQdmGYhxNYAAAh+QQJCgADACwAAAAAGwAbAAACcJyPoRvofxSYFCgIZZ36"+
237       "YhNsHBNaX1mRpPmglKqykQjD8uCOUh02oGjRBG2+nK7HMxmPvEUQyGw+ocMmkkqrTbEp4hLr"+
238       "5QJjje9rsyqChdXejJpj3Mxjp+8tlt8TeftpXbKXYeQU9OHA9iJ4GKFyWAAAIfkECQoAAwAs"+
239       "AAAAABsAGwAAAm6cj6Eb6H8UmBQoCKVl+mITTNwofuE2ks1zoilnOe1LxxFFv/Yw5/rak/gW"+
240       "sRYKNwxpRMSk0FirqIwuYoXZpFav0iz3C/aCudQmb9yFKkFo6VV4a3/XcXl3x7a/M3qcqb9i"+
241       "ovUW+AEy6GHIMmJYAAAh+QQJCgADACwAAAAAGwAbAAACb5yPoRvofxSYFCgIpWX6YhNM3Ch+"+
242       "4YhKzXOmqeW07ruC24zCNq5Riy5zVXynQfA1JG5uyKRvyQwOQ5WW6HelZpM9BnPq7DadSiKH"+
243       "zDVWveiqrQ3XqePk2pyeTuCnmX0JoxVnlxEYNmhSePWByLFYAAAh+QQJCgADACwAAAAAGwAb"+
244       "AAACbZyPoRvofxSYFCgIpWX6YhNM3Ch+4YhKzXOmYfWu0ZZWtpXQ5A3PLn9btVC2V3EwJFIW"+
245       "PNUPBnTWgMWkkiqyXqlSlzGq7WJx2qUmCtIxGVhZmI2eZXdx+fzczIyDpv3yA/LVJAOIJJhV"+
246       "mDECWAAAIfkECQoAAwAsAAAAABsAGwAAAm6cj6Eb6H8UmBQoCKXV9WITTEtFWl/IlGXzoKO6"+
247       "Oi4Hkyy40VZt4kxIecFYMx2w1ijqeEHlkin5CZki5xRqlR5VUa0WmfUOB+Ei1yeuboORnNQd"+
248       "a3vfNpk73cncf3H9/vxBBvTkEQiyVmWYIRVYAAAh+QQJCgADACwAAAAAGwAbAAACb5yPoRvo"+
249       "fxSYFCgIZd22YRNw4uRl41km55oOobZy6RuP5RtWee3hOsxydXakGkWyMNpwwA2DSGI2o0+n"+
250       "tPlk/IbZLHfb7R6BjWtYegRxz15SZM2GzeDxtCqudT/MYb2JnteylyPW8eEwZXiYkXVYAAA7";
251     var innSpan = msg.appendChild(document.createElement("k8_msg_span"));
252     if (typeof(opera) != "undefined") innSpan.setAttribute("style", "display:inline-block;padding:0 6px 0 8px;");
253     else innSpan.setAttribute("style", "display:inline;padding:0 6px 0 4px;");
254     msg.k8InnTN = innSpan.appendChild(document.createTextNode(""));
255     db.insertBefore(msg, fc);
256   }
257   msg.k8InnTN.nodeValue = msgs+"";
258   var wx = Math.ceil((window.innerWidth-msg.offsetWidth)/2);
259   var wy = Math.ceil((window.innerHeight-msg.offsetHeight)/2);
260   msg.style.left = wx+"px";
261   msg.style.top = wy+"px";
262   if (clickToRemove) {
263     msg.onclick = function () { removeMessage(true); };
264     msg.k8ClickToRemove = true;
265   } else {
266     msg.onclick = function () {};
267     if (msg.k8ClickToRemove) delete msg.k8ClickToRemove;
268   }
269   var fnrs = function () {
270     var wx = Math.ceil((window.innerWidth-msg.offsetWidth)/2);
271     var wy = Math.ceil((window.innerHeight-msg.offsetHeight)/2);
272     msg.style.left = wx+"px";
273     msg.style.top = wy+"px";
274   };
275   msg.k8FNRS = fnrs;
276   window.addEventListener("resize", fnrs, false);
280 function blockPage () {
281   var i = document.getElementById("k8PageBlocker");
282   if (i) return;
283   var db = document.body;
284   var ovr = document.createElement("div");
285   ovr.setAttribute("style", "z-index:6666;opacity:0.5;width:100%;height:100%;display:block;position:fixed;left:0;top:0;background:#000");
286   ovr.id = "k8PageBlocker";
287   var fc = document.body.firstChild;
288   db.insertBefore(ovr, fc);
292 function unblockPage () {
293   removeMessage(false);
294   var i = document.getElementById("k8PageBlocker");
295   if (!i) return;
296   i.style.display = "none";
297   setTimeout(function () { i.parentNode.removeChild(i); }, 1);
299 ///////////////////////////////////////////////////////////////////////////////////////////////////////
302 /////////////////////////////////////////////////////////////////////////
303 // image builders
305 function convertPalette () {
306   function toHC (n) { return (n>15?"":"0")+(n.toString(16)); }
307   mainPal = [];
308   var pd = me.data.palmain;
309   //opera.postError(pd.length);
310   for (var f = 0; f < 256; f++) mainPal[f] = "#"+toHC(pd[f*3+0]*4)+toHC(pd[f*3+1]*4)+toHC(pd[f*3+2]*4);
314 function buildImageMasked (darr, dpos, w, h) {
315   var cc = document.createElement("canvas");
316   cc.setAttribute("width", w);
317   cc.setAttribute("height", h);
318   var dc = cc.getContext("2d");
319   var pl = me.data.palmain;
320   for (var y = 0; y < h; y++) {
321     for (var x = 0; x < w; x++) {
322       var c = darr[dpos++];
323       if (!c) continue;
324       dc.fillStyle = mainPal[c];
325       dc.fillRect(x, y, 1, 1);
326     }
327   }
328   img = document.createElement("img");
329   img.setAttribute("width", w);
330   img.setAttribute("height", h);
331   img.src = cc.toDataURL();
332   return img;
336 function buildImageMaskedShiny (darr, dpos, brightness, w, h) {
337   var cc = document.createElement("canvas");
338   cc.setAttribute("width", w);
339   cc.setAttribute("height", h);
340   var dc = cc.getContext("2d");
341   var pl = me.data.palmain;
342   for (var y = 0; y < h; y++) {
343     for (var x = 0; x < w; x++) {
344       var c = darr[dpos++];
345       if (!c) continue;
346       var b = (c&15)-brightness;
347       if (b < 0) b = 0; else if (b > 15) b = 15;
348       dc.fillStyle = mainPal[(c&240)|b];
349       dc.fillRect(x, y, 1, 1);
350     }
351   }
352   img = document.createElement("img");
353   img.setAttribute("width", w);
354   img.setAttribute("height", h);
355   img.src = cc.toDataURL();
356   return img;
360 function buildImageMaskedInk (darr, dpos, ink, w, h) {
361   ink = (ink||0); if (ink < 0) ink = 0; ink *= 16;
362   var cc = document.createElement("canvas");
363   cc.setAttribute("width", w);
364   cc.setAttribute("height", h);
365   var dc = cc.getContext("2d");
366   var pl = me.data.palmain;
367   for (var y = 0; y < h; y++) {
368     for (var x = 0; x < w; x++) {
369       var c = darr[dpos++];
370       if (!c) continue;
371       dc.fillStyle = mainPal[c+ink];
372       dc.fillRect(x, y, 1, 1);
373     }
374   }
375   img = document.createElement("img");
376   img.setAttribute("width", w);
377   img.setAttribute("height", h);
378   img.src = cc.toDataURL();
379   return img;
383 function buildImageBrk (darr, dpos, ink, paper, skip, w, h, rep, masked) {
384   skip = skip||0; ink *= 16; w = w||8; h = h||8; rep = rep||1;
385   var cc = document.createElement("canvas");
386   cc.setAttribute("width", w*rep);
387   cc.setAttribute("height", h);
388   var dc = cc.getContext("2d");
389   var pl = me.data.palmain;
390   for (var y = 0; y < h; y++) {
391     for (var x = 0; x < w; x++) {
392       var c = y>=skip?darr[dpos++]:0;
393       if (!c && masked) continue;
394       dc.fillStyle = mainPal[c?ink+c:paper];
395       for (var r = 0; r < rep; r++) dc.fillRect(x+r*w, y, 1, 1);
396     }
397   }
398   img = document.createElement("img");
399   img.setAttribute("width", w*rep);
400   img.setAttribute("height", h);
401   img.src = cc.toDataURL();
402   return img;
406 function buildWilly () {
407   var cc = document.createElement("canvas");
408   cc.setAttribute("width", 16*16);
409   cc.setAttribute("height", 16+16);
410   var dc = cc.getContext("2d");
411   var ww = me.data.willy, pl = me.data.palmain;
412   for (var f = 0; f < 16; f++) {
413     for (var y = 0; y < 16; y++) {
414       for (var x = 0; x < 16; x++) {
415         var c = me.data.willy[f*256+y*16+x];
416         if (!c) continue;
417         dc.fillStyle = mainPal[c];
418         dc.fillRect(f*16+x, y, 1, 1);
419         // white
420         dc.fillStyle = mainPal[15];
421         dc.fillRect(f*16+x, y+16, 1, 1);
422       }
423     }
424   }
425   willySpr = document.createElement("img");
426   willySpr.setAttribute("width", 16*16);
427   willySpr.setAttribute("height", 16+16);
428   willySpr.src = cc.toDataURL();
432 /////////////////////////////////////////////////////////////////////////
433 // image cachers
435 function cacheBrickImages () {
436   function buildBrickImage (brk, skip) {
437     return buildImageBrk(me.data.blocks, brk.gfx*64, brk.ink, curRoom.paper, skip||0);
438   }
439   brickCache = [];
440   for (var f = 0; f <= 7; f++) {
441     var r;
442     switch (f) {
443       //case 0: case 7: r = buildBrickImage(curRoom.wall, 16); break;
444       case 1: r = buildBrickImage(curRoom.platforms[0]); break;
445       case 2: r = buildBrickImage(curRoom.platforms[1]); break;
446       case 3: r = buildBrickImage(curRoom.wall); break;
447       //case 4: r = buildBrickImage(curRoom.crumb); break;
448       case 5: r = buildBrickImage(curRoom.deadlies[0]); break;
449       case 6: r = buildBrickImage(curRoom.deadlies[1]); break;
450     }
451     brickCache.push(r);
452   }
453   for (var f = 0; f <= 8; f++) brickCache.push(buildBrickImage(curRoom.crumb, f));
457 function cacheConvImages () {
458   convCache = []; //monsterCache = [];
459   if (conv.y <= 0 || conv.l < 1) return;
460   for (var f = 0; f <= 3; f++) {
461     convCache.push(buildImageBrk(me.data.conv, conv.gfx*256+f*64, conv.ink, curRoom.paper, 0, 8, 8, conv.l));
462   }
466 function cacheExit () {
467   exitCache = [];
468   exitCache.push(buildImageMasked(me.data.exits, curRoom.exit.gfx*256, 16, 16));
469   for (var f = 0; f <= 15; f++) exitCache.push(buildImageMaskedShiny(me.data.exits, curRoom.exit.gfx*256, f, 16, 16));
473 function cacheKeys () {
474   keyCache = [];
475   for (var f = 0; f <= 15; f++) keyCache.push(buildImageMaskedShiny(me.data.keys, curRoom.keys.gfx*64, f, 8, 8));
479 function cacheMonsters () {
480   monsterCache = []; monsterOfsCache = [];
481   var l = curMonsters.length;
482   for (var f = 0; f < l; f++) {
483     var m = curMonsters[f];
484     if (m.x < 0 || m.y < 0) continue;
485     var r = [], cc = [];
486     if (m.vert) {
487       for (var c = 0; c <= 3; c++) {
488         var n = (m.gfx+c)*256;
489         cc.push(n);
490         r.push(buildImageMaskedInk(me.data.vrobo, n, m.ink-1, 16, 16));
491       }
492     } else {
493       for (var c = 0; c <= m.anim>>1; c++) {
494         var n = (m.gfx+c)*256;
495         cc.push(n);
496         r.push(buildImageMaskedInk(me.data.hrobo, n, m.ink-1, 16, 16));
497         n += m.flip*256;
498         cc.push(n);
499         r.push(buildImageMaskedInk(me.data.hrobo, n, m.ink-1, 16, 16));
500       }
501     }
502     monsterOfsCache.push(cc);
503     monsterCache.push(r);
504   }
508 function cacheEugene () {
509   eugeneSpr = [];
510   for (var f = 0; f <= 256; f += 256)
511     for (var c = 0; c <= 7; c++)
512       eugeneSpr.push(buildImageMaskedInk(me.data.eugene, f, c, 16, 16));
516 function cacheKong () {
517   kongSpr = [];
518   for (var f = 0; f <= 3; f++) {
519     for (var c = 0; c <= 7; c++) {
520       kongSpr.push(buildImageMaskedInk(me.data.kong, f*256, c, 16, 16));
521     }
522   }
526 function cacheSkyLab () {
527   skylabSpr = [];
528   for (var f = 0; f <= 7; f++)
529     for (var c = 0; c <= 7; c++)
530       skylabSpr.push(buildImageMaskedInk(me.data.sky, f*256, c, 16, 16));
534 function cacheSwitch () {
535   switchesSpr = [];
536   for (var f = 0; f < 2; f++) {
537     switchesSpr.push(buildImageBrk(me.data.switches, f*64, curRoom.platforms[1].ink, curRoom.paper, 0));
538   }
542 /////////////////////////////////////////////////////////////////////////
543 // painters
545 function eraseRect (x0, y0, w, h) {
546   ctx.fillStyle = mainPal[curRoom.paper];
547   ctx.fillRect(x0, y0, w, h);
548   var xe = x0+w, ye = y0+h;
549   for (var dy = y0; dy <= ye; dy += 8) {
550     for (var dx = x0; dx <= xe; dx += 8) {
551       var x = dx>>3, y = dy>>3;
552       if (x < 0 || y < 0 || x > 31 || y > 15) continue;
553       var blk = curMap[y*32+x];
554       if (blk < 1 || blk == 7) continue;
555       if (blk == 4) blk = 8;
556       ctx.drawImage(brickCache[blk], 0, 0, 8, 8, x*8, y*8, 8, 8);
557     }
558   }
562 function drawRoom () {
563   var pos = 0;
564   ctx.fillStyle = mainPal[curRoom.paper];
565   ctx.fillRect(0, 0, 32*8, 16*8);
566   for (var y = 0; y < 16; y++) {
567     for (var x = 0; x < 32; x++) {
568       var blk = curMap[pos++];
569       if (blk < 1 || blk == 7) continue;
570       if (blk == 4) blk = 8;
571       ctx.drawImage(brickCache[blk], 0, 0, 8, 8, x*8, y*8, 8, 8);
572     }
573   }
574   if (curRoomNo == 19) {
575     ctx.drawImage(finalSpr, 0, 0, 256, 64, 0, 0, 256, 64);
576     ctx.drawImage(sunSpr, 0, 0, 24, 16, 60, 32, 24, 16);
577   }
581 function drawConveyor () {
582   if (conv.l < 1) return;
583   var y = conv.y;
584   if (y <= 0) return;
585   ctx.drawImage(convCache[conv.frame], 0, 0, conv.l*8, 8, conv.x, conv.y, conv.l*8, 8);
589 function drawExit () {
590   if (keysLeft) br = 0;
591   else {
592     br = frameNo&31;
593     if (br > 15) br = 31-br;
594     br++;
595   }
596   ctx.drawImage(exitCache[br], 0, 0, 16, 16, curRoom.exit.x, curRoom.exit.y, 16, 16);
600 function drawKeys () {
601   //if (!keysLeft) return;
602   var ff = frameNo%16;
603   if (ff >= 8) ff = 15-ff;
604   var img = keyCache[ff];
605   for (var f = curKeys.length-1; f >= 0; f--) {
606     var ki = curKeys[f];
607     if (ki.x < 0 || ki.y < 0) continue;
608     eraseRect(ki.x, ki.y, 8, 8);
609     if (!ki.s) continue;
610     ctx.drawImage(img, 0, 0, 8, 8, ki.x&248, ki.y, 8, 8);
611   }
615 function eraseMonsters () {
616   var l = curMonsters.length;
617   for (var f = 0; f < l; f++) {
618     var m = curMonsters[f];
619     //if (m.x < 0 || m.y < 0) continue; // we can't kill monsters now
620     var x = m.x; if (!m.vert) x = x&248;
621     eraseRect(x, m.y, 16, 16);
622   }
623   if (eugene) eraseRect(eugene.x, eugene.y, 16, 16);
624   if (kong) eraseRect(kong.x, kong.y, 16, 16);
625   if (skylab) {
626     for (var f = 2; f >= 0; f--) eraseRect(skylab[f].x, skylab[f].y, 16, 16);
627   }
631 function drawMonsters () {
632   //return;
633   var l = curMonsters.length;
634   for (var f = 0; f < l; f++) {
635     var m = curMonsters[f];
636     //if (m.x < 0 || m.y < 0) continue; // we can't kill monsters now
637     var slist = monsterCache[f];
638     var x = m.x;
639     if (m.vert) {
640       //for (var c = 0; c <= m.anim; c++) r.push(buildImageMaskedInk(me.data.vrobo, (m.gfx+c)*256, m.ink-1, 16, 16, false));
641       ctx.drawImage(slist[m.anim], 0, 0, 16, 16, x, m.y, 16, 16);
642     } else ctx.drawImage(slist[((x&m.anim)&0xfe)+m.dir], 0, 0, 16, 16, x&248, m.y, 16, 16);
643     //} else ctx.drawImage(slist[frameNo&m.anim], 0, 0, 16, 16, x&248, m.y, 16, 16);
644   }
645   if (eugene) {
646     ctx.drawImage(eugeneSpr[curLSet*8+eugene.ink], 0, 0, 16, 16, eugene.x, eugene.y, 16, 16);
647   }
648   if (kong) {
649     ctx.drawImage(kongSpr[kong.frame*8+kong.ink], 0, 0, 16, 16, kong.x, kong.y, 16, 16);
650   }
651   if (skylab) {
652     for (var f = 0; f < skylab.length; f++) {
653       var sk = skylab[f];
654       ctx.drawImage(skylabSpr[sk.frame*8+sk.ink], 0, 0, 16, 16, sk.x, sk.y, 16, 16);
655     }
656   }
660 function drawSwitch () {
661   var l = switches.length;
662   for (var f = 0; f < l; f++) {
663     var ss = switches[f];
664     ctx.drawImage(switchesSpr[ss.state], 0, 0, 8, 8, ss.x, ss.y, 8, 8);
665   }
669 function drawSPG (noerase) {
670   if (!spg) return;
671   for (var f = 0; f < spg.length; f++) {
672     var x = spg[f].x*8, y = spg[f].y*8;
673     if (x < 0 || y < 0) break;
674     ctx.fillStyle = mainPal[noerase?6:curRoom.paper];
675     ctx.fillRect(x, y, 8, 8);
676   }
681 function drawWilly () {
682   var willyPos = (willyX&15)>>1;
683   if (willyDir < 0) willyPos += 8;
684   var wy = 0;
685   if (DEBUG_COLDET && dbgColDetected) wy = 16;
686   ctx.drawImage(willySpr, willyPos*16, wy, 16, 16, willyX&248, willyY, 16, 16);
690 /////////////////////////////////////////////////////////////////////////
691 // checkers
693 // x & y: in pixels
694 function getBlockAt (x, y, simplify) {
695   x = x>>3, y = y>>3;
696   if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
697   var b = curMap[y*32+x];
698   if (simplify === true) {
699     if (b > 15) b = 0;
700     else if (b > 7) b = 4;
701     else if (b == 6) b = 5;
702     else if (b == 2) b = 1;
703   }
704   return b;
708 function isWillyFalling () {
709   if (willyY&7) return true;
710   for (var dx = 0; dx <= 8; dx += 8) {
711     var b = getBlockAt(willyX+dx, willyY+16, true);
712     if (b > 0 && b != 5) return false;
713   }
714   return true;
718 function isWillyInDeadly () {
719   for (var dx = 0; dx <= 8; dx += 8) {
720     for (var dy = 0; dy <= 16; dy += 8) {
721       if (getBlockAt(willyX+dx, willyY+dy, true) == 5) return true;
722     }
723   }
724   return false;
728 function isWillyOnConv () {
729   if (willyY&7) return false;
730   var b0 = getBlockAt(willyX, willyY+16);
731   var b1 = getBlockAt(willyX+8, willyY+16);
732   return b0 == 7 || b1 == 7;
736 function checkExit () {
737   if (keysLeft) return false;
738   var x = curRoom.exit.x, y = curRoom.exit.y;
739   return (willyX >= x-2 && willyX+10 <= x+18 && willyY >= y-5 && willyY+16 <= y+22);
743 // pixel-perfect collision detector
744 // fully stolen from Andy's sources %-)
745 var mpcGrid = new Array(256);
746 function pixelCheckMonster (rx, ry, darr, dpos) {
747   var x, y, w;
748   //
749   rx -= willyX&248;
750   ry -= willyY;
751   if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
752   // clear grid
753   for (var f = 255; f >= 0; f--) mpcGrid[f] = 0;
754   if (rx < 0) x = 0, rx = -rx, w = 16-rx; else x = rx, rx = 0, w = 16-x;
755   // partial plot monster
756   for (y = ry+15; y >= ry; y--) {
757     if (y >= 0 && y < 16) {
758       var gp = y*16+x, rp = dpos+(y-ry)*16+rx;
759       for (var dx = 0; dx < w; dx++, gp++, rp++) mpcGrid[gp] = darr[rp];
760     }
761   }
762   var warr = me.data.willy, wptr = ((willyX&15)>>1)*256+(willyDir<0?2048:0);
763   // check for collision
764   var mp = 0;
765   for (x = 255; x >= 0; x--, wptr++, mp++) if (warr[wptr] && mpcGrid[mp]) return true;
766   return 0;
770 function checkMonsters () {
771   var l = curMonsters.length;
772   for (var f = 0; f < l; f++) {
773     var m = curMonsters[f], cc = monsterOfsCache[f];
774     //if (m.x < 0 || m.y < 0) continue; // we can't kill monsters now
775     if (m.vert) {
776       if (pixelCheckMonster(m.x, m.y, me.data.vrobo, cc[m.anim])) return true;
777     } else {
778       if (pixelCheckMonster(m.x&248, m.y, me.data.hrobo, cc[((m.x&m.anim)&0xfe)+m.dir])) return true;
779     }
780   }
781   // Eugene?
782   if (eugene) {
783     if (pixelCheckMonster(eugene.x, eugene.y, me.data.eugene, 256*curLSet)) return true;
784   }
785   // Kong?
786   if (kong) {
787     if (pixelCheckMonster(kong.x, kong.y, me.data.kong, 256*kong.frame)) return true;
788   }
789   // SkyLab?
790   if (skylab) {
791     for (var f = 2; f >= 0; f--) {
792       var sk = skylab[f];
793       if (pixelCheckMonster(sk.x, sk.y, me.data.sky, 256*sk.frame)) return true;
794     }
795   }
799 function checkSwitch () {
800   var l = switches.length;
801   for (var f = 0; f < l; f++) {
802     var ss = switches[f];
803     if (ss.state) continue;
804     var x = ss.x, y = ss.y;
805     if (x+7 >= willyX && y+7 >= willyY && x < willyX+8 && y < willyY+16) ss.state = 1;
806   }
810 function checkSPG () {
811   if (!spg) return;
812   for (var f = 0; f < spg.length; f++) {
813     var x = spg[f].x*8, y = spg[f].y*8;
814     if (x < 0 || y < 0) break;
815     if (x+7 >= willyX && x < willyX+8 && y+7 >= willyY && y < willyY+16) return true;
816   }
817   return false;
822 /////////////////////////////////////////////////////////////////////////
823 // doers
825 function spgCheckMonster (x, y) {
826   x *= 8; y *= 8;
827   for (var f = 0; f < curMonsters.length; f++) {
828     var cm = curMonsters[f];
829     var mx = cm.x, my = cm.y;
830     if (x+7 >= mx && x < mx+15 && y+7 >= my && y < my+15) return true;
831   }
832   return false;
836 function buildSPG () {
837   var x = 23, y = 0, done = false;
838   var dir = 0, idx = 0;
840   if (!spg) spg = [];
842   function addXY (x, y) {
843     idx++;
844     while (spg.length < idx) spg.push({x:-1, y:-1});
845     spg[idx-1].x = x; spg[idx-1].y = y;
846   }
848   do {
849     var blockhit = curMap[y*32+x];
850     var robohit = spgCheckMonster(x, y);
852     if (blockhit && robohit) {
853       addXY(-1, -1);
854       done = true;
855     } else if (!blockhit && robohit) {
856       if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
857         spg[idx-1].x = spg[idx-1].y = -1;
858         done = true;
859       } else addXY(x, y);
860       dir ^= 1;
861     } else if (!blockhit && !robohit) {
862       addXY(x, y);
863     } else if (blockhit && !robohit) {
864       if (idx && spg[idx-1].x == x && spg[idx-1].y == y) {
865         spg[idx-1].x = spg[idx-1].y = -1;
866         done = true;
867       }
868       dir ^= 1;
869     }
871     if (!blockhit) {
872       if (!dir) {
873         y++;
874         blockhit = curMap[y*32+x];
875         if (y == 15 || blockhit) done = true;
876       } else {
877         x--;
878         blockhit = curMap[y*32+x];
879         if (x == 0 || blockhit) done = true;
880       }
881     } else {
882       if (!dir) { x--; if (!x) done = true; }
883       else { y++; if (++y == 15) done = true; }
884     }
885   } while (!done);
886   addXY(-1, -1);
890 function doKeys () {
891   if (!keysLeft) return;
892   for (var f = curKeys.length-1; f >= 0; f--) {
893     var ki = curKeys[f];
894     if (!ki.s) continue;
895     var kx = ki.x, ky = ki.y;
896     if (kx+7 >= willyX && kx < willyX+10 && ky+7 >= willyY && ky < willyY+16) {
897       ki.s = false; keysLeft--;
898       score += 100;
899     }
900   }
904 function doCrumb () {
905   if (willyY&7) return false;
906   for (var f = 0; f <= 8; f += 8) {
907     var x = willyX+f, y = willyY+16;
908     var b = getBlockAt(x, y);
909     if (b == 4) b = 8;
910     if (b < 8) continue;
911     x >>= 3; y >>= 3;
912     if (++b > 15) b = 0;
913     curMap[y*32+x] = b;
914     eraseRect(x*8, y*8, 8, 8);
915   }
919 function doMonsters () {
920   var l = curMonsters.length;
921   for (var f = 0; f < l; f++) {
922     var m = curMonsters[f];
923     //if (m.x < 0 || m.y < 0) continue; // we can't kill monsters now
924     if (m.vert) {
925       var y = m.y, spd = m.speed;
926       if (m.dir != 0) {
927         // up
928         y -= spd;
929         if (y < m.min || y < 0) y += spd, m.dir = 0;
930       } else {
931         // down
932         y += spd;
933         if (y > m.max) y -= spd, m.dir = 1;
934       }
935       m.y = y;
936       m.anim = (m.anim+1)&3;
937     } else {
938       var x = m.x, spd = (2>>m.speed);
939       if (m.dir != 0) {
940         // left
941         x -= spd;
942         if (x < m.min) m.dir = 0, x += spd;
943       } else {
944         // right
945         x += spd;
946         if (x > m.max+6) m.dir = 1, x -= spd;
947       }
948       m.x = x;
949     }
950   }
951   // Eugene
952   if (eugene) {
953     if (!keysLeft) {
954       // no keys, Eugene tries to block the exit
955       eugene.ink = (eugene.ink+1)&7;
956       eugene.y += eugene.y<eugene.max?1:0;
957     } else {
958       if (eugene.dir != 0) {
959         // up
960         eugene.y--;
961         if (eugene.y < eugene.min) eugene.dir = 0, eugene.y++;
962       } else {
963         // down
964         eugene.y++;
965         if (eugene.y > eugene.max) eugene.dir = 1, eugene.y--;
966       }
967     }
968   }
969   // Kong
970   if (kong) {
971     switch (kong.falling) {
972       case 1: // just started
973         curMap[2*32+15] = 0;
974         curMap[2*32+16] = 0;
975         eraseRect(16, 120, 16, 8);
976         kong.falling = 2;
977         break;
978       case 2:
979         kong.ink = 4; kong.frame += 2;
980         kong.falling = 3;
981         break;
982       case 3:
983         kong.y += 4;
984         if (kong.y >= kong.max) kong = false, score += 100;
985         if (!kong.delay) kong.delay = 4, kong.frame = ((kong.frame-1)&1)+2; else kong.delay--;
986         break;
987       default:
988         if (!kong.delay) kong.delay = 8, kong.frame = (kong.frame+1)&1; else kong.delay--;
989     }
990   }
991   // SkyLab
992   if (skylab) {
993     for (var f = 0; f < skylab.length; f++) {
994       var sk = skylab[f];
995       switch (sk.m) {
996         case 0:
997           sk.y += sk.s;
998           if (sk.y > sk.max) {
999             sk.y = sk.max;
1000             sk.m = 1;
1001             sk.frame++;
1002           }
1003           break;
1004         case 1:
1005           sk.frame++;
1006           if (sk.frame == 7) sk.m = 2;
1007           break;
1008         case 2:
1009           sk.p = (sk.p+1)&3;
1010           sk.x = skylabCoords[f][sk.p].x;
1011           sk.y = skylabCoords[f][sk.p].y;
1012           sk.frame = sk.m = 0;
1013           break;
1014       }
1015     }
1016   }
1020 function doSwitch () {
1021   // hole?
1022   if (holeLen && switches.length && switches[0].state) {
1023     if (holeLen < 0) {
1024       curMonsters[1].max += 24;
1025       holeLen = 0;
1026     } else {
1027       holeY++;
1028       eraseRect(136, 88, 8, 16);
1029       ctx.fillStyle = mainPal[curRoom.paper];
1030       ctx.fillRect(136, 88+16-holeY, 8, holeY);
1031       if (holeY == 16) {
1032         curMap[11*32+17] = 0;
1033         curMap[12*32+17] = 0;
1034         holeLen = -1;
1035       }
1036     }
1037   }
1038   // Kong?
1039   if (kong && !kong.falling && switches.length > 1 && switches[1].state) kong.falling = 1;
1043 function doWillyLeft () {
1044   if (willyDir > 0) { willyDir = -1; return true; }
1045   var xx = willyX-2;
1046   var b0 = getBlockAt(xx, willyY);
1047   var b1 = getBlockAt(xx, willyY+8);
1048   var b2 = willyY&7?getBlockAt(xx, willyY+16):b1;
1049   if (b0 == 3 || b1 == 3 || b2 == 3) return false;
1050   willyX -= 2;
1051   if (willyX < 0) willyX += 240;
1052   willyLastMoveDir = -1;
1053   return true;
1057 function doWillyRight () {
1058   if (willyDir < 0) { willyDir = 1; return true; }
1059   if (willyX > 245) return false;
1060   var xx = willyX+10;
1061   var b0 = getBlockAt(xx, willyY);
1062   var b1 = getBlockAt(xx, willyY+8);
1063   var b2 = willyY&7?getBlockAt(xx, willyY+16):b1;
1064   if (b0 == 3 || b1 == 3 || b2 == 3) return false;
1065   willyX += 2;
1066   if (willyX > 240) willyX -= 240;
1067   willyLastMoveDir = 1;
1068   return true;
1072 function doWillyJump () {
1073   if (!willyJump) return;
1074   willyY += willyJ[willyJump];
1075   var x = willyX, mv = false;
1076   if (willyJumpDir < 0) mv = doWillyLeft(); else if (willyJumpDir > 0) mv = doWillyRight();
1077   if (willyJump < 9) {
1078     willyFall = 0;
1079     // up
1080     var b0 = getBlockAt(x, willyY);
1081     var b1 = getBlockAt(x+8, willyY);
1082     if (b0 == 3 || b1 == 3) {
1083       // headboom! (apstenu %-)
1084       willyX = x; willyY -= willyJ[willyJump];
1085       willyJump = 0; // enough flying
1086       return;
1087     }
1088   } else {
1089     // down
1090     if (willyJump > 12) willyFall += willyJ[willyJump];
1091     if ((willyY&7) == 0) {
1092       var b0 = getBlockAt(willyX, willyY+16);
1093       var b1 = getBlockAt(willyX+8, willyY+16);
1094       if (b0 || b1) {
1095   if (b0 == 3 || b1 == 3) willyX = x;
1096         willyFall = 0; // can't fall too deep while jumping
1097         willyJump = 0; // enough flying
1098         if (b0 == 7 || b1 == 7) willyStall = 1; // conveyor?
1099         return;
1100       }
1101     }
1102   }
1103   willyJump++;
1104   if (willyJump > 18) willyJump = 0;
1108 function doWillyActions () {
1109   if (willyDead) return;
1110   checkSwitch();
1111   if (isWillyInDeadly()) { willyDead = true; return; }
1112   if (!DEBUG_COLDET) {
1113     if (checkMonsters()) { willyDead = true; return; }
1114   }
1116   var wasJump = false;
1117   if (willyJump) {
1118     willyLastMoveDir = 0;
1119     doWillyJump();
1120     if (willyJump) return;
1121     wasJump = true;
1122   }
1124   var falling = isWillyFalling();
1125   if (!kDown && falling) {
1126     willyConv = willyStall = willyLastMoveDir = 0;
1127     willyFall += 4;
1128     willyY += 4;
1129     if (willyY > 112) willyY -= 112;
1130     return;
1131   }
1133   if (!falling && willyFall > 34) willyDead = true; // too high!
1134   var lfall = willyFall;
1135   willyFall = 0;
1137   if (willyDead) return;
1139   var dx = kLeft?-1:kRight?1:0;
1140   if (isWillyOnConv()) {
1141     var cdir = conv.d?1:-1;
1142     //dx==cdir,!dx,lastmove==cdir
1143     if (willyLastMoveDir == cdir || dx == cdir || !dx) willyConv = cdir, willyStall = 0; // was moving in conv. dir or standing
1144     if (!willyConv) {
1145       // Willy just steps on the conveyor, and Willy walking to the opposite side
1146       //opera.postError("cdir="+cdir+"; wj="+wasJump+"; lmd="+willyLastMoveDir+"; lf="+lfall+"; ws="+willyStall);
1147       willyStall = 0;
1148       if (wasJump && willyLastMoveDir == -cdir) willyConv = dx; // from jump, can do opposite
1149       else {
1150         willyConv = dx;
1151         if (lfall > 0 || !willyLastMoveDir) dx = 0, willyStall = 1; // lands on conveyor, not from jump
1152       }
1153     } else {
1154       // Willy was on conveyor
1155       dx = willyStall?0:willyConv;
1156     }
1157   } else willyConv = willyStall = 0;
1159   //if (willyConv) dx = willyConv;
1160   if (kUp && !wasJump) {
1161     willyConv = willyStall = willyLastMoveDir = 0;
1162     willyJumpDir = dx;
1163     willyJump = 1;
1164     doWillyJump();
1165     return;
1166   }
1167   if (kDown) willyY -= 8;
1168   willyLastMoveDir = 0;
1169   if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
1173 /////////////////////////////////////////////////////////////////////////
1174 // game ticking
1176 function frameDraw () {
1177   drawSPG(true);
1178   drawConveyor();
1179   drawKeys();
1180   drawMonsters();
1181   drawSwitch();
1182   if (keysLeft) drawExit();
1183   drawWilly();
1184   if (!keysLeft) drawExit();
1188 var startRoom; // forward declaration
1189 function gameStep () {
1190   gameRunning = true;
1191   if (dbgSingleStep && !dbgNF) return
1192   dbgNF = false;
1193   frameNo++;
1194   conv.frame += conv.d?-1:1;
1195   if (conv.frame < 0) conv.frame = 3; else if (conv.frame > 3) conv.frame = 0;
1197   // must erase SPG, Willy and monsters here -- before coords change
1198   drawSPG(false);
1199   eraseMonsters();
1200   eraseRect(willyX&248, willyY, 16, 16);
1201   if (!willyJump) doCrumb();
1202   doMonsters();
1203   doWillyActions();
1204   if (DEBUG_COLDET) {
1205     if (checkMonsters()) {
1206       dbgSingleStep = true; dbgNF = false;
1207       dbgColDetected = true;
1208     } else dbgColDetected = false;
1209   }
1211   doKeys();
1212   doSwitch();
1213   if (spg) buildSPG();
1214   frameDraw();
1216   if (willyDead) {
1217     // dead %-(
1218     gameRunning = false;
1219     if (gameTID) clearInterval(gameTID), gameTID = null;
1220     blockPage();
1221     message("you are dead!");
1222     setTimeout(startRoom, 1000);
1223     return;
1224   }
1226   if (checkExit()) {
1227     // room complete!
1228     gameRunning = false;
1229     if (gameTID) clearInterval(gameTID), gameTID = null;
1230     blockPage();
1231     message(curRoom.title+" complete!");
1232     curRoomNo++;
1233     if (curRoomNo >= me.lsets[curLSet].rooms.length) {
1234       curRoomNo = 0;
1235       curLSet++;
1236       if (curLSet >= me.lsets.length) curLSet = 0;
1237     }
1238     setTimeout(startRoom, 2000);
1239     return;
1240   }
1244 /////////////////////////////////////////////////////////////////////////
1245 // game initializers
1247 function initRoom () {
1248   gameRunning = false;
1249   frameNo = 0;
1250   // copy map
1251   curMap = []; curRoom = me.lsets[curLSet].rooms[curRoomNo];
1252   for (var f = 0; f < 512; f++) {
1253     var c = curRoom.map[f];
1254     if (c == 4) c = 8; // 'crumb'
1255     else if (c < 1 || c > 7) c = 0; // emptyness
1256     curMap.push(c);
1257   }
1258   // copy keys
1259   conv = curRoom.conveyor; conv.frame = 0;
1260   var k = curRoom.keys;
1261   curKeys = [];
1262   for (var f = 0; f < k.info.length; f++) {
1263     var ki = k.info[f];
1264     if (ki.s && ki.x >= 0 && ki.y >= 0) curKeys.push({x:ki.x, y:ki.y, s:true});
1265   }
1266   keysLeft = curKeys.length;
1267   // copy monsters
1268   curMonsters = [];
1269   var mns = curRoom.enemies;
1270   for (var f = 0; f < mns.length; f++) {
1271     var mm = mns[f];
1272     if (mm.x < 0 || mm.y < 0) continue;
1273     curMonsters.push({
1274       vert:mm.vert, x:mm.x, y:mm.y, min:mm.min, max:mm.max, anim:mm.anim, dir:mm.d, speed:mm.s,
1275       gfx:mm.gfx, flip:mm.flip, ink:mm.ink
1276     });
1277   }
1278   // Eugene?
1279   if (curRoomNo == 4) {
1280     cacheEugene();
1281     eugene = { x:120, y:1, dir:0, min:1, max:87, ink:6 };
1282   } else eugene = eugeneSpr = false;
1283   // Kong?
1284   if (curRoomNo == 7 || curRoomNo == 11) {
1285     cacheKong();
1286     kong = { x:120, y:0, max:104, frame:0, ink:2, m:0 };
1287     holeLen = 2*8; holeY = 0;
1288   } else kong = kongSpr = holeLen = holeY = false;
1289   // SkyLab?
1290   if (curRoomNo == 13) {
1291     curMonsters = [];
1292     cacheSkyLab();
1293     skylab = [
1294       {p:0, s:4, ink:6, max:72, m:0, frame:0},
1295       {p:2, s:3, ink:5, max:56, m:0, frame:0},
1296       {p:1, s:1, ink:4, max:32, m:0, frame:0}
1297     ];
1298     for (var f = 0; f < 3; f++) {
1299       var s = skylab[f];
1300       s.x = skylabCoords[f][skylab[f].p].x;
1301       s.y = skylabCoords[f][skylab[f].p].y;
1302     }
1303   } else skylab = skylabSpr = false;
1304   // solar power generator
1305   spg = false;
1306   if (curRoomNo == 18) buildSPG();
1307   // switches
1308   switches = [];
1309   for (var f = 0; f < curRoom.switches.length; f++) {
1310     var ss = curRoom.switches[f];
1311     if (ss.x < 0 || ss.y < 0 || !ss.s) continue;
1312     switches.push({ x:ss.x, y:ss.y, state: ss.s-1 });
1313   }
1314   //opera.postError(switches.length);
1315   // caching
1316   cacheBrickImages();
1317   cacheConvImages();
1318   cacheExit();
1319   cacheKeys();
1320   cacheMonsters();
1321   cacheSwitch();
1322   // init willy
1323   willyX = curRoom.willy.x; willyY = curRoom.willy.y;
1324   willyDir = curRoom.willy.sd>0?-1:1;
1325   willyJump = willyFall = willyConv = willyStall = 0;
1326   willyDead = false;
1327   willyLastMoveDir = 0;
1328   // reset keys
1329   kLeft = kRight = kJump = kUp = kDown = false;
1330   dbgNF = dbgSingleStep = false;
1334 startRoom = function () {
1335   gameRunning = false;
1336   blockPage();
1337   message("loading");
1338   setTimeout(function () {
1339     initRoom();
1340     drawRoom();
1341     frameDraw();
1342     message("entering "+curRoom.title);
1343     setTimeout(function () {
1344       removeMessage();
1345       unblockPage();
1346       gameTID = setInterval(gameStep, 50);
1347     }, 2000);
1348   }, 1);
1352 /////////////////////////////////////////////////////////////////////////
1353 // controls, etc
1355 function kbdTogglePause () {
1356   if (!gameRunning) return;
1357   if (gameTID) clearInterval(gameTID), gameTID = null;
1358   else gameTID = setInterval(gameStep, 50);
1360   if (!gameTID) { blockPage(); message("paused"); } else { removeMessage(); unblockPage(); }
1361   //kLeft = kRight = kJump = false;
1365 kbdActions = [
1366   { key: ("P").charCodeAt(0), action: kbdTogglePause },
1367   { key: ("K").charCodeAt(0), action: function () { keysLeft = 0; } },
1368   { key: ("N").charCodeAt(0), action: function () { keysLeft = 0; willyX = curRoom.exit.x; willyY = curRoom.exit.y; } },
1369   { key: ("R").charCodeAt(0), action: function () { willyDead = true; } },
1370   { key: ("S").charCodeAt(0), action: function () { dbgSingleStep = !dbgSingleStep; dbgNF = false; } },
1371   { key: ("C").charCodeAt(0), action: function () { DEBUG_COLDET = !DEBUG_COLDET; dbgColDetected = false; } },
1372   { key: (" ").charCodeAt(0), action: function () { dbgNF = true; }, upAction: function () { dbgNF = false; } },
1374   { key: 40, action: function () { kDown = true; }, upAction: function () { kDown = false; } },
1375   { key: 37, action: function () { kLeft = true; }, upAction: function () { kLeft = false; } },
1376   { key: 38, action: function () { kUp = true; }, upAction: function () { kUp = false; } },
1377   { key: 39, action: function () { kRight = true; }, upAction: function () { kRight = false; } },
1379   { key: 65500 }
1383 function kbdFindKey (evt) {
1384   for (var f = kbdActions.length-1; f >= 0; f--) {
1385     var k = kbdActions[f];
1386     if (k.key != evt.keyCode) continue;
1387     if (typeof(k.ctrl) == "boolean" && k.ctrl != evt.ctrlKey) continue;
1388     if (typeof(k.alt) == "boolean" && k.alt != evt.altKey) continue;
1389     if (typeof(k.shift) == "boolean" && k.shift != evt.shiftKey) continue;
1390     return k;
1391   }
1392   return false;
1396 function onKeyDown (evt) {
1397   if (evt.ctrlKey) kJump = true;
1398   var k = kbdFindKey(evt);
1399   if (!k) return true;
1400   evt.preventDefault();
1401   k.down = true;
1402   if (k.action) k.action();
1403   return false;
1407 function onKeyUp (evt) {
1408   if (evt.ctrlKey) kJump = false;
1409   var k = kbdFindKey(evt);
1410   if (!k) return true;
1411   evt.preventDefault();
1412   k.down = false;
1413   if (k.upAction) k.upAction();
1414   return false;
1418 function onKeyPress (evt) {
1419   //opera.postError("kp: "+evt.keyCode);
1420   if (!gameTID) return true;
1421   evt.preventDefault();
1422   return false;
1426 function hookKeys () {
1427   for (var f in kbdActions) kbdActions[f].down = false;
1428   document.addEventListener("keydown", onKeyDown, false);
1429   document.addEventListener("keyup", onKeyUp, false);
1430   document.addEventListener("keypress", onKeyPress, false);
1434 /* prepare images */
1435 function loadData () {
1436   blockPage();
1437   message("initializing");
1438   setTimeout(function () {
1439     convertPalette();
1440     buildWilly();
1441     finalSpr = buildImageMasked(me.data.final, 0, 256, 64);
1442     sunSpr = buildImageMasked(me.data.sun, 0, 24, 16);
1443     hookKeys();
1444     gameTID = false;
1445     score = 0;
1446     curLSet = 0;
1447     curRoomNo = 0;
1448     setTimeout(startRoom, 10);
1449   }, 10);
1453 function initCanvas () {
1454   canvas = document.createElement("canvas");
1455   canvas.setAttribute("width", 512);
1456   canvas.setAttribute("height", 256);
1457   ctx = canvas.getContext("2d");
1458   ctx.scale(2, 2);
1460   ctx.fillStyle = "#202020";
1461   //ctx.fillStyle = "rgba(32, 32, 32, 50)";
1462   ctx.fillRect(0, 0, 320, 200);
1464   mainX = Math.ceil((window.innerWidth-512)/2);
1465   mainY = Math.ceil((window.innerHeight-256)/2);
1467   //mainX = 20; mainY = 20;
1469   canvas.style.position = "fixed";
1470   canvas.style.left = mainX+"px";
1471   canvas.style.top = mainY+"px";
1473   //window.onresize = function () {
1474   window.addEventListener("resize", function () {
1475     mainX = Math.ceil((window.innerWidth-512)/2);
1476     mainY = Math.ceil((window.innerHeight-256)/2);
1477     canvas.style.position = "fixed";
1478     canvas.style.left = mainX+"px";
1479     canvas.style.top = mainY+"px";
1480   }, false);
1484 this.run = function () {
1485   gameRunning = false;
1486   initCanvas();
1487   setTimeout(function () {
1488     document.body.appendChild(canvas);
1489     setTimeout(loadData, 10);
1490   }, 10);
1494 return this;
1497 window.miner = new ManicMiner();