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%)
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
// Game Settings
const GRAVITY = 0.8;
const FRICTION = 0.8;
const PLAYER_SPEED = 5;
const JUMP_FORCE = -15;
// Game State
let score = 0;
let gameActive = true;
let gameOverMessage = "";
// Input Handling
const keys = {};
window.addEventListener('keydown', (e) => keys[e.code] = true);
window.addEventListener('keyup', (e) => keys[e.code] = false);
// Entities
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 30;
this.height = 40;
this.velX = 0;
this.velY = 0;
this.grounded = false;
this.color = '#FF4500'; // OrangeRed
}
update() {
// Movement
if (keys['ArrowLeft'] || keys['KeyA']) {
if (this.velX > -PLAYER_SPEED) this.velX--;
}
if (keys['ArrowRight'] || keys['KeyD']) {
if (this.velX < PLAYER_SPEED) this.velX++;
}
if ((keys['ArrowUp'] || keys['KeyW'] || keys['Space']) && this.grounded) {
this.velY = JUMP_FORCE;
this.grounded = false;
}
// Apply Physics
this.velX *= FRICTION;
this.velY += GRAVITY;
this.x += this.velX;
this.y += this.velY;
// Boundary checks (horizontal)
if (this.x < 0) this.x = 0;
if (this.x + this.width > canvas.width) this.x = canvas.width - this.width;
// World floor (death if falling too far)
if (this.y > canvas.height) {
this.reset();
}
}
reset() {
this.x = 50;
this.y = 300;
this.velX = 0;
this.velY = 0;
// Note: score doesn't reset if we want it to persist,
// but usually you lose progress. Let's reset score too.
score = 0;
scoreElement.innerText = score;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
// Eyes for direction
ctx.fillStyle = 'white';
let eyeOffset = this.velX >= 0 ? 20 : 5;
ctx.fillRect(this.x + eyeOffset, this.y + 10, 5, 5);
}
}
class Platform {
constructor(x, y, width, height, color = '#8B4513') {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
class Coin {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 10;
this.collected = false;
}
draw() {
if (this.collected) return;
ctx.fillStyle = '#FFD700'; // Gold
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#B8860B';
ctx.stroke();
}
}
class Goal {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 20;
this.height = 60;
}
draw() {
// Flag pole
ctx.fillStyle = '#333';
ctx.fillRect(this.x, this.y, 5, this.height);
// Flag
ctx.fillStyle = 'red';
ctx.fillRect(this.x + 5, this.y, 25, 15);
}
}
// Initialization
const player = new Player(50, 300);
const platforms = [
new Platform(0, 400, 800, 50, '#228B22'), // Main ground
new Platform(150, 300, 150, 20), // Platform 1
new Platform(400, 220, 150, 20), // Platform 2
new Platform(600, 150, 100, 20), // Platform 3
new Platform(300, 100, 100, 20), // Platform 4
];
const coins = [
new Coin(225, 270),
new Coin(475, 190),
new Coin(650, 120),
new Coin(350, 70),
];
const goal = new Goal(750, 340);
function checkCollisions() {
player.grounded = false;
// Platform collision
for (let platform of platforms) {
if (player.x < platform.x + platform.width &&
player.x + player.width > platform.x &&
player.y < platform.y + platform.height &&
player.y + player.height > platform.y) {
// Determine collision side
let overlapTop = (player.y + player.height) - platform.y;
let overlapBottom = (platform.y + platform.height) - player.y;
let overlapLeft = (player.x + player.width) - platform.x;
let overlapRight = (platform.x + platform.width) - player.x;
let minOverlap = Math.min(overlapTop, overlapBottom, overlapLeft, overlapRight);
if (minOverlap === overlapTop && player.velY >= 0) {
// Colliding with top of platform
player.y = platform.y - player.height;
player.velY = 0;
player.grounded = true;
} else if (minOverlap === overlapBottom && player.velY <= 0) {
// Colliding with bottom of platform
player.y = platform.y + platform.height;
player.velY = 0;
} else if (minOverlap === overlapLeft) {
// Colliding with left side
player.x = platform.x - player.width;
player.velX = 0;
} else if (minOverlap === overlapRight) {
// Colliding with right side
player.x = platform.x + platform.width;
player.velX = 0;
}
}
}
// Coin collection
for (let coin of coins) {
if (!coin.collected) {
let dx = player.x + player.width / 2 - coin.x;
let dy = player.y + player.height / 2 - coin.y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < player.width / 2 + coin.radius) {
coin.collected = true;
score += 10;
scoreElement.innerText = score;
}
}
}
// Goal collision
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;
gameOverMessage = "YOU WIN!";
}
}
function update() {
if (!gameActive) return;
player.update();
checkCollisions();
}
function draw() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw Background (optional - sky is already in CSS)
// Draw entities
platforms.forEach(p => p.draw());
coins.forEach(c => c.draw());
goal.draw();
player.draw();
if (!gameActive) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = 'bold 48px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(gameOverMessage, canvas.width / 2, canvas.height / 2);
ctx.font = '24px sans-serif';
ctx.fillText('Refresh to play again', canvas.width / 2, canvas.height / 2 + 40);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// Start game
gameLoop();
<!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;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #333;
color: white;
font-family: sans-serif;
}
canvas {
background-color: #87CEEB; /* Sky Blue */
border: 4px solid #000;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
.ui {
margin-top: 10px;
font-size: 24px;
}
.controls {
margin-top: 10px;
font-size: 14px;
color: #ccc;
}
</style>
</head>
<body>
<div class="ui">Score: <span id="score">0</span></div>
<canvas id="game" width="800" height="450"></canvas>
<div class="controls">Use Arrow Keys or WASD to move and jump. Reach the Flag!</div>
<script src="game.js"></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%)
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const ui = document.getElementById('ui');
// Game Constants
const GRAVITY = 0.6;
const FRICTION = 0.85;
const PLAYER_SPEED = 5;
const JUMP_FORCE = -12;
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 450;
// Game State
let score = 0;
let gameOver = false;
let win = false;
// Input handling
const keys = {};
window.addEventListener('keydown', (e) => {
keys[e.code] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.code] = false;
});
class Entity {
constructor(x, y, width, height, color) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
class Player extends Entity {
constructor(x, y) {
super(x, y, 30, 30, '#ff0000'); // Red player
this.velX = 0;
this.velY = 0;
this.grounded = false;
}
update() {
// Movement
if (keys['ArrowLeft'] || keys['KeyA']) {
if (this.velX > -PLAYER_SPEED) this.velX--;
}
if (keys['ArrowRight'] || keys['KeyD']) {
if (this.velX < PLAYER_SPEED) this.velX++;
}
// Jump
if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && this.grounded) {
this.velY = JUMP_FORCE;
this.grounded = false;
}
// Physics
this.velX *= FRICTION;
this.velY += GRAVITY;
this.x += this.velX;
this.y += this.velY;
// Level bounds
if (this.x < 0) this.x = 0;
if (this.x + this.width > camera.x + CANVAS_WIDTH) {
// We'll handle camera scrolling instead of hard stopping for now,
// but let's ensure player doesn't go off screen left
}
this.grounded = false;
}
draw() {
// Draw player body
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
// Draw eyes to show direction
ctx.fillStyle = 'white';
let eyeOffset = this.velX >= 0 ? 15 : 5;
ctx.fillRect(this.x + eyeOffset, this.y + 5, 5, 5);
}
}
class Platform extends Entity {
constructor(x, y, width, height, color = '#8B4513') {
super(x, y, width, height, color);
}
}
class Coin extends Entity {
constructor(x, y) {
super(x, y, 15, 15, '#FFD700');
this.collected = false;
}
draw() {
if (this.collected) return;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
ctx.fill();
// Shine
ctx.fillStyle = 'white';
ctx.fillRect(this.x + 4, this.y + 4, 3, 3);
}
}
class Goal extends Entity {
constructor(x, y) {
super(x, y, 40, 100, '#00FF00');
}
draw() {
// Flag pole
ctx.fillStyle = '#333';
ctx.fillRect(this.x + 15, this.y, 10, this.height);
// Flag
ctx.fillStyle = 'red';
ctx.fillRect(this.x + 25, this.y, 20, 15);
}
}
// Level Data
const platforms = [
new Platform(0, 400, 1000, 50), // Ground
new Platform(1200, 400, 800, 50), // Ground gap
new Platform(2200, 400, 1000, 50), // Ground gap
new Platform(200, 300, 150, 20), // Platform 1
new Platform(450, 220, 150, 20), // Platform 2
new Platform(700, 300, 150, 20), // Platform 3
new Platform(1300, 300, 100, 20), // Platform 4
new Platform(1500, 200, 100, 20), // Platform 5
new Platform(1700, 300, 200, 20), // Platform 6
new Platform(2400, 300, 200, 20), // Platform 7
];
const coins = [
new Coin(250, 260),
new Coin(500, 180),
new Coin(750, 260),
new Coin(1350, 260),
new Coin(1550, 160),
new Coin(1800, 260),
new Coin(2500, 260),
];
const goal = new Goal(3000, 300);
const player = new Player(50, 300);
const camera = { x: 0 };
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 (gameOver || win) return;
player.update();
// Platform collisions
platforms.forEach(p => {
if (checkCollision(player, p)) {
// Determine collision side
const overlapX = Math.min(player.x + player.width, p.x + p.width) - Math.max(player.x, p.x);
const overlapY = Math.min(player.y + player.height, p.y + p.height) - Math.max(player.y, p.y);
if (overlapX > overlapY) {
// Vertical collision
if (player.velY > 0 && player.y < p.y) {
// Falling onto platform
player.y = p.y - player.height;
player.velY = 0;
player.grounded = true;
} else if (player.velY < 0 && player.y > p.y) {
// Hitting head
player.y = p.y + p.height;
player.velY = 0;
}
} else {
// Horizontal collision
if (player.velX > 0 && player.x < p.x) {
player.x = p.x - player.width;
player.velX = 0;
} else if (player.velX < 0 && player.x > p.x) {
player.x = p.x + p.width;
player.velX = 0;
}
}
}
});
// Coin collisions
coins.forEach(c => {
if (!c.collected && checkCollision(player, c)) {
c.collected = true;
score += 10;
ui.innerText = `Score: ${score}`;
}
});
// Goal collision
if (checkCollision(player, goal)) {
win = true;
}
// Death condition (fall off map)
if (player.y > CANVAS_HEIGHT) {
gameOver = true;
}
// Camera follow
camera.x = player.x - CANVAS_WIDTH / 4;
if (camera.x < 0) camera.x = 0;
}
function draw() {
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.save();
ctx.translate(-camera.x, 0);
// Draw platforms
platforms.forEach(p => p.draw());
// Draw coins
coins.forEach(c => c.draw());
// Draw goal
goal.draw();
// Draw player
player.draw();
ctx.restore();
if (gameOver) {
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('GAME OVER', CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2);
ctx.font = '24px Arial';
ctx.fillText('Press F5 to restart', CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2 + 40);
}
if (win) {
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.fillStyle = 'gold';
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2);
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2 + 40);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mario Style Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #333;
font-family: Arial, sans-serif;
overflow: hidden;
}
canvas {
background-color: #5c94fc; /* Classic Mario Sky Blue */
border: 4px solid #000;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
#ui {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 24px;
text-shadow: 2px 2px 4px #000;
pointer-events: none;
user-select: none;
}
</style>
</head>
<body>
<div id="ui">Score: 0</div>
<canvas id="game" width="800" height="450"></canvas>
<script src="game.js"></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%)
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
// Game Constants
const GRAVITY = 0.5;
const FRICTION = 0.8;
const PLAYER_SPEED = 5;
const JUMP_FORCE = -12;
const TILE_SIZE = 30;
// Game State
let score = 0;
let gameOver = false;
let win = false;
const keys = {};
// Player Object
const player = {
x: 50,
y: 300,
width: 30,
height: 30,
velX: 0,
velY: 0,
jumping: false,
color: '#ff0000' // Red character
};
// Level Data
// Platforms: { x, y, width, height }
const platforms = [
{ x: 0, y: 420, width: 2000, height: 30 }, // Ground
{ x: 200, y: 320, width: 100, height: 20 }, // Platform 1
{ x: 400, y: 240, width: 150, height: 20 }, // Platform 2
{ x: 650, y: 320, width: 100, height: 20 }, // Platform 3
{ x: 850, y: 220, width: 120, height: 20 }, // Platform 4
{ x: 1100, y: 320, width: 200, height: 20 }, // Platform 5
{ x: 1400, y: 200, width: 100, height: 20 }, // Platform 6
];
// Collectibles: { x, y, radius, collected }
const collectibles = [
{ x: 250, y: 290, radius: 8, collected: false },
{ x: 475, y: 210, radius: 8, collected: false },
{ x: 700, y: 290, radius: 8, collected: false },
{ x: 910, y: 190, radius: 8, collected: false },
{ x: 1150, y: 290, radius: 8, collected: false },
{ x: 1450, y: 170, radius: 8, collected: false },
];
// Goal: { x, y, width, height }
const goal = {
x: 1600,
y: 320,
width: 40,
height: 100,
color: '#ffd700' // Gold flag
};
// Camera
const camera = {
x: 0
};
// Input Listeners
window.addEventListener('keydown', (e) => {
keys[e.code] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.code] = false;
});
function update() {
if (gameOver || win) return;
// Movement
if (keys['ArrowLeft'] || keys['KeyA']) {
if (player.velX > -PLAYER_SPEED) player.velX--;
}
if (keys['ArrowRight'] || keys['KeyD']) {
if (player.velX < PLAYER_SPEED) player.velX++;
}
if ((keys['ArrowUp'] || keys['KeyW'] || keys['Space']) && !player.jumping) {
player.velY = JUMP_FORCE;
player.jumping = true;
}
// Apply Physics
player.velX *= FRICTION;
player.velY += GRAVITY;
player.x += player.velX;
player.y += player.velY;
// Collisions with Platforms
player.jumping = true; // Assume jumping until proven on ground
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) {
// Simple collision resolution
// Check if player is falling onto the platform
if (player.velY > 0 && (player.y + player.height - player.velY) <= plat.y) {
player.y = plat.y - player.height;
player.velY = 0;
player.jumping = false;
}
// Check if player is hitting the bottom of the platform
else if (player.velY < 0 && (player.y - player.velY) >= plat.y + plat.height) {
player.y = plat.y + plat.height;
player.velY = 0;
}
// Side collisions (basic)
else {
// For simplicity in this small demo, we'll just prevent passing through sides
// but mostly focus on top/bottom for platformer feel.
// If we hit a side, let's just push the player back a bit or stop movement.
if (player.x + player.width/2 < plat.x + plat.width/2) {
player.x = plat.x - player.width;
} else {
player.x = plat.x + plat.width;
}
player.velX = 0;
}
}
}
// Collectibles
for (const coin of collectibles) {
if (!coin.collected) {
const dx = player.x + player.width/2 - coin.x;
const dy = player.y + player.height/2 - coin.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < player.width/2 + coin.radius) {
coin.collected = true;
score += 10;
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) {
win = true;
}
// Falling off world
if (player.y > canvas.height + 100) {
gameOver = true;
}
// Camera follows player
camera.x = player.x - canvas.width / 2;
if (camera.x < 0) camera.x = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-camera.x, 0);
// Draw Platforms
ctx.fillStyle = '#8B4513'; // Brown
for (const plat of platforms) {
ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
// Add a little "grass" top
ctx.fillStyle = '#228B22'; // Green
ctx.fillRect(plat.x, plat.y, plat.width, 5);
ctx.fillStyle = '#8B4513';
}
// Draw Collectibles
ctx.fillStyle = '#FFD700'; // Gold
for (const coin of collectibles) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x, coin.y, coin.radius, 0, Math.PI * 2);
ctx.fill();
}
}
// 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, 5, goal.height); // Flag pole
// Draw Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
// Simple "eye" to see direction
ctx.fillStyle = 'black';
const eyeX = player.velX >= 0 ? player.x + 20 : player.x + 5;
ctx.fillRect(eyeX, player.y + 5, 5, 5);
ctx.restore();
// UI Overlays
if (gameOver) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '40px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2);
ctx.font = '20px sans-serif';
ctx.fillText('Refresh to try again', canvas.width / 2, canvas.height / 2 + 40);
}
if (win) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FFD700';
ctx.font = '40px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', canvas.width / 2, canvas.height / 2);
ctx.fillStyle = 'white';
ctx.font = '20px sans-serif';
ctx.fillText(`Final Score: ${score}`, canvas.width / 2, canvas.height / 2 + 40);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mario Style 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-container {
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
canvas {
display: block;
background-color: #5c94fc; /* Classic Mario Sky Blue */
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 20px;
text-shadow: 2px 2px 2px black;
pointer-events: none;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game" width="800" height="450"></canvas>
<div id="ui">Score: <span id="score">0</span></div>
</div>
<script src="game.js"></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%)
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const msgElement = document.getElementById('msg');
// Game Constants
const GRAVITY = 0.8;
const FRICTION = 0.8;
const PLAYER_SPEED = 5;
const PLAYER_JUMP_FORCE = -15;
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 450;
// Game State
let score = 0;
let gameActive = true;
let cameraX = 0;
// Input Handling
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 32;
this.height = 32;
this.vx = 0;
this.vy = 0;
this.onGround = false;
this.color = '#ff4444'; // Red character
}
update(platforms) {
// Input
if (keys['ArrowLeft'] || keys['KeyA']) {
if (this.vx > -PLAYER_SPEED) this.vx--;
}
if (keys['ArrowRight'] || keys['KeyD']) {
if (this.vx < PLAYER_SPEED) this.vx++;
}
if ((keys['ArrowUp'] || keys['KeyW'] || keys['Space']) && this.onGround) {
this.vy = PLAYER_JUMP_FORCE;
this.onGround = false;
}
// Physics
this.vy += GRAVITY;
this.vx *= FRICTION;
// Proposed movement
this.x += this.vx;
this.y += this.vy;
// Collision with platforms
this.onGround = false;
for (const platform of platforms) {
if (this.checkCollision(this, platform)) {
// Check if falling onto top of platform
if (this.vy > 0 && this.y + this.height - this.vy <= platform.y) {
this.y = platform.y - this.height;
this.vy = 0;
this.onGround = true;
}
// Check if hitting bottom of platform
else if (this.vy < 0 && this.y - this.vy >= platform.y + platform.height) {
this.y = platform.y + platform.height;
this.vy = 0;
}
// Check left/right collision
else {
// This is a very basic AABB collision.
// For a simple platformer, we prioritize vertical landing.
// If we are moving horizontally, we push the player out.
if (this.vx > 0) {
this.x = platform.x - this.width;
this.vx = 0;
} else if (this.vx < 0) {
this.x = platform.x + platform.width;
this.vx = 0;
}
}
}
}
// Bounds
if (this.x < 0) this.x = 0;
// Fall off map (death)
if (this.y > CANVAS_HEIGHT) {
resetGame();
}
}
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;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x - cameraX, this.y, this.width, this.height);
// Eyes for the character
ctx.fillStyle = 'white';
ctx.fillRect(this.x - cameraX + 20, this.y + 5, 5, 5);
ctx.fillRect(this.x - cameraX + 5, this.y + 5, 5, 5);
}
}
class Platform {
constructor(x, y, width, height, color = '#8B4513') {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x - cameraX, this.y, this.width, this.height);
// Add some texture (grass on top if it's a ground)
if (this.color === '#8B4513') {
ctx.fillStyle = '#228B22';
ctx.fillRect(this.x - cameraX, this.y, this.width, 5);
}
}
}
class Coin {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 15;
this.height = 15;
this.collected = false;
}
draw() {
if (this.collected) return;
ctx.fillStyle = '#FFD700'; // Gold
ctx.beginPath();
ctx.arc(this.x - cameraX + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
ctx.fill();
}
}
class Goal {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 40;
this.height = 100;
}
draw() {
// Flag pole
ctx.fillStyle = '#ddd';
ctx.fillRect(this.x - cameraX + 15, this.y, 10, this.height);
// Flag
ctx.fillStyle = '#FFD700';
ctx.fillRect(this.x - cameraX + 25, this.y + 10, 30, 20);
}
}
// Game instances
let player;
let platforms = [];
let coins = [];
let goal;
function resetGame() {
score = 0;
scoreElement.innerText = `Coins: ${score}`;
msgElement.innerText = "";
gameActive = true;
cameraX = 0;
player = new Player(50, 300);
// Level design
platforms = [
new Platform(0, 400, 1000, 50), // Floor
new Platform(1200, 400, 800, 50), // Second floor
new Platform(300, 300, 150, 20), // Floating platform
new Platform(550, 220, 150, 20), // Floating platform
new Platform(850, 300, 150, 20), // Floating platform
new Platform(1300, 280, 100, 20), // Floating platform
new Platform(1500, 180, 100, 20), // Floating platform
new Platform(1800, 300, 300, 20), // Platform before goal
];
coins = [
new Coin(350, 250),
new Coin(600, 180),
new Coin(900, 250),
new Coin(1350, 240),
new Coin(1550, 140),
new Coin(1900, 250),
];
goal = new Goal(2000, 300);
}
function update() {
if (!gameActive) return;
player.update(platforms);
// Camera follow (smoothly)
const targetCameraX = player.x - CANVAS_WIDTH / 2;
cameraX += (targetCameraX - cameraX) * 0.1;
if (cameraX < 0) cameraX = 0;
// Coin collection
coins.forEach(coin => {
if (!coin.collected && player.checkCollision(player, coin)) {
coin.collected = true;
score++;
scoreElement.innerText = `Coins: ${score}`;
}
});
// Goal check
if (player.checkCollision(player, goal)) {
gameActive = false;
msgElement.innerText = "YOU WIN!";
msgElement.style.color = "#FFD700";
}
}
function draw() {
// Clear canvas
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw Background (sky already done by CSS, but let's add some clouds for depth)
// We can draw simple clouds
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.beginPath();
ctx.arc(100 - (cameraX * 0.2 % 800), 100, 30, 0, Math.PI * 2);
ctx.arc(130 - (cameraX * 0.2 % 800), 100, 40, 0, Math.PI * 2);
ctx.arc(160 - (cameraX * 0.2 % 800), 100, 30, 0, Math.PI * 2);
ctx.fill();
// Draw game objects
platforms.forEach(p => p.draw());
coins.forEach(c => c.draw());
goal.draw();
player.draw();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// Start the game
resetGame();
loop();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mario Style 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;
color: white;
overflow: hidden;
}
canvas {
background-color: #5c94fc; /* Classic Mario Sky Blue */
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
border: 4px solid #000;
}
#ui {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
pointer-events: none;
}
.instructions {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: #ddd;
font-size: 0.9em;
}
</style>
</head>
<body>
<div id="ui">
<h2 id="score">Coins: 0</h2>
<h1 id="msg"></h1>
</div>
<canvas id="game" width="800" height="450"></canvas>
<div class="instructions">
Use ARROW KEYS or WASD to Move & Jump | Reach the Golden Flag!
</div>
<script src="game.js"></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%)
/**
* Super Mario Style Platformer
* Vanilla JavaScript
*/
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 PLAYER_SPEED = 5;
const JUMP_FORCE = -12;
const TILE_SIZE = 40;
// Game State
let score = 0;
let gameRunning = true;
let gameOver = false;
let win = false;
const keys = {
left: false,
right: false,
up: false
};
// Player Object
const player = {
x: 50,
y: 300,
width: 32,
height: 40,
vx: 0,
vy: 0,
grounded: false,
color: '#ff4444'
};
// Level Data
// Platforms: {x, y, width, height}
const platforms = [
{ x: 0, y: 410, width: 2000, height: 40 }, // Ground
{ x: 200, y: 300, width: 120, height: 20 },
{ x: 400, y: 220, width: 120, height: 20 },
{ x: 600, y: 300, width: 120, height: 20 },
{ x: 850, y: 200, width: 200, height: 20 },
{ x: 1100, y: 300, width: 150, height: 20 },
{ x: 1400, y: 200, width: 100, height: 20 },
];
// Collectibles: {x, y, radius, collected}
const collectibles = [
{ x: 250, y: 270, radius: 10, collected: false },
{ x: 450, y: 190, radius: 10, collected: false },
{ x: 650, y: 270, radius: 10, collected: false },
{ x: 950, y: 170, radius: 10, collected: false },
{ x: 1150, y: 270, radius: 10, collected: false },
{ x: 1450, y: 170, radius: 10, collected: false },
];
// Goal: {x, y, width, height}
const goal = { x: 1700, y: 310, width: 40, height: 100 };
// Camera
const camera = {
x: 0
};
// Input Listeners
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 (!gameRunning) return;
// Player movement
if (keys.left) {
player.vx -= 0.8;
}
if (keys.right) {
player.vx += 0.8;
}
// Apply friction and gravity
player.vx *= FRICTION;
player.vy += GRAVITY;
// Apply velocity to position
player.x += player.vx;
player.y += player.vy;
// Collision detection with platforms
player.grounded = false;
for (const platform of platforms) {
if (player.x < platform.x + platform.width &&
player.x + player.width > platform.x &&
player.y < platform.y + platform.height &&
player.y + player.height > platform.y) {
// Collision detected. We need to resolve it based on which side it hit.
// For a simple platformer, we check the overlap.
const overlapTop = (player.y + player.height) - platform.y;
const overlapBottom = (platform.y + platform.height) - player.y;
const overlapLeft = (player.x + player.width) - platform.x;
const overlapRight = (platform.x + platform.width) - player.x;
// Find the smallest overlap to determine collision side
const minOverlap = Math.min(overlapTop, overlapBottom, overlapLeft, overlapRight);
if (minOverlap === overlapTop && player.vy >= 0) {
// Hit top of platform (falling)
player.y = platform.y - player.height;
player.vy = 0;
player.grounded = true;
} else if (minOverlap === overlapBottom && player.vy < 0) {
// Hit bottom of platform (jumping)
player.y = platform.y + platform.height;
player.vy = 0;
} else if (minOverlap === overlapLeft) {
// Hit left side
player.x = platform.x - player.width;
player.vx = 0;
} else if (minOverlap === overlapRight) {
// Hit right side
player.x = platform.x + platform.width;
player.vx = 0;
}
}
}
// Jumping
if (player.grounded && keys.up) {
player.vy = JUMP_FORCE;
player.grounded = false;
}
// Boundary checks
if (player.x < 0) player.x = 0;
// If player falls off world (if there was a pit, but here ground is long)
if (player.y > canvas.height + 500) {
gameOver = true;
gameRunning = false;
}
// Collectibles collision
for (const coin of collectibles) {
if (!coin.collected) {
const dx = player.x + player.width/2 - coin.x;
const dy = player.y + player.height/2 - coin.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < player.width/2 + coin.radius) {
coin.collected = true;
score += 10;
scoreElement.innerText = score;
}
}
}
// Goal collision
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) {
win = true;
gameRunning = false;
}
// Camera movement (follows player)
camera.x = player.x - canvas.width / 4;
if (camera.x < 0) camera.x = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(-camera.x, 0);
// Draw Platforms
ctx.fillStyle = '#8B4513'; // Brownish ground
for (const platform of platforms) {
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Grass top
ctx.fillStyle = '#228B22';
ctx.fillRect(platform.x, platform.y, platform.width, 5);
ctx.fillStyle = '#8B4513';
}
// Draw Collectibles
ctx.fillStyle = '#FFD700'; // Gold
for (const coin of collectibles) {
if (!coin.collected) {
ctx.beginPath();
ctx.arc(coin.x, coin.y, coin.radius, 0, Math.PI * 2);
ctx.fill();
}
}
// Draw Goal (a flag)
ctx.fillStyle = '#00FF00';
ctx.fillRect(goal.x, goal.y, goal.width, goal.height);
ctx.fillStyle = '#000';
ctx.fillRect(goal.x, goal.y, 5, goal.height); // Flag pole
// Draw Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.restore();
// UI Overlay
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0,0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2);
}
if (win) {
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0,0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', canvas.width/2, canvas.height/2);
ctx.font = '20px Arial';
ctx.fillText(`Final Score: ${score}`, canvas.width/2, canvas.height/2 + 40);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mario Style Platformer</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #333;
font-family: Arial, sans-serif;
overflow: hidden;
}
canvas {
background-color: #5c94fc; /* Classic Mario sky blue */
box-shadow: 0 0 20px rgba(0,0,0,0.5);
display: block;
}
#ui {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
pointer-events: none;
}
</style>
</head>
<body>
<div id="ui">Score: <span id="score">0</span></div>
<canvas id="game" width="800" height="450"></canvas>
<script src="game.js"></script>
</body>
</html>