-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbomberman.htm
More file actions
378 lines (327 loc) · 9.29 KB
/
bomberman.htm
File metadata and controls
378 lines (327 loc) · 9.29 KB
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="height=device-height, initial-scale=1.0">
<meta charset="UTF-8">
<title>Bomberman</title>
<link rel="shortcut icon" href="bomberman.icon.png" type="image/x-icon">
<style>
html, body {
height: 100%;
margin: 0;
}
body {
background: black;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
background: forestgreen;
}
</style>
</head>
<body>
<canvas width="960" height="832" id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const grid = 64;
const numRows = 13;
const numCols = 15;
// soft wall image
const softWallImg = new Image();
softWallImg.src = './soft-wall.png';
// wall image
const wallImg = new Image();
wallImg.src = './wall.png';
// create a mapping of object types
const types = {
wall: '▉',
softWall: 1,
bomb: 2
};
// keep track of all entities
let entities = [];
// keep track of what is in every cell of the game using a 2d array. the
// template is used to note where walls are and where soft walls cannot spawn.
// '▉' represents a wall
// 'x' represents a cell that cannot have a soft wall (player start zone)
let cells = [];
const template = [
['▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉'],
['▉','x','x', , , , , , , , , ,'x','x','▉'],
['▉','x','▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉','x','▉'],
['▉','x', , , , , , , , , , , ,'x','▉'],
['▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉'],
['▉', , , , , , , , , , , , , ,'▉'],
['▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉'],
['▉', , , , , , , , , , , , , ,'▉'],
['▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉'],
['▉','x', , , , , , , , , , , ,'x','▉'],
['▉','x','▉', ,'▉', ,'▉', ,'▉', ,'▉', ,'▉','x','▉'],
['▉','x','x', , , , , , , , , ,'x','x','▉'],
['▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉','▉']
];
// populate the level with walls and soft walls
function generateLevel() {
cells = [];
for (let row = 0; row < numRows; row++) {
cells[row] = [];
for (let col = 0; col < numCols; col++) {
// 90% chance cells will contain a soft wall
if (!template[row][col] && Math.random() < 0.90) {
cells[row][col] = types.softWall;
}
else if (template[row][col] === types.wall) {
cells[row][col] = types.wall;
}
}
}
}
// blow up a bomb and its surrounding tiles
function blowUpBomb(bomb) {
// bomb has already exploded so don't blow up again
if (!bomb.alive) return;
bomb.alive = false;
// remove bomb from grid
cells[bomb.row][bomb.col] = null;
// explode bomb outward by size
const dirs = [{
// up
row: -1,
col: 0
}, {
// down
row: 1,
col: 0
}, {
// left
row: 0,
col: -1
}, {
// right
row: 0,
col: 1
}];
dirs.forEach((dir) => {
for (let i = 0; i < bomb.size; i++) {
const row = bomb.row + dir.row * i;
const col = bomb.col + dir.col * i;
const cell = cells[row][col];
// stop the explosion if it hit a wall
if (cell === types.wall) {
return;
}
// center of the explosion is the first iteration of the loop
entities.push(new Explosion(row, col, dir, i === 0 ? true : false));
cells[row][col] = null;
// bomb hit another bomb so blow that one up too
if (cell === types.bomb) {
// find the bomb that was hit by comparing positions
const nextBomb = entities.find((entity) => {
return (
entity.type === types.bomb &&
entity.row === row && entity.col === col
);
});
blowUpBomb(nextBomb);
}
// stop the explosion if hit anything
if (cell) {
return;
}
}
});
}
// bomb constructor function
function Bomb(row, col, size, owner) {
this.row = row;
this.col = col;
this.radius = grid * 0.4;
this.size = size; // the size of the explosion
this.owner = owner; // which player placed this bomb
this.alive = true;
this.type = types.bomb;
// bomb blows up after 3 seconds
this.timer = 3000;
// update the bomb each frame
this.update = function(dt) {
this.timer -= dt;
// blow up bomb if timer is done
if (this.timer <= 0) {
return blowUpBomb(this);
}
// change the size of the bomb every half second. we can determine the size
// by dividing by 500 (half a second) and taking the ceiling of the result.
// then we can check if the result is even or odd and change the size
const interval = Math.ceil(this.timer / 500);
if (interval % 2 === 0) {
this.radius = grid * 0.4;
}
else {
this.radius = grid * 0.5;
}
};
// render the bomb each frame
this.render = function() {
const x = (this.col + 0.5) * grid;
const y = (this.row + 0.5) * grid;
// draw bomb
context.fillStyle = 'black';
context.beginPath();
context.arc(x, y, this.radius, 0, 2 * Math.PI);
context.fill();
// draw bomb fuse moving up and down with the bomb size
const fuseY = (this.radius === grid * 0.5 ? grid * 0.15 : 0);
context.strokeStyle = 'white';
context.lineWidth = 5;
context.beginPath();
context.arc(
(this.col + 0.75) * grid,
(this.row + 0.25) * grid - fuseY,
10, Math.PI, -Math.PI / 2
);
context.stroke();
};
}
// explosion constructor function
function Explosion(row, col, dir, center) {
this.row = row;
this.col = col;
this.dir = dir;
this.alive = true;
// show explosion for 0.3 seconds
this.timer = 300;
// update the explosion each frame
this.update = function(dt) {
this.timer -= dt;
if (this.timer <=0) {
this.alive = false;
}
};
// render the explosion each frame
this.render = function() {
const x = this.col * grid;
const y = this.row * grid;
const horizontal = this.dir.col;
const vertical = this.dir.row;
// create a fire effect by stacking red, orange, and yellow on top of
// each other using progressively smaller rectangles
context.fillStyle = '#D72B16'; // red
context.fillRect(x, y, grid, grid);
context.fillStyle = '#F39642'; // orange
// determine how to draw based on if it's vertical or horizontal
// center draws both ways
if (center || horizontal) {
context.fillRect(x, y + 6, grid, grid - 12);
}
if (center || vertical) {
context.fillRect(x + 6, y, grid - 12, grid);
}
context.fillStyle = '#FFE5A8'; // yellow
if (center || horizontal) {
context.fillRect(x, y + 12, grid, grid - 24);
}
if (center || vertical) {
context.fillRect(x + 12, y, grid - 24, grid);
}
};
}
// player character (just a simple circle)
const player = {
row: 1,
col: 1,
numBombs: 1,
bombSize: 3,
radius: grid * 0.35,
render() {
const x = (this.col + 0.5) * grid;
const y = (this.row + 0.5) * grid;
context.save();
context.fillStyle = 'white';
context.beginPath();
context.arc(x, y, this.radius, 0, 2 * Math.PI);
context.fill();
}
}
// game loop
let last;
let dt;
function loop(timestamp) {
requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// calculate the time difference since the last update. requestAnimationFrame
// passes the current timestamp as a parameter to the loop
if (!last) {
last = timestamp;
}
dt = timestamp - last;
last = timestamp;
// update and render everything in the grid
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
switch(cells[row][col]) {
case types.wall:
context.drawImage(wallImg, col * grid, row * grid);
break;
case types.softWall:
context.drawImage(softWallImg, col * grid, row * grid);
break;
}
}
}
// update and render all entities
entities.forEach((entity) => {
entity.update(dt);
entity.render();
});
// remove dead entities
entities = entities.filter((entity) => entity.alive);
player.render();
}
// listen to keyboard events to move the snake
document.addEventListener('keydown', function(e) {
let row = player.row;
let col = player.col;
// left arrow key
if (e.which === 37) {
col--;
}
// up arrow key
else if (e.which === 38) {
row--;
}
// right arrow key
else if (e.which === 39) {
col++;
}
// down arrow key
else if (e.which === 40) {
row++;
}
// space key (bomb)
else if (
e.which === 32 && !cells[row][col] &&
// count the number of bombs the player has placed
entities.filter((entity) => {
return entity.type === types.bomb && entity.owner === player
}).length < player.numBombs
) {
// place bomb
const bomb = new Bomb(row, col, player.bombSize, player);
entities.push(bomb);
cells[row][col] = types.bomb;
}
// don't move the player if something is already at that position
if (!cells[row][col]) {
player.row = row;
player.col = col;
}
});
// start the game
generateLevel();
requestAnimationFrame(loop);
</script>
</body>
</html>