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];
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}]
19 var scrollText = ". . . . . . . . . . . "+
21 "\u00a9 BUG-BYTE Ltd. 1983 . . "+
22 "By Matthew Smith . . . "+
24 "Guide Miner Willy through 20 lethal caverns "+
28 function unpackRoom (roomS) {
29 var room = {}, pos = 0;
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;
38 function getIP (arr) {
43 function getGIP (arr) {
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++; }
55 // params and Willy start
57 room.willy = { x:getNum(), y:getNum(), sd:getNum() };
58 room.exit = { gfx:getNum(), x:getNum(), y:getNum() };
59 room.border = getNum();
65 var t = {}; getGIP(t);
66 room.platforms.push(t);
68 room.wall = {}; getGIP(room.wall);
69 room.crumb = {}; getGIP(room.crumb);
74 var t = {}; getGIP(t);
75 room.deadlies.push(t);
78 room.conveyor = { x:getNum(), y:getNum(), d:getNum(), l:getNum() };
79 getGIP(room.conveyor);
82 //LogPrint("keys: "+cnt);
83 room.keys = { gfx: getNum(), info:[] };
84 for (var f = 0; f < 5; f++) {
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);
93 for (var f = 0; f < 2; f++) {
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);
102 for (var f = 0; f < 8; f++) {
106 e.vert = roomS.charCodeAt(pos++)=="V";
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 };
119 room.enemies.push(e);
122 room.title = roomS.substr(pos);
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+
131 * modified by Ketmar // Avalon Group
133 function lzwUnpack (str) {
135 var data = (str+"").split("");
136 var currChar = data[0];
137 var oldPhrase = currChar;
138 var out = [currChar];
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);
146 currChar = phrase.charAt(0);
147 dict[code] = oldPhrase+currChar;
151 var s = out.join("");
153 for (var f = 0; f < s.length; f++) res.push(s.charCodeAt(f));
157 var me = this, canvas, ctx, mainX, mainY, mainPal;
159 this.setData = function (data) {
164 if (typeof(v) == "string") data[k] = lzwUnpack(v);
167 this.addLevelSet = function (lset) {
172 for (var f = 0; f < r.length; f++) lset.rooms.push(unpackRoom(r[f]));
174 this.lsets.push(lset);
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;
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;
196 var dbgNF, dbgSingleStep;
199 function removeMessage (force) {
200 var msg = document.getElementById("k8PageMesssage");
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);
213 function message (msgs, clickToRemove) {
214 while (!msgs) msgs = rndMessages[Math.ceil(Math.random()*rndMessages.length)];
215 var msg = document.getElementById("k8PageMesssage");
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);
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";
263 msg.onclick = function () { removeMessage(true); };
264 msg.k8ClickToRemove = true;
266 msg.onclick = function () {};
267 if (msg.k8ClickToRemove) delete msg.k8ClickToRemove;
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";
276 window.addEventListener("resize", fnrs, false);
280 function blockPage () {
281 var i = document.getElementById("k8PageBlocker");
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");
296 i.style.display = "none";
297 setTimeout(function () { i.parentNode.removeChild(i); }, 1);
299 ///////////////////////////////////////////////////////////////////////////////////////////////////////
302 /////////////////////////////////////////////////////////////////////////
305 function convertPalette () {
306 function toHC (n) { return (n>15?"":"0")+(n.toString(16)); }
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++];
324 dc.fillStyle = mainPal[c];
325 dc.fillRect(x, y, 1, 1);
328 img = document.createElement("img");
329 img.setAttribute("width", w);
330 img.setAttribute("height", h);
331 img.src = cc.toDataURL();
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++];
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);
352 img = document.createElement("img");
353 img.setAttribute("width", w);
354 img.setAttribute("height", h);
355 img.src = cc.toDataURL();
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++];
371 dc.fillStyle = mainPal[c+ink];
372 dc.fillRect(x, y, 1, 1);
375 img = document.createElement("img");
376 img.setAttribute("width", w);
377 img.setAttribute("height", h);
378 img.src = cc.toDataURL();
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);
398 img = document.createElement("img");
399 img.setAttribute("width", w*rep);
400 img.setAttribute("height", h);
401 img.src = cc.toDataURL();
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];
417 dc.fillStyle = mainPal[c];
418 dc.fillRect(f*16+x, y, 1, 1);
420 dc.fillStyle = mainPal[15];
421 dc.fillRect(f*16+x, y+16, 1, 1);
425 willySpr = document.createElement("img");
426 willySpr.setAttribute("width", 16*16);
427 willySpr.setAttribute("height", 16+16);
428 willySpr.src = cc.toDataURL();
432 /////////////////////////////////////////////////////////////////////////
435 function cacheBrickImages () {
436 function buildBrickImage (brk, skip) {
437 return buildImageBrk(me.data.blocks, brk.gfx*64, brk.ink, curRoom.paper, skip||0);
440 for (var f = 0; f <= 7; 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;
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));
466 function cacheExit () {
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 () {
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;
487 for (var c = 0; c <= 3; c++) {
488 var n = (m.gfx+c)*256;
490 r.push(buildImageMaskedInk(me.data.vrobo, n, m.ink-1, 16, 16));
493 for (var c = 0; c <= m.anim>>1; c++) {
494 var n = (m.gfx+c)*256;
496 r.push(buildImageMaskedInk(me.data.hrobo, n, m.ink-1, 16, 16));
499 r.push(buildImageMaskedInk(me.data.hrobo, n, m.ink-1, 16, 16));
502 monsterOfsCache.push(cc);
503 monsterCache.push(r);
508 function cacheEugene () {
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 () {
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));
526 function cacheSkyLab () {
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 () {
536 for (var f = 0; f < 2; f++) {
537 switchesSpr.push(buildImageBrk(me.data.switches, f*64, curRoom.platforms[1].ink, curRoom.paper, 0));
542 /////////////////////////////////////////////////////////////////////////
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);
562 function drawRoom () {
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);
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);
581 function drawConveyor () {
582 if (conv.l < 1) 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;
593 if (br > 15) br = 31-br;
596 ctx.drawImage(exitCache[br], 0, 0, 16, 16, curRoom.exit.x, curRoom.exit.y, 16, 16);
600 function drawKeys () {
601 //if (!keysLeft) return;
603 if (ff >= 8) ff = 15-ff;
604 var img = keyCache[ff];
605 for (var f = curKeys.length-1; f >= 0; f--) {
607 if (ki.x < 0 || ki.y < 0) continue;
608 eraseRect(ki.x, ki.y, 8, 8);
610 ctx.drawImage(img, 0, 0, 8, 8, ki.x&248, ki.y, 8, 8);
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);
623 if (eugene) eraseRect(eugene.x, eugene.y, 16, 16);
624 if (kong) eraseRect(kong.x, kong.y, 16, 16);
626 for (var f = 2; f >= 0; f--) eraseRect(skylab[f].x, skylab[f].y, 16, 16);
631 function drawMonsters () {
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];
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);
646 ctx.drawImage(eugeneSpr[curLSet*8+eugene.ink], 0, 0, 16, 16, eugene.x, eugene.y, 16, 16);
649 ctx.drawImage(kongSpr[kong.frame*8+kong.ink], 0, 0, 16, 16, kong.x, kong.y, 16, 16);
652 for (var f = 0; f < skylab.length; f++) {
654 ctx.drawImage(skylabSpr[sk.frame*8+sk.ink], 0, 0, 16, 16, sk.x, sk.y, 16, 16);
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);
669 function drawSPG (noerase) {
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);
681 function drawWilly () {
682 var willyPos = (willyX&15)>>1;
683 if (willyDir < 0) willyPos += 8;
685 if (DEBUG_COLDET && dbgColDetected) wy = 16;
686 ctx.drawImage(willySpr, willyPos*16, wy, 16, 16, willyX&248, willyY, 16, 16);
690 /////////////////////////////////////////////////////////////////////////
694 function getBlockAt (x, y, simplify) {
696 if (x < 0 || y < 0 || x > 31 || y > 15) return 0; // empty
697 var b = curMap[y*32+x];
698 if (simplify === true) {
700 else if (b > 7) b = 4;
701 else if (b == 6) b = 5;
702 else if (b == 2) b = 1;
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;
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;
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) {
751 if (rx < -15 || rx > 15 || ry < -15 || ry > 15) return false;
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];
762 var warr = me.data.willy, wptr = ((willyX&15)>>1)*256+(willyDir<0?2048:0);
763 // check for collision
765 for (x = 255; x >= 0; x--, wptr++, mp++) if (warr[wptr] && mpcGrid[mp]) return true;
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
776 if (pixelCheckMonster(m.x, m.y, me.data.vrobo, cc[m.anim])) return true;
778 if (pixelCheckMonster(m.x&248, m.y, me.data.hrobo, cc[((m.x&m.anim)&0xfe)+m.dir])) return true;
783 if (pixelCheckMonster(eugene.x, eugene.y, me.data.eugene, 256*curLSet)) return true;
787 if (pixelCheckMonster(kong.x, kong.y, me.data.kong, 256*kong.frame)) return true;
791 for (var f = 2; f >= 0; f--) {
793 if (pixelCheckMonster(sk.x, sk.y, me.data.sky, 256*sk.frame)) return true;
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;
810 function checkSPG () {
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;
822 /////////////////////////////////////////////////////////////////////////
825 function spgCheckMonster (x, y) {
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;
836 function buildSPG () {
837 var x = 23, y = 0, done = false;
838 var dir = 0, idx = 0;
842 function addXY (x, y) {
844 while (spg.length < idx) spg.push({x:-1, y:-1});
845 spg[idx-1].x = x; spg[idx-1].y = y;
849 var blockhit = curMap[y*32+x];
850 var robohit = spgCheckMonster(x, y);
852 if (blockhit && robohit) {
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;
861 } else if (!blockhit && !robohit) {
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;
874 blockhit = curMap[y*32+x];
875 if (y == 15 || blockhit) done = true;
878 blockhit = curMap[y*32+x];
879 if (x == 0 || blockhit) done = true;
882 if (!dir) { x--; if (!x) done = true; }
883 else { y++; if (++y == 15) done = true; }
891 if (!keysLeft) return;
892 for (var f = curKeys.length-1; f >= 0; f--) {
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--;
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);
914 eraseRect(x*8, y*8, 8, 8);
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
925 var y = m.y, spd = m.speed;
929 if (y < m.min || y < 0) y += spd, m.dir = 0;
933 if (y > m.max) y -= spd, m.dir = 1;
936 m.anim = (m.anim+1)&3;
938 var x = m.x, spd = (2>>m.speed);
942 if (x < m.min) m.dir = 0, x += spd;
946 if (x > m.max+6) m.dir = 1, x -= spd;
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;
958 if (eugene.dir != 0) {
961 if (eugene.y < eugene.min) eugene.dir = 0, eugene.y++;
965 if (eugene.y > eugene.max) eugene.dir = 1, eugene.y--;
971 switch (kong.falling) {
972 case 1: // just started
975 eraseRect(16, 120, 16, 8);
979 kong.ink = 4; kong.frame += 2;
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--;
988 if (!kong.delay) kong.delay = 8, kong.frame = (kong.frame+1)&1; else kong.delay--;
993 for (var f = 0; f < skylab.length; f++) {
1006 if (sk.frame == 7) sk.m = 2;
1010 sk.x = skylabCoords[f][sk.p].x;
1011 sk.y = skylabCoords[f][sk.p].y;
1012 sk.frame = sk.m = 0;
1020 function doSwitch () {
1022 if (holeLen && switches.length && switches[0].state) {
1024 curMonsters[1].max += 24;
1028 eraseRect(136, 88, 8, 16);
1029 ctx.fillStyle = mainPal[curRoom.paper];
1030 ctx.fillRect(136, 88+16-holeY, 8, holeY);
1032 curMap[11*32+17] = 0;
1033 curMap[12*32+17] = 0;
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; }
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;
1051 if (willyX < 0) willyX += 240;
1052 willyLastMoveDir = -1;
1057 function doWillyRight () {
1058 if (willyDir < 0) { willyDir = 1; return true; }
1059 if (willyX > 245) return false;
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;
1066 if (willyX > 240) willyX -= 240;
1067 willyLastMoveDir = 1;
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) {
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
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);
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?
1104 if (willyJump > 18) willyJump = 0;
1108 function doWillyActions () {
1109 if (willyDead) return;
1111 if (isWillyInDeadly()) { willyDead = true; return; }
1112 if (!DEBUG_COLDET) {
1113 if (checkMonsters()) { willyDead = true; return; }
1116 var wasJump = false;
1118 willyLastMoveDir = 0;
1120 if (willyJump) return;
1124 var falling = isWillyFalling();
1125 if (!kDown && falling) {
1126 willyConv = willyStall = willyLastMoveDir = 0;
1129 if (willyY > 112) willyY -= 112;
1133 if (!falling && willyFall > 34) willyDead = true; // too high!
1134 var lfall = willyFall;
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
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);
1148 if (wasJump && willyLastMoveDir == -cdir) willyConv = dx; // from jump, can do opposite
1151 if (lfall > 0 || !willyLastMoveDir) dx = 0, willyStall = 1; // lands on conveyor, not from jump
1154 // Willy was on conveyor
1155 dx = willyStall?0:willyConv;
1157 } else willyConv = willyStall = 0;
1159 //if (willyConv) dx = willyConv;
1160 if (kUp && !wasJump) {
1161 willyConv = willyStall = willyLastMoveDir = 0;
1167 if (kDown) willyY -= 8;
1168 willyLastMoveDir = 0;
1169 if (dx < 0) doWillyLeft(); else if (dx > 0) doWillyRight();
1173 /////////////////////////////////////////////////////////////////////////
1176 function frameDraw () {
1182 if (keysLeft) drawExit();
1184 if (!keysLeft) drawExit();
1188 var startRoom; // forward declaration
1189 function gameStep () {
1191 if (dbgSingleStep && !dbgNF) return
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
1200 eraseRect(willyX&248, willyY, 16, 16);
1201 if (!willyJump) doCrumb();
1205 if (checkMonsters()) {
1206 dbgSingleStep = true; dbgNF = false;
1207 dbgColDetected = true;
1208 } else dbgColDetected = false;
1213 if (spg) buildSPG();
1218 gameRunning = false;
1219 if (gameTID) clearInterval(gameTID), gameTID = null;
1221 message("you are dead!");
1222 setTimeout(startRoom, 1000);
1228 gameRunning = false;
1229 if (gameTID) clearInterval(gameTID), gameTID = null;
1231 message(curRoom.title+" complete!");
1233 if (curRoomNo >= me.lsets[curLSet].rooms.length) {
1236 if (curLSet >= me.lsets.length) curLSet = 0;
1238 setTimeout(startRoom, 2000);
1244 /////////////////////////////////////////////////////////////////////////
1245 // game initializers
1247 function initRoom () {
1248 gameRunning = false;
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
1259 conv = curRoom.conveyor; conv.frame = 0;
1260 var k = curRoom.keys;
1262 for (var f = 0; f < k.info.length; f++) {
1264 if (ki.s && ki.x >= 0 && ki.y >= 0) curKeys.push({x:ki.x, y:ki.y, s:true});
1266 keysLeft = curKeys.length;
1269 var mns = curRoom.enemies;
1270 for (var f = 0; f < mns.length; f++) {
1272 if (mm.x < 0 || mm.y < 0) continue;
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
1279 if (curRoomNo == 4) {
1281 eugene = { x:120, y:1, dir:0, min:1, max:87, ink:6 };
1282 } else eugene = eugeneSpr = false;
1284 if (curRoomNo == 7 || curRoomNo == 11) {
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;
1290 if (curRoomNo == 13) {
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}
1298 for (var f = 0; f < 3; f++) {
1300 s.x = skylabCoords[f][skylab[f].p].x;
1301 s.y = skylabCoords[f][skylab[f].p].y;
1303 } else skylab = skylabSpr = false;
1304 // solar power generator
1306 if (curRoomNo == 18) buildSPG();
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 });
1314 //opera.postError(switches.length);
1323 willyX = curRoom.willy.x; willyY = curRoom.willy.y;
1324 willyDir = curRoom.willy.sd>0?-1:1;
1325 willyJump = willyFall = willyConv = willyStall = 0;
1327 willyLastMoveDir = 0;
1329 kLeft = kRight = kJump = kUp = kDown = false;
1330 dbgNF = dbgSingleStep = false;
1334 startRoom = function () {
1335 gameRunning = false;
1338 setTimeout(function () {
1342 message("entering "+curRoom.title);
1343 setTimeout(function () {
1346 gameTID = setInterval(gameStep, 50);
1352 /////////////////////////////////////////////////////////////////////////
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;
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; } },
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;
1396 function onKeyDown (evt) {
1397 if (evt.ctrlKey) kJump = true;
1398 var k = kbdFindKey(evt);
1399 if (!k) return true;
1400 evt.preventDefault();
1402 if (k.action) k.action();
1407 function onKeyUp (evt) {
1408 if (evt.ctrlKey) kJump = false;
1409 var k = kbdFindKey(evt);
1410 if (!k) return true;
1411 evt.preventDefault();
1413 if (k.upAction) k.upAction();
1418 function onKeyPress (evt) {
1419 //opera.postError("kp: "+evt.keyCode);
1420 if (!gameTID) return true;
1421 evt.preventDefault();
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 () {
1437 message("initializing");
1438 setTimeout(function () {
1441 finalSpr = buildImageMasked(me.data.final, 0, 256, 64);
1442 sunSpr = buildImageMasked(me.data.sun, 0, 24, 16);
1448 setTimeout(startRoom, 10);
1453 function initCanvas () {
1454 canvas = document.createElement("canvas");
1455 canvas.setAttribute("width", 512);
1456 canvas.setAttribute("height", 256);
1457 ctx = canvas.getContext("2d");
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";
1484 this.run = function () {
1485 gameRunning = false;
1487 setTimeout(function () {
1488 document.body.appendChild(canvas);
1489 setTimeout(loadData, 10);
1497 window.miner = new ManicMiner();