A B Cannon

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>