Fox Ache

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
<!doctype html>
<meta charset='utf-8'>
<meta name="viewport" content="width=525">
<title>🦊 Fox Ache</title>
<div id='instructions'>You are πŸ•, stop 🦊 from eating πŸ‡πŸ“πŸ–πŸ‘πŸ¦˜πŸ¦™. Press an arrow to start</div>
<div id='time_left' style='display:inline-block;background:#6688aa;'>Time&nbsp;Left</div><br>
<div id="field" style='background:#338833;display:inline-block;padding:0px;font-size:0px;'>
	<canvas id='canvas' width=500 height=500
		style='border:9px dashed brown;border-radius:50px;'></canvas>
</div>
<canvas id='sprite_canvas' style='display:none;' width=1760 height=80></canvas>
<div id='arrows' style='width:516px;border:2px outset;'></div>
<style>*, html{-webkit-user-select:none;touch-action:none;user-select:none;</style>
<script>
	var livestock = [..."πŸ‡πŸ“πŸ–πŸ‘πŸ¦˜πŸ¦™"], plants = [..."πŸ€πŸ€πŸŒ»πŸŒ»πŸŒΌπŸŒΌπŸŒΎπŸŒΎπŸŒ΅πŸŒ³πŸŒ΄πŸŒ²"];
	var dog = {symbol: "πŸ•", x: 200, y: 300, radius: 25, speed_x:0, speed_y: 0};
	var items = [dog], keys={}, start_time = false, last_time = false, game_started = false;
	var drawing = canvas.getContext("2d"),
		sprite_drawing = sprite_canvas.getContext("2d");
	Object.assign(sprite_drawing, {textAlign:"center", textBaseline:"middle", font:"60px serif"});
	var press = onkeydown =(e)=>{ 
		keys[e.key] = true;
		if (e.key.startsWith("Arrow") && e.preventDefault) e.preventDefault();
		if (!game_started) game_started = e.key.startsWith("Arrow");
	};
	onkeyup = (e) =>{ keys[e.key] = false };
	arrows.innerHTML = [["Up","↑"], ["Left","←"], ["Down","↓"], ["Right","β†’"]].map((key,index)=>{
		var style=`height:60px; width:60px; margin:5px ${index==0 ? '220px;':'52px;'};`;
		return `<button style='${style}' name='Arrow${key[0]}'>${key[1]}</button> `;
	}).join("");
	arrows.addEventListener("touchstart", (e)=>{
		if (e.target.name) press({key:e.target.name});
		e.preventDefault();
	});
	arrows.addEventListener("touchend", (e)=>{if (e.target.name) keys[e.target.name] =false});
	arrows.addEventListener("touchcancel", (e)=>{if (e.target.name) keys[e.target.name] =false});
	var sprite_indexes = {};
	[..."πŸ•πŸ¦ŠπŸ„πŸπŸπŸ‘πŸ“πŸ‚πŸƒπŸ‡πŸŽπŸ–πŸ¦™πŸ¦˜πŸŒ²πŸŒ³πŸŒ΄πŸŒ΅πŸŒΎπŸŒ»πŸŒΌπŸ€"].forEach((symbol,index)=>{
		sprite_drawing.fillText(symbol, index * 80 + 40, 40);
		sprite_indexes[symbol] = index;
	});
	var hit=(a,b)=>Math.hypot(a.x - b.x, a.y - b.y) < a.radius + b.radius;
	var box_collision=(a,b)=>{
		if (a.x < b.right && b.x < a.right && a.y < b.bottom && b.y < a.bottom) {
			if ((a.x < b.x && a.right - b.x < 20 && a.speed_x > 0) || 
				(a.right > b.right && b.right - a.x < 20 && a.speed_x < 0)) return "x";
			if ((a.y < b.y && a.bottom - b.y < 20 && a.speed_y > 0) ||
				(a.bottom > b.bottom && b.bottom - a.y < 20 && a.speed_y < 0)) return "y";
		}
	}
	var up_to=(max)=>Math.random()*max;
	for (var i = 0; i < 6; i++)
		items.unshift({
			type:"livestock", symbol: livestock[i], radius: 15 + (i*2),
			x: 150 + up_to(100), y: 150 + up_to(100),
			speed_x: 5 - up_to(10), speed_y: 5 - up_to(10),
		});
	var foxes = [];
	for (var fox = 0; fox < 50; fox++){
		var x = (Math.random() > 0.5) ? canvas.width + 10 : -50;
		var speed_x = x > 0 ? -(60 +fox): 60+fox;
		var y = up_to(canvas.height);
		var speed_y = y > canvas.height / 2 ? - (up_to(40 +fox)) : up_to(40+fox);
		var delay = fox * (3000 - (fox*30));
		foxes.push({symbol:"🦊", radius: 18, x, y, speed_x, speed_y, delay, fox:true});
	}
	plants.forEach((p,i)=>items.unshift({plant:p, x: up_to(500), y: up_to(500), radius: 12+i}));
	var time_limit = foxes[foxes.length-1].delay + 5000;
	requestAnimationFrame(function create_one_frame(now){
		var interval = game_started ? Math.min(0.2, (now - last_time)/1000) : 0;
		last_time = now;
		start_time = game_started ? start_time || now : now;
		if (interval > 0.1 && foxes.length) start_time = now - foxes[0]?.delay;
		if (now - start_time > foxes[0]?.delay) items.push(foxes.shift());
		time_left.style.width = Math.max(0, ((1 - (now - start_time) / time_limit) * 518)) + "px";
		dog.speed_x += keys.ArrowRight? 1000 * interval : keys.ArrowLeft ? -1000 * interval: 0;
		dog.speed_y += keys.ArrowDown? 1000 * interval : keys.ArrowUp ? -1000 * interval: 0;
		["speed_x", "speed_y"].forEach(speed => dog[speed] -= dog[speed] * interval * 2);
		drawing.clearRect(0,0, canvas.width, canvas.height);
		items.forEach(i=>{
			i.right = (i.x = i.x + (i.speed_x|0) * interval) + (i.radius*2);
			i.bottom = (i.y = i.y + (i.speed_y|0) * interval) + (i.radius*2);
			if (i.symbol || i.plant) {
				drawing.globalAlpha = (i.plant) ? 0.5 : 1;
				drawing.drawImage(sprite_canvas, (sprite_indexes[i.symbol || i.plant] * 80), 0, 
					80, 80, i.x, i.y, i.radius*2.2, i.radius*2.2);
			}
			if (i.fox) items.forEach(i2=>{if(i2.type=="livestock" && hit(i, i2)) i2.dead=true});
			if (i != dog && box_collision(i,dog)) i["speed_"+box_collision(i,dog)] *= -1;
			if (i.speed_x > 0 && i.right > canvas.width && !i.fox) i.speed_x *= -1;
			if (i.speed_x < 0 && i.x < 0 && !i.fox) i.speed_x *= -1;
			if (i.speed_y > 0 && i.bottom > canvas.height && !i.fox) i.speed_y *= -1;
			if (i.speed_y < 0 && i.y < 0 && !i.fox) i.speed_y *= -1;
		});
		if (!items.some(i=>i.type=="livestock")) instructions.textContent= "Bad Dog. Game over";
		else if (now > start_time + time_limit) instructions.textContent = "Good Dog. Relax";
		requestAnimationFrame(create_one_frame);
		items = items.filter(i=>(!i.dead && i.x > -1000 && i.x < 1000));
	});
</script>