-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
226 lines (206 loc) · 7.96 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Boulder Dash - js1k 2017</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/themes/prism-okaidia.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-javascript.min.js"></script>
<script src="main.js"></script>
</head>
<body id="body">
<header>
<h1>Boulder Dash - js1k 2017</h1>
<p>by <a href="http://magnetiq.com">Ates Goral</a> aka magna.</p>
<p>
A minimal Boulder Dash clone in 1024 bytes of JavaScript.
My contribution to <a href="https://js1k.com/2017-magic/">JS1k 2017 - Magic</a>.
Use arrow keys to reach the exit on the bottom right.
Don't get trapped among boulders.
Don't get squashed by boulders.
Level 11 is unbeatable (it's not your fault).
Click on the canvas to focus it if the page starts scrolling up and down.
Fork on <a href="https://github.com/atesgoral/bd1k">GitHub</a>.
</p>
</header>
<main>
<p id="error"></p>
<p><canvas id="canvas" width="1024" height="640" tabindex="0"></canvas></p>
<p id="bytes"></p>
<h2>Minified</h2>
<pre><code id="minified" class="language-javascript"></code></pre>
<h2>Annotated</h2>
<pre><code id="source" class="language-javascript"></code></pre>
</main>
<footer>
© 2017 <a href="http://magnetiq.com">Ates Goral</a>
</footer>
<script id="rawSrc" type="text/plain">
// predefined global variables:
// ctx - canvas 2D context
// piggybacks (on predefined global variables):
// processed - tile process markers - piggybacked on canvas element
// keyDown - key down flags - piggybacked on body element
// playerVectors - player vectors - piggybacked on canvas 2D context
// tiles
(tiles = new Image).src =
// browsers are resilient to omitting the content type (image/png) and the trailing base-64 paddings (=)
'data:;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAQAgMAAAAZudU+AAAADFBMVEUAAABSUlKlKgD///8IGsOpAAAAl0lEQVQI12NgYOBQCg3lP8AAAguaQkPNN7AIAJlqKo6Ob/+wBgCZixYFBlbuF3QBMru6CgvD6sFMiRmhoanpYAWZXqGhUfGMDiAFS0JDQyGiWlmhoa5hwkeAzElA0cA03gIgc0aSo6NrKDPIPim1wMDAULC2jo7CwpAQ7gcgNywKDRUN4VAAMjsVQkNZJoCZCh2hQJU2NgC8WipFK+hqJAAAAABJRU5ErkJggg';
// current level
level = 1;
// processed frame counter to prevent reprocessing a moved tile
processCount =
// turn off image smoothing to get crispy and chunky pixels
ctx.imageSmoothingEnabled = 0;
onkeydown = event =>
playerVectors[
keyDown[
event = event.which // grab arrow key code (37: left, 38: up, 39: right, 40: down)
] = 1, // mark key as down
event & 1 // vector index based on key (0: y vector, 1: x vector)
] = (event & 2) - 1; // set vector to -1 or 1 depending on key (y vector is inverted)
onkeyup = event =>
keyDown[event = event.which] // if key was down
? playerVectors[event & 1] = // then set corresponding vector to 0
keyDown[event] = 0 // and mark key as not down
: 0;
mapHeight = 20;
mapWidth = tileSize = 32;
mapArea = mapHeight * mapWidth;
exit = mapArea - mapWidth - 1; // inside bottom right wall
map = [
levelTransition = mapHeight // level transition counter (0: run game, >0: level transition)
];
seed = 5; // PRNG (pseudo-random number generator) seed
// randomly returns true/false, with 80% preference for true
preferably = _ => (
(seed += seed * seed | 5) // PRNG
% // modulus to contain random values within range
mapArea // just a large value we already have
) / mapArea < .8;
// calls given callback on every map tile
iterate = callback => {
for (j = 0; j < mapArea;)
callback(j, j % mapWidth, j++ / mapWidth | 0)
};
// starts a level
startLevel = _ => {
// initialize map
iterate(
(j, x, y) =>
map[j] =
x * y // if not top or left edge
&& x < mapWidth - 1 // and not right edge
&& y < mapHeight - 1 // and not bottom edge
// then pick a random non-wall tile
? preferably()
? preferably()
? 1 // dirt - 80% * 80% = 64%
: 3 // boulder - 80% * 20% = 16%
: 0 // empty space - 20%
// else put a wall
: 2 // wall
);
// place player on top left
map[mapWidth + 1] = 4; // player
// place exit
map[exit] = 0 // empty space
};
setInterval(_ => {
// render map
iterate((j, x, y) =>
ctx.drawImage(
tiles, // source image
// source tile x
(levelTransition // if level transition in progress
&& (x + y) % mapHeight <= mapHeight - levelTransition // diagonal blinds
? 2 // wall
: map[j]) * 8, // tile from map
0, // source tile y
8, // source tile width
16, // source tile height
x * mapWidth, // destination tile x
y * mapWidth, // destination tile y
tileSize, // destination tile width
tileSize // destination tile height
)
);
ctx.font = '99px monospace';
ctx.fillStyle = '#fff';
levelTransition && ctx.fillText(
level,
mapWidth,
mapArea - mapWidth
);
levelTransition // if in the process of level transition
? --levelTransition || // then decrement counter
startLevel(level++) // start next level if counter has reached 0
: map[exit] // else if exit is occupied (by player)
? levelTransition = mapHeight // then start level transition
: iterate( // else process map
j =>
processed[j] != processCount // tile hasn't been already processed in this frame (might have been moved into this position)
&& (handler = { // then invoke tile handler
3: _ => // boulder handler
!map[dest = j + mapWidth] // if beneath the boulder is empty
? 1 // move boulder down
: map[dest] == tile // else if resting on another boulder
? map[dest = j - 1] + map[j + mapWidth - 1] < 1 // else if can slide left
? 1 // move boulder left
: map[dest = j + 1] + map[j + mapWidth + 1] < 1 // else if can slide right
: map[dest] == 4 // else if the player is beneath the boulder
&& processed[j] == processCount - 1 // and if the boulder is falling
&& (
level = 1, // restart game
levelTransition = mapHeight, // restart level
seed = 5 // reset seed
), // crush player
4: _ => // player handler
playerVectors[0] & map[dest = j + playerVectors[0] * -mapWidth] < 2 // if the y vector is set and the player can move
|| playerVectors[1] & map[dest = j + playerVectors[1]] < 2 // if the x vector is set and the player can move
}[tile = map[j]]) // get tile and if a tile handler exists
&& handler() // invoke tile handler and if a move should happen
? map[ // then set tile at new position
map[j] = 0, // empty old position
processed[dest] = processCount, // mark tile at new position as processed
dest // destination tile position
] = tile // set tile
: 0
);
processCount++ // increment process count
}, 99) // render and process every 99 ms
</script>
<script id="rawVars" type="text/plain">
{
iterate: 'i',
preferably: 'r',
seed: 'q',
mapHeight: 'h',
mapWidth: 'w',
tileSize: 's',
mapArea: 'A',
exit: 'e',
levelTransition: 'o',
level: 'l',
tiles: 'p',
map: 'm',
tile: 't',
handler: 'g',
startLevel: '$',
processCount: 'f',
dest: 'k',
// arguments
prefix: 'p',
event: 'e',
callback: 'f',
// predefined globals
ctx: 'c',
// piggybacks
processed: 'a',
keyDown: 'b',
playerVectors: 'c'
}
</script>
</body>
</html>