├── README.md ├── boulderdash.css ├── boulderdash.js ├── caves.js ├── help.html ├── help ├── amoeba.gif ├── boulder.gif ├── butterfly.gif ├── diamond.gif ├── dirt.gif ├── exit.gif ├── firefly.gif ├── magic.gif ├── rockford.gif ├── space.gif ├── steel.gif └── wall.gif ├── images ├── down.png ├── sprites.png └── up.png ├── index.html └── stats.js /README.md: -------------------------------------------------------------------------------- 1 | Javascript BoulderDash 2 | ====================== 3 | 4 | An HTML5 Boulderdash Game 5 | 6 | * [play the game](https://jakesgordon.com/games/boulderdash/) 7 | * view the [source](https://github.com/jakesgordon/javascript-boulderdash) 8 | * read about [how it works](https://jakesgordon.com/writing/javascript-boulderdash/) 9 | 10 | >> _*SUPPORTED BROWSERS*: Chrome, Firefox, Safari, Opera and IE9+_ 11 | 12 | TODO 13 | ==== 14 | 15 | * performance - runtime resize of sprites to fit block size to avoid ctx.drawImage scaling performance hit 16 | 17 | FUTURE 18 | ====== 19 | 20 | * menu screen 21 | * demo mode 22 | * sound fx 23 | * music 24 | * touch support 25 | * level editor 26 | 27 | License 28 | ======= 29 | 30 | [MIT](http://en.wikipedia.org/wiki/MIT_License) license. 31 | 32 | 33 | -------------------------------------------------------------------------------- /boulderdash.css: -------------------------------------------------------------------------------- 1 | body { font-family: Arial, Helvetica, sans-serif; background-color: #EEE; } 2 | #boulderdash { position: relative; margin: 0 auto; } 3 | #description { color: #333; font-style: italic; padding-left: 0.5em; } 4 | #help { float: right; color: blue; font-size: 12pt; } 5 | #back { color: blue; font-size: 10pt; padding-left: 1em; } 6 | #keys { color: #333; font-style: italic; font-size: 0.8em; margin-top: 1em; } 7 | #stats { position: absolute; top: 0; right: -100px; } 8 | #canvas { display: block; margin: 0 auto; box-shadow: 10px 10px 10px #999; border: 2px solid black; } 9 | 10 | #header img { cursor: pointer; width: 1.25em; float: left; } 11 | #header img.disabled { cursor: default; opacity: 0.5; } 12 | #header img:active { opacity: 0.7; } 13 | 14 | #instructions { padding: 1em; font-size: 12pt; width: 40em; } 15 | #instructions ul li { padding: 1em 0.5em; list-style-type: none; vertical-align: middle; border-top: 1px solid #CCC; min-height: 2em; } 16 | #instructions ul li img { width: 32px; height: 32px; float: left; margin-right: 8px; border: 2px solid black; } 17 | #instructions #keys li { min-height: 0; border: 0; padding: 0; margin-left: 2em; list-style-type: disc; } 18 | 19 | @media screen and (min-width: 0px) and (min-height: 0px) { #boulderdash { font-size: 0.75em; width: 640px; } #canvas { width: 640px; height: 368px; } } /* 16px chunks */ 20 | @media screen and (min-width: 840px) and (min-height: 520px) { #boulderdash { font-size: 1.00em; width: 800px; } #canvas { width: 800px; height: 460px; } } /* 20px chunks */ 21 | @media screen and (min-width: 1000px) and (min-height: 602px) { #boulderdash { font-size: 1.25em; width: 960px; } #canvas { width: 960px; height: 552px; } } /* 24px chunks */ 22 | @media screen and (min-width: 1160px) and (min-height: 694px) { #boulderdash { font-size: 1.50em; width: 1120px; } #canvas { width: 1120px; height: 644px; } } /* 28px chunks */ 23 | @media screen and (min-width: 1320px) and (min-height: 786px) { #boulderdash { font-size: 1.75em; width: 1280px; } #canvas { width: 1280px; height: 736px; } } /* 32px chunks */ 24 | 25 | -------------------------------------------------------------------------------- /boulderdash.js: -------------------------------------------------------------------------------- 1 | Boulderdash = function() { 2 | 3 | //========================================================================= 4 | // GENERAL purpose constants and helper methods 5 | //========================================================================= 6 | 7 | var KEY = { ENTER: 13, ESC: 27, SPACE: 32, PAGEUP: 33, PAGEDOWN: 34, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 }; 8 | 9 | var Dom = { 10 | get: function(id) { return (id instanceof HTMLElement) ? id : document.getElementById(id); }, 11 | set: function(id, html) { Dom.get(id).innerHTML = html; }, 12 | disable: function(id, on) { Dom.get(id).className = on ? "disabled" : "" } 13 | } 14 | 15 | function timestamp() { return new Date().getTime(); }; 16 | function random(min, max) { return (min + (Math.random() * (max - min))); }; 17 | function randomInt(min, max) { return Math.floor(random(min,max)); }; 18 | function randomChoice(choices) { return choices[Math.round(random(0, choices.length-1))]; }; 19 | 20 | if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 21 | window.requestAnimationFrame = window.webkitRequestAnimationFrame || 22 | window.mozRequestAnimationFrame || 23 | window.oRequestAnimationFrame || 24 | window.msRequestAnimationFrame || 25 | function(callback, element) { 26 | window.setTimeout(callback, 1000 / 60); 27 | } 28 | } 29 | 30 | //========================================================================= 31 | // GAME constants and helpers 32 | //========================================================================= 33 | 34 | var DIR = { UP: 0, UPRIGHT: 1, RIGHT: 2, DOWNRIGHT: 3, DOWN: 4, DOWNLEFT: 5, LEFT: 6, UPLEFT: 7 }; 35 | var DIRX = [ 0, 1, 1, 1, 0, -1, -1, -1 ]; 36 | var DIRY = [ -1, -1, 0, 1, 1, 1, 0, -1 ]; 37 | 38 | function rotateLeft(dir) { return (dir-2) + (dir < 2 ? 8 : 0); }; 39 | function rotateRight(dir) { return (dir+2) - (dir > 5 ? 8 : 0); }; 40 | function horizontal(dir) { return (dir === DIR.LEFT) || (dir === DIR.RIGHT); }; 41 | function vertical(dir) { return (dir === DIR.UP) || (dir === DIR.DOWN); }; 42 | 43 | //------------------------------------------------------------------------- 44 | 45 | var OBJECT = { 46 | SPACE: { code: 0x00, rounded: false, explodable: false, consumable: true, sprite: { x: 0, y: 6 }, flash: { x: 4, y: 0 } }, 47 | DIRT: { code: 0x01, rounded: false, explodable: false, consumable: true, sprite: { x: 1, y: 7 } }, 48 | BRICKWALL: { code: 0x02, rounded: true, explodable: false, consumable: true, sprite: { x: 3, y: 6 } }, 49 | MAGICWALL: { code: 0x03, rounded: false, explodable: false, consumable: true, sprite: { x: 4, y: 6, f: 4, fps: 20 } }, 50 | PREOUTBOX: { code: 0x04, rounded: false, explodable: false, consumable: false, sprite: { x: 1, y: 6 } }, 51 | OUTBOX: { code: 0x05, rounded: false, explodable: false, consumable: false, sprite: { x: 1, y: 6, f: 2, fps: 4 } }, 52 | STEELWALL: { code: 0x07, rounded: false, explodable: false, consumable: false, sprite: { x: 1, y: 6 } }, 53 | FIREFLY1: { code: 0x08, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 9, f: 8, fps: 20 } }, 54 | FIREFLY2: { code: 0x09, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 9, f: 8, fps: 20 } }, 55 | FIREFLY3: { code: 0x0A, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 9, f: 8, fps: 20 } }, 56 | FIREFLY4: { code: 0x0B, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 9, f: 8, fps: 20 } }, 57 | BOULDER: { code: 0x10, rounded: true, explodable: false, consumable: true, sprite: { x: 0, y: 7 } }, 58 | BOULDERFALLING: { code: 0x12, rounded: false, explodable: false, consumable: true, sprite: { x: 0, y: 7 } }, 59 | DIAMOND: { code: 0x14, rounded: true, explodable: false, consumable: true, sprite: { x: 0, y: 10, f: 8, fps: 20 } }, 60 | DIAMONDFALLING: { code: 0x16, rounded: false, explodable: false, consumable: true, sprite: { x: 0, y: 10, f: 8, fps: 20 } }, 61 | EXPLODETOSPACE0: { code: 0x1B, rounded: false, explodable: false, consumable: false, sprite: { x: 3, y: 7 } }, 62 | EXPLODETOSPACE1: { code: 0x1C, rounded: false, explodable: false, consumable: false, sprite: { x: 4, y: 7 } }, 63 | EXPLODETOSPACE2: { code: 0x1D, rounded: false, explodable: false, consumable: false, sprite: { x: 5, y: 7 } }, 64 | EXPLODETOSPACE3: { code: 0x1E, rounded: false, explodable: false, consumable: false, sprite: { x: 4, y: 7 } }, 65 | EXPLODETOSPACE4: { code: 0x1F, rounded: false, explodable: false, consumable: false, sprite: { x: 3, y: 7 } }, 66 | EXPLODETODIAMOND0: { code: 0x20, rounded: false, explodable: false, consumable: false, sprite: { x: 3, y: 7 } }, 67 | EXPLODETODIAMOND1: { code: 0x21, rounded: false, explodable: false, consumable: false, sprite: { x: 4, y: 7 } }, 68 | EXPLODETODIAMOND2: { code: 0x22, rounded: false, explodable: false, consumable: false, sprite: { x: 5, y: 7 } }, 69 | EXPLODETODIAMOND3: { code: 0x23, rounded: false, explodable: false, consumable: false, sprite: { x: 4, y: 7 } }, 70 | EXPLODETODIAMOND4: { code: 0x24, rounded: false, explodable: false, consumable: false, sprite: { x: 3, y: 7 } }, 71 | PREROCKFORD1: { code: 0x25, rounded: false, explodable: false, consumable: false, sprite: { x: 1, y: 6, f: 2, fps: 4 } }, 72 | PREROCKFORD2: { code: 0x26, rounded: false, explodable: false, consumable: false, sprite: { x: 1, y: 0 } }, 73 | PREROCKFORD3: { code: 0x27, rounded: false, explodable: false, consumable: false, sprite: { x: 2, y: 0 } }, 74 | PREROCKFORD4: { code: 0x28, rounded: false, explodable: false, consumable: false, sprite: { x: 3, y: 0 } }, 75 | BUTTERFLY1: { code: 0x30, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 11, f: 8, fps: 20 } }, 76 | BUTTERFLY2: { code: 0x31, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 11, f: 8, fps: 20 } }, 77 | BUTTERFLY3: { code: 0x32, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 11, f: 8, fps: 20 } }, 78 | BUTTERFLY4: { code: 0x33, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 11, f: 8, fps: 20 } }, 79 | ROCKFORD: { code: 0x38, rounded: false, explodable: true, consumable: true, sprite: { x: 0, y: 0 }, // standing still 80 | left: { x: 0, y: 4, f: 8, fps: 20 }, // running left 81 | right: { x: 0, y: 5, f: 8, fps: 20 }, // running right 82 | blink: { x: 0, y: 1, f: 8, fps: 20 }, // blinking 83 | tap: { x: 0, y: 2, f: 8, fps: 20 }, // foot tapping 84 | blinktap: { x: 0, y: 3, f: 8, fps: 20 } }, // foot tapping and blinking 85 | AMOEBA: { code: 0x3A, rounded: false, explodable: false, consumable: true, sprite: { x: 0, y: 8, f: 8, fps: 20 } } 86 | }; 87 | 88 | for(var key in OBJECT) { 89 | OBJECT[key].name = key; // give it a human friendly name 90 | OBJECT[OBJECT[key].code] = OBJECT[key]; // and allow lookup by code 91 | } 92 | 93 | var FIREFLIES = []; 94 | FIREFLIES[DIR.LEFT] = OBJECT.FIREFLY1; 95 | FIREFLIES[DIR.UP] = OBJECT.FIREFLY2; 96 | FIREFLIES[DIR.RIGHT] = OBJECT.FIREFLY3; 97 | FIREFLIES[DIR.DOWN] = OBJECT.FIREFLY4; 98 | 99 | var BUTTERFLIES = []; 100 | BUTTERFLIES[DIR.LEFT] = OBJECT.BUTTERFLY1; 101 | BUTTERFLIES[DIR.UP] = OBJECT.BUTTERFLY2; 102 | BUTTERFLIES[DIR.RIGHT] = OBJECT.BUTTERFLY3; 103 | BUTTERFLIES[DIR.DOWN] = OBJECT.BUTTERFLY4; 104 | 105 | var PREROCKFORDS = [ 106 | OBJECT.PREROCKFORD1, 107 | OBJECT.PREROCKFORD2, 108 | OBJECT.PREROCKFORD3, 109 | OBJECT.PREROCKFORD4, 110 | OBJECT.ROCKFORD 111 | ]; 112 | 113 | var EXPLODETOSPACE = [ 114 | OBJECT.EXPLODETOSPACE0, 115 | OBJECT.EXPLODETOSPACE1, 116 | OBJECT.EXPLODETOSPACE2, 117 | OBJECT.EXPLODETOSPACE3, 118 | OBJECT.EXPLODETOSPACE4, 119 | OBJECT.SPACE 120 | ]; 121 | 122 | var EXPLODETODIAMOND = [ 123 | OBJECT.EXPLODETODIAMOND0, 124 | OBJECT.EXPLODETODIAMOND1, 125 | OBJECT.EXPLODETODIAMOND2, 126 | OBJECT.EXPLODETODIAMOND3, 127 | OBJECT.EXPLODETODIAMOND4, 128 | OBJECT.DIAMOND 129 | ]; 130 | 131 | function isFirefly(o) { return (OBJECT.FIREFLY1.code <= o.code) && (o.code <= OBJECT.FIREFLY4.code); } 132 | function isButterfly(o) { return (OBJECT.BUTTERFLY1.code <= o.code) && (o.code <= OBJECT.BUTTERFLY4.code); } 133 | 134 | //---------------------------------------------------------------------------- 135 | 136 | var Point = function(x, y, dir) { 137 | this.x = x + (DIRX[dir] || 0); 138 | this.y = y + (DIRY[dir] || 0); 139 | } 140 | 141 | //========================================================================= 142 | // GAME LOGIC 143 | //========================================================================= 144 | 145 | var Game = function(options) { 146 | this.options = options || {}; 147 | this.storage = window.localStorage || {}; 148 | this.score = 0; 149 | }; 150 | 151 | Game.prototype = { 152 | 153 | reset: function(n) { 154 | n = Math.min(CAVES.length-1, Math.max(0, (typeof n === 'number' ? n : this.storage.level || 0))); 155 | this.index = this.storage.level = n; // cave index 156 | this.cave = CAVES[this.index]; // cave definition 157 | this.width = this.cave.width; // cave cell width 158 | this.height = this.cave.height; // cave cell height 159 | this.cells = []; // will be built up into 2 dimensional array below 160 | this.frame = 0; // game frame counter starts at zero 161 | this.fps = 10; // how many game frames per second 162 | this.step = 1/this.fps; // how long is each game frame (in seconds) 163 | this.birth = 2*this.fps; // in which frame is rockford born ? 164 | this.timer = this.cave.caveTime; // seconds allowed to complete this cave 165 | this.idle = { blink: false, tap: false }; // is rockford showing any idle animation ? 166 | this.flash = false; // trigger white flash when rockford has collected enought diamonds 167 | this.won = false; // set to true when rockford enters the outbox 168 | this.diamonds = { 169 | collected: 0, 170 | needed: this.cave.diamondsNeeded, 171 | value: this.cave.initialDiamondValue, 172 | extra: this.cave.extraDiamondValue 173 | }; 174 | this.amoeba = { 175 | max: this.cave.amoebaMaxSize, 176 | slow: this.cave.amoebaSlowGrowthTime/this.step 177 | }; 178 | this.magic = { 179 | active: false, 180 | time: this.cave.magicWallMillingTime/this.step 181 | }; 182 | var x, y, o, pt; 183 | for(y = 0 ; y < this.height ; ++y) { 184 | for(x = 0 ; x < this.width ; ++x) { 185 | this.cells[x] = this.cells[x] || []; 186 | this.cells[x][y] = { p: new Point(x,y), frame: 0, object: OBJECT[this.cave.map[x][y]] }; 187 | } 188 | } 189 | this.publish('level', this.cave); 190 | }, 191 | 192 | prev: function() { if (this.index > 0) this.reset(this.index-1); }, 193 | next: function() { if (this.index < CAVES.length-1) this.reset(this.index+1); }, 194 | 195 | get: function(p,dir) { return this.cells[p.x + (DIRX[dir] || 0)][p.y + (DIRY[dir] || 0)].object; }, 196 | set: function(p,o,dir) { var cell = this.cells[p.x + (DIRX[dir] || 0)][p.y + (DIRY[dir] || 0)]; cell.object = o; cell.frame = this.frame; this.publish('cell', cell) }, 197 | clear: function(p,dir) { this.set(p,OBJECT.SPACE,dir); }, 198 | move: function(p,dir,o) { this.clear(p); this.set(p,o,dir); }, 199 | isempty: function(p,dir) { var o = this.get(p,dir); return OBJECT.SPACE === o; }, 200 | isdirt: function(p,dir) { var o = this.get(p,dir); return OBJECT.DIRT === o; }, 201 | isboulder: function(p,dir) { var o = this.get(p,dir); return OBJECT.BOULDER === o; }, 202 | isrockford: function(p,dir) { var o = this.get(p,dir); return OBJECT.ROCKFORD === o; }, 203 | isdiamond: function(p,dir) { var o = this.get(p,dir); return OBJECT.DIAMOND === o; }, 204 | isamoeba: function(p,dir) { var o = this.get(p,dir); return OBJECT.AMOEBA === o; }, 205 | ismagic: function(p,dir) { var o = this.get(p,dir); return OBJECT.MAGICWALL === o; }, 206 | isoutbox: function(p,dir) { var o = this.get(p,dir); return OBJECT.OUTBOX === o; }, 207 | isfirefly: function(p,dir) { var o = this.get(p,dir); return isFirefly(o); }, 208 | isbutterfly: function(p,dir) { var o = this.get(p,dir); return isButterfly(o); }, 209 | isexplodable: function(p,dir) { var o = this.get(p,dir); return o.explodable; }, 210 | isconsumable: function(p,dir) { var o = this.get(p,dir); return o.consumable; }, 211 | isrounded: function(p,dir) { var o = this.get(p,dir); return o.rounded; }, 212 | 213 | isfallingdiamond: function(p,dir) { var o = this.get(p,dir); return OBJECT.DIAMONDFALLING === o; }, 214 | isfallingboulder: function(p,dir) { var o = this.get(p,dir); return OBJECT.BOULDERFALLING === o; }, 215 | 216 | eachCell: function(fn, thisArg) { 217 | for(var y = 0 ; y < this.height ; y++) { 218 | for(var x = 0 ; x < this.width ; x++) { 219 | fn.call(thisArg || this, this.cells[x][y]); 220 | } 221 | } 222 | }, 223 | 224 | update: function() { 225 | this.beginFrame(); 226 | this.eachCell(function(cell) { 227 | if (cell.frame < this.frame) { 228 | switch(cell.object) { 229 | case OBJECT.PREROCKFORD1: this.updatePreRockford(cell.p, 1); break; 230 | case OBJECT.PREROCKFORD2: this.updatePreRockford(cell.p, 2); break; 231 | case OBJECT.PREROCKFORD3: this.updatePreRockford(cell.p, 3); break; 232 | case OBJECT.PREROCKFORD4: this.updatePreRockford(cell.p, 4); break; 233 | case OBJECT.ROCKFORD: this.updateRockford(cell.p, moving.dir); break; 234 | case OBJECT.BOULDER: this.updateBoulder(cell.p); break; 235 | case OBJECT.BOULDERFALLING: this.updateBoulderFalling(cell.p); break; 236 | case OBJECT.DIAMOND: this.updateDiamond(cell.p); break; 237 | case OBJECT.DIAMONDFALLING: this.updateDiamondFalling(cell.p); break; 238 | case OBJECT.FIREFLY1: this.updateFirefly(cell.p, DIR.LEFT); break; 239 | case OBJECT.FIREFLY2: this.updateFirefly(cell.p, DIR.UP); break; 240 | case OBJECT.FIREFLY3: this.updateFirefly(cell.p, DIR.RIGHT); break; 241 | case OBJECT.FIREFLY4: this.updateFirefly(cell.p, DIR.DOWN); break; 242 | case OBJECT.BUTTERFLY1: this.updateButterfly(cell.p, DIR.LEFT); break; 243 | case OBJECT.BUTTERFLY2: this.updateButterfly(cell.p, DIR.UP); break; 244 | case OBJECT.BUTTERFLY3: this.updateButterfly(cell.p, DIR.RIGHT); break; 245 | case OBJECT.BUTTERFLY4: this.updateButterfly(cell.p, DIR.DOWN); break; 246 | case OBJECT.EXPLODETOSPACE0: this.updateExplodeToSpace(cell.p, 0); break; 247 | case OBJECT.EXPLODETOSPACE1: this.updateExplodeToSpace(cell.p, 1); break; 248 | case OBJECT.EXPLODETOSPACE2: this.updateExplodeToSpace(cell.p, 2); break; 249 | case OBJECT.EXPLODETOSPACE3: this.updateExplodeToSpace(cell.p, 3); break; 250 | case OBJECT.EXPLODETOSPACE4: this.updateExplodeToSpace(cell.p, 4); break; 251 | case OBJECT.EXPLODETODIAMOND0: this.updateExplodeToDiamond(cell.p, 0); break; 252 | case OBJECT.EXPLODETODIAMOND1: this.updateExplodeToDiamond(cell.p, 1); break; 253 | case OBJECT.EXPLODETODIAMOND2: this.updateExplodeToDiamond(cell.p, 2); break; 254 | case OBJECT.EXPLODETODIAMOND3: this.updateExplodeToDiamond(cell.p, 3); break; 255 | case OBJECT.EXPLODETODIAMOND4: this.updateExplodeToDiamond(cell.p, 4); break; 256 | case OBJECT.AMOEBA: this.updateAmoeba(cell.p); break; 257 | case OBJECT.PREOUTBOX: this.updatePreOutbox(cell.p); break; 258 | } 259 | } 260 | }); 261 | this.endFrame(); 262 | }, 263 | 264 | decreaseTimer: function(n) { 265 | this.timer = Math.max(0, this.timer - (n || 1)); 266 | this.publish('timer', this.timer); 267 | return (this.timer === 0); 268 | }, 269 | 270 | autoDecreaseTimer: function() { 271 | if ((this.frame > this.birth) && ((this.frame % this.fps) == 0)) 272 | this.decreaseTimer(1); 273 | }, 274 | 275 | runOutTimer: function() { 276 | var amount = Math.min(3, this.timer); 277 | this.increaseScore(amount); 278 | if (this.decreaseTimer(amount)) 279 | this.next(); 280 | }, 281 | 282 | collectDiamond: function() { 283 | this.diamonds.collected++; 284 | this.increaseScore(this.diamonds.collected > this.diamonds.needed ? this.diamonds.extra : this.diamonds.value); 285 | this.publish('diamond', this.diamonds); 286 | }, 287 | 288 | increaseScore: function(n) { 289 | this.score += n; 290 | this.publish('score', this.score); 291 | }, 292 | 293 | flashWhenEnoughDiamondsCollected: function() { 294 | if (!this.flash && (this.diamonds.collected >= this.diamonds.needed)) 295 | this.flash = this.frame + Math.round(this.fps/5); // flash for 1/5th of a second 296 | if (this.frame <= this.flash) 297 | this.publish('flash'); 298 | }, 299 | 300 | loseLevel: function() { 301 | this.reset(); 302 | }, 303 | 304 | winLevel: function() { 305 | this.won = true; 306 | }, 307 | 308 | beginFrame: function() { 309 | this.frame++; 310 | this.amoeba.size = 0; 311 | this.amoeba.enclosed = true; 312 | this.idle = moving.dir ? {} : { 313 | blink: (randomInt(1,4)==1) ? !this.idle.blink : this.idle.blink, 314 | tap: (randomInt(1,16)==1) ? !this.idle.tap : this.idle.tap 315 | } 316 | }, 317 | 318 | endFrame: function() { 319 | if (!this.amoeba.dead) { 320 | if (this.amoeba.enclosed) 321 | this.amoeba.dead = OBJECT.DIAMOND; 322 | else if (this.amoeba.size > this.amoeba.max) 323 | this.amoeba.dead = OBJECT.BOULDER; 324 | else if (this.amoeba.slow > 0) 325 | this.amoeba.slow--; 326 | } 327 | this.magic.active = this.magic.active && (--this.magic.time > 0); 328 | this.flashWhenEnoughDiamondsCollected(); 329 | if (this.won) 330 | this.runOutTimer(); 331 | else if (this.frame - this.foundRockford > (4 * this.fps)) 332 | this.loseLevel(); 333 | else 334 | this.autoDecreaseTimer(); 335 | }, 336 | 337 | updatePreRockford: function(p, n) { 338 | if (this.frame >= this.birth) 339 | this.set(p, PREROCKFORDS[n+1]); 340 | }, 341 | 342 | updatePreOutbox: function(p) { 343 | if (this.diamonds.collected >= this.diamonds.needed) 344 | this.set(p, OBJECT.OUTBOX); 345 | }, 346 | 347 | updateRockford: function(p, dir) { 348 | this.foundRockford = this.frame; 349 | if (this.won) { 350 | // do nothing - dont let rockford move if he already found the outbox 351 | } 352 | else if (this.timer === 0) { 353 | this.explode(p); 354 | } 355 | else if (moving.grab) { 356 | if (this.isdirt(p, dir)) { 357 | this.clear(p, dir); 358 | } 359 | else if (this.isdiamond(p,dir) || this.isfallingdiamond(p, dir)) { 360 | this.clear(p, dir); 361 | this.collectDiamond(); 362 | } 363 | else if (horizontal(dir) && this.isboulder(p, dir)) { 364 | this.push(p, dir); 365 | } 366 | } 367 | else if (this.isempty(p, dir) || this.isdirt(p, dir)) { 368 | this.move(p, dir, OBJECT.ROCKFORD); 369 | } 370 | else if (this.isdiamond(p, dir)) { 371 | this.move(p, dir, OBJECT.ROCKFORD); 372 | this.collectDiamond(); 373 | } 374 | else if (horizontal(dir) && this.isboulder(p, dir)) { 375 | this.push(p, dir); 376 | } 377 | else if (this.isoutbox(p, dir)) { 378 | this.move(p, dir, OBJECT.ROCKFORD); 379 | this.winLevel(); 380 | } 381 | }, 382 | 383 | updateBoulder: function(p) { 384 | if (this.isempty(p, DIR.DOWN)) 385 | this.set(p, OBJECT.BOULDERFALLING); 386 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.LEFT) && this.isempty(p, DIR.DOWNLEFT)) 387 | this.move(p, DIR.LEFT, OBJECT.BOULDERFALLING); 388 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.RIGHT) && this.isempty(p, DIR.DOWNRIGHT)) 389 | this.move(p, DIR.RIGHT, OBJECT.BOULDERFALLING); 390 | }, 391 | 392 | updateBoulderFalling: function(p) { 393 | if (this.isempty(p, DIR.DOWN)) 394 | this.move(p, DIR.DOWN, OBJECT.BOULDERFALLING); 395 | else if (this.isexplodable(p, DIR.DOWN)) 396 | this.explode(p, DIR.DOWN); 397 | else if (this.ismagic(p, DIR.DOWN)) 398 | this.domagic(p, OBJECT.DIAMOND); 399 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.LEFT) && this.isempty(p, DIR.DOWNLEFT)) 400 | this.move(p, DIR.LEFT, OBJECT.BOULDERFALLING); 401 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.RIGHT) && this.isempty(p, DIR.DOWNRIGHT)) 402 | this.move(p, DIR.RIGHT, OBJECT.BOULDERFALLING); 403 | else 404 | this.set(p, OBJECT.BOULDER); 405 | }, 406 | 407 | updateDiamond: function(p) { 408 | if (this.isempty(p, DIR.DOWN)) 409 | this.set(p, OBJECT.DIAMONDFALLING); 410 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.LEFT) && this.isempty(p, DIR.DOWNLEFT)) 411 | this.move(p, DIR.LEFT, OBJECT.DIAMONDFALLING); 412 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.RIGHT) && this.isempty(p, DIR.DOWNRIGHT)) 413 | this.move(p, DIR.RIGHT, OBJECT.DIAMONDFALLING); 414 | }, 415 | 416 | updateDiamondFalling: function(p) { 417 | if (this.isempty(p, DIR.DOWN)) 418 | this.move(p, DIR.DOWN, OBJECT.DIAMONDFALLING); 419 | else if (this.isexplodable(p, DIR.DOWN)) 420 | this.explode(p, DIR.DOWN); 421 | else if (this.ismagic(p, DIR.DOWN)) 422 | this.domagic(p, OBJECT.BOULDER); 423 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.LEFT) && this.isempty(p, DIR.DOWNLEFT)) 424 | this.move(p, DIR.LEFT, OBJECT.DIAMONDFALLING); 425 | else if (this.isrounded(p, DIR.DOWN) && this.isempty(p, DIR.RIGHT) && this.isempty(p, DIR.DOWNRIGHT)) 426 | this.move(p, DIR.RIGHT, OBJECT.DIAMONDFALLING); 427 | else 428 | this.set(p, OBJECT.DIAMOND); 429 | }, 430 | 431 | updateFirefly: function(p, dir) { 432 | var newdir = rotateLeft(dir); 433 | if (this.isrockford(p, DIR.UP) || this.isrockford(p, DIR.DOWN) || this.isrockford(p, DIR.LEFT) || this.isrockford(p, DIR.RIGHT)) 434 | this.explode(p); 435 | else if (this.isamoeba(p, DIR.UP) || this.isamoeba(p, DIR.DOWN) || this.isamoeba(p, DIR.LEFT) || this.isamoeba(p, DIR.RIGHT)) 436 | this.explode(p); 437 | else if (this.isempty(p, newdir)) 438 | this.move(p, newdir, FIREFLIES[newdir]); 439 | else if (this.isempty(p, dir)) 440 | this.move(p, dir, FIREFLIES[dir]); 441 | else 442 | this.set(p, FIREFLIES[rotateRight(dir)]); 443 | }, 444 | 445 | updateButterfly: function(p, dir) { 446 | var newdir = rotateRight(dir); 447 | if (this.isrockford(p, DIR.UP) || this.isrockford(p, DIR.DOWN) || this.isrockford(p, DIR.LEFT) || this.isrockford(p, DIR.RIGHT)) 448 | this.explode(p); 449 | else if (this.isamoeba(p, DIR.UP) || this.isamoeba(p, DIR.DOWN) || this.isamoeba(p, DIR.LEFT) || this.isamoeba(p, DIR.RIGHT)) 450 | this.explode(p); 451 | else if (this.isempty(p, newdir)) 452 | this.move(p, newdir, BUTTERFLIES[newdir]); 453 | else if (this.isempty(p, dir)) 454 | this.move(p, dir, BUTTERFLIES[dir]); 455 | else 456 | this.set(p, BUTTERFLIES[rotateLeft(dir)]); 457 | }, 458 | 459 | updateExplodeToSpace: function(p, n) { 460 | this.set(p, EXPLODETOSPACE[n+1]); 461 | }, 462 | 463 | updateExplodeToDiamond: function(p, n) { 464 | this.set(p, EXPLODETODIAMOND[n+1]); 465 | }, 466 | 467 | updateAmoeba: function(p) { 468 | if (this.amoeba.dead) { 469 | this.set(p, this.amoeba.dead); 470 | } 471 | else { 472 | this.amoeba.size++; 473 | if (this.isempty(p, DIR.UP) || this.isempty(p, DIR.DOWN) || this.isempty(p, DIR.RIGHT) || this.isempty(p, DIR.LEFT) || 474 | this.isdirt(p, DIR.UP) || this.isdirt(p, DIR.DOWN) || this.isdirt(p, DIR.RIGHT) || this.isdirt(p, DIR.LEFT)) { 475 | this.amoeba.enclosed = false; 476 | } 477 | if (this.frame >= this.birth) { 478 | var grow = this.amoeba.slow ? (randomInt(1, 128) < 4) : (randomInt(1, 4) == 1); 479 | var dir = randomChoice([DIR.UP, DIR.DOWN, DIR.LEFT, DIR.RIGHT]); 480 | if (grow && (this.isdirt(p, dir) || this.isempty(p, dir))) 481 | this.set(p, OBJECT.AMOEBA, dir); 482 | } 483 | } 484 | }, 485 | 486 | explode: function(p, dir) { 487 | var p2 = new Point(p.x, p.y, dir); 488 | var explosion = (this.isbutterfly(p2) ? OBJECT.EXPLODETODIAMOND0 : OBJECT.EXPLODETOSPACE0); 489 | this.set(p2, explosion); 490 | for(dir = 0 ; dir < 8 ; ++dir) { // for each of the 8 directions 491 | if (this.isexplodable(p2, dir)) 492 | this.explode(p2, dir); 493 | else if (this.isconsumable(p2, dir)) 494 | this.set(p2, explosion, dir); 495 | } 496 | }, 497 | 498 | push: function(p, dir) { 499 | p2 = new Point(p.x, p.y, dir); 500 | if (this.isempty(p2, dir)) { 501 | if (randomInt(1,8) == 1) { 502 | this.move(p2, dir, OBJECT.BOULDER); 503 | if (!moving.grab) 504 | this.move(p, dir, OBJECT.ROCKFORD); 505 | } 506 | } 507 | }, 508 | 509 | domagic: function(p, to) { 510 | if (this.magic.time > 0) { 511 | this.magic.active = true; 512 | this.clear(p); 513 | var p2 = new Point(p.x, p.y + 2); 514 | if (this.isempty(p2)) 515 | this.set(p2, to); 516 | } 517 | }, 518 | 519 | subscribe: function(event, callback, target) { 520 | this.subscribers = this.subscribers || {}; 521 | this.subscribers[event] = this.subscribers[event] || []; 522 | this.subscribers[event].push({ callback: callback, target: target }); 523 | }, 524 | 525 | publish: function(event) { 526 | if (this.subscribers && this.subscribers[event]) { 527 | var subs = this.subscribers[event]; 528 | var args = [].slice.call(arguments, 1); 529 | var n, sub; 530 | for(n = 0 ; n < subs.length ; ++n) { 531 | sub = subs[n]; 532 | sub.callback.apply(sub.target, args); 533 | } 534 | } 535 | } 536 | 537 | } 538 | 539 | //========================================================================= 540 | // GAME RENDERING 541 | //========================================================================= 542 | 543 | function Render(game) { 544 | game.subscribe('level', this.onChangeLevel, this); 545 | game.subscribe('score', this.invalidateScore, this); 546 | game.subscribe('timer', this.invalidateScore, this); 547 | game.subscribe('flash', this.invalidateCave, this); 548 | game.subscribe('cell', this.invalidateCell, this); 549 | } 550 | 551 | Render.prototype = { 552 | 553 | reset: function(sprites) { 554 | this.canvas = Dom.get('canvas'); 555 | this.ctx = this.canvas.getContext('2d'); 556 | this.sprites = sprites; 557 | this.fps = 30; 558 | this.step = 1/this.fps; 559 | this.frame = 0; 560 | this.ctxSprites = document.createElement('canvas').getContext('2d'); 561 | this.ctxSprites.canvas.width = this.sprites.width; 562 | this.ctxSprites.canvas.height = this.sprites.height; 563 | this.ctxSprites.drawImage(this.sprites, 0, 0, this.sprites.width, this.sprites.height, 0, 0, this.sprites.width, this.sprites.height); 564 | this.resize(); 565 | }, 566 | 567 | onChangeLevel: function(info) { 568 | this.description(info.description); 569 | this.colors(info.color1, info.color2); 570 | this.invalidateCave(); 571 | this.invalidateScore(); 572 | Dom.disable('prev', info.index === 0); 573 | Dom.disable('next', info.index === CAVES.length-1); 574 | }, 575 | 576 | invalid: { score: true, cave: true }, 577 | invalidateScore: function() { this.invalid.score = true; }, 578 | invalidateCave: function() { this.invalid.cave = true; }, 579 | invalidateCell: function(cell) { cell.invalid = true; }, 580 | validateScore: function() { this.invalid.score = false; }, 581 | validateCave: function() { this.invalid.cave = false; }, 582 | validateCell: function(cell) { cell.invalid = false; }, 583 | 584 | update: function() { 585 | this.frame++; 586 | this.score(); 587 | game.eachCell(this.cell, this); 588 | this.validateCave(); 589 | }, 590 | 591 | score: function() { 592 | if (this.invalid.score) { 593 | this.ctx.fillStyle='black'; 594 | this.ctx.fillRect(0, 0, this.canvas.width, this.dy); 595 | this.number(3, game.diamonds.needed, 2, true); 596 | this.letter( 5, '$'); 597 | this.number(6, game.diamonds.collected >= game.diamonds.needed ? game.diamonds.extra : game.diamonds.value, 2); 598 | this.number(12, game.diamonds.collected, 2, true); 599 | this.number(25, game.timer, 3); 600 | this.number(31, game.score, 6); 601 | this.validateScore(); 602 | } 603 | }, 604 | 605 | number: function(x, n, width, yellow) { 606 | var i, word = ("000000" + n.toString()).slice(-(width||2)); 607 | for(i = 0 ; i < word.length ; ++i) 608 | this.letter(x+i, word[i], yellow); 609 | }, 610 | 611 | letter: function(x, c, yellow) { 612 | this.ctx.drawImage(this.ctxSprites.canvas, (yellow ? 9 : 8) * 32, (c.charCodeAt(0)-32) * 16, 32, 16, (x*this.dx), 0, this.dx, this.dy-4); // auto scaling here from 32/32 to dx/dy can be slow... we should optimize and precatch rendered sprites at exact cell size (dx,dy) 613 | }, 614 | 615 | cell: function(cell) { 616 | var object = cell.object, 617 | sprite = object.sprite; 618 | if (this.invalid.cave || cell.invalid || (sprite.f > 1) || (object === OBJECT.ROCKFORD)) { 619 | if (object === OBJECT.ROCKFORD) 620 | return this.rockford(cell); 621 | else if ((object === OBJECT.SPACE) && (game.flash > game.frame)) 622 | sprite = OBJECT.SPACE.flash; 623 | else if ((object === OBJECT.MAGICWALL) && !game.magic.active) 624 | sprite = OBJECT.BRICKWALL.sprite; 625 | this.sprite(sprite, cell); 626 | this.validateCell(cell); 627 | } 628 | }, 629 | 630 | sprite: function(sprite, cell) { 631 | var f = sprite.f ? (Math.floor((sprite.fps/this.fps) * this.frame) % sprite.f) : 0; 632 | this.ctx.drawImage(this.ctxSprites.canvas, (sprite.x + f) * 32, sprite.y * 32, 32, 32, cell.p.x * this.dx, (1+cell.p.y) * this.dy, this.dx, this.dy); // auto scaling here from 32/32 to dx/dy can be slow... we should optimize and precatch rendered sprites at exact cell size (dx,dy) 633 | }, 634 | 635 | rockford: function(cell) { 636 | if ((moving.dir == DIR.LEFT) || (vertical(moving.dir) && (moving.lastXDir == DIR.LEFT))) 637 | this.sprite(OBJECT.ROCKFORD.left, cell); 638 | else if ((moving.dir == DIR.RIGHT) || (vertical(moving.dir) && (moving.lastXDir == DIR.RIGHT))) 639 | this.sprite(OBJECT.ROCKFORD.right, cell); 640 | else if (game.idle.blink && !game.idle.tap) 641 | this.sprite(OBJECT.ROCKFORD.blink, cell); 642 | else if (!game.idle.blink && game.idle.tap) 643 | this.sprite(OBJECT.ROCKFORD.tap, cell); 644 | else if (game.idle.blink && game.idle.tap) 645 | this.sprite(OBJECT.ROCKFORD.blinktap, cell); 646 | else 647 | this.sprite(OBJECT.ROCKFORD.sprite, cell); 648 | }, 649 | 650 | description: function(msg) { 651 | Dom.set('description', msg); 652 | }, 653 | 654 | colors: function(color1, color2) { 655 | this.ctxSprites.drawImage(this.sprites, 0, 0, this.sprites.width, this.sprites.height, 0, 0, this.sprites.width, this.sprites.height); 656 | var pixels = this.ctxSprites.getImageData(0, 0, this.sprites.width, this.sprites.height); 657 | var x, y, n, r, g, b, a; 658 | for(y = 0 ; y < this.sprites.height ; ++y) { 659 | for(x = 0 ; x < this.sprites.width ; ++x) { 660 | n = (y*this.sprites.width*4) + (x*4); 661 | color = (pixels.data[n + 0] << 16) + 662 | (pixels.data[n + 1] << 8) + 663 | (pixels.data[n + 2] << 0); 664 | if (color == 0x3F3F3F) { // mostly the metalic wall 665 | pixels.data[n + 0] = (color2 >> 16) & 0xFF; 666 | pixels.data[n + 1] = (color2 >> 8) & 0xFF; 667 | pixels.data[n + 2] = (color2 >> 0) & 0xFF; 668 | } 669 | else if (color == 0xA52A00) { // mostly the dirt 670 | pixels.data[n + 0] = (color1 >> 16) & 0xFF; 671 | pixels.data[n + 1] = (color1 >> 8) & 0xFF; 672 | pixels.data[n + 2] = (color1 >> 0) & 0xFF; 673 | } 674 | } 675 | } 676 | this.ctxSprites.putImageData(pixels, 0, 0); 677 | }, 678 | 679 | resize: function() { 680 | var visibleArea = { w: 40, h: 23 }; // 40x22 + 1 row for score at top - TODO: scrollable area 681 | this.canvas.width = this.canvas.clientWidth; // set canvas logical size equal to its physical size 682 | this.canvas.height = this.canvas.clientHeight; // (ditto) 683 | this.dx = this.canvas.width / visibleArea.w; // calculate pixel size of a single game cell 684 | this.dy = this.canvas.height / visibleArea.h; // (ditto) 685 | this.invalidateScore(); 686 | this.invalidateCave(); 687 | } 688 | 689 | } 690 | 691 | //========================================================================= 692 | // GAME LOOP 693 | //========================================================================= 694 | 695 | var game = new Game(), // the boulderdash game logic (rendering independent) 696 | render = new Render(game), // the boulderdash game renderer 697 | stats = new Stats(); // the FPS counter widget 698 | 699 | //------------------------------------------------------------------------- 700 | 701 | function run() { 702 | 703 | var now, last = timestamp(), dt = 0, gdt = 0, rdt = 0; 704 | function frame() { 705 | now = timestamp(); 706 | dt = Math.min(1, (now - last) / 1000); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab 707 | gdt = gdt + dt; 708 | while (gdt > game.step) { 709 | gdt = gdt - game.step; 710 | game.update(); 711 | } 712 | rdt = rdt + dt; 713 | if (rdt > render.step) { 714 | rdt = rdt - render.step; 715 | render.update(); 716 | } 717 | stats.update(); 718 | last = now; 719 | requestAnimationFrame(frame, render.canvas); 720 | } 721 | 722 | load(function(sprites) { 723 | render.reset(sprites); // reset the canvas renderer with the loaded sprites 724 | game.reset(); // reset the game 725 | addEvents(); // attach keydown and resize event handlers 726 | showStats(); // initialize FPS counter 727 | frame(); // ... and start the first frame ! 728 | }); 729 | 730 | }; 731 | 732 | function load(cb) { 733 | var sprites = document.createElement('img'); 734 | sprites.addEventListener('load', function() { cb(sprites); } , false); 735 | sprites.src = 'images/sprites.png'; 736 | }; 737 | 738 | function showStats() { 739 | stats.domElement.id = 'stats'; 740 | Dom.get('boulderdash').appendChild(stats.domElement); 741 | }; 742 | 743 | function addEvents() { 744 | document.addEventListener('keydown', keydown, false); 745 | document.addEventListener('keyup', keyup, false); 746 | window.addEventListener('resize', function() { render.resize() }, false); 747 | Dom.get('prev').addEventListener('click', function() { game.prev(); }, false); 748 | Dom.get('next').addEventListener('click', function() { game.next(); }, false); 749 | }; 750 | 751 | function keydown(ev) { 752 | var handled = false; 753 | switch(ev.keyCode) { 754 | case KEY.UP: moving.startUp(); handled = true; break; 755 | case KEY.DOWN: moving.startDown(); handled = true; break; 756 | case KEY.LEFT: moving.startLeft(); handled = true; break; 757 | case KEY.RIGHT: moving.startRight(); handled = true; break; 758 | case KEY.ESC: game.reset(); handled = true; break; 759 | case KEY.PAGEUP: game.prev(); handled = true; break; 760 | case KEY.PAGEDOWN: game.next(); handled = true; break; 761 | case KEY.SPACE: moving.startGrab(); handled = true; break; 762 | } 763 | if (handled) 764 | ev.preventDefault(); // prevent arrow keys from scrolling the page (supported in IE9+ and all other browsers) 765 | } 766 | 767 | function keyup(ev) { 768 | switch(ev.keyCode) { 769 | case KEY.UP: moving.stopUp(); handled = true; break; 770 | case KEY.DOWN: moving.stopDown(); handled = true; break; 771 | case KEY.LEFT: moving.stopLeft(); handled = true; break; 772 | case KEY.RIGHT: moving.stopRight(); handled = true; break; 773 | case KEY.SPACE: moving.stopGrab(); handled = true; break; 774 | } 775 | } 776 | 777 | var moving = { 778 | dir: DIR.NONE, 779 | lastXDir: DIR.NONE, 780 | up: false, down: false, left: false, right: false, grab: false, 781 | startUp: function() { this.up = true; this.dir = DIR.UP; }, 782 | startDown: function() { this.down = true; this.dir = DIR.DOWN; }, 783 | startLeft: function() { this.left = true; this.dir = DIR.LEFT; this.lastXDir = DIR.LEFT; }, 784 | startRight: function() { this.right = true; this.dir = DIR.RIGHT; this.lastXDir = DIR.RIGHT; }, 785 | startGrab: function() { this.grab = true; }, 786 | stopUp: function() { this.up = false; this.dir = (this.dir == DIR.UP) ? this.where() : this.dir; }, 787 | stopDown: function() { this.down = false; this.dir = (this.dir == DIR.DOWN) ? this.where() : this.dir; }, 788 | stopLeft: function() { this.left = false; this.dir = (this.dir == DIR.LEFT) ? this.where() : this.dir; }, 789 | stopRight: function() { this.right = false, this.dir = (this.dir == DIR.RIGHT) ? this.where() : this.dir; }, 790 | stopGrab: function() { this.grab = false; }, 791 | where: function() { 792 | if (this.up) 793 | return DIR.UP; 794 | else if (this.down) 795 | return DIR.DOWN; 796 | else if (this.left) 797 | return DIR.LEFT; 798 | else if (this.right) 799 | return DIR.RIGHT; 800 | } 801 | } 802 | 803 | //--------------------------------------------------------------------------- 804 | 805 | run.game = game; // debug access using Boulderdash.game 806 | run.render = render; // debug access using Boulderdash.render 807 | 808 | return run; 809 | 810 | }(); 811 | 812 | -------------------------------------------------------------------------------- /caves.js: -------------------------------------------------------------------------------- 1 | CAVES = function() { // ported from c version available at http://www.bd-fans.com/Files/FanStuff/Programming/decodecaves.c 2 | 3 | var DIR = { UP: 0, UPRIGHT: 1, RIGHT: 2, DOWNRIGHT: 3, DOWN: 4, DOWNLEFT: 5, LEFT: 6, UPLEFT: 7 }; 4 | 5 | var SPACE = 0x00; 6 | var DIRT = 0x01; 7 | var BRICK = 0x02; 8 | var MAGIC = 0x03; 9 | var STEEL = 0x07; 10 | var FIREFLY = 0x08; 11 | var BOULDER = 0x10; 12 | var DIAMOND = 0x14; 13 | var BUTTERFLY = 0x30; 14 | var ROCKFORD = 0x38; 15 | var AMOEBA = 0x3A; 16 | 17 | var COLORS = { // converted c64 colors - see http://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes#C-64 18 | BLACK: { n: 0x00, rgb: 0x000000 }, 19 | WHITE: { n: 0x01, rgb: 0xFFFFFF }, 20 | RED: { n: 0x02, rgb: 0x984B43 }, 21 | CYAN: { n: 0x03, rgb: 0x79C1C8 }, 22 | PURPLE: { n: 0x04, rgb: 0x9B51A5 }, 23 | GREEN: { n: 0x05, rgb: 0x68AE5C }, 24 | BLUE: { n: 0x06, rgb: 0x52429D }, 25 | YELLOW: { n: 0x07, rgb: 0xC9D684 }, 26 | ORANGE: { n: 0x08, rgb: 0x9B6739 }, 27 | BROWN: { n: 0x09, rgb: 0x6A5400 }, 28 | LIGHTRED: { n: 0x0A, rgb: 0xC37B75 }, 29 | DARKGRAY: { n: 0x0B, rgb: 0x636363 }, 30 | GRAY: { n: 0x0C, rgb: 0x8A8A8A }, 31 | LIGHTGREEN: { n: 0x0D, rgb: 0xA3E599 }, 32 | LIGHTBLUE: { n: 0x0E, rgb: 0x8A7BCE }, 33 | LIGHTGRAY: { n: 0x0F, rgb: 0xADADAD } 34 | } 35 | for(var name in COLORS) 36 | COLORS[COLORS[name].n] = COLORS[name]; // allow lookup by either index or name 37 | 38 | var DATA = [ 39 | [ 0x01, 0x14, 0x0A, 0x0F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x96, 0x6E, 0x46, 0x28, 0x1E, 0x08, 0x0B, 0x09, 0xD4, 0x20, 0x00, 0x10, 0x14, 0x00, 0x3C, 0x32, 0x09, 0x00, 0x42, 0x01, 0x09, 0x1E, 0x02, 0x42, 0x09, 0x10, 0x1E, 0x02, 0x25, 0x03, 0x04, 0x04, 0x26, 0x12, 0xFF ], 40 | [ 0x02, 0x14, 0x14, 0x32, 0x03, 0x00, 0x01, 0x57, 0x58, 0x0A, 0x0C, 0x09, 0x0D, 0x0A, 0x96, 0x6E, 0x46, 0x46, 0x46, 0x0A, 0x04, 0x09, 0x00, 0x00, 0x00, 0x10, 0x14, 0x08, 0x3C, 0x32, 0x09, 0x02, 0x42, 0x01, 0x08, 0x26, 0x02, 0x42, 0x01, 0x0F, 0x26, 0x02, 0x42, 0x08, 0x03, 0x14, 0x04, 0x42, 0x10, 0x03, 0x14, 0x04, 0x42, 0x18, 0x03, 0x14, 0x04, 0x42, 0x20, 0x03, 0x14, 0x04, 0x40, 0x01, 0x05, 0x26, 0x02, 0x40, 0x01, 0x0B, 0x26, 0x02, 0x40, 0x01, 0x12, 0x26, 0x02, 0x40, 0x14, 0x03, 0x14, 0x04, 0x25, 0x12, 0x15, 0x04, 0x12, 0x16, 0xFF ], 41 | [ 0x03, 0x00, 0x0F, 0x00, 0x00, 0x32, 0x36, 0x34, 0x37, 0x18, 0x17, 0x18, 0x17, 0x15, 0x96, 0x64, 0x5A, 0x50, 0x46, 0x09, 0x08, 0x09, 0x04, 0x00, 0x02, 0x10, 0x14, 0x00, 0x64, 0x32, 0x09, 0x00, 0x25, 0x03, 0x04, 0x04, 0x27, 0x14, 0xFF ], 42 | [ 0x04, 0x14, 0x05, 0x14, 0x00, 0x6E, 0x70, 0x73, 0x77, 0x24, 0x24, 0x24, 0x24, 0x24, 0x78, 0x64, 0x50, 0x3C, 0x32, 0x04, 0x08, 0x09, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x25, 0x01, 0x03, 0x04, 0x26, 0x16, 0x81, 0x08, 0x0A, 0x04, 0x04, 0x00, 0x30, 0x0A, 0x0B, 0x81, 0x10, 0x0A, 0x04, 0x04, 0x00, 0x30, 0x12, 0x0B, 0x81, 0x18, 0x0A, 0x04, 0x04, 0x00, 0x30, 0x1A, 0x0B, 0x81, 0x20, 0x0A, 0x04, 0x04, 0x00, 0x30, 0x22, 0x0B, 0xFF ], 43 | [ 0x05, 0x14, 0x32, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x06, 0x07, 0x08, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0x09, 0x0A, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x01, 0x03, 0x04, 0x27, 0x16, 0x80, 0x08, 0x0A, 0x03, 0x03, 0x00, 0x80, 0x10, 0x0A, 0x03, 0x03, 0x00, 0x80, 0x18, 0x0A, 0x03, 0x03, 0x00, 0x80, 0x20, 0x0A, 0x03, 0x03, 0x00, 0x14, 0x09, 0x0C, 0x08, 0x0A, 0x0A, 0x14, 0x11, 0x0C, 0x08, 0x12, 0x0A, 0x14, 0x19, 0x0C, 0x08, 0x1A, 0x0A, 0x14, 0x21, 0x0C, 0x08, 0x22, 0x0A, 0x80, 0x08, 0x10, 0x03, 0x03, 0x00, 0x80, 0x10, 0x10, 0x03, 0x03, 0x00, 0x80, 0x18, 0x10, 0x03, 0x03, 0x00, 0x80, 0x20, 0x10, 0x03, 0x03, 0x00, 0x14, 0x09, 0x12, 0x08, 0x0A, 0x10, 0x14, 0x11, 0x12, 0x08, 0x12, 0x10, 0x14, 0x19, 0x12, 0x08, 0x1A, 0x10, 0x14, 0x21, 0x12, 0x08, 0x22, 0x10, 0xFF ], 44 | [ 0x06, 0x14, 0x28, 0x3C, 0x00, 0x14, 0x15, 0x16, 0x17, 0x04, 0x06, 0x07, 0x08, 0x08, 0x96, 0x78, 0x64, 0x5A, 0x50, 0x0E, 0x0A, 0x09, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x82, 0x01, 0x03, 0x0A, 0x04, 0x00, 0x82, 0x01, 0x06, 0x0A, 0x04, 0x00, 0x82, 0x01, 0x09, 0x0A, 0x04, 0x00, 0x82, 0x01, 0x0C, 0x0A, 0x04, 0x00, 0x41, 0x0A, 0x03, 0x0D, 0x04, 0x14, 0x03, 0x05, 0x08, 0x04, 0x05, 0x14, 0x03, 0x08, 0x08, 0x04, 0x08, 0x14, 0x03, 0x0B, 0x08, 0x04, 0x0B, 0x14, 0x03, 0x0E, 0x08, 0x04, 0x0E, 0x82, 0x1D, 0x03, 0x0A, 0x04, 0x00, 0x82, 0x1D, 0x06, 0x0A, 0x04, 0x00, 0x82, 0x1D, 0x09, 0x0A, 0x04, 0x00, 0x82, 0x1D, 0x0C, 0x0A, 0x04, 0x00, 0x41, 0x1D, 0x03, 0x0D, 0x04, 0x14, 0x24, 0x05, 0x08, 0x23, 0x05, 0x14, 0x24, 0x08, 0x08, 0x23, 0x08, 0x14, 0x24, 0x0B, 0x08, 0x23, 0x0B, 0x14, 0x24, 0x0E, 0x08, 0x23, 0x0E, 0x25, 0x03, 0x14, 0x04, 0x26, 0x14, 0xFF ], 45 | [ 0x07, 0x4B, 0x0A, 0x14, 0x02, 0x07, 0x08, 0x0A, 0x09, 0x0F, 0x14, 0x19, 0x19, 0x19, 0x78, 0x78, 0x78, 0x78, 0x78, 0x09, 0x0A, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x64, 0x28, 0x02, 0x00, 0x42, 0x01, 0x07, 0x0C, 0x02, 0x42, 0x1C, 0x05, 0x0B, 0x02, 0x7A, 0x13, 0x15, 0x02, 0x02, 0x14, 0x04, 0x06, 0x14, 0x04, 0x0E, 0x14, 0x04, 0x16, 0x14, 0x22, 0x04, 0x14, 0x22, 0x0C, 0x14, 0x22, 0x16, 0x25, 0x14, 0x03, 0x04, 0x27, 0x07, 0xFF ], 46 | [ 0x08, 0x14, 0x0A, 0x14, 0x01, 0x03, 0x04, 0x05, 0x06, 0x0A, 0x0F, 0x14, 0x14, 0x14, 0x78, 0x6E, 0x64, 0x5A, 0x50, 0x02, 0x0E, 0x09, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x5A, 0x32, 0x02, 0x00, 0x14, 0x04, 0x06, 0x14, 0x22, 0x04, 0x14, 0x22, 0x0C, 0x04, 0x00, 0x05, 0x25, 0x14, 0x03, 0x42, 0x01, 0x07, 0x0C, 0x02, 0x42, 0x01, 0x0F, 0x0C, 0x02, 0x42, 0x1C, 0x05, 0x0B, 0x02, 0x42, 0x1C, 0x0D, 0x0B, 0x02, 0x43, 0x0E, 0x11, 0x08, 0x02, 0x14, 0x0C, 0x10, 0x00, 0x0E, 0x12, 0x14, 0x13, 0x12, 0x41, 0x0E, 0x0F, 0x08, 0x02, 0xFF ], 47 | [ 0x09, 0x14, 0x05, 0x0A, 0x64, 0x89, 0x8C, 0xFB, 0x33, 0x4B, 0x4B, 0x50, 0x55, 0x5A, 0x96, 0x96, 0x82, 0x82, 0x78, 0x08, 0x04, 0x09, 0x00, 0x00, 0x10, 0x14, 0x00, 0x00, 0xF0, 0x78, 0x00, 0x00, 0x82, 0x05, 0x0A, 0x0D, 0x0D, 0x00, 0x01, 0x0C, 0x0A, 0x82, 0x19, 0x0A, 0x0D, 0x0D, 0x00, 0x01, 0x1F, 0x0A, 0x42, 0x11, 0x12, 0x09, 0x02, 0x40, 0x11, 0x13, 0x09, 0x02, 0x25, 0x07, 0x0C, 0x04, 0x08, 0x0C, 0xFF ], 48 | [ 0x0A, 0x14, 0x19, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x96, 0x82, 0x78, 0x6E, 0x64, 0x06, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0D, 0x03, 0x04, 0x27, 0x16, 0x54, 0x05, 0x04, 0x11, 0x03, 0x54, 0x15, 0x04, 0x11, 0x05, 0x80, 0x05, 0x0B, 0x11, 0x03, 0x08, 0xC2, 0x01, 0x04, 0x15, 0x11, 0x00, 0x0D, 0x04, 0xC2, 0x07, 0x06, 0x0D, 0x0D, 0x00, 0x0D, 0x06, 0xC2, 0x09, 0x08, 0x09, 0x09, 0x00, 0x0D, 0x08, 0xC2, 0x0B, 0x0A, 0x05, 0x05, 0x00, 0x0D, 0x0A, 0x82, 0x03, 0x06, 0x03, 0x0F, 0x08, 0x00, 0x04, 0x06, 0x54, 0x04, 0x10, 0x04, 0x04, 0xFF ], 49 | [ 0x0B, 0x14, 0x32, 0x00, 0x00, 0x04, 0x66, 0x97, 0x64, 0x06, 0x06, 0x06, 0x06, 0x06, 0x78, 0x78, 0x96, 0x96, 0xF0, 0x0B, 0x08, 0x09, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x64, 0x50, 0x02, 0x00, 0x42, 0x0A, 0x03, 0x09, 0x04, 0x42, 0x14, 0x03, 0x09, 0x04, 0x42, 0x1E, 0x03, 0x09, 0x04, 0x42, 0x09, 0x16, 0x09, 0x00, 0x42, 0x0C, 0x0F, 0x11, 0x02, 0x42, 0x05, 0x0B, 0x09, 0x02, 0x42, 0x0F, 0x0B, 0x09, 0x02, 0x42, 0x19, 0x0B, 0x09, 0x02, 0x42, 0x1C, 0x13, 0x0B, 0x01, 0x14, 0x04, 0x03, 0x14, 0x0E, 0x03, 0x14, 0x18, 0x03, 0x14, 0x22, 0x03, 0x14, 0x04, 0x16, 0x14, 0x23, 0x15, 0x25, 0x14, 0x14, 0x04, 0x26, 0x11, 0xFF ], 50 | [ 0x0C, 0x14, 0x14, 0x00, 0x00, 0x3C, 0x02, 0x3B, 0x66, 0x13, 0x13, 0x0E, 0x10, 0x15, 0xB4, 0xAA, 0xA0, 0xA0, 0xA0, 0x0C, 0x0A, 0x09, 0x00, 0x00, 0x00, 0x10, 0x14, 0x00, 0x3C, 0x32, 0x09, 0x00, 0x42, 0x0A, 0x05, 0x12, 0x04, 0x42, 0x0E, 0x05, 0x12, 0x04, 0x42, 0x12, 0x05, 0x12, 0x04, 0x42, 0x16, 0x05, 0x12, 0x04, 0x42, 0x02, 0x06, 0x0B, 0x02, 0x42, 0x02, 0x0A, 0x0B, 0x02, 0x42, 0x02, 0x0E, 0x0F, 0x02, 0x42, 0x02, 0x12, 0x0B, 0x02, 0x81, 0x1E, 0x04, 0x04, 0x04, 0x00, 0x08, 0x20, 0x05, 0x81, 0x1E, 0x09, 0x04, 0x04, 0x00, 0x08, 0x20, 0x0A, 0x81, 0x1E, 0x0E, 0x04, 0x04, 0x00, 0x08, 0x20, 0x0F, 0x25, 0x03, 0x14, 0x04, 0x27, 0x16, 0xFF ], 51 | [ 0x0D, 0x8C, 0x05, 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x32, 0x37, 0x3C, 0x46, 0x50, 0xA0, 0x9B, 0x96, 0x91, 0x8C, 0x06, 0x08, 0x0D, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x25, 0x12, 0x03, 0x04, 0x0A, 0x03, 0x3A, 0x14, 0x03, 0x42, 0x05, 0x12, 0x1E, 0x02, 0x70, 0x05, 0x13, 0x1E, 0x02, 0x50, 0x05, 0x14, 0x1E, 0x02, 0xC1, 0x05, 0x15, 0x1E, 0x02, 0xFF ], 52 | [ 0x0E, 0x14, 0x0A, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x23, 0x28, 0x2A, 0x2D, 0x96, 0x91, 0x8C, 0x87, 0x82, 0x0C, 0x08, 0x09, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x0A, 0x0A, 0x0D, 0x0D, 0x00, 0x70, 0x0B, 0x0B, 0x0C, 0x03, 0xC1, 0x0C, 0x0A, 0x03, 0x0D, 0xC1, 0x10, 0x0A, 0x03, 0x0D, 0xC1, 0x14, 0x0A, 0x03, 0x0D, 0x50, 0x16, 0x08, 0x0C, 0x02, 0x48, 0x16, 0x07, 0x0C, 0x02, 0xC1, 0x17, 0x06, 0x03, 0x04, 0xC1, 0x1B, 0x06, 0x03, 0x04, 0xC1, 0x1F, 0x06, 0x03, 0x04, 0x25, 0x03, 0x03, 0x04, 0x27, 0x14, 0xFF ], 53 | [ 0x0F, 0x08, 0x0A, 0x14, 0x01, 0x1D, 0x1E, 0x1F, 0x20, 0x0F, 0x14, 0x14, 0x19, 0x1E, 0x78, 0x78, 0x78, 0x78, 0x8C, 0x08, 0x0E, 0x09, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x64, 0x50, 0x02, 0x00, 0x42, 0x02, 0x04, 0x0A, 0x03, 0x42, 0x0F, 0x0D, 0x0A, 0x01, 0x41, 0x0C, 0x0E, 0x03, 0x02, 0x43, 0x0C, 0x0F, 0x03, 0x02, 0x04, 0x14, 0x16, 0x25, 0x14, 0x03, 0xFF ], 54 | [ 0x10, 0x14, 0x0A, 0x14, 0x01, 0x78, 0x81, 0x7E, 0x7B, 0x0C, 0x0F, 0x0F, 0x0F, 0x0C, 0x96, 0x96, 0x96, 0x96, 0x96, 0x09, 0x0A, 0x09, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x25, 0x01, 0x03, 0x04, 0x27, 0x04, 0x81, 0x08, 0x13, 0x04, 0x04, 0x00, 0x08, 0x0A, 0x14, 0xC2, 0x07, 0x0A, 0x06, 0x08, 0x43, 0x07, 0x0A, 0x06, 0x02, 0x81, 0x10, 0x13, 0x04, 0x04, 0x00, 0x08, 0x12, 0x14, 0xC2, 0x0F, 0x0A, 0x06, 0x08, 0x43, 0x0F, 0x0A, 0x06, 0x02, 0x81, 0x18, 0x13, 0x04, 0x04, 0x00, 0x08, 0x1A, 0x14, 0x81, 0x20, 0x13, 0x04, 0x04, 0x00, 0x08, 0x22, 0x14, 0xFF ], 55 | [ 0x11, 0x14, 0x1E, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0E, 0x02, 0x09, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0xFF, 0x09, 0x00, 0x00, 0x87, 0x00, 0x02, 0x28, 0x16, 0x07, 0x87, 0x00, 0x02, 0x14, 0x0C, 0x00, 0x32, 0x0A, 0x0C, 0x10, 0x0A, 0x04, 0x01, 0x0A, 0x05, 0x25, 0x03, 0x05, 0x04, 0x12, 0x0C, 0xFF ], 56 | [ 0x12, 0x14, 0x0A, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x06, 0x0F, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x02, 0x28, 0x16, 0x07, 0x87, 0x00, 0x02, 0x14, 0x0C, 0x01, 0x50, 0x01, 0x03, 0x09, 0x03, 0x48, 0x02, 0x03, 0x08, 0x03, 0x54, 0x01, 0x05, 0x08, 0x03, 0x50, 0x01, 0x06, 0x07, 0x03, 0x50, 0x12, 0x03, 0x09, 0x05, 0x54, 0x12, 0x05, 0x08, 0x05, 0x50, 0x12, 0x06, 0x07, 0x05, 0x25, 0x01, 0x04, 0x04, 0x12, 0x04, 0xFF ], 57 | [ 0x13, 0x04, 0x0A, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x14, 0x14, 0x14, 0x14, 0x14, 0x06, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x02, 0x28, 0x16, 0x07, 0x87, 0x00, 0x02, 0x14, 0x0C, 0x00, 0x54, 0x01, 0x0C, 0x12, 0x02, 0x88, 0x0F, 0x09, 0x04, 0x04, 0x08, 0x25, 0x08, 0x03, 0x04, 0x12, 0x07, 0xFF ], 58 | [ 0x14, 0x03, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x14, 0x14, 0x14, 0x14, 0x14, 0x06, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x02, 0x28, 0x16, 0x07, 0x87, 0x00, 0x02, 0x14, 0x0C, 0x01, 0xD0, 0x0B, 0x03, 0x03, 0x02, 0x80, 0x0B, 0x07, 0x03, 0x06, 0x00, 0x43, 0x0B, 0x06, 0x03, 0x02, 0x43, 0x0B, 0x0A, 0x03, 0x02, 0x50, 0x08, 0x07, 0x03, 0x03, 0x25, 0x03, 0x03, 0x04, 0x09, 0x0A, 0xFF ] 59 | ] 60 | 61 | var NAMES = [ 62 | "Intro", "Rooms", "Maze", "Butterflies", 63 | "Guards", "Firefly dens", "Amoeba", "Enchanted wall", 64 | "Greed", "Tracks", "Crowd", "Walls", 65 | "Apocalypse", "Zigzag", "Funnel", "Enchanted boxes", 66 | "Interval 1", "Interval 2", "Interval 3", "Interval 4", 67 | ] 68 | 69 | var DESCRIPTIONS = [ 70 | "Pick up jewels and exit before time is up", 71 | "Pick up jewels, but you must move boulders to get all jewels", 72 | "Pick up jewels. You must get every jewel to exit", 73 | "Drop boulders on butterflies to create jewels", 74 | "The jewels are there for grabbing, but they are guarded by the deadly fireflies", 75 | "Each firefly is guarding a jewel", 76 | "Surround the amoeba with boulders. Pick up jewels when it suffocates", 77 | "Activate the enchanted wall and create as many jewels as you can", 78 | "You have to get a lot of jewels here, lucky there are so many", 79 | "Get the jewels, avoid the fireflies", 80 | "You must move a lot of boulders around in some tight spaces", 81 | "Drop a boulder on a firefly at the right time to blast through walls", 82 | "Bring the butterflies and amoeba together and watch the jewels fly", 83 | "Magically transform the butterflies into jewels, but don't waste any boulders", 84 | "There is an enchanted wall at the bottom of the rock tunnel", 85 | "The top of each room is an enchanted wall, but you'll have to blast your way inside", 86 | "Interval 1", 87 | "Interval 2", 88 | "Interval 3", 89 | "Interval 4" 90 | ] 91 | 92 | function assert(condition, msg) { 93 | if (!condition) 94 | throw msg; 95 | }; 96 | 97 | function decodeCave(cave) { 98 | var n, x, y, seeds, object, kind, prob, index = cave[0x00]; 99 | assert(cave.length > 0x20, 'cave is too short'); 100 | var result = { 101 | index: index - 1, 102 | name: NAMES[index - 1], 103 | description: DESCRIPTIONS[index - 1], 104 | width: 40, // all caves in BD1 were 40x22 105 | height: 22, // all caves in BD1 were 40x22 106 | magicWallMillingTime: cave[0x01], 107 | amoebaSlowGrowthTime: cave[0x01], // same as magicWallMillingTime 108 | initialDiamondValue: cave[0x02], 109 | extraDiamondValue: cave[0x03], 110 | randomSeed: cave[0x04], // at other difficulty levels: cave[0x05], cave[0x06], cave[0x07], cave[0x08]], 111 | diamondsNeeded: cave[0x09], // at other difficulty levels: cave[0x0A], cave[0x0B], cave[0x0C], cave[0x0D]], 112 | caveTime: cave[0x0E], // at other difficulty levels: cave[0x0F], cave[0x10], cave[0x11], cave[0x12]], 113 | color1: COLORS[cave[0x13]].rgb, 114 | color2: COLORS[cave[0x14]].rgb, 115 | randomObjects: [cave[0x18], cave[0x19], cave[0x1A], cave[0x1B]], 116 | randomObjectProb: [cave[0x1C], cave[0x1D], cave[0x1E], cave[0x1F]], 117 | amoebaMaxSize: 200, // hard coded for a 40x22 cave (based on c64 version) 118 | map: [ ] 119 | }; 120 | 121 | seeds = [0, result.randomSeed]; 122 | 123 | for(y = 0 ; y < result.height ; ++y) 124 | for (x = 0 ; x < result.width ; ++x) 125 | drawSingleObject(result, SPACE, x, y); 126 | 127 | for(y = 1 ; y < result.height-1 ; ++y) { 128 | for (x = 0 ; x < result.width ; ++x) { 129 | object = DIRT; 130 | bdrandom(seeds); 131 | for(n = 0 ; n < result.randomObjects.length ; ++n) 132 | if (seeds[0] < result.randomObjectProb[n]) 133 | object = result.randomObjects[n]; 134 | drawSingleObject(result, object, x, y); 135 | } 136 | } 137 | 138 | drawRect(result, STEEL, 0, 0, result.width, result.height); 139 | 140 | n = 0x20; 141 | while ((n < cave.length) && (cave[n] < 0xFF)) { 142 | object = (cave[n] & 0x3F); // low 6 bits 143 | kind = (cave[n] & 0xC0) >> 6; // high 2 bits 144 | n++; 145 | x = cave[n++]; 146 | y = cave[n++] - 2; // raw data assumes top 2 lines are for displaying scores 147 | switch(kind) { 148 | case 0: drawSingleObject(result, object, x, y); break; 149 | case 1: drawLine( result, object, x, y, cave[n++], cave[n++]); break; 150 | case 2: drawFilledRect( result, object, x, y, cave[n++], cave[n++], cave[n++]); break; 151 | case 3: drawRect( result, object, x, y, cave[n++], cave[n++]); break; 152 | default: 153 | assert(false, 'unexpected kind' + kind); 154 | } 155 | } 156 | 157 | return result; 158 | } 159 | 160 | function drawSingleObject(result, object, x, y) { 161 | result.map[x] = result.map[x] || []; 162 | result.map[x][y] = object; 163 | } 164 | 165 | function drawLine(result, object, x, y, length, dir) { 166 | var dx = [ 0, 1, 1, 1, 0, -1, -1, -1 ][dir], 167 | dy = [-1, -1, 0, 1, 1, 1, 0, -1 ][dir]; 168 | for(var n = 0 ; n < length ; n++) { 169 | drawSingleObject(result, object, x, y); 170 | x += dx; 171 | y += dy; 172 | } 173 | } 174 | 175 | function drawFilledRect(result, object, x, y, width, height, fill) { 176 | drawRect(result, object, x, y, width, height); 177 | var minx = x + 1, maxx = x + width - 1, 178 | miny = y + 1, maxy = y + height - 1; 179 | for(x = minx ; x < maxx ; x++) 180 | for(y = miny ; y < maxy ; y++) 181 | drawSingleObject(result, fill, x, y); 182 | } 183 | 184 | function drawRect(result, object, x, y, width, height) { 185 | drawLine(result, object, x, y, width, DIR.RIGHT); 186 | drawLine(result, object, x, y+height-1, width, DIR.RIGHT); 187 | drawLine(result, object, x, y, height, DIR.DOWN); 188 | drawLine(result, object, x+width-1, y, height, DIR.DOWN); 189 | } 190 | 191 | function bdrandom(seeds) { // ported from c version that was ported from original C64 6510 assembler - see http://www.bd-fans.com/Files/FanStuff/Programming/decodecaves.c 192 | var tmp1, tmp2, carry, result; 193 | 194 | assert(seeds.length === 2, 'expected 2 seed numbers'); 195 | assert((seeds[0] >= 0) && (seeds[0] <= 0xFF), 'expected seed 0 to be between 0 and 0xFF'); 196 | assert((seeds[1] >= 0) && (seeds[1] <= 0xFF), 'expected seed 1 to be between 0 and 0xFF'); 197 | 198 | tmp1 = (seeds[0] & 0x0001) * 0x0080; 199 | tmp2 = (seeds[1] >> 1) & 0x007F; 200 | 201 | result = seeds[1] + (seeds[1] & 0x0001) * 0x0080; 202 | carry = (result > 0x00FF); 203 | result = result & 0x00FF; 204 | result = result + carry + 0x13; 205 | carry = (result > 0x00FF); 206 | seeds[1] = result & 0x00FF; 207 | result = seeds[0] + carry + tmp1; 208 | carry = (result > 0x00FF); 209 | result = result & 0x00FF; 210 | result = result + carry + tmp2; 211 | seeds[0] = result & 0x00FF; 212 | 213 | assert((seeds[0] >= 0) && (seeds[0] <= 0xFF), 'expected seed 0 to STILL be between 0 and 0xFF'); 214 | assert((seeds[1] >= 0) && (seeds[1] <= 0xFF), 'expected seed 0 to STILL be between 0 and 0xFF'); 215 | } 216 | 217 | var caves = []; 218 | for(var n = 0 ; n < DATA.length ; n++) 219 | caves.push(decodeCave(DATA[n])); 220 | 221 | return caves; 222 | 223 | }(); 224 | 225 | -------------------------------------------------------------------------------- /help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Javascript Boulderdash Instructions 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 17 | 18 |
19 | 20 | 21 | 40 | 41 | back to game 42 | 43 |
44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /help/amoeba.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/amoeba.gif -------------------------------------------------------------------------------- /help/boulder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/boulder.gif -------------------------------------------------------------------------------- /help/butterfly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/butterfly.gif -------------------------------------------------------------------------------- /help/diamond.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/diamond.gif -------------------------------------------------------------------------------- /help/dirt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/dirt.gif -------------------------------------------------------------------------------- /help/exit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/exit.gif -------------------------------------------------------------------------------- /help/firefly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/firefly.gif -------------------------------------------------------------------------------- /help/magic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/magic.gif -------------------------------------------------------------------------------- /help/rockford.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/rockford.gif -------------------------------------------------------------------------------- /help/space.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/space.gif -------------------------------------------------------------------------------- /help/steel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/steel.gif -------------------------------------------------------------------------------- /help/wall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/help/wall.gif -------------------------------------------------------------------------------- /images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/images/down.png -------------------------------------------------------------------------------- /images/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/images/sprites.png -------------------------------------------------------------------------------- /images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-boulderdash/61f0bb59785878669a28796c471845bb9a91402e/images/up.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Javascript Boulderdash 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 18 | 19 | Sorry, this example cannot be run because your browser does not support the <canvas> element 20 | 21 |
Use the arrow keys to control Rockford.
22 |
23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r6 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function s(a,g,d){var f,c,e;for(c=0;c<30;c++)for(f=0;f<73;f++)e=(f+c*74)*4,a[e]=a[e+4],a[e+1]=a[e+5],a[e+2]=a[e+6];for(c=0;c<30;c++)e=(73+c*74)*4,c'+n+" MS ("+z+"-"+A+")";o.putImageData(B,0,0);F=j;if(j> 9 | v+1E3){l=Math.round(u*1E3/(j-v));w=Math.min(w,l);x=Math.max(x,l);s(y.data,Math.min(30,30-l/100*30),"fps");d.innerHTML=''+l+" FPS ("+w+"-"+x+")";m.putImageData(y,0,0);if(t==3)p=performance.memory.usedJSHeapSize*9.54E-7,C=Math.min(C,p),D=Math.max(D,p),s(E.data,Math.min(30,30-p/2),"mb"),i.innerHTML=''+Math.round(p)+" MB ("+Math.round(C)+"-"+Math.round(D)+")",q.putImageData(E,0,0);v=j;u=0}}}}; 10 | 11 | --------------------------------------------------------------------------------