screenshot

CHECK PASS: canvas #game exists and is sized CHECK PASS: no uncaught JS errors on load CHECK PASS: keyboard input does not throw score: 3/3 (100%)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mini Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #333;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
#game-container {
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
border: 4px solid #fff;
}
canvas {
display: block;
background-color: #5c94fc; /* Classic Mario sky blue */
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 24px;
text-shadow: 2px 2px 0 #000;
pointer-events: none;
user-select: none;
}
#message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 48px;
text-align: center;
text-shadow: 4px 4px 0 #000;
display: none;
pointer-events: none;
}
</style>
</head>
<body>
<div id="game-container">
<div id="ui">Coins: <span id="score">0</span></div>
<div id="message">YOU WIN!</div>
<canvas id="game" width="800" height="450"></canvas>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const messageElement = document.getElementById('message');
// Game Constants
const GRAVITY = 0.6;
const FRICTION = 0.8;
const PLAYER_SPEED = 5;
const JUMP_FORCE = -12;
// Game State
let score = 0;
let gameActive = true;
let cameraX = 0;
const keys = {
left: false,
right: false,
up: false
};
// Player object
const player = {
x: 50,
y: 300,
width: 32,
height: 32,
vx: 0,
vy: 0,
grounded: false,
color: '#e74c3c'
};
// Level Design
const platforms = [
// Ground
{ x: 0, y: 400, width: 1200, height: 50, color: '#8B4513' },
{ x: 1300, y: 400, width: 800, height: 50, color: '#8B4513' },
// Raised platforms
{ x: 200, y: 300, width: 100, height: 20, color: '#f1c40f' },
{ x: 400, y: 220, width: 100, height: 20, color: '#f1c40f' },
{ x: 600, y: 300, width: 100, height: 20, color: '#f1c40f' },
{ x: 800, y: 220, width: 150, height: 20, color: '#f1c40f' },
{ x: 1100, y: 300, width: 100, height: 20, color: '#f1c40f' },
{ x: 1400, y: 250, width: 100, height: 20, color: '#f1c40f' },
{ x: 1600, y: 150, width: 100, height: 20, color: '#f1c40f' },
];
const coins = [
{ x: 230, y: 260, size: 15, collected: false },
{ x: 430, y: 180, size: 15, collected: false },
{ x: 630, y: 260, size: 15, collected: false },
{ x: 850, y: 180, size: 15, collected: false },
{ x: 1130, y: 260, size: 15, collected: false },
{ x: 1430, y: 210, size: 15, collected: false },
{ x: 1630, y: 110, size: 15, collected: false },
];
const goal = {
x: 1900,
y: 320,
width: 40,
height: 80,
color: '#2ecc71'
};
// Input handling
window.addEventListener('keydown', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = true;
});
window.addEventListener('keyup', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = false;
});
function update() {
if (!gameActive) return;
// Horizontal Movement
if (keys.left) player.vx -= 0.8;
if (keys.right) player.vx += 0.8;
player.vx *= FRICTION;
// Jump
if (keys.up && player.grounded) {
player.vy = JUMP_FORCE;
player.grounded = false;
}
// Gravity
player.vy += GRAVITY;
// Apply Velocity
player.x += player.vx;
player.y += player.vy;
// Collision Detection (Platforms)
player.grounded = false;
for (const plat of platforms) {
if (player.x < plat.x + plat.width &&
player.x + player.width > plat.x &&
player.y < plat.y + plat.height &&
player.y + player.height > plat.y) {
// Resolve Collision
const overlapX = Math.min(player.x + player.width - plat.x, plat.x + plat.width - player.x);
const overlapY = Math.min(player.y + player.height - plat.y, plat.y + plat.height - player.y);
if (overlapX > overlapY) {
if (player.vy > 0 && player.y < plat.y) {
player.y = plat.y - player.height;
player.vy = 0;
player.grounded = true;
} else if (player.vy < 0 && player.y > plat.y) {
player.y = plat.y + plat.height;
player.vy = 0;
}
} else {
if (player.vx > 0 && player.x < plat.x) {
player.x = plat.x - player.width;
player.vx = 0;
} else if (player.vx < 0 && player.x > plat.x) {
player.x = plat.x + plat.width;
player.vx = 0;
}
}
}
}
// Collectibles
for (const coin of coins) {
if (!coin.collected &&
player.x < coin.x + coin.size &&
player.x + player.width > coin.x &&
player.y < coin.y + coin.size &&
player.y + player.height > coin.y) {
coin.collected = true;
score++;
scoreElement.innerText = score;
}
}
// Goal
if (player.x < goal.x + goal.width &&
player.x + player.width > goal.x &&
player.y < goal.y + goal.height &&
player.y + player.height > goal.y) {
gameActive = false;
messageElement.style.display = 'block';
}
// Fall off map
if (player.y > canvas.height) {
player.x = 50;
player.y = 300;
player.vx = 0;
player.vy = 0;
}
// Camera following player
cameraX = player.x - canvas.width / 2;
if (cameraX < 0) cameraX = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-cameraX, 0);
// Draw Platforms
for (const plat of platforms) {
ctx.fillStyle = plat.color;
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
// Top highlight
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.fillRect(plat.x, plat.y, plat.width, 4);
}
// Draw Coins
ctx.fillStyle = '#ffd700';
for (const coin of coins) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x + coin.size/2, coin.y + coin.size/2, coin.size/2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#b8860b';
ctx.lineWidth = 2;
ctx.stroke();
}
}
// Draw Goal
ctx.fillStyle = goal.color;
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = 'white';
ctx.fillRect(goal.x, goal.y, 10, goal.height); // Pole
// Draw Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
// Eyes for player
ctx.fillStyle = 'white';
ctx.fillRect(player.x + 20, player.y + 5, 6, 6);
ctx.fillStyle = 'black';
ctx.fillRect(player.x + 22, player.y + 7, 3, 3);
ctx.restore();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
screenshot

