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
<!doctype html>
<meta charset='utf-8'>
<title>πΈ A B Cannon</title>
<meta name="viewport" content="width=650">
<div>Shoot the red πΏππΎπΉπ£π. Let the green πΆππΈπππ§ through. Type a letter to start.</div>
<div id='scorebox'>Score: 0, Health: 10</div>
<canvas id='canvas' width=640 height=480 style="background:#ddeeff;border-radius:10px;"></canvas>
<canvas id='emoji_canvas' height=40 width=480 style="display:none;"></canvas><br>
<div id='keyarea' style='text-align:center;width:640px;touch-action:none;'></div>
<style>button {width:52px;height:52px;margin:5px;font-size:25px;padding:0px;}</style>
<script>
var drawing = canvas.getContext("2d"),
emoji_sheet = emoji_canvas.getContext("2d"),
keyboard = ["qwertyuiop", "asdfghjkl", "zxcvbnm"],
alphabet = keyboard.join("").split("").sort(),
good_guys = [..."πΆππΈπππ§"], bad_guys = [..."πΏππΎπΉπ£π"];
Object.assign(drawing, {textAlign: "center", textBaseline: "middle", font: "18px sans-serif"});
Object.assign(emoji_sheet, {textAlign: "center", textBaseline: "middle", font: "34px serif"});
var score = 0, lives=100, info="", game_started = false;
var gravity = 400, friction = 1.5;
var items_on_screen_at_once = 8, item_interval = 2, interval_change = 0.995, last_time = 0;
var items = [], last_shot = {}, emoji_x = {};
[...good_guys, ...bad_guys].forEach((symbol, index)=>{
emoji_sheet.fillText(symbol, 20 + 40*index, 20);
emoji_x[symbol] = 40 *index;
});
var draw_cannon = (letter,index) => {
var opacity = 1;
if (last_shot[letter] > Date.now()-5000) opacity = (Date.now()-last_shot[letter])/15000;
drawing.fillStyle = `rgba(00,44,66, ${opacity} )`;
drawing.fillRect(49 + index * 21, 435, 18, 45);
drawing.fillStyle = "white";
drawing.fillText(letter.toUpperCase(), 58+ index* 21, 456);
}
function circle(x, y, radius, colour = "black") {
drawing.fillStyle = colour;
drawing.beginPath();
drawing.arc(x, y, radius, 0, 2*Math.PI);
drawing.fill();
}
var collision=(a,b)=> Math.hypot(a.x - b.x, a.y - b.y) < (a.radius + b.radius);
requestAnimationFrame(function do_frame(now){
var frame_interval = (now && last_time) ? (now-last_time)/1000: 0.016;
last_time = now;
drawing.clearRect(0,0, canvas.width, canvas.height);
items.forEach(i=>{
if (i.y >= canvas.height - (i.radius/2)) i.grounded = true;
else if (i.dead && !i.grounded) i.speed_y += gravity * frame_interval;
i.x += (i.speed_x|0) * frame_interval;
if (i.grounded) i.speed_x -= i.speed_x * friction * frame_interval;
else i.y += (i.speed_y|0) * frame_interval;
items.forEach(i2=>{
if (i.dead || i2.dead) return;
if (i !== i2 && !!i.symbol != !!i2.symbol && i.x > 0 && collision(i,i2)){
[i,i2].forEach(x=>{
if (x.symbol) x.dead = true;
else x.deleted = true;
if (x.type == good_guys) lives--;
else if (x.type == bad_guys) score++;
});
}
});
if (i.type == "cannonball") circle(i.x, i.y, i.radius, i.colour);
if (i.x > canvas.width + i.radius && i.type == good_guys) score++;
if (i.x > canvas.width + i.radius && i.type == bad_guys) lives--
});
alphabet.forEach(draw_cannon);
items = items.filter(i=>!i.deleted && i.x <= canvas.width + i.radius && i.y > 0);
items.filter(i=>!!i.symbol && i.x >= -i.radius).forEach(i=>{
circle(i.x, i.y, i.radius, i.colour);
drawing.drawImage(emoji_canvas, emoji_x[i.symbol], 0, 40, 40, i.x-15, i.y-15, 30, 30);
});
if (lives > 0) requestAnimationFrame(do_frame);
else info = "Game Over!!!! ";
scorebox.textContent = `${info}Score: ${score}, Lives: ${lives}`;
});
function random_item(array) { return array[Math.floor(Math.random()*array.length)] };
function add_item() {
var type = Math.random() * 10 > 6 ? good_guys : bad_guys;
var colour = type == good_guys ? "green" : "red";
var average_speed_x = ((canvas.width)/item_interval) / items_on_screen_at_once;
var speed_x = average_speed_x * (0.6 + (Math.random() * 0.8));
var item = { radius: 20, speed_x, colour, type, x: -31, speed_y: 0 };
items.push({...item, y: 50 + Math.random() * 200, symbol: random_item(type)});
if (lives) return setTimeout(add_item, (item_interval=item_interval*interval_change)*1000);
}
function try_to_shoot(letter) {
if (!alphabet.includes(letter)) return
if (last_shot[letter] && last_shot[letter] > Date.now() - 5000) return;
var cannonball = {type: "cannonball", radius: 12, speed_y: -600, colour:"#442266"};
items.push({...cannonball, x: 57 + alphabet.indexOf(letter)* 21, y: 440});
last_shot[letter] = Date.now();
if (!game_started) game_started = add_item();
}
var press = onkeydown = (e) => {try_to_shoot(e.key.toLowerCase())}
var make_row=(keys)=>keys.map(key=>`<button name='${key}'>${key}</button>`).join("");
keyarea.innerHTML = keyboard.map(row=>make_row(row.toUpperCase().split(""))).join("<br>");
keyarea.onpointerdown=(e)=>{if (e.target.name) press({key: e.target.name})};
</script>