Browse Source

first commit

master
zapashcanon 3 years ago
commit
d7a2b327f2
Signed by: zapashcanon GPG Key ID: 8981C3C62D1D28F1
  1. 38
      bomberman.conf
  2. 16
      scripts/install.sh
  3. BIN
      src/audio/intro-screen.mp3
  4. BIN
      src/audio/intro-screen.ogg
  5. 17
      src/css/style.css
  6. BIN
      src/img/betty_bottom.png
  7. BIN
      src/img/betty_dead.png
  8. BIN
      src/img/betty_left.png
  9. BIN
      src/img/betty_right.png
  10. BIN
      src/img/betty_roasted.png
  11. BIN
      src/img/betty_top.png
  12. BIN
      src/img/bettytwo_bottom.png
  13. BIN
      src/img/bettytwo_dead.png
  14. BIN
      src/img/bettytwo_left.png
  15. BIN
      src/img/bettytwo_right.png
  16. BIN
      src/img/bettytwo_roasted.png
  17. BIN
      src/img/bettytwo_top.png
  18. BIN
      src/img/bomb_0.png
  19. BIN
      src/img/bomb_1.png
  20. BIN
      src/img/bomb_2.png
  21. BIN
      src/img/bomb_3.png
  22. BIN
      src/img/bomb_4.png
  23. BIN
      src/img/bomberman_bottom.png
  24. BIN
      src/img/bomberman_dead.png
  25. BIN
      src/img/bomberman_left.png
  26. BIN
      src/img/bomberman_right.png
  27. BIN
      src/img/bomberman_roasted.png
  28. BIN
      src/img/bomberman_top.png
  29. BIN
      src/img/george_bottom.png
  30. BIN
      src/img/george_dead.png
  31. BIN
      src/img/george_left.png
  32. BIN
      src/img/george_right.png
  33. BIN
      src/img/george_roasted.png
  34. BIN
      src/img/george_top.png
  35. BIN
      src/img/tile_grass.png
  36. BIN
      src/img/tile_wall.png
  37. BIN
      src/img/tile_wood.png
  38. 54
      src/index.html
  39. 25
      src/js/bomb.js
  40. 80
      src/js/main.js
  41. 29
      src/js/mover.js
  42. 28
      src/js/point.js
  43. 74
      src/js/renderer.js
  44. 110
      src/js/state.js
  45. 30
      src/json/lvl_01.json

38
bomberman.conf

@ -0,0 +1,38 @@
Define short_name bomberman
Define server_name ${short_name}.zapashcanon.fr
Define admin_mail leo@ndrs.fr
Define cert_name ${server_name}
Define cert_dir /etc/letsencrypt/live
Define log_dir /var/log/apache2
Define log_file ${log_dir}/${short_name}
Define error_log_file ${log_file}.error.log
Define access_log_file ${log_file}.access.log
Define doc_root /var/www/${server_name}/src
<VirtualHost *:80>
RewriteEngine on
RewriteCond %{SERVER_NAME} =${server_name}
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName ${server_name}
ServerAdmin ${admin_mail}
DocumentRoot ${doc_root}
ErrorLog ${error_log_file}
CustomLog ${access_log_file} combined
Protocols h2 http/1.1
SSLCertificateFile ${cert_dir}/${cert_name}/fullchain.pem
SSLCertificateKeyFile ${cert_dir}/${cert_name}/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

16
scripts/install.sh

@ -0,0 +1,16 @@
#!/bin/sh
set -eu
( cd "$(dirname "$0")/../"
domain=bomberman.zapashcanon.fr
sudo cp ./*.conf /etc/apache2/sites-available/
dst=/var/www/${domain}/
sudo mkdir -p $dst
sudo cp -r src/ $dst
sudo service apache2 restart
echo "Installed !"
)

BIN
src/audio/intro-screen.mp3

BIN
src/audio/intro-screen.ogg

17
src/css/style.css

@ -0,0 +1,17 @@
#canvas {
position: relative;
width: 1000px;
height: 600px;
border: 2px solid black;
z-index:1;
}
.object {
position: absolute;
z-index:2;
background-color: black;
}
/*#audioplayer {
display: none;
}*/

BIN
src/img/betty_bottom.png

After

Width: 48  |  Height: 48  |  Size: 706 B

BIN
src/img/betty_dead.png

After

Width: 48  |  Height: 48  |  Size: 9.1 KiB

BIN
src/img/betty_left.png

After

Width: 48  |  Height: 48  |  Size: 622 B

BIN
src/img/betty_right.png

After

Width: 48  |  Height: 48  |  Size: 623 B

BIN
src/img/betty_roasted.png

After

Width: 48  |  Height: 48  |  Size: 673 B

BIN
src/img/betty_top.png

After

Width: 48  |  Height: 48  |  Size: 660 B

BIN
src/img/bettytwo_bottom.png

After

Width: 48  |  Height: 48  |  Size: 704 B

BIN
src/img/bettytwo_dead.png

After

Width: 48  |  Height: 48  |  Size: 9.1 KiB

BIN
src/img/bettytwo_left.png

After

Width: 48  |  Height: 48  |  Size: 613 B

BIN
src/img/bettytwo_right.png

After

Width: 48  |  Height: 48  |  Size: 614 B

BIN
src/img/bettytwo_roasted.png

After

Width: 48  |  Height: 48  |  Size: 666 B

BIN
src/img/bettytwo_top.png

After

Width: 48  |  Height: 48  |  Size: 655 B

BIN
src/img/bomb_0.png

After

Width: 28  |  Height: 28  |  Size: 566 B

BIN
src/img/bomb_1.png

After

Width: 28  |  Height: 28  |  Size: 611 B

BIN
src/img/bomb_2.png

After

Width: 28  |  Height: 28  |  Size: 626 B

BIN
src/img/bomb_3.png

After

Width: 28  |  Height: 28  |  Size: 611 B

BIN
src/img/bomb_4.png

After

Width: 28  |  Height: 28  |  Size: 695 B

BIN
src/img/bomberman_bottom.png

After

Width: 27  |  Height: 40  |  Size: 454 B

BIN
src/img/bomberman_dead.png

After

Width: 27  |  Height: 40  |  Size: 4.3 KiB

BIN
src/img/bomberman_left.png

After

Width: 27  |  Height: 40  |  Size: 375 B

BIN
src/img/bomberman_right.png

After

Width: 27  |  Height: 40  |  Size: 380 B

BIN
src/img/bomberman_roasted.png

After

Width: 27  |  Height: 40  |  Size: 4.3 KiB

BIN
src/img/bomberman_top.png

After

Width: 27  |  Height: 40  |  Size: 438 B

BIN
src/img/george_bottom.png

After

Width: 48  |  Height: 48  |  Size: 632 B

BIN
src/img/george_dead.png

After

Width: 48  |  Height: 48  |  Size: 9.1 KiB

BIN
src/img/george_left.png

After

Width: 48  |  Height: 48  |  Size: 582 B

BIN
src/img/george_right.png

After

Width: 48  |  Height: 48  |  Size: 587 B

BIN
src/img/george_roasted.png

After

Width: 48  |  Height: 48  |  Size: 605 B

BIN
src/img/george_top.png

After

Width: 48  |  Height: 48  |  Size: 549 B

BIN
src/img/tile_grass.png

After

Width: 32  |  Height: 32  |  Size: 49 KiB

BIN
src/img/tile_wall.png

After

Width: 32  |  Height: 32  |  Size: 57 KiB

BIN
src/img/tile_wood.png

After

Width: 32  |  Height: 32  |  Size: 54 KiB

54
src/index.html

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bomberman</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<link href="https://unpkg.com/nes.css/css/nes.min.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
</head>
<body>
<div class="container-fluid text-center">
<br />
<h1><i class="nes-icon star is-large"></i> Bomberman <i class="nes-icon star is-large"></i></h1>
<br />
</div>
<div id="canvasdiv" class="container-fluid text-center">
<canvas id="canvas" width="800" height="600"> </canvas>
</div>
<br />
<div class="container-fluid text-center">
<button type="button" class="nes-btn" id="new-game">New Game</button>
<br />
<br />
<audio id="audioplayer" controls loop>
<source src="audio/intro-screen.ogg" type="audio/ogg">
<source src="audio/intro-screen.mp3" type="audio/mpeg">
<p>Update pliiiiiz</p>
</audio>
</div>
<br />
<div class="nes-container is-dark with-title container-fluid">
<p class="title">Commands</p>
<p>Player 1: <kbd>e</kbd> and <kbd>d</kbd><br />
Player 2: <kbd>o</kbd> and <kbd>l</kbd></p>
</div>
<script defer type="module" src="js/main.js"></script>
<div style="display:none;">
<img id="tile_grass" src="img/tile_grass.png">
<img id="tile_wall" src="img/tile_wall.png">
<img id="tile_wood" src="img/tile_wood.png">
<img id="betty_bottom" src="img/betty_bottom.png">
<img id="bomb_0" src="img/bomb_0.png">
<img id="bomb_1" src="img/bomb_1.png">
<img id="bomb_2" src="img/bomb_2.png">
<img id="bomb_3" src="img/bomb_3.png">
<img id="bomb_4" src="img/bomb_4.png">
</div>
</body>
</html>

25
src/js/bomb.js

@ -0,0 +1,25 @@
// @flow
"use strict";
import * as point from "./point.js";
export class Bomb extends point.Point {
constructor (p) {
super(p.x, p.y);
this.created = Date.now();
this.power = 2; // TODO: how much ?
};
age () {
return Math.floor ((Date.now() - this.created) / 1000);
};
is_alive () {
return this.age() < 4; // TODO: how long ?
};
get_pos () {
return new point.Point(this.x, this.y);
};
has_pos (p) {
return this.x == p.x && this.y == p.y;
}
}

80
src/js/main.js

@ -0,0 +1,80 @@
// @flow
"use strict";
import * as renderer from "./renderer.js";
import * as mover from "./mover.js";
import * as state from "./state.js";
const get_json_callback = function(file_name, callback) {
let req = new XMLHttpRequest();
req.open('GET', "json/" + file_name);
req.responseType = 'json';
req.send();
req.onload = function() {
callback(req.response);
}
}
const mv_step = 0.25;
window.addEventListener("load", get_json_callback("lvl_01.json", function (map) {
const s = new state.State(map);
const up = function(p) {
s.move_p(0, -mv_step);
};
const down = function(p) {
s.move_p(0, mv_step);
};
const left = function(p) {
s.move_p(-mv_step, 0);
};
const right = function(p) {
s.move_p(mv_step, 0);
}
const keyboard = function(e /*:: : KeyboardEvent */) {
// TODO: forçage de meme, parser directement le .html avec les indications de touche
switch (e.key) {
case "z":
up(s);
break;
case "s":
down(s);
break;
case "q":
left(s);
break;
case "d":
right(s);
break;
case "b":
s.add_bomb();
}
};
document.addEventListener("keydown", keyboard);
// TODO: better FPS count
//let previous = Date.now();
let interval;
interval = setInterval(function () {
try {
//const delta = (Date.now() - previous) / 1000;
//previous = Date.now();
//console.log(1 / delta);
renderer.draw_state(s);
s.rm_bombs();
} catch (e) {
clearInterval(interval);
throw(e);
}
}, 1000 / 60);
}));

29
src/js/mover.js

@ -0,0 +1,29 @@
// @flow
"use strict";
import * as point from "./point.js";
const dec_x = -0.1; // TODO
const dec_y = +0.2; // TODO
export class Mover extends point.Point {
/*:: x: number */
/*:: y: number */
constructor (x /*: number */, y /*: number */) {
super(x, y);
this.alive = true;
}
kill () {
this.alive = false;
}
copy () {
let c = new Mover(this.x, this.y);
c.alive = this.alive;
return c;
}
move (p) {
this.x += p.x;
this.y += p.y;
}
}

28
src/js/point.js

@ -0,0 +1,28 @@
// @flow
"use strict";
export class Point {
/*:: x: number */
/*:: y: number */
constructor (x /*: number */, y /*: number */) {
this.x = x;
this.y = y;
};
add (p) {
return new Point(this.x + p.x, this.y + p.y);
};
round () {
return new Point(Math.round(this.x), Math.round(this.y));
};
eq (p) {
return this.x == p.x && this.y == p.y;
};
}
const left = new Point(-1, 0);
const right = new Point(1, 0);
const bottom = new Point(0, 1);
const top = new Point(0, -1);
export {left, right, bottom, top};

74
src/js/renderer.js

@ -0,0 +1,74 @@
// @flow
"use strict";
import * as state from "./state.js";
const assert_valid = function (el, t) {
if (!(el && el instanceof t)) {
throw "invalid object";
}
}
const canvas = document.getElementById("canvas");
assert_valid(canvas, HTMLCanvasElement);
const width = canvas.width;
const height = canvas.height;
const context = canvas.getContext("2d");
assert_valid(context, CanvasRenderingContext2D);
const tile_grass = document.getElementById("tile_grass");
const tile_wall = document.getElementById("tile_wall");
const tile_wood = document.getElementById("tile_wood");
const betty_bottom = document.getElementById("betty_bottom");
const bomb_0 = document.getElementById("bomb_0");
const bomb_1 = document.getElementById("bomb_1");
const bomb_2 = document.getElementById("bomb_2");
const bomb_3 = document.getElementById("bomb_3");
const bomb_4 = document.getElementById("bomb_4");
const img_of_block = function(b) {
switch (b) {
case state.block.Grass:
return tile_grass;
case state.block.Wood:
return tile_wood;
case state.block.Wall:
return tile_wall;
default:
console.log("Missing block img");
}
}
const draw_state = function (s) {
const step_x = width / s.width;
const step_y = height / s.height;
context.clearRect(0, 0, width, height);
for (let i = 0; i < s.width; i++) {
for (let j = 0; j < s.height; j++) {
context.drawImage(img_of_block(s.grid[i][j]), i * step_x, j * step_y, step_x, step_y);
}
}
// TODO: draw bombs U { p } starting with the one on the top so they don't overlap weirdly
s.bombs.forEach(function (b) {
let img = bomb_0;
let a = b.age();
if (a >= 4) {
img = bomb_4;
} else if (a == 3) {
img = bomb_3;
} else if (a == 2) {
img = bomb_2;
} else if (a == 1) {
img = bomb_1;
}
context.drawImage(img, b.x * step_x, b.y * step_y, step_x, step_y);
}
);
context.drawImage(betty_bottom, s.p.x * step_x, s.p.y * step_y, step_x, step_y);
context.fill();
}
export { draw_state };

110
src/js/state.js

@ -0,0 +1,110 @@
// @flow
"use strict";
import * as mover from "./mover.js";
import * as bomb from "./bomb.js";
import * as point from "./point.js";
const block = {
Empty : 1,
Grass : 2,
Wood : 3,
Wall: 4
}
export class State {
constructor (map) {
this.width = map.width;
this.height = map.height;
this.grid = map.grid;
this.p = new mover.Mover(map.p.x, map.p.y);
this.bombs = [];
};
get_block(p) {
return this.grid[p.x][p.y];
};
set_block(p, v) {
this.grid[p.x][p.y] = v;
}
is_block_empty(p) {
const b = this.get_block(p);
return b == block.Empty || b == block.Grass;
};
is_block_in_grid(p) {
return p.x >= 0 && p.y >= 0 && p.x < this.width && p.y < this.height;
};
move_p(x, y) {
if (this.p.alive) {
// we move a copy
let p2 = this.p.copy();
p2.move(new point.Point(x, y));
let p_round = p2.round();
// if it's valid, we use the copy
if (this.is_block_in_grid(p_round)) {
if (this.is_block_empty(p_round)) {
this.p = p2;
}
}
}
};
block_has_bomb(p) {
let res = false;
this.bombs.forEach(function (b) {
if (b.has_pos(p)) {
res = false; // TODO: escape the foreach
}
});
return res;
}
add_bomb() {
if (this.p.alive) {
const p_round = this.p.round();
if (!this.block_has_bomb(p_round)) {
this.bombs.push(new bomb.Bomb(p_round));
}
}
};
explode_block(b_pos) {
if (this.is_block_in_grid(b_pos)) {
let b = this.get_block(b_pos);
if (b == block.Wood) {
this.set_block(b_pos, block.Grass);
} else if (b_pos.eq(this.p)) { // TODO ugly and unclear
this.p.alive = false;
}
// TODO: explode others bombs
}
};
explode_dir(p, dir, power) {
if (power > 0) {
this.explode_block(p); // TODO: if something is destroyed or if there's an unbreakable block, return false
this.explode_dir(p.add(dir), dir, power - 1); // TODO: if the previous function returned false, we don't make this call
}
};
explode_bomb(p, power) {
this.explode_block(p);
this.explode_dir(p, point.left, power);
this.explode_dir(p, point.right, power);
this.explode_dir(p, point.top, power);
this.explode_dir(p, point.bottom, power);
};
rm_bombs() {
let bombs2 = [];
const s = this;
this.bombs.forEach(function (b) {
if (b) {
if (b.is_alive()) {
bombs2.push(b);
} else {
s.explode_bomb(b.get_pos(), b.power);
}
}
});
this.bombs = bombs2;
};
}
export { block }

30
src/json/lvl_01.json

@ -0,0 +1,30 @@
{ "width": 20,
"height": 20,
"grid": [
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,2,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4],
[2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3],
[2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4,2,4]
],
"p": {
"x": 3,
"y": 2
}
}
Loading…
Cancel
Save