CHECK PASS: canvas #game exists and is sized CHECK PASS: no uncaught JS errors on load CHECK PASS: keyboard input does not throw score: 3/3 (100%)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mini Mario Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #333;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
#game-container {
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
border: 4px solid #000;
}
canvas {
display: block;
background-color: #5C94FC; /* Classic Mario sky blue */
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 2px 2px 0 #000;
pointer-events: none;
}
#overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
justify-content: center;
align-items: center;
background: rgba(0,0,0,0.6);
color: white;
font-size: 40px;
text-align: center;
text-shadow: 3px 3px 0 #000;
}
</style>
</head>
<body>
<div id="game-container">
<div id="ui">Coins: <span id="score">0</span></div>
<div id="overlay">YOU WIN!<br><span style="font-size: 20px;">Press Space to Restart</span></div>
<canvas id="game" width="800" height="450"></canvas>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const overlay = document.getElementById('overlay');
// Game Constants
const GRAVITY = 0.6;
const FRICTION = 0.8;
const JUMP_FORCE = -12;
const MOVE_SPEED = 5;
// Game State
let score = 0;
let gameActive = true;
let cameraX = 0;
const keys = {
left: false,
right: false,
up: false
};
const player = {
x: 50,
y: 300,
width: 32,
height: 32,
velX: 0,
velY: 0,
jumping: false,
color: '#E74C3C'
};
const platforms = [
// Ground
{ x: 0, y: 400, width: 2000, height: 50, color: '#8B4513' },
// Raised platforms
{ x: 200, y: 300, width: 150, height: 20, color: '#F1C40F' },
{ x: 450, y: 220, width: 150, height: 20, color: '#F1C40F' },
{ x: 700, y: 300, width: 150, height: 20, color: '#F1C40F' },
{ x: 950, y: 200, width: 200, height: 20, color: '#F1C40F' },
{ x: 1200, y: 300, width: 150, height: 20, color: '#F1C40F' },
{ x: 1400, y: 150, width: 100, height: 20, color: '#F1C40F' },
];
const coins = [
{ x: 250, y: 260, width: 15, height: 15, collected: false },
{ x: 500, y: 180, width: 15, height: 15, collected: false },
{ x: 750, y: 260, width: 15, height: 15, collected: false },
{ x: 1000, y: 160, width: 15, height: 15, collected: false },
{ x: 1430, y: 110, width: 15, height: 15, collected: false },
];
const goal = {
x: 1700,
y: 300,
width: 40,
height: 100,
color: '#2ECC71'
};
// Input Handlers
window.addEventListener('keydown', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = true;
if (!gameActive && e.code === 'Space') restartGame();
});
window.addEventListener('keyup', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = false;
});
function restartGame() {
score = 0;
scoreElement.innerText = score;
player.x = 50;
player.y = 300;
player.velX = 0;
player.velY = 0;
cameraX = 0;
coins.forEach(c => c.collected = false);
gameActive = true;
overlay.style.display = 'none';
requestAnimationFrame(update);
}
function checkCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
function update() {
if (!gameActive) return;
// Movement
if (keys.left) player.velX = -MOVE_SPEED;
else if (keys.right) player.velX = MOVE_SPEED;
else player.velX *= FRICTION;
if (keys.up && !player.jumping) {
player.velY = JUMP_FORCE;
player.jumping = true;
}
player.velY += GRAVITY;
player.x += player.velX;
player.y += player.velY;
// Platform collisions
player.jumping = true;
for (let plat of platforms) {
if (checkCollision(player, plat)) {
// Resolve collision from top
if (player.velY > 0 && player.y + player.height - player.velY <= plat.y) {
player.y = plat.y - player.height;
player.velY = 0;
player.jumping = false;
}
// Resolve collision from bottom
else if (player.velY < 0 && player.y - player.velY >= plat.y + plat.height) {
player.y = plat.y + plat.height;
player.velY = 0;
}
// Resolve side collisions
else if (player.velX > 0 && player.x + player.width - player.velX <= plat.x) {
player.x = plat.x - player.width;
}
else if (player.velX < 0 && player.x - player.velX >= plat.x + plat.width) {
player.x = plat.x + plat.width;
}
}
}
// Coin collection
for (let coin of coins) {
if (!coin.collected && checkCollision(player, coin)) {
coin.collected = true;
score++;
scoreElement.innerText = score;
}
}
// Goal check
if (checkCollision(player, goal)) {
gameActive = false;
overlay.style.display = 'flex';
}
// World bounds
if (player.x < 0) player.x = 0;
if (player.y > canvas.height) {
// Reset if fall off (though we have ground)
player.x = 50;
player.y = 300;
player.velY = 0;
}
// Camera follow
cameraX = player.x - canvas.width / 4;
if (cameraX < 0) cameraX = 0;
draw();
requestAnimationFrame(update);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-cameraX, 0);
// Draw platforms
for (let plat of platforms) {
ctx.fillStyle = plat.color;
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
// Add simple shading
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(plat.x, plat.y + plat.height - 5, plat.width, 5);
}
// Draw coins
ctx.fillStyle = '#FFD700';
for (let coin of coins) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x + coin.width/2, coin.y + coin.height/2, coin.width/2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#B8860B';
ctx.stroke();
}
}
// Draw Goal
ctx.fillStyle = goal.color;
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = 'white';
ctx.fillRect(goal.x, goal.y, goal.width, 20); // Goal cap
// Draw player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
// Eyes
ctx.fillStyle = 'white';
ctx.fillRect(player.x + 20, player.y + 5, 5, 5);
ctx.fillRect(player.x + 10, player.y + 5, 5, 5);
ctx.restore();
}
// Start game
update();
</script>
</body>
</html>
screenshot

