├── 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 |