Joe Hat

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'>
<title>🤠 Joe Hat</title>
<meta name="viewport" content="width=520, user-scalable=no">
<div id='instructions'>Press any button to jump. Get to the top, avoid robots</div>
<span id='score_box'>Level: 0, Heath: 100, Score: 0</span><br>
<canvas id='canvas' style="border:5px solid;border-radius:10px;" width=500 height=600></canvas>
<style>*, html{-webkit-user-select:none;touch-action:none;user-select:none;</style>
<script>
	var drawing = canvas.getContext("2d");
	Object.assign(drawing, {textAlign: "center", textBaseline: "middle", font: "14px serif"});
	var player = { health: 100, treasure:0, colour: "darkblue",
		x: 40, y: canvas.height-30, width: 20, height: 40,
		speed_x: -105, speed_y: 0};
	var entry = {colour:"#000000", width: 20, height: 35, x: 480, y: 565};
	var exit = {colour:"#880000", width: 20, height: 35, x: 10, y: 30};
	var level_seeds = [67,52,50,54,40,21,60,28,3,5,8,9,11,49,16,12,18,29,19,10]; // Seed mining
	var game = {level: 0, last_frame: 0, items: [], gravity: 200, seed_number: 0};
	function non_random_number() {
		game.seed_number = (((+game.seed_number) + 123456) * 654321).toString().slice(-10, -1);
		return parseFloat("0."+game.seed_number);
	}
	var between = (min, max) => min+Math.floor(non_random_number() * ((max-min)+1));	
	var a_colour=()=>"#"+["334455","553344","445533","443355","335544","554433"][between(0,5)];
	var enemy = {symbol:"🤖", colour:"#EE0000", width:10, height:18, speed_x:-50, damage:20};
	var treasure = {colour: "#FFFF00", wealth: 1, width: 10, height: 10};
	function create_level(){
		game.seed_number = level_seeds[game.level++] || game.seed_number;
		game.items = [entry, exit, player = Object.assign(player, {x: 470, y: canvas.height-30})];		
		for (var y = canvas.height-95; y > 40; y-= 95) // Generate the level
			for (var x = 0, width=0; x < canvas.width; x+= width + between(45, 90)){
				if (x == 0 && between(0,2) == 2) x += between(45, 90);
				width = Math.min(between(50, 250), canvas.width - x);
				var platform = {x,y, width, colour: a_colour(), height: 10};
				game.items.push(platform, {...treasure, x: between(x, x + width-10), y: y-20});
				if (width > 50 && between(0,1)) 
					game.items.push({...enemy, x:platform.x, y: y-25, platform});
			}
		canvas.style.background = (a_colour())+"44";
	}
	var draw_item = (item) => {
		drawing.beginPath();
		Object.assign(drawing,{strokeStyle:item.colour, lineWidth:4, fillStyle:item.colour+"CC"});
		drawing.roundRect(item.x+1, item.y+1, item.width-2, item.height-2,[3]);
		drawing.stroke();
		drawing.fill();
		if (item.symbol) drawing.fillText(item.symbol, item.x + item.width/2, item.y+item.width/2);
	}
	var feet_apart = 10, feet_move = 60;
	var draw_player=(interval)=>{
		drawing.beginPath();
		[   [10, 15], [10, 28], // backbone
			[10 - (player.grounded ? feet_apart : 10), 40], [10, 28],	// left foot to hips
			[10 + (player.grounded ? feet_apart : 10), 40], // to right foot
			[0, 20, true], [20, 20], // arms
		].forEach((point, index)=>{
			if (index == 0 || point[2]) drawing.moveTo(player.x + point[0], player.y+point[1]);
			else drawing.lineTo(player.x + point[0], player.y+point[1]);
		});
		Object.assign(drawing, {lineWidth: 4, strokeStyle: player.colour, fillStyle: "black"});
		drawing.stroke();
		feet_apart -= feet_move * interval;
		feet_move = (feet_apart <= 0) ? -60 : (feet_apart >=10) ? 60 : feet_move;
		drawing.fillText(player.health > 0 ? "🤠": "💀", player.x+10, player.y+10); // head
	};
	var collides=(a,b)=> a.right > b.x && b.right > a.x && a.bottom > b.y && b.bottom > a.y;
	var collect_treasure=(t)=> t.deleted = player.treasure = (t.wealth + (player.treasure|0));
	var last_time = performance.now();
	requestAnimationFrame(function do_one_frame(now){
		var interval = Math.min(0.05, (now - last_time)/1000);
		if (interval == 0) return requestAnimationFrame(do_one_frame);
		last_time = now;
		player.last_health = player.health;
		if (game.items.length == 0 || collides(player,exit)) create_level();
		drawing.clearRect(0,0, canvas.width, canvas.height);
		game.items.forEach(i=>{
			i.right = (i.x = i.x + (i.speed_x|0) * interval) + i.width;
			i.bottom = (i.y = i.y + (i.speed_y|0) * interval) + i.height;
			if (i.grounded = i.bottom > canvas.height) i.y = canvas.height - (i.height-1);
			if ((i.right>canvas.width && i.speed_x>0) || (i.x<0 && i.speed_x<0)) i.speed_x *=-1;
			if (i.platform && i.right > i.platform.right && i.speed_x > 0) i.speed_x *=-1;
			if (i.platform && i.x < i.platform.x && i.speed_x < 0) i.speed_x *=-1;
			if (i !== player && !i.deleted) draw_item(i);
			if (i== player || i.deleted || i == entry || !collides(i, player)) return;
			player.health -= (i.damage|0) * interval;
			if (i.wealth) return collect_treasure(i);
			if (player.bottom < i.y + 5) player.grounded = player.y = i.y - (player.height-1);
			else if (player.y > i.bottom - 5 && player.speed_y < 0) player.speed_y *= -1;
			else if (player.right < i.x + 5 && player.speed_x > 0) player.speed_x *= -1;
			else if (player.x > i.x - 5 && player.speed_x < 0) player.speed_x *= -1;
		});
		draw_player(interval); 
		player.speed_y = player.grounded? 0 : Math.min(player.speed_y+game.gravity*interval, 300);
		canvas.style.borderColor = (player.health < player.last_health) ? "red" : "black";
		score_box.textContent = `Level: ${game.level}, Score: ${player.treasure},
			Health: ${(Math.max(0, Math.ceil(player.health*10)/10)).toFixed(1)}%`;
		if (player.health > 0) requestAnimationFrame(do_one_frame);
	});
	onkeydown = onmousedown = ontouchstart = (e)=>{if (player.grounded) player.speed_y = -200};
</script>