CHECK PASS: canvas #game exists and is sized CHECK PASS: no uncaught JS errors on load CHECK PASS: keyboard input does not throw score: 3/3 (100%)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #333;
font-family: sans-serif;
overflow: hidden;
}
#game {
background-color: #87CEEB; /* Sky blue */
border: 4px solid #000;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
.ui {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
text-shadow: 2px 2px 0 #000;
pointer-events: none;
}
</style>
</head>
<body>
<div class="ui">Coins: <span id="score">0</span></div>
<canvas id="game" width="800" height="450"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
// Game constants
const GRAVITY = 0.6;
const FRICTION = 0.8;
const JUMP_FORCE = -12;
const MOVE_SPEED = 5;
// Game state
let score = 0;
let gameWon = false;
let cameraX = 0;
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
const player = {
x: 50,
y: 300,
width: 32,
height: 32,
vx: 0,
vy: 0,
grounded: false,
color: '#E74C3C'
};
const platforms = [
// Ground
{ x: 0, y: 400, width: 2000, height: 50, color: '#2ECC71' },
// Platforms
{ x: 200, y: 300, width: 100, height: 20, color: '#8B4513' },
{ x: 400, y: 220, width: 150, height: 20, color: '#8B4513' },
{ x: 650, y: 300, width: 120, height: 20, color: '#8B4513' },
{ x: 850, y: 200, width: 100, height: 20, color: '#8B4513' },
{ x: 1100, y: 300, width: 200, height: 20, color: '#8B4513' },
{ x: 1400, y: 200, width: 100, height: 20, color: '#8B4513' },
];
const coins = [
{ x: 230, y: 260, width: 15, height: 15, collected: false },
{ x: 450, y: 180, width: 15, height: 15, collected: false },
{ x: 700, y: 260, width: 15, height: 15, collected: false },
{ x: 880, y: 160, width: 15, height: 15, collected: false },
{ x: 1200, y: 260, width: 15, height: 15, collected: false },
];
const goal = {
x: 1700,
y: 320,
width: 40,
height: 80,
color: '#F1C40F'
};
function update() {
if (gameWon) return;
// Movement
if (keys['ArrowRight'] || keys['KeyD']) {
if (player.vx < MOVE_SPEED) player.vx++;
} else if (keys['ArrowLeft'] || keys['KeyA']) {
if (player.vx > -MOVE_SPEED) player.vx--;
} else {
player.vx *= FRICTION;
}
if ((keys['ArrowUp'] || keys['KeyW'] || keys['Space']) && player.grounded) {
player.vy = JUMP_FORCE;
player.grounded = false;
}
// Gravity
player.vy += GRAVITY;
player.x += player.vx;
player.y += player.vy;
// Collision Detection
player.grounded = false;
for (const plat of platforms) {
if (player.x < plat.x + plat.width &&
player.x + player.width > plat.x &&
player.y < plat.y + plat.height &&
player.y + player.height > plat.y) {
// Resolve collision from top
if (player.vy > 0 && player.y + player.height - player.vy <= plat.y) {
player.y = plat.y - player.height;
player.vy = 0;
player.grounded = true;
}
// Resolve from bottom
else if (player.vy < 0 && player.y - player.vy >= plat.y + plat.height) {
player.y = plat.y + plat.height;
player.vy = 0;
}
// Resolve from sides
else if (player.vx > 0 && player.x + player.width - player.vx <= plat.x) {
player.x = plat.x - player.width;
player.vx = 0;
}
else if (player.vx < 0 && player.x - player.vx >= plat.x + plat.width) {
player.x = plat.x + plat.width;
player.vx = 0;
}
}
}
// Coin collection
for (const coin of coins) {
if (!coin.collected &&
player.x < coin.x + coin.width &&
player.x + player.width > coin.x &&
player.y < coin.y + coin.height &&
player.y + player.height > coin.y) {
coin.collected = true;
score++;
scoreElement.innerText = score;
}
}
// Goal detection
if (player.x < goal.x + goal.width &&
player.x + player.width > goal.x &&
player.y < goal.y + goal.height &&
player.y + player.height > goal.y) {
gameWon = true;
}
// Camera follow
cameraX = player.x - canvas.width / 2;
if (cameraX < 0) cameraX = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-cameraX, 0);
// Draw Platforms
for (const plat of platforms) {
ctx.fillStyle = plat.color;
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
}
// Draw Coins
ctx.fillStyle = '#FFD700';
for (const coin of coins) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x + coin.width/2, coin.y + coin.height/2, coin.width/2, 0, Math.PI * 2);
ctx.fill();
}
}
// Draw Goal
ctx.fillStyle = goal.color;
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = '#000';
ctx.fillRect(goal.x, goal.y, 10, goal.height); // Goal pole
// Draw Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.restore();
if (gameWon) {
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', canvas.width / 2, canvas.height / 2);
ctx.font = '24px Arial';
ctx.fillText('Press F5 to play again', canvas.width / 2, canvas.height / 2 + 40);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
screenshot

CHECK PASS: canvas #game exists and is sized CHECK PASS: no uncaught JS errors on load CHECK PASS: keyboard input does not throw score: 3/3 (100%)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mini Mario Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #333;
color: white;
font-family: sans-serif;
overflow: hidden;
}
#ui {
margin-bottom: 10px;
font-size: 24px;
font-weight: bold;
}
canvas {
background-color: #5C94FC; /* Classic Mario Sky Blue */
border: 4px solid #fff;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
display: block;
}
.instructions {
margin-top: 10px;
color: #ccc;
}
</style>
</head>
<body>
<div id="ui">Coins: <span id="score">0</span></div>
<canvas id="game" width="800" height="450"></canvas>
<div class="instructions">ARROWS / WASD to Move & Jump</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
// Game State
let score = 0;
let gameActive = true;
let cameraX = 0;
const GRAVITY = 0.6;
const FRICTION = 0.8;
const JUMP_FORCE = -12;
const MOVE_SPEED = 5;
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
const player = {
x: 50,
y: 300,
width: 30,
height: 40,
vx: 0,
vy: 0,
grounded: false,
color: '#E74C3C'
};
const platforms = [
// Floor
{ x: 0, y: 400, width: 2000, height: 50, color: '#8B4513' },
// Platforms
{ x: 200, y: 300, width: 100, height: 20, color: '#F1C40F' },
{ x: 400, y: 220, width: 150, height: 20, color: '#F1C40F' },
{ x: 650, y: 300, width: 100, height: 20, color: '#F1C40F' },
{ x: 850, y: 200, width: 200, height: 20, color: '#F1C40F' },
{ x: 1100, y: 300, width: 100, height: 20, color: '#F1C40F' },
];
const coins = [
{ x: 230, y: 260, width: 15, height: 15, collected: false },
{ x: 450, y: 180, width: 15, height: 15, collected: false },
{ x: 500, y: 180, width: 15, height: 15, collected: false },
{ x: 680, y: 260, width: 15, height: 15, collected: false },
{ x: 900, y: 160, width: 15, height: 15, collected: false },
{ x: 1130, y: 260, width: 15, height: 15, collected: false },
];
const goal = {
x: 1300,
y: 300,
width: 40,
height: 100,
color: '#2ECC71'
};
function update() {
if (!gameActive) return;
// Input
if (keys['ArrowLeft'] || keys['KeyA']) {
player.vx -= MOVE_SPEED * 0.2;
} else if (keys['ArrowRight'] || keys['KeyD']) {
player.vx += MOVE_SPEED * 0.2;
} else {
player.vx *= FRICTION;
}
if ((keys['ArrowUp'] || keys['KeyW'] || keys['Space']) && player.grounded) {
player.vy = JUMP_FORCE;
player.grounded = false;
}
// Physics
player.vy += GRAVITY;
player.x += player.vx;
player.y += player.vy;
// Clamp horizontal velocity
player.vx = Math.max(-MOVE_SPEED, Math.min(MOVE_SPEED, player.vx));
// Platform Collisions
player.grounded = false;
for (const plat of platforms) {
if (player.x < plat.x + plat.width &&
player.x + player.width > plat.x &&
player.y < plat.y + plat.height &&
player.y + player.height > plat.y) {
// Determine collision side
const overlapX = Math.min(player.x + player.width - plat.x, plat.x + plat.width - player.x);
const overlapY = Math.min(player.y + player.height - plat.y, plat.y + plat.height - player.y);
if (overlapX > overlapY) {
if (player.vy > 0 && player.y < plat.y) {
player.y = plat.y - player.height;
player.vy = 0;
player.grounded = true;
} else if (player.vy < 0 && player.y > plat.y) {
player.y = plat.y + plat.height;
player.vy = 0;
}
} else {
if (player.vx > 0 && player.x < plat.x) {
player.x = plat.x - player.width;
player.vx = 0;
} else if (player.vx < 0 && player.x > plat.x) {
player.x = plat.x + plat.width;
player.vx = 0;
}
}
}
}
// Coin Collection
for (const coin of coins) {
if (!coin.collected &&
player.x < coin.x + coin.width &&
player.x + player.width > coin.x &&
player.y < coin.y + coin.height &&
player.y + player.height > coin.y) {
coin.collected = true;
score++;
scoreElement.innerText = score;
}
}
// Goal Check
if (player.x < goal.x + goal.width &&
player.x + player.width > goal.x &&
player.y < goal.y + goal.height &&
player.y + player.height > goal.y) {
gameActive = false;
setTimeout(() => {
alert(`You reached the goal! Final Score: ${score}`);
location.reload();
}, 10);
}
// Camera follow
cameraX = player.x - canvas.width / 2 + player.width / 2;
if (cameraX < 0) cameraX = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-cameraX, 0);
// Platforms
platforms.forEach(plat => {
ctx.fillStyle = plat.color;
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
// Add a simple border to platforms
ctx.strokeStyle = '#5D4037';
ctx.strokeRect(plat.x, plat.y, plat.width, plat.height);
});
// Coins
ctx.fillStyle = '#FFD700';
coins.forEach(coin => {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x + coin.width/2, coin.y + coin.height/2, coin.width/2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#B8860B';
ctx.stroke();
}
});
// Goal
ctx.fillStyle = goal.color;
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = 'white';
ctx.font = 'bold 16px sans-serif';
ctx.fillText('GOAL', goal.x + 5, goal.y - 10);
// Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
// Eyes for player
ctx.fillStyle = 'white';
ctx.fillRect(player.x + 18, player.y + 8, 5, 5);
ctx.fillRect(player.x + 8, player.y + 8, 5, 5);
ctx.restore();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
screenshot

