const canvas = document.getElementById('gameCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.backgroundColor = '#f0f0f0';
const ctx = canvas.getContext('2d');
(function(){
const COLORS = {
player: '#2d2d2d',
particles: '#2d2d2d',
objects: '#2d2d2d',
terrain: '#2d2d2d'
};
const gravity = 1;
const jumpForce = 14.75;
const player = {
x: 200,
y: 0,
vx: 16,
vy: 16,
vm: 32,
jumpForce: gravity + jumpForce,
onGround: false,
width: 20,
height: 20,
distanceTraveled: 0
};
const terrain = {
start: { x: 0, y: 0 },
end: { x: canvas.width, y: canvas.height },
height: 8
};
const objects = [];
let lastObjectX = 0;
const particles = [];
const createObject = () => {
const object = {
x: lastObjectX + 10 * canvas.width / 10,
width: Math.random() * 100,
height: Math.random() * 22
};
let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y;
object.y = terrainY - object.height;
objects.push(object);
lastObjectX = object.x;
};
const isColliding = (obj1, obj2) => (
obj1.x < obj2.x + obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y + obj2.height &&
obj1.y + obj1.height > obj2.y
);
const checkCollision = () => {
objects.forEach(object => {
if (isColliding(player, object)) {
if (player.vx >= 1) {
player.vx -= 0.5;
player.vy -= 0.5;
}
}
});
};
const updatePlayer = () => {
player.vy += gravity;
player.x += player.vx;
player.y += player.vy;
const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y;
if (player.y + player.height > terrainY) {
player.y = terrainY - player.height;
player.vy = 0;
}
if (particlesEnabled) {
particles.push({
x: player.x,
y: player.y,
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1,
lifespan: player.vx * 4
});
}
};
const updateObjects = () => {
for (let object of objects) {
object.y += gravity;
let terrainY = ((terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x)) * (object.x - terrain.start.x) + terrain.start.y;
if (object.y + object.height > terrainY) {
object.y = terrainY - object.height;
}
}
};
const drawPlayer = () => {
if (player.vx < 4) {
ctx.fillStyle = COLORS.player;
ctx.font = '22px Arial';
const text = 'Jump to build up speed!';
ctx.fillText(text, player.x, player.y - 12);
} else {
if (distanceMeterEnabled) {
ctx.fillStyle = '#aaa';
ctx.font = '22px Arial';
const text = player.distanceTraveled.toFixed(0) + ' px';
ctx.fillText(text, player.x, player.y - 12);
}
}
ctx.fillStyle = COLORS.player;
ctx.fillRect(player.x, player.y, player.width, player.height);
};
const drawObjects = () => {
ctx.fillStyle = COLORS.objects;
objects.forEach(object => {
ctx.beginPath();
ctx.arc(object.x, object.y, object.width / 2, 0, Math.PI * 2, true);
ctx.fill();
});
};
const drawTerrain = () => {
ctx.beginPath();
ctx.moveTo(terrain.start.x, terrain.start.y);
ctx.lineTo(terrain.end.x, terrain.end.y);
ctx.strokeStyle = COLORS.terrain;
ctx.lineWidth = terrain.height;
ctx.stroke();
};
const drawParticles = () => {
if (particlesEnabled) {
ctx.fillStyle = COLORS.particles;
particles.forEach((particle, index) => {
ctx.fillRect(particle.x, particle.y, 5, 5);
particle.x += particle.vx;
particle.y += particle.vy;
particle.lifespan--;
if (particle.lifespan === 0) {
particles.splice(index, 1);
}
});
}
};
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-player.x + canvas.width / 4, -player.y + canvas.height / 2);
drawPlayer();
terrain.start.x = player.x - canvas.width;
terrain.end.x = player.x + canvas.width;
drawObjects();
drawTerrain();
drawParticles();
ctx.restore();
};
const update = (currentTime = 0) => {
const deltaTime = currentTime - lastUpdateTime;
player.distanceTraveled = Math.abs(player.x - 200);
if (deltaTime >= frameDelay) {
createObject();
updatePlayer();
updateObjects();
checkCollision();
lastUpdateTime = currentTime;
}
requestAnimationFrame(update);
};
const initialize = () => {
terrain.start = { x: 0, y: 0 };
terrain.end = { x: canvas.width, y: canvas.height };
};
const gameLoop = () => {
initialize();
draw();
requestAnimationFrame(gameLoop);
};
const playerDoJump = () => {
const terrainY = (terrain.end.y - terrain.start.y) / (terrain.end.x - terrain.start.x) * (player.x - terrain.start.x) + terrain.start.y;
if (player.y + player.height >= terrainY) {
player.vy = player.jumpForce * -1;
if (player.vx < player.vm) {
player.vx += 0.5;
}
}
};
const handleKeyDown = event => {
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') {
playerDoJump();
}
};
canvas.addEventListener('click', playerDoJump);
canvas.addEventListener('touchstart', playerDoJump);
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw();
});
const helpText = 'Controls:\n\nJump: space, up arrow, w or tap the screen on mobile\nFullscreen: f or the button in the upper-right\nParticles: p\nDistance: d\nHelp: h'
document.addEventListener('keydown', function(event) {
if (event.key === 'f') {
if (canvas.requestFullscreen) {
canvas.requestFullscreen();
} else if (canvas.mozRequestFullScreen) {
canvas.mozRequestFullScreen();
} else if (canvas.webkitRequestFullscreen) {
canvas.webkitRequestFullscreen();
} else if (canvas.msRequestFullscreen) {
canvas.msRequestFullscreen();
}
} else if (event.key === 'p' || event.key === 'P') {
particlesEnabled = !particlesEnabled;
} else if (event.key === 'h' || event.key === 'H') {
alert(helpText);
} else if (event.key === 'd' || event.key === 'D') {
distanceMeterEnabled = !distanceMeterEnabled;
}
});
let particlesEnabled = true;
let distanceMeterEnabled = false;
const fullscreenButton = document.createElement('button');
fullscreenButton.style.position = 'absolute';
fullscreenButton.style.top = '10px';
fullscreenButton.style.right = '10px';
fullscreenButton.textContent = 'Fullscreen';
const distanceMeterButton = document.createElement('button');
distanceMeterButton.style.position = 'absolute';
distanceMeterButton.style.top = '40px';
distanceMeterButton.style.right = '10px';
distanceMeterButton.textContent = 'Distance';
const particlesButton = document.createElement('button');
particlesButton.style.position = 'absolute';
particlesButton.style.top = '70px';
particlesButton.style.right = '10px';
particlesButton.textContent = 'Particles';
const helpButton = document.createElement('button');
helpButton.style.position = 'absolute';
helpButton.style.top = '100px';
helpButton.style.right = '10px';
helpButton.textContent = 'Help';
fullscreenButton.addEventListener('click', function() {
if (canvas.requestFullscreen) {
canvas.requestFullscreen();
} else if (canvas.mozRequestFullScreen) {
canvas.mozRequestFullScreen();
} else if (canvas.webkitRequestFullscreen) {
canvas.webkitRequestFullscreen();
} else if (canvas.msRequestFullscreen) {
canvas.msRequestFullscreen();
}
});
distanceMeterButton.addEventListener('click', function() {
distanceMeterEnabled = !distanceMeterEnabled;
});
particlesButton.addEventListener('click', function() {
particlesEnabled = !particlesEnabled;
});
helpButton.addEventListener('click', function() {
alert(helpText);
});
canvas.parentNode.appendChild(fullscreenButton);
canvas.parentNode.appendChild(distanceMeterButton);
canvas.parentNode.appendChild(particlesButton);
canvas.parentNode.appendChild(helpButton);
let lastUpdateTime = 0;
const fps = 60;
const frameDelay = 1000 / fps;
update();
gameLoop();
} )();