@ -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> |
@ -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 !" |
|||
) |
@ -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; |
|||
}*/ |
After Width: | Height: | Size: 706 B |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 622 B |
After Width: | Height: | Size: 623 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 660 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 613 B |
After Width: | Height: | Size: 614 B |
After Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 655 B |
After Width: | Height: | Size: 566 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 695 B |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 375 B |
After Width: | Height: | Size: 380 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 438 B |
After Width: | Height: | Size: 632 B |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 582 B |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 605 B |
After Width: | Height: | Size: 549 B |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 54 KiB |
@ -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> |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
|
|||
})); |
@ -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; |
|||
} |
|||
} |
@ -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}; |
@ -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 }; |
@ -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 } |
@ -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 |
|||
} |
|||
|
|||
} |