CHECK PASS: canvas #game exists and is sized CHECK PASS: no uncaught JS errors on load CHECK PASS: keyboard input does not throw score: 3/3 (100%)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Platformer</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
overflow: hidden;
}
#game-container {
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
canvas {
background-color: #5c94fc; /* Classic Mario Sky Blue */
display: block;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
font-size: 20px;
text-shadow: 2px 2px 0 #000;
pointer-events: none;
}
#message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 48px;
text-align: center;
text-shadow: 4px 4px 0 #000;
display: none;
}
</style>
</head>
<body>
<div id="game-container">
<div id="ui">Coins: <span id="score">0</span></div>
<div id="message">YOU WIN!</div>
<canvas id="game" width="800" height="450"></canvas>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const messageElement = document.getElementById('message');
// Game Constants
const GRAVITY = 0.6;
const FRICTION = 0.8;
const WALK_SPEED = 5;
const JUMP_FORCE = -12;
// Game State
let score = 0;
let gameActive = true;
let cameraX = 0;
const keys = {
left: false,
right: false,
up: false
};
const player = {
x: 50,
y: 300,
width: 32,
height: 32,
velX: 0,
velY: 0,
grounded: false,
color: '#ff4444'
};
// Level Definition
const platforms = [
{ x: 0, y: 400, width: 2000, height: 50, color: '#8b4513' }, // Ground
{ x: 300, y: 300, width: 100, height: 20, color: '#ffcc00' },
{ x: 450, y: 220, width: 150, height: 20, color: '#ffcc00' },
{ x: 650, y: 300, width: 100, height: 20, color: '#ffcc00' },
{ x: 850, y: 200, width: 200, height: 20, color: '#ffcc00' },
{ x: 1100, y: 300, width: 100, height: 20, color: '#ffcc00' },
{ x: 1250, y: 150, width: 100, height: 20, color: '#ffcc00' },
{ x: 1400, y: 300, width: 200, height: 20, color: '#ffcc00' },
];
const coins = [
{ x: 330, y: 260, width: 20, height: 20, collected: false },
{ x: 480, y: 180, width: 20, height: 20, collected: false },
{ x: 880, y: 160, width: 20, height: 20, collected: false },
{ x: 1270, y: 110, width: 20, height: 20, collected: false },
{ x: 1450, y: 260, width: 20, height: 20, collected: false },
];
const goal = {
x: 1700,
y: 300,
width: 40,
height: 100,
color: '#00ff00'
};
window.addEventListener('keydown', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = true;
});
window.addEventListener('keyup', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.up = false;
});
function checkCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
function update() {
if (!gameActive) return;
if (keys.left) player.velX -= 0.8;
if (keys.right) player.velX += 0.8;
player.velX *= FRICTION;
if (keys.up && player.grounded) {
player.velY = JUMP_FORCE;
player.grounded = false;
}
player.velY += GRAVITY;
player.x += player.velX;
player.y += player.velY;
player.grounded = false;
for (const plat of platforms) {
if (checkCollision(player, plat)) {
if (player.velY > 0 && player.y + player.height - player.velY <= plat.y) {
player.y = plat.y - player.height;
player.velY = 0;
player.grounded = true;
}
else if (player.velY < 0 && player.y - player.velY >= plat.y + plat.height) {
player.y = plat.y + plat.height;
player.velY = 0;
}
else if (player.velX > 0 && player.x + player.width - player.velX <= plat.x) {
player.x = plat.x - player.width;
player.velX = 0;
}
else if (player.velX < 0 && player.x - player.velX >= plat.x + plat.width) {
player.x = plat.x + plat.width;
player.velX = 0;
}
}
}
for (const coin of coins) {
if (!coin.collected && checkCollision(player, coin)) {
coin.collected = true;
score++;
scoreElement.innerText = score;
}
}
if (checkCollision(player, goal)) {
gameActive = false;
messageElement.style.display = 'block';
}
if (player.x < 0) player.x = 0;
if (player.y > canvas.height + 100) {
player.x = 50;
player.y = 300;
player.velX = 0;
player.velY = 0;
}
let targetCameraX = player.x - canvas.width / 2;
cameraX += (targetCameraX - cameraX) * 0.1;
if (cameraX < 0) cameraX = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-cameraX, 0);
for (const plat of platforms) {
ctx.fillStyle = plat.color;
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.fillRect(plat.x, plat.y, plat.width, 4);
}
ctx.fillStyle = '#ffff00';
for (const coin of coins) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x + 10, coin.y + 10, 10, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ccaa00';
ctx.lineWidth = 2;
ctx.stroke();
}
}
ctx.fillStyle = goal.color;
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = 'white';
ctx.fillRect(goal.x, goal.y, goal.width, 10);
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.fillStyle = 'white';
ctx.fillRect(player.x + (player.velX >= 0 ? 18 : 6), player.y + 6, 6, 6);
ctx.restore();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>