JSFiddle - React, Tailwind, and code Playground

by Ben Gillbanks

HTML

<canvas id="game" width="900" height="600"></canvas>

CSS

html, body {
	margin: 0;
	padding: 0;
	background: #1b1b1b;
	overflow: hidden;
	font-family: sans-serif;
}

canvas {
	display: block;
	margin: 0 auto;
	background: #5c9452;
	image-rendering: pixelated;
	image-rendering: crisp-edges;
}

JavaScript

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

const keys = {};

window.addEventListener("keydown", (event) => {
	keys[event.key.toLowerCase()] = true;
});

window.addEventListener("keyup", (event) => {
	keys[event.key.toLowerCase()] = false;
});

const track = {
	x: 60,
	y: 60,
	width: canvas.width - 120,
	height: canvas.height - 120
};

const car = {
	x: canvas.width * 0.5,
	y: canvas.height * 0.5,
	angle: -Math.PI * 0.5,

	vx: 0,
	vy: 0,

	radius: 18,

	acceleration: 700,
	reverseAcceleration: 400,
	maxSpeed: 320,
	drag: 0.985,
	grip: 0.05,
	steerRate: 2.8,
	steerSpeed: 140,
	bounce: 0.35,

	width: 26,
	length: 40,
	stackHeight: 10,

	lean: 0,
	pitch: 0
};

let lastTime = 0;

function clamp(value, min, max) {
	return Math.max(min, Math.min(max, value));
}

function lerp(from, to, amount) {
	return from + (to - from) * amount;
}

function dot(ax, ay, bx, by) {
	return ax * bx + ay * by;
}

function length(x, y) {
	return Math.hypot(x, y);
}

function update(delta) {
	handleInput(delta);
	applyCarPhysics(delta);
	moveCar(delta);
	handleWallCollisions();
	updateVisualEffects(delta);
}

function handleInput(delta) {
	const forwardX = Math.cos(car.angle);
	const forwardY = Math.sin(car.angle);

	const forwardSpeed = dot(car.vx, car.vy, forwardX, forwardY);

	let throttle = 0;
	if (keys["arrowup"] || keys["w"]) throttle += 1;
	if (keys["arrowdown"] || keys["s"]) throttle -= 1;

	let steer = 0;
	if (keys["arrowleft"] || keys["a"]) steer -= 1;
	if (keys["arrowright"] || keys["d"]) steer += 1;

	if (throttle > 0) {
		car.vx += forwardX * car.acceleration * delta;
		car.vy += forwardY * car.acceleration * delta;
	} else if (throttle < 0) {
		car.vx -= forwardX * car.reverseAcceleration * delta;
		car.vy -= forwardY * car.reverseAcceleration * delta;
	}

	const speedFactor = clamp(Math.abs(forwardSpeed) / car.steerSpeed, 0, 1);
	const...