<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>THE IMPOSSIBLE GAME</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
mario: {
red: '#e52521',
blue: '#4a94ce',
brown: '#966c4a',
yellow: '#fbd000',
green: '#7dc21e',
sky: '#5c94fc',
black: '#211a1a'
}
}
}
}
}
</script>
<style>
body {
overflow: hidden;
touch-action: none;
}
canvas {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
.game-container {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.virtual-buttons {
touch-action: manipulation;
}
/* Dark mode support */
.dark body {
background-color: #181818;
color: #ffffff;
}
.dark .game-border {
border-color: #444;
}
.pixel-art {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* Title animation */
@keyframes glitch {
0% { transform: translate(0); }
20% { transform: translate(-3px, 3px); }
40% { transform: translate(-3px, -3px); }
60% { transform: translate(3px, 3px); }
80% { transform: translate(3px, -3px); }
100% { transform: translate(0); }
}
.glitch-title {
animation: glitch 0.5s ease-in-out infinite alternate;
text-shadow: 2px 2px #ff0000, -2px -2px #00ffff;
}
</style>
</head>
<body class="bg-gray-100 dark:bg-gray-900 min-h-screen flex flex-col items-center py-4">
<div class="game-container">
<h1 class="text-center text-2xl md:text-3xl font-bold mb-2 text-red-600 glitch-title">THE IMPOSSIBLE GAME :X</h1>
<div class="relative border-4 border-mario-brown rounded-lg game-border shadow-lg bg-mario-sky overflow-hidden">
<canvas id="gameCanvas" class="w-full pixel-art"></canvas>
<!-- Game UI overlay -->
<div id="startScreen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center p-4">
<h2 class="text-yellow-300 text-3xl md:text-4xl font-bold mb-4 glitch-title">THE IMPOSSIBLE GAME</h2>
<p class="text-white text-lg md:text-xl mb-6">Are you ready to suffer?</p>
<div class="grid grid-cols-2 gap-4 mb-4">
<button id="lvl1Btn" class="bg-mario-red hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full text-lg">LEVEL 1<br/><span class="text-xs">Easy</span></button>
<button id="lvl2Btn" class="bg-mario-blue hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full text-lg">LEVEL 2<br/><span class="text-xs">Medium</span></button>
<button id="lvl3Btn" class="bg-mario-yellow hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded-full text-lg">LEVEL 3<br/><span class="text-xs">Hard</span></button>
<button id="lvl4Btn" class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-full text-lg">LEVEL 4<br/><span class="text-xs">Extreme</span></button>
</div>
<div class="grid grid-cols-2 gap-4">
<button id="lvl5Btn" class="bg-purple-700 hover:bg-purple-800 text-white font-bold py-2 px-4 rounded-full text-lg">LEVEL 5<br/><span class="text-xs">Extra Hard</span></button>
<button id="lvl6Btn" class="bg-black hover:bg-red-900 text-red-500 font-bold py-2 px-4 rounded-full text-lg border-2 border-red-500">LEVEL 6<br/><span class="text-xs">IMPOSSIBLE</span></button>
</div>
</div>
<div id="levelCompleteBanner" class="absolute top-1/3 left-0 right-0 bg-black bg-opacity-70 py-4 text-center hidden">
<h2 class="text-yellow-300 text-2xl md:text-3xl font-bold">LEVEL COMPLETE!</h2>
<p class="text-white text-md md:text-lg">Next level starting in <span id="countdown">3</span>...</p>
</div>
<div id="gameOver" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center p-4 hidden">
<h2 class="text-red-500 text-3xl md:text-4xl font-bold mb-4">GAME OVER</h2>
<p id="finalScore" class="text-white text-xl mb-6">Score: 0</p>
<button id="restartButton" class="bg-mario-red hover:bg-red-700 text-white font-bold py-2 px-8 rounded-full text-lg">PLAY AGAIN</button>
<button id="menuButton" class="bg-mario-blue hover:bg-blue-700 text-white font-bold py-2 px-8 rounded-full text-lg mt-4">MAIN MENU</button>
</div>
<div id="gameComplete" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center p-4 hidden">
<h2 class="text-yellow-300 text-3xl md:text-4xl font-bold mb-4">GAME COMPLETE!</h2>
<p id="winScore" class="text-white text-xl mb-6">Score: 0</p>
<button id="playAgainButton" class="bg-mario-green hover:bg-green-700 text-white font-bold py-2 px-8 rounded-full text-lg">PLAY AGAIN</button>
<button id="winMenuButton" class="bg-mario-blue hover:bg-blue-700 text-white font-bold py-2 px-8 rounded-full text-lg mt-4">MAIN MENU</button>
</div>
<!-- Score display -->
<div class="absolute top-2 left-2 bg-black bg-opacity-50 text-white px-3 py-1 rounded-full" id="scoreDisplay">
COINS: 0
</div>
<!-- Lives display -->
<div class="absolute top-2 right-2 bg-black bg-opacity-50 text-white px-3 py-1 rounded-full" id="livesDisplay">
LIVES: 3
</div>
<!-- Level display -->
<div class="absolute bottom-2 left-2 bg-black bg-opacity-50 text-white px-3 py-1 rounded-full" id="levelDisplay">
LEVEL: 1
</div>
</div>
<!-- Virtual controls for touch devices -->
<div class="virtual-buttons grid grid-cols-2 gap-2 mt-4 md:hidden">
<div class="flex justify-start gap-2">
<button id="leftBtn" class="bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 w-16 h-16 rounded-full flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg>
</button>
<button id="rightBtn" class="bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 w-16 h-16 rounded-full flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
</button>
</div>
<div class="flex justify-end">
<button id="jumpBtn" class="bg-mario-red hover:bg-red-700 w-16 h-16 rounded-full text-white font-bold text-2xl">
A
</button>
</div>
</div>
<div class="text-center mt-4 text-sm text-gray-600 dark:text-gray-400">
<p>Use arrow keys to move, space to jump (or touch controls on mobile)</p>
<p>Collect coins and reach the flag to complete the level!</p>
</div>
</div>
<script>
// Check for dark mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
if (event.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
});
// Game initialization
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game state variables
let gameRunning = false;
let animationFrameId = null;
let score = 0;
let lives = 3;
let currentLevel = 1;
let maxLevel = 6;
// Canvas sizing
function resizeCanvas() {
const container = canvas.parentElement;
const containerWidth = container.clientWidth;
// Set a fixed aspect ratio (16:9)
const containerHeight = containerWidth * 0.5625;
canvas.width = 640; // Base resolution
canvas.height = 360;
canvas.style.width = containerWidth + 'px';
canvas.style.height = containerHeight + 'px';
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Player properties
const player = {
x: 50,
y: 250,
width: 32,
height: 32,
speed: 4,
jumpStrength: 12,
velX: 0,
velY: 0,
jumping: false,
grounded: false,
facingRight: true,
animationFrame: 0,
frameCounter: 0,
invincible: false,
invincibleTimer: 0
};
// Level elements
let platforms = [];
let movingPlatforms = [];
let disappearingPlatforms = [];
let coins = [];
let enemies = [];
let spikes = [];
let lavaPools = [];
let flagpole = { x: 600, y: 100, width: 10, height: 250 };
// Level backgrounds
const levelBackgrounds = [
'#5c94fc', // Level 1 - Sky blue
'#87CEEB', // Level 2 - Lighter blue
'#FFA500', // Level 3 - Orange (sunset)
'#191970', // Level 4 - Dark blue (night)
'#4B0082', // Level 5 - Indigo
'#000000' // Level 6 - Black (impossible level)
];
// Create level
function createLevel(level) {
// Reset all arrays
platforms = [];
movingPlatforms = [];
disappearingPlatforms = [];
coins = [];
enemies = [];
spikes = [];
lavaPools = [];
// Set level display
document.getElementById('levelDisplay').textContent = `LEVEL: ${level}`;
// Adjust player speed based on level
player.speed = 4 + (level * 0.2);
switch(level) {
case 1:
createLevel1();
break;
case 2:
createLevel2();
break;
case 3:
createLevel3();
break;
case 4:
createLevel4();
break;
case 5:
createLevel5();
break;
case 6:
createLevel6();
break;
default:
createLevel1();
}
}
function createLevel1() {
// Ground platforms
for (let i = 0; i < 20; i++) {
// Skip adding ground at position 13 to create a gap
if (i !== 13) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// Floating platforms
const floatingPlatforms = [
{ x: 150, y: 240, width: 96, height: 16 },
{ x: 300, y: 200, width: 128, height: 16 },
{ x: 490, y: 240, width: 64, height: 16 }
];
platforms = platforms.concat(floatingPlatforms);
// Add elevated ground for flagpole
platforms.push({ x: 576, y: 288, width: 64, height: 32, type: 'ground' });
// Coins
const coinPositions = [
{ x: 160, y: 200 },
{ x: 190, y: 200 },
{ x: 220, y: 200 },
{ x: 320, y: 160 },
{ x: 350, y: 160 },
{ x: 380, y: 160 },
{ x: 500, y: 200 },
{ x: 520, y: 200 }
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// Enemies
enemies = [
{ x: 220, y: 288, width: 32, height: 32, speed: 1, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 350, y: 168, width: 32, height: 32, speed: 1, direction: 1, type: 'goomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole
flagpole = { x: 600, y: 100, width: 10, height: 190 };
}
function createLevel2() {
// Ground platforms with more gaps
for (let i = 0; i < 25; i++) {
// Create more gaps
if (![5, 10, 15].includes(i)) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// Floating platforms
const floatingPlatforms = [
{ x: 120, y: 240, width: 64, height: 16 },
{ x: 240, y: 220, width: 64, height: 16 },
{ x: 350, y: 180, width: 64, height: 16 },
{ x: 450, y: 220, width: 64, height: 16 },
{ x: 550, y: 180, width: 64, height: 16 }
];
platforms = platforms.concat(floatingPlatforms);
// Moving platforms
movingPlatforms = [
{ x: 200, y: 180, width: 64, height: 16, startX: 200, startY: 180, endX: 200, endY: 280,
speed: 1, direction: 1, axis: 'y', type: 'moving' },
{ x: 400, y: 250, width: 64, height: 16, startX: 400, startY: 250, endX: 500, endY: 250,
speed: 1.5, direction: 1, axis: 'x', type: 'moving' }
];
// Coins
const coinPositions = [
{ x: 160, y: 200 },
{ x: 200, y: 150 },
{ x: 260, y: 180 },
{ x: 350, y: 140 },
{ x: 400, y: 210 },
{ x: 450, y: 180 },
{ x: 490, y: 120 },
{ x: 550, y: 140 },
{ x: 590, y: 200 }
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// Enemies - more and faster
enemies = [
{ x: 150, y: 288, width: 32, height: 32, speed: 1.5, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 350, y: 288, width: 32, height: 32, speed: 1.5, direction: 1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 480, y: 288, width: 32, height: 32, speed: 1.5, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 350, y: 148, width: 32, height: 32, speed: 2, direction: 1, type: 'goomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole
flagpole = { x: 760, y: 100, width: 10, height: 220 };
}
function createLevel3() {
// Ground platforms with even more gaps
for (let i = 0; i < 30; i++) {
// Create many gaps
if (![4, 8, 12, 16, 20, 24].includes(i)) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// More difficult floating platforms
const floatingPlatforms = [
{ x: 120, y: 250, width: 48, height: 16 },
{ x: 210, y: 220, width: 48, height: 16 },
{ x: 300, y: 190, width: 48, height: 16 },
{ x: 390, y: 160, width: 48, height: 16 },
{ x: 480, y: 130, width: 48, height: 16 },
{ x: 580, y: 180, width: 48, height: 16 },
{ x: 670, y: 230, width: 48, height: 16 },
{ x: 760, y: 200, width: 64, height: 16 },
];
platforms = platforms.concat(floatingPlatforms);
// Multiple moving platforms
movingPlatforms = [
{ x: 150, y: 180, width: 48, height: 16, startX: 150, startY: 180, endX: 150, endY: 280,
speed: 2, direction: 1, axis: 'y', type: 'moving' },
{ x: 240, y: 150, width: 48, height: 16, startX: 240, startY: 150, endX: 340, endY: 150,
speed: 2, direction: 1, axis: 'x', type: 'moving' },
{ x: 420, y: 220, width: 48, height: 16, startX: 420, startY: 220, endX: 520, endY: 220,
speed: 2.5, direction: 1, axis: 'x', type: 'moving' },
{ x: 590, y: 130, width: 48, height: 16, startX: 590, startY: 130, endX: 590, endY: 230,
speed: 2.5, direction: 1, axis: 'y', type: 'moving' }
];
// Add spikes
spikes = [
{ x: 270, y: 308, width: 96, height: 12 },
{ x: 400, y: 308, width: 96, height: 12 },
{ x: 650, y: 308, width: 64, height: 12 }
];
// Coins
const coinPositions = [
{ x: 120, y: 210 },
{ x: 210, y: 180 },
{ x: 300, y: 150 },
{ x: 390, y: 120 },
{ x: 480, y: 90 },
{ x: 550, y: 150 },
{ x: 640, y: 190 },
{ x: 730, y: 160 },
{ x: 780, y: 160 },
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// More enemies with new red type (faster)
enemies = [
{ x: 120, y: 288, width: 32, height: 32, speed: 2, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 500, y: 288, width: 32, height: 32, speed: 2, direction: 1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 390, y: 128, width: 32, height: 32, speed: 2, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 760, y: 168, width: 32, height: 32, speed: 3, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole further away
flagpole = { x: 900, y: 80, width: 10, height: 240 };
}
function createLevel4() {
// Extreme level - very few ground platforms
for (let i = 0; i < 40; i++) {
// Only add ground at specific positions
if ([0, 1, 2, 9, 10, 17, 18, 25, 26, 33, 34, 39].includes(i)) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// Small, difficult to reach platforms
const floatingPlatforms = [
{ x: 100, y: 250, width: 32, height: 16 },
{ x: 170, y: 200, width: 32, height: 16 },
{ x: 240, y: 150, width: 32, height: 16 },
{ x: 310, y: 200, width: 32, height: 16 },
{ x: 380, y: 250, width: 32, height: 16 },
{ x: 450, y: 200, width: 32, height: 16 },
{ x: 520, y: 150, width: 32, height: 16 },
{ x: 590, y: 200, width: 32, height: 16 },
{ x: 660, y: 250, width: 32, height: 16 },
{ x: 730, y: 200, width: 32, height: 16 },
{ x: 800, y: 150, width: 32, height: 16 },
{ x: 870, y: 100, width: 32, height: 16 },
{ x: 940, y: 150, width: 32, height: 16 },
{ x: 1010, y: 200, width: 32, height: 16 },
{ x: 1080, y: 150, width: 32, height: 16 }
];
platforms = platforms.concat(floatingPlatforms);
// Fast moving platforms
movingPlatforms = [
{ x: 200, y: 230, width: 40, height: 16, startX: 200, startY: 230, endX: 200, endY: 270,
speed: 3, direction: 1, axis: 'y', type: 'moving' },
{ x: 400, y: 180, width: 40, height: 16, startX: 400, startY: 180, endX: 480, endY: 180,
speed: 3, direction: 1, axis: 'x', type: 'moving' },
{ x: 600, y: 120, width: 40, height: 16, startX: 600, startY: 120, endX: 600, endY: 250,
speed: 3, direction: 1, axis: 'y', type: 'moving' },
{ x: 780, y: 230, width: 40, height: 16, startX: 780, startY: 230, endX: 880, endY: 230,
speed: 4, direction: 1, axis: 'x', type: 'moving' },
{ x: 900, y: 180, width: 40, height: 16, startX: 900, startY: 80, endX: 900, endY: 180,
speed: 4, direction: -1, axis: 'y', type: 'moving' }
];
// Lots of spikes
spikes = [
{ x: 96, y: 308, width: 192, height: 12 },
{ x: 352, y: 308, width: 192, height: 12 },
{ x: 608, y: 308, width: 192, height: 12 },
{ x: 864, y: 308, width: 192, height: 12 }
];
// Few coins (harder to reach)
const coinPositions = [
{ x: 170, y: 160 },
{ x: 240, y: 110 },
{ x: 450, y: 160 },
{ x: 520, y: 110 },
{ x: 730, y: 160 },
{ x: 800, y: 110 },
{ x: 870, y: 60 },
{ x: 1010, y: 160 },
{ x: 1080, y: 110 }
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// Many fast enemies
enemies = [
{ x: 150, y: 288, width: 32, height: 32, speed: 2.5, direction: -1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 380, y: 218, width: 32, height: 32, speed: 2.5, direction: 1, type: 'goomba', animationFrame: 0, frameCounter: 0 },
{ x: 520, y: 118, width: 32, height: 32, speed: 3, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 660, y: 218, width: 32, height: 32, speed: 3, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 800, y: 118, width: 32, height: 32, speed: 3.5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1010, y: 168, width: 32, height: 32, speed: 3.5, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole very far away
flagpole = { x: 1200, y: 50, width: 10, height: 270 };
}
function createLevel5() {
// Extra Hard level - extremely few ground platforms
for (let i = 0; i < 50; i++) {
// Only add ground at specific positions with most being gaps
if ([0, 1, 12, 13, 25, 26, 38, 39, 49].includes(i)) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// Tiny, very difficult to reach platforms
const floatingPlatforms = [
{ x: 100, y: 250, width: 24, height: 16 },
{ x: 190, y: 190, width: 24, height: 16 },
{ x: 280, y: 140, width: 24, height: 16 },
{ x: 400, y: 170, width: 24, height: 16 },
{ x: 550, y: 220, width: 24, height: 16 },
{ x: 650, y: 150, width: 24, height: 16 },
{ x: 780, y: 200, width: 24, height: 16 },
{ x: 900, y: 120, width: 24, height: 16 },
{ x: 1050, y: 180, width: 24, height: 16 },
{ x: 1200, y: 140, width: 24, height: 16 },
{ x: 1350, y: 100, width: 24, height: 16 }
];
platforms = platforms.concat(floatingPlatforms);
// Add disappearing platforms
disappearingPlatforms = [
{ x: 330, y: 180, width: 32, height: 16, timer: 0, maxTimer: 30, visible: true },
{ x: 470, y: 200, width: 32, height: 16, timer: 0, maxTimer: 25, visible: true },
{ x: 600, y: 180, width: 32, height: 16, timer: 0, maxTimer: 20, visible: true },
{ x: 720, y: 150, width: 32, height: 16, timer: 0, maxTimer: 20, visible: true },
{ x: 840, y: 160, width: 32, height: 16, timer: 0, maxTimer: 15, visible: true },
{ x: 980, y: 150, width: 32, height: 16, timer: 0, maxTimer: 15, visible: true },
{ x: 1120, y: 120, width: 32, height: 16, timer: 0, maxTimer: 10, visible: true },
{ x: 1280, y: 130, width: 32, height: 16, timer: 0, maxTimer: 10, visible: true }
];
// Very fast moving platforms
movingPlatforms = [
{ x: 150, y: 200, width: 32, height: 16, startX: 150, startY: 200, endX: 150, endY: 280,
speed: 4, direction: 1, axis: 'y', type: 'moving' },
{ x: 350, y: 130, width: 32, height: 16, startX: 350, startY: 130, endX: 450, endY: 130,
speed: 4, direction: 1, axis: 'x', type: 'moving' },
{ x: 520, y: 100, width: 32, height: 16, startX: 520, startY: 100, endX: 520, endY: 250,
speed: 5, direction: 1, axis: 'y', type: 'moving' },
{ x: 700, y: 230, width: 32, height: 16, startX: 700, startY: 230, endX: 820, endY: 230,
speed: 5, direction: 1, axis: 'x', type: 'moving' },
{ x: 950, y: 180, width: 32, height: 16, startX: 950, startY: 80, endX: 950, endY: 220,
speed: 6, direction: -1, axis: 'y', type: 'moving' },
{ x: 1120, y: 190, width: 32, height: 16, startX: 1120, startY: 190, endX: 1250, endY: 190,
speed: 6, direction: 1, axis: 'x', type: 'moving' }
];
// Even more spikes
spikes = [
{ x: 64, y: 308, width: 320, height: 12 },
{ x: 448, y: 308, width: 352, height: 12 },
{ x: 864, y: 308, width: 384, height: 12 },
{ x: 1280, y: 308, width: 288, height: 12 }
];
// Add lava pools that instantly kill
lavaPools = [
{ x: 420, y: 320, width: 28, height: 40 },
{ x: 800, y: 320, width: 64, height: 40 },
{ x: 1248, y: 320, width: 32, height: 40 }
];
// Very few coins (extremely hard to reach)
const coinPositions = [
{ x: 190, y: 150 },
{ x: 400, y: 130 },
{ x: 650, y: 110 },
{ x: 900, y: 80 },
{ x: 1200, y: 100 },
{ x: 1350, y: 60 }
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// Many extremely fast enemies
enemies = [
{ x: 150, y: 288, width: 32, height: 32, speed: 3.5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 430, y: 140, width: 32, height: 32, speed: 3.5, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 580, y: 188, width: 32, height: 32, speed: 4, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 820, y: 170, width: 32, height: 32, speed: 4, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 950, y: 148, width: 32, height: 32, speed: 4.5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1120, y: 88, width: 32, height: 32, speed: 4.5, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1300, y: 100, width: 32, height: 32, speed: 5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole extremely far away
flagpole = { x: 1500, y: 40, width: 10, height: 280 };
}
function createLevel6() {
// Impossible level - almost no ground
for (let i = 0; i < 60; i++) {
// Only add ground at start and end
if (i <= 1 || i >= 58) {
platforms.push({
x: i * 32,
y: 320,
width: 32,
height: 40,
type: 'ground'
});
}
}
// Tiny, extremely difficult platforms
const floatingPlatforms = [
{ x: 150, y: 280, width: 20, height: 12 },
{ x: 240, y: 230, width: 20, height: 12 },
{ x: 340, y: 190, width: 20, height: 12 },
{ x: 450, y: 150, width: 20, height: 12 },
{ x: 590, y: 120, width: 20, height: 12 },
{ x: 740, y: 150, width: 20, height: 12 },
{ x: 900, y: 180, width: 20, height: 12 },
{ x: 1050, y: 140, width: 20, height: 12 },
{ x: 1200, y: 110, width: 20, height: 12 },
{ x: 1360, y: 150, width: 20, height: 12 },
{ x: 1500, y: 180, width: 20, height: 12 },
{ x: 1650, y: 140, width: 20, height: 12 },
{ x: 1800, y: 100, width: 20, height: 12 }
];
platforms = platforms.concat(floatingPlatforms);
// Many disappearing platforms
disappearingPlatforms = [
{ x: 200, y: 250, width: 24, height: 12, timer: 0, maxTimer: 10, visible: true },
{ x: 290, y: 210, width: 24, height: 12, timer: 0, maxTimer: 8, visible: true },
{ x: 390, y: 170, width: 24, height: 12, timer: 0, maxTimer: 6, visible: true },
{ x: 520, y: 130, width: 24, height: 12, timer: 0, maxTimer: 8, visible: true },
{ x: 670, y: 140, width: 24, height: 12, timer: 0, maxTimer: 5, visible: true },
{ x: 820, y: 160, width: 24, height: 12, timer: 0, maxTimer: 8, visible: true },
{ x: 970, y: 150, width: 24, height: 12, timer: 0, maxTimer: 5, visible: true },
{ x: 1120, y: 120, width: 24, height: 12, timer: 0, maxTimer: 6, visible: true },
{ x: 1280, y: 130, width: 24, height: 12, timer: 0, maxTimer: 4, visible: true },
{ x: 1430, y: 160, width: 24, height: 12, timer: 0, maxTimer: 5, visible: true },
{ x: 1580, y: 150, width: 24, height: 12, timer: 0, maxTimer: 3, visible: true },
{ x: 1730, y: 120, width: 24, height: 12, timer: 0, maxTimer: 4, visible: true }
];
// Extremely fast moving platforms
movingPlatforms = [
{ x: 100, y: 200, width: 24, height: 12, startX: 100, startY: 200, endX: 100, endY: 300,
speed: 8, direction: 1, axis: 'y', type: 'moving' },
{ x: 700, y: 110, width: 24, height: 12, startX: 700, startY: 110, endX: 770, endY: 110,
speed: 9, direction: 1, axis: 'x', type: 'moving' },
{ x: 620, y: 150, width: 24, height: 12, startX: 620, startY: 150, endX: 620, endY: 250,
speed: 9, direction: 1, axis: 'y', type: 'moving' },
{ x: 1100, y: 90, width: 24, height: 12, startX: 1100, startY: 90, endX: 1150, endY: 90,
speed: 10, direction: 1, axis: 'x', type: 'moving' },
{ x: 1300, y: 120, width: 24, height: 12, startX: 1300, startY: 70, endX: 1300, endY: 120,
speed: 10, direction: -1, axis: 'y', type: 'moving' },
{ x: 1500, y: 130, width: 24, height: 12, startX: 1500, startY: 130, endX: 1560, endY: 130,
speed: 12, direction: 1, axis: 'x', type: 'moving' }
];
// Spikes everywhere
spikes = [
{ x: 64, y: 308, width: 1792, height: 12 }
];
// Lots of lava pools
lavaPools = [
{ x: 100, y: 320, width: 40, height: 40 },
{ x: 320, y: 320, width: 80, height: 40 },
{ x: 500, y: 320, width: 100, height: 40 },
{ x: 700, y: 320, width: 120, height: 40 },
{ x: 900, y: 320, width: 140, height: 40 },
{ x: 1200, y: 320, width: 160, height: 40 },
{ x: 1500, y: 320, width: 180, height: 40 }
];
// Few extremely hard to get coins
const coinPositions = [
{ x: 240, y: 190 },
{ x: 450, y: 110 },
{ x: 740, y: 110 },
{ x: 1050, y: 100 },
{ x: 1500, y: 140 },
{ x: 1800, y: 60 }
];
coinPositions.forEach(pos => {
coins.push({
x: pos.x,
y: pos.y,
width: 16,
height: 16,
collected: false,
animationFrame: 0,
frameCounter: 0
});
});
// Many super fast enemies
enemies = [
{ x: 290, y: 180, width: 32, height: 32, speed: 6, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 450, y: 118, width: 32, height: 32, speed: 6, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 670, y: 110, width: 32, height: 32, speed: 6.5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 900, y: 148, width: 32, height: 32, speed: 6.5, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1050, y: 108, width: 32, height: 32, speed: 7, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1360, y: 118, width: 32, height: 32, speed: 7, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1500, y: 148, width: 32, height: 32, speed: 7.5, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1650, y: 108, width: 32, height: 32, speed: 7.5, direction: 1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 },
{ x: 1800, y: 68, width: 32, height: 32, speed: 8, direction: -1, type: 'redgoomba', animationFrame: 0, frameCounter: 0 }
];
// Flagpole almost impossibly far away
flagpole = { x: 1900, y: 30, width: 10, height: 290 };
}
// Key state tracking
const keys = {};
document.addEventListener('keydown', function(e) {
keys[e.code] = true;
});
document.addEventListener('keyup', function(e) {
keys[e.code] = false;
});
// Mobile controls
const leftBtn = document.getElementById('leftBtn');
const rightBtn = document.getElementById('rightBtn');
const jumpBtn = document.getElementById('jumpBtn');
leftBtn.addEventListener('touchstart', () => keys['ArrowLeft'] = true);
leftBtn.addEventListener('touchend', () => keys['ArrowLeft'] = false);
rightBtn.addEventListener('touchstart', () => keys['ArrowRight'] = true);
rightBtn.addEventListener('touchend', () => keys['ArrowRight'] = false);
jumpBtn.addEventListener('touchstart', () => keys['Space'] = true);
jumpBtn.addEventListener('touchend', () => keys['Space'] = false);
// Mouse controls for testing on desktop
leftBtn.addEventListener('mousedown', () => keys['ArrowLeft'] = true);
leftBtn.addEventListener('mouseup', () => keys['ArrowLeft'] = false);
leftBtn.addEventListener('mouseleave', () => keys['ArrowLeft'] = false);
rightBtn.addEventListener('mousedown', () => keys['ArrowRight'] = true);
rightBtn.addEventListener('mouseup', () => keys['ArrowRight'] = false);
rightBtn.addEventListener('mouseleave', () => keys['ArrowRight'] = false);
jumpBtn.addEventListener('mousedown', () => keys['Space'] = true);
jumpBtn.addEventListener('mouseup', () => keys['Space'] = false);
jumpBtn.addEventListener('mouseleave', () => keys['Space'] = false);
// Game physics
const gravity = 0.6;
const friction = 0.8;
// Camera
let cameraOffset = 0;
// Game loop
function gameLoop() {
if (!gameRunning) return;
// Clear the canvas
ctx.fillStyle = levelBackgrounds[currentLevel - 1]; // Sky background based on level
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update camera position
updateCamera();
// Update player position
updatePlayer();
// Update enemy positions
updateEnemies();
// Update moving platforms
updateMovingPlatforms();
// Update disappearing platforms
updateDisappearingPlatforms();
// Update coin animations
updateCoins();
// Draw game elements
ctx.save();
ctx.translate(-cameraOffset, 0);
drawPlatforms();
drawMovingPlatforms();
drawDisappearingPlatforms();
drawCoins();
drawEnemies();
drawSpikes();
drawLavaPools();
drawFlagpole();
drawPlayer();
ctx.restore();
// Update UI
document.getElementById('scoreDisplay').textContent = `COINS: ${score}`;
document.getElementById('livesDisplay').textContent = `LIVES: ${lives}`;
document.getElementById('levelDisplay').textContent = `LEVEL: ${currentLevel}`;
// Continue animation
animationFrameId = requestAnimationFrame(gameLoop);
}
function updateCamera() {
// Follow player horizontally
const targetOffset = Math.max(0, player.x - canvas.width / 3);
// Smooth camera movement
cameraOffset += (targetOffset - cameraOffset) * 0.1;
// Level boundaries - don't show beyond level start
cameraOffset = Math.max(0, cameraOffset);
}
function updatePlayer() {
// Horizontal movement
player.velX *= friction;
if (keys['ArrowLeft'] || keys['KeyA']) {
player.velX = -player.speed;
player.facingRight = false;
player.frameCounter++;
}
if (keys['ArrowRight'] || keys['KeyD']) {
player.velX = player.speed;
player.facingRight = true;
player.frameCounter++;
}
// Jumping
if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && !player.jumping && player.grounded) {
player.jumping = true;
player.grounded = false;
player.velY = -player.jumpStrength;
playSound('jump');
}
// Apply gravity
player.velY += gravity;
// Animation
if (player.frameCounter > 5) {
player.frameCounter = 0;
player.animationFrame = (player.animationFrame + 1) % 3;
}
// Move player
player.x += player.velX;
player.y += player.velY;
// Keep player in bounds (left side only)
if (player.x < 0) {
player.x = 0;
}
// Handle falling off the screen
if (player.y > canvas.height) {
loseLife();
return;
}
// Process invincibility frames
if (player.invincible) {
player.invincibleTimer--;
if (player.invincibleTimer <= 0) {
player.invincible = false;
}
}
// Check collisions
checkCollisions();
}
function updateMovingPlatforms() {
movingPlatforms.forEach(platform => {
if (platform.axis === 'x') {
// Horizontal movement
platform.x += platform.speed * platform.direction;
// Check boundaries and reverse direction
if (platform.direction > 0 && platform.x >= platform.endX) {
platform.direction = -1;
} else if (platform.direction < 0 && platform.x <= platform.startX) {
platform.direction = 1;
}
} else {
// Vertical movement
platform.y += platform.speed * platform.direction;
// Check boundaries and reverse direction
if (platform.direction > 0 && platform.y >= platform.endY) {
platform.direction = -1;
} else if (platform.direction < 0 && platform.y <= platform.startY) {
platform.direction = 1;
}
}
});
}
function updateDisappearingPlatforms() {
disappearingPlatforms.forEach(platform => {
// If player is standing on the platform, start timer
if (player.grounded &&
player.x + player.width > platform.x &&
player.x < platform.x + platform.width &&
Math.abs(player.y + player.height - platform.y) < 2) {
if (platform.visible) {
platform.timer++;
// Check if timer expired
if (platform.timer >= platform.maxTimer) {
platform.visible = false;
platform.timer = 0;
}
}
} else if (!platform.visible) {
// Regenerate platform after delay
platform.timer++;
if (platform.timer >= platform.maxTimer * 2) {
platform.visible = true;
platform.timer = 0;
}
}
});
}
function checkCollisions() {
// Reset grounded state
player.grounded = false;
// Platform collisions
platforms.forEach(platform => {
// Get the overlap between player and platform
const overlapX = Math.max(0, Math.min(player.x + player.width, platform.x + platform.width) - Math.max(player.x, platform.x));
const overlapY = Math.max(0, Math.min(player.y + player.height, platform.y + platform.height) - Math.max(player.y, platform.y));
// Check for collision
if (overlapX > 0 && overlapY > 0) {
// Determine which overlap is smaller
if (overlapX > overlapY) {
// Vertical collision
if (player.y < platform.y) {
// Player is above the platform
player.y = platform.y - player.height;
player.velY = 0;
player.grounded = true;
player.jumping = false;
} else {
// Player is below the platform
player.y = platform.y + platform.height;
player.velY = 0;
}
} else {
// Horizontal collision
if (player.x < platform.x) {
// Player is to the left of the platform
player.x = platform.x - player.width;
} else {
// Player is to the right of the platform
player.x = platform.x + platform.width;
}
player.velX = 0;
}
}
});
// Moving platform collisions
movingPlatforms.forEach(platform => {
// Get the overlap
const overlapX = Math.max(0, Math.min(player.x + player.width, platform.x + platform.width) - Math.max(player.x, platform.x));
const overlapY = Math.max(0, Math.min(player.y + player.height, platform.y + platform.height) - Math.max(player.y, platform.y));
// Check for collision
if (overlapX > 0 && overlapY > 0) {
// Determine which overlap is smaller
if (overlapX > overlapY) {
// Vertical collision
if (player.y < platform.y) {
// Player is above the platform
player.y = platform.y - player.height;
player.velY = 0;
player.grounded = true;
player.jumping = false;
// Move player with platform if it's moving horizontally
if (platform.axis === 'x') {
player.x += platform.speed * platform.direction;
}
} else {
// Player is below the platform
player.y = platform.y + platform.height;
player.velY = 0;
}
} else {
// Horizontal collision
if (player.x < platform.x) {
// Player is to the left of the platform
player.x = platform.x - player.width;
} else {
// Player is to the right of the platform
player.x = platform.x + platform.width;
}
player.velX = 0;
}
}
});
// Disappearing platform collisions
disappearingPlatforms.forEach(platform => {
if (platform.visible) {
// Get the overlap
const overlapX = Math.max(0, Math.min(player.x + player.width, platform.x + platform.width) - Math.max(player.x, platform.x));
const overlapY = Math.max(0, Math.min(player.y + player.height, platform.y + platform.height) - Math.max(player.y, platform.y));
// Check for collision
if (overlapX > 0 && overlapY > 0) {
// Determine which overlap is smaller
if (overlapX > overlapY) {
// Vertical collision
if (player.y < platform.y) {
// Player is above the platform
player.y = platform.y - player.height;
player.velY = 0;
player.grounded = true;
player.jumping = false;
} else {
// Player is below the platform
player.y = platform.y + platform.height;
player.velY = 0;
}
} else {
// Horizontal collision
if (player.x < platform.x) {
// Player is to the left of the platform
player.x = platform.x - player.width;
} else {
// Player is to the right of the platform
player.x = platform.x + platform.width;
}
player.velX = 0;
}
}
}
});
// Coin collisions
coins.forEach(coin => {
if (!coin.collected && isColliding(player, coin)) {
coin.collected = true;
score++;
playSound('coin');
}
});
// Enemy collisions
enemies.forEach(enemy => {
if (isColliding(player, enemy)) {
// Check if player is jumping on enemy
if (player.velY > 0 && player.y + player.height - 10 < enemy.y) {
// Player jumped on enemy
enemy.type = 'squished';
player.velY = -8; // Bounce up
playSound('stomp');
// Remove enemy after a delay
setTimeout(() => {
enemies = enemies.filter(e => e !== enemy);
}, 500);
} else if (!player.invincible) {
// Player was hit by enemy
loseLife();
}
}
});
// Spike collisions
spikes.forEach(spike => {
if (isColliding(player, spike) && !player.invincible) {
loseLife();
}
});
// Lava pool collisions (instant death)
lavaPools.forEach(lava => {
if (isColliding(player, lava) && !player.invincible) {
// Instant death in lava
lives = 0;
loseLife();
}
});
// Flagpole collision (victory)
if (isColliding(player, flagpole)) {
levelComplete();
}
}
function updateEnemies() {
enemies.forEach(enemy => {
if (enemy.type === 'squished') return;
// Move enemy
enemy.x += enemy.speed * enemy.direction;
// Animation
enemy.frameCounter++;
if (enemy.frameCounter > 10) {
enemy.frameCounter = 0;
enemy.animationFrame = (enemy.animationFrame + 1) % 2;
}
// Check for platform edges and reverse direction
let onPlatform = false;
let aboutToFall = true;
const allPlatforms = [platforms, movingPlatforms, disappearingPlatforms.filter(p => p.visible)];
allPlatforms.forEach(platform => {
// Check if enemy is on this platform
if (enemy.y + enemy.height === platform.y) {
if (enemy.x + enemy.width / 2 >= platform.x &&
enemy.x + enemy.width / 2 <= platform.x + platform.width) {
onPlatform = true;
// Check if about to walk off
if ((enemy.direction < 0 && enemy.x <= platform.x) ||
(enemy.direction > 0 && enemy.x + enemy.width >= platform.x + platform.width)) {
enemy.direction *= -1;
}
// Check if there is a platform in front
allPlatforms.forEach(checkPlatform => {
if (checkPlatform.y === platform.y) {
if (enemy.direction < 0 &&
enemy.x - 1 >= checkPlatform.x &&
enemy.x - 1 <= checkPlatform.x + checkPlatform.width) {
aboutToFall = false;
} else if (enemy.direction > 0 &&
enemy.x + enemy.width + 1 >= checkPlatform.x &&
enemy.x + enemy.width + 1 <= checkPlatform.x + checkPlatform.width) {
aboutToFall = false;
}
}
});
if (aboutToFall) {
enemy.direction *= -1;
}
}
}
});
// Check if colliding with edges or other enemies
if (enemy.x <= 0) {
enemy.direction = 1;
}
enemies.forEach(otherEnemy => {
if (enemy !== otherEnemy && isColliding(enemy, otherEnemy)) {
enemy.direction *= -1;
otherEnemy.direction *= -1;
}
});
});
}
function updateCoins() {
coins.forEach(coin => {
if (!coin.collected) {
coin.frameCounter++;
if (coin.frameCounter > 8) {
coin.frameCounter = 0;
coin.animationFrame = (coin.animationFrame + 1) % 4;
}
}
});
}
function drawPlatforms() {
platforms.forEach(platform => {
if (platform.type === 'ground') {
// Draw ground block
ctx.fillStyle = '#8b4513'; // Brown
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Draw dirt texture
ctx.fillStyle = '#6b3203';
for (let i = 0; i < platform.width / 8; i++) {
for (let j = 0; j < platform.height / 8; j++) {
if ((i + j) % 2 === 0) {
ctx.fillRect(platform.x + i * 8, platform.y + j * 8, 8, 8);
}
}
}
// Draw grass on top if it's ground
if (platform.y < 320) {
ctx.fillStyle = '#7dc21e'; // Green
ctx.fillRect(platform.x, platform.y, platform.width, 5);
}
} else {
// Draw brick platform
ctx.fillStyle = '#c8733a'; // Brick color
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Draw brick pattern
ctx.fillStyle = '#9e5d2e';
for (let i = 0; i < platform.width; i += 8) {
ctx.fillRect(platform.x + i, platform.y, 2, platform.height);
}
ctx.fillRect(platform.x, platform.y + platform.height / 2, platform.width, 2);
}
});
}
function drawMovingPlatforms() {
movingPlatforms.forEach(platform => {
// Draw moving platform
ctx.fillStyle = '#4a94ce'; // Blue platform
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Add pattern
ctx.fillStyle = '#3a74ae';
ctx.fillRect(platform.x, platform.y, platform.width, 2);
ctx.fillRect(platform.x, platform.y + platform.height - 2, platform.width, 2);
// Add arrows to indicate movement
ctx.fillStyle = '#ffffff';
if (platform.axis === 'x') {
// Draw horizontal arrows
ctx.beginPath();
ctx.moveTo(platform.x + 5, platform.y + platform.height / 2);
ctx.lineTo(platform.x + 15, platform.y + platform.height / 2 - 5);
ctx.lineTo(platform.x + 15, platform.y + platform.height / 2 + 5);
ctx.fill();
ctx.beginPath();
ctx.moveTo(platform.x + platform.width - 5, platform.y + platform.height / 2);
ctx.lineTo(platform.x + platform.width - 15, platform.y + platform.height / 2 - 5);
ctx.lineTo(platform.x + platform.width - 15, platform.y + platform.height / 2 + 5);
ctx.fill();
} else {
// Draw vertical arrows
ctx.beginPath();
ctx.moveTo(platform.x + platform.width / 2, platform.y + 5);
ctx.lineTo(platform.x + platform.width / 2 - 5, platform.y + 15);
ctx.lineTo(platform.x + platform.width / 2 + 5, platform.y + 15);
ctx.fill();
ctx.beginPath();
ctx.moveTo(platform.x + platform.width / 2, platform.y + platform.height - 5);
ctx.lineTo(platform.x + platform.width / 2 - 5, platform.y + platform.height - 15);
ctx.lineTo(platform.x + platform.width / 2 + 5, platform.y + platform.height - 15);
ctx.fill();
}
});
}
function drawDisappearingPlatforms() {
disappearingPlatforms.forEach(platform => {
if (platform.visible) {
// Change color based on disappearing timer
const alpha = 1 - (platform.timer / platform.maxTimer) * 0.7;
// Draw platform
ctx.fillStyle = `rgba(150, 50, 150, ${alpha})`; // Purple platform that fades out
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
// Add pattern
const patternAlpha = Math.max(0, alpha - 0.2);
ctx.fillStyle = `rgba(120, 30, 120, ${patternAlpha})`;
// Draw warning cracks as platform is about to disappear
if (platform.timer > platform.maxTimer * 0.5) {
const cracksCount = Math.floor((platform.timer / platform.maxTimer) * 5);
for (let i = 0; i < cracksCount; i++) {
const crackX = platform.x + (platform.width * (i + 0.5)) / (cracksCount + 1);
ctx.beginPath();
ctx.moveTo(crackX, platform.y);
ctx.lineTo(crackX + 3, platform.y + platform.height / 3);
ctx.lineTo(crackX - 2, platform.y + platform.height * 2/3);
ctx.lineTo(crackX + 1, platform.y + platform.height);
ctx.stroke();
}
}
}
});
}
function drawCoins() {
coins.forEach(coin => {
if (!coin.collected) {
// Coin animation frames
ctx.fillStyle = '#fbd000'; // Gold
// Calculate coin width based on animation frame (simulate rotation)
const widths = [12, 8, 4, 8];
const currentWidth = widths[coin.animationFrame];
// Draw the coin centered at its position
const centerX = coin.x + coin.width / 2;
ctx.fillRect(centerX - currentWidth / 2, coin.y, currentWidth, coin.height);
// Add a shine effect
ctx.fillStyle = '#ffffff';
ctx.fillRect(centerX - currentWidth / 4, coin.y + 3, 2, 2);
}
});
}
function drawEnemies() {
enemies.forEach(enemy => {
if (enemy.type === 'squished') {
// Draw squished enemy
ctx.fillStyle = '#8B4513';
ctx.fillRect(enemy.x, enemy.y + enemy.height - 10, enemy.width, 10);
} else if (enemy.type === 'goomba') {
// Draw goomba body
ctx.fillStyle = '#8B4513';
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
// Draw goomba face
ctx.fillStyle = '#FFFFFF';
// Eyes
ctx.fillRect(enemy.x + 8, enemy.y + 10, 4, 4);
ctx.fillRect(enemy.x + enemy.width - 12, enemy.y + 10, 4, 4);
// Pupils
ctx.fillStyle = '#000000';
ctx.fillRect(enemy.x + 9, enemy.y + 11, 2, 2);
ctx.fillRect(enemy.x + enemy.width - 11, enemy.y + 11, 2, 2);
// Mouth (frown)
ctx.fillRect(enemy.x + 10, enemy.y + 20, enemy.width - 20, 2);
// Feet (alternate based on animation frame)
ctx.fillStyle = '#654321';
if (enemy.animationFrame === 0) {
ctx.fillRect(enemy.x + 4, enemy.y + enemy.height - 4, 8, 4);
ctx.fillRect(enemy.x + enemy.width - 12, enemy.y + enemy.height - 4, 8, 4);
} else {
ctx.fillRect(enemy.x + 6, enemy.y + enemy.height - 4, 8, 4);
ctx.fillRect(enemy.x + enemy.width - 14, enemy.y + enemy.height - 4, 8, 4);
}
} else if (enemy.type === 'redgoomba') {
// Draw red goomba body
ctx.fillStyle = '#e52521'; // Red
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
// Draw goomba face
ctx.fillStyle = '#FFFFFF';
// Eyes
ctx.fillRect(enemy.x + 8, enemy.y + 10, 4, 4);
ctx.fillRect(enemy.x + enemy.width - 12, enemy.y + 10, 4, 4);
// Pupils
ctx.fillStyle = '#000000';
ctx.fillRect(enemy.x + 9, enemy.y + 11, 2, 2);
ctx.fillRect(enemy.x + enemy.width - 11, enemy.y + 11, 2, 2);
// Angry eyebrows
ctx.fillRect(enemy.x + 7, enemy.y + 7, 6, 2);
ctx.fillRect(enemy.x + enemy.width - 13, enemy.y + 7, 6, 2);
// Mouth (angrier frown)
ctx.fillRect(enemy.x + 10, enemy.y + 22, enemy.width - 20, 3);
// Feet (alternate based on animation frame)
ctx.fillStyle = '#b01c1a';
if (enemy.animationFrame === 0) {
ctx.fillRect(enemy.x + 4, enemy.y + enemy.height - 4, 8, 4);
ctx.fillRect(enemy.x + enemy.width - 12, enemy.y + enemy.height - 4, 8, 4);
} else {
ctx.fillRect(enemy.x + 6, enemy.y + enemy.height - 4, 8, 4);
ctx.fillRect(enemy.x + enemy.width - 14, enemy.y + enemy.height - 4, 8, 4);
}
}
});
}
function drawSpikes() {
spikes.forEach(spike => {
// Base of spike
ctx.fillStyle = '#808080'; // Gray
ctx.fillRect(spike.x, spike.y + spike.height - 4, spike.width, 4);
// Draw individual spikes
ctx.fillStyle = '#C0C0C0'; // Silver
const spikeWidth = 8;
const numSpikes = Math.floor(spike.width / spikeWidth);
for (let i = 0; i < numSpikes; i++) {
const spikeX = spike.x + i * spikeWidth;
// Draw triangle spike
ctx.beginPath();
ctx.moveTo(spikeX, spike.y + spike.height - 4);
ctx.lineTo(spikeX + spikeWidth / 2, spike.y);
ctx.lineTo(spikeX + spikeWidth, spike.y + spike.height - 4);
ctx.closePath();
ctx.fill();
// Draw shine
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.moveTo(spikeX + 2, spike.y + spike.height - 6);
ctx.lineTo(spikeX + spikeWidth / 2 - 1, spike.y + 4);
ctx.lineTo(spikeX + 3, spike.y + spike.height - 6);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#C0C0C0'; // Reset for next spike
}
});
}
function drawLavaPools() {
lavaPools.forEach(lava => {
// Draw lava base
ctx.fillStyle = '#FF4500'; // Orange-red
ctx.fillRect(lava.x, lava.y, lava.width, lava.height);
// Draw lava surface with animation
const time = Date.now() / 200;
ctx.fillStyle = '#FF8C00'; // Darker orange
// Animate bubbling lava
for (let i = 0; i < lava.width; i += 8) {
const height = 3 + Math.sin(time + i * 0.1) * 2;
ctx.fillRect(lava.x + i, lava.y - height + 3, 6, height);
}
// Draw some lava bubbles
ctx.fillStyle = '#FFFF00'; // Yellow
const bubble1X = lava.x + (lava.width * 0.3) + Math.sin(time * 0.5) * 5;
const bubble2X = lava.x + (lava.width * 0.7) + Math.sin(time * 0.7) * 5;
ctx.beginPath();
ctx.arc(bubble1X, lava.y + 5, 2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(bubble2X, lava.y + 7, 3, 0, Math.PI * 2);
ctx.fill();
});
}
function drawFlagpole() {
// Draw pole
ctx.fillStyle = '#c0c0c0';
ctx.fillRect(flagpole.x, flagpole.y, flagpole.width, flagpole.height);
// Draw flag
ctx.fillStyle = '#e52521'; // Red
ctx.beginPath();
ctx.moveTo(flagpole.x, flagpole.y);
ctx.lineTo(flagpole.x - 25, flagpole.y + 15);
ctx.lineTo(flagpole.x, flagpole.y + 30);
ctx.fill();
}
function drawPlayer() {
if (player.invincible && Math.floor(Date.now() / 100) % 2 === 0) {
// Flicker when invincible
return;
}
// Draw character body
ctx.fillStyle = '#e52521'; // Red overalls
ctx.fillRect(player.x, player.y, player.width, player.height);
// Draw character features
// Cap
ctx.fillStyle = '#e52521'; // Red cap
ctx.fillRect(player.x, player.y, player.width, player.height / 3);
// Face
ctx.fillStyle = '#f8c8a0'; // Skin tone
ctx.fillRect(player.x + 6, player.y + 6, player.width - 12, player.height / 3);
// Eyes
ctx.fillStyle = '#000000';
if (player.facingRight) {
ctx.fillRect(player.x + player.width - 12, player.y + 10, 4, 4);
} else {
ctx.fillRect(player.x + 8, player.y + 10, 4, 4);
}
// Mustache
ctx.fillRect(player.x + 6, player.y + 18, player.width - 12, 4);
// Overall straps
ctx.fillStyle = '#4a94ce'; // Blue
ctx.fillRect(player.x + 8, player.y + player.height / 3, 4, player.height * 2/3);
ctx.fillRect(player.x + player.width - 12, player.y + player.height / 3, 4, player.height * 2/3);
// Buttons
ctx.fillStyle = '#fbd000'; // Yellow
ctx.fillRect(player.x + 12, player.y + player.height / 2, 4, 4);
ctx.fillRect(player.x + player.width - 16, player.y + player.height / 2, 4, 4);
// Arms and legs (animation based on movement)
ctx.fillStyle = '#f8c8a0'; // Skin tone
// Arms
if (player.jumping) {
// Arms raised when jumping
ctx.fillRect(player.x, player.y + player.height / 3, 6, 10);
ctx.fillRect(player.x + player.width - 6, player.y + player.height / 3, 6, 10);
} else if (player.frameCounter > 0) {
// Arms swinging when moving
if (player.animationFrame === 0) {
ctx.fillRect(player.x - 4, player.y + player.height / 2, 8, 6);
ctx.fillRect(player.x + player.width - 4, player.y + player.height / 2, 8, 6);
} else if (player.animationFrame === 1) {
ctx.fillRect(player.x, player.y + player.height / 2, 6, 8);
ctx.fillRect(player.x + player.width - 6, player.y + player.height / 2, 6, 8);
} else {
ctx.fillRect(player.x + 4, player.y + player.height / 2, 8, 6);
ctx.fillRect(player.x + player.width - 12, player.y + player.height / 2, 8, 6);
}
} else {
// Arms at rest
ctx.fillRect(player.x, player.y + player.height / 2, 6, 8);
ctx.fillRect(player.x + player.width - 6, player.y + player.height / 2, 6, 8);
}
// Legs
if (player.jumping) {
// Legs tucked up when jumping
ctx.fillRect(player.x + 6, player.y + player.height - 10, 8, 10);
ctx.fillRect(player.x + player.width - 14, player.y + player.height - 10, 8, 10);
} else if (player.frameCounter > 0) {
// Legs walking animation
if (player.animationFrame === 0) {
ctx.fillRect(player.x + 6, player.y + player.height - 12, 8, 12);
ctx.fillRect(player.x + player.width - 14, player.y + player.height - 6, 8, 6);
} else if (player.animationFrame === 1) {
ctx.fillRect(player.x + 6, player.y + player.height - 8, 8, 8);
ctx.fillRect(player.x + player.width - 14, player.y + player.height - 8, 8, 8);
} else {
ctx.fillRect(player.x + 6, player.y + player.height - 6, 8, 6);
ctx.fillRect(player.x + player.width - 14, player.y + player.height - 12, 8, 12);
}
} else {
// Legs standing
ctx.fillRect(player.x + 6, player.y + player.height - 10, 8, 10);
ctx.fillRect(player.x + player.width - 14, player.y + player.height - 10, 8, 10);
}
}
// Helper functions
function isColliding(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}
function loseLife() {
if (player.invincible) return;
lives--;
player.invincible = true;
player.invincibleTimer = 60; // 60 frames of invincibility
playSound('die');
if (lives <= 0) {
gameOver();
} else {
// Reset player position
player.x = 50;
player.y = 250;
player.velX = 0;
player.velY = 0;
}
}
function gameOver() {
gameRunning = false;
cancelAnimationFrame(animationFrameId);
document.getElementById('gameOver').classList.remove('hidden');
document.getElementById('finalScore').textContent = `Score: ${score}`;
playSound('gameover');
}
function levelComplete() {
// Stop the game loop
gameRunning = false;
cancelAnimationFrame(animationFrameId);
// Show level complete banner
const levelCompleteBanner = document.getElementById('levelCompleteBanner');
levelCompleteBanner.classList.remove('hidden');
// Update score
document.getElementById('scoreDisplay').textContent = `COINS: ${score}`;
// Play victory sound
playSound('victory');
// Next level countdown
let countdown = 3;
document.getElementById('countdown').textContent = countdown;
const countdownInterval = setInterval(() => {
countdown--;
document.getElementById('countdown').textContent = countdown;
if (countdown <= 0) {
clearInterval(countdownInterval);
levelCompleteBanner.classList.add('hidden');
// Check if all levels are complete
if (currentLevel >= maxLevel) {
gameVictory();
} else {
// Next level
currentLevel++;
createLevel(currentLevel);
resetPlayer();
gameRunning = true;
gameLoop();
}
}
}, 1000);
}
function gameVictory() {
gameRunning = false;
document.getElementById('gameComplete').classList.remove('hidden');
document.getElementById('winScore').textContent = `Score: ${score}`;
playSound('victory');
}
// Sound effects (using AudioContext to create simple sounds)
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
switch(type) {
case 'jump':
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(330, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(800, audioContext.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.1);
break;
case 'coin':
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(987.77, audioContext.currentTime);
setTimeout(() => oscillator.frequency.setValueAtTime(1318.51, audioContext.currentTime), 80);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.2);
break;
case 'stomp':
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(300, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(150, audioContext.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.1);
break;
case 'die':
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(44, audioContext.currentTime + 0.5);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.5);
break;
case 'gameover':
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(220, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(110, audioContext.currentTime + 1);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 1);
oscillator.start();
oscillator.stop(audioContext.currentTime + 1);
break;
case 'victory':
const notes = [330, 370, 392, 440, 494, 554, 587, 659];
const startTime = audioContext.currentTime;
notes.forEach((note, i) => {
const oscNote = audioContext.createOscillator();
const gainNote = audioContext.createGain();
oscNote.connect(gainNote);
gainNote.connect(audioContext.destination);
oscNote.type = 'square';
oscNote.frequency.value = note;
gainNote.gain.setValueAtTime(0.3, startTime + i * 0.1);
gainNote.gain.exponentialRampToValueAtTime(0.01, startTime + i * 0.1 + 0.1);
oscNote.start(startTime + i * 0.1);
oscNote.stop(startTime + i * 0.1 + 0.1);
});
break;
}
}
// Start game with level buttons
document.getElementById('lvl1Btn').addEventListener('click', function() {
startGame(1);
});
document.getElementById('lvl2Btn').addEventListener('click', function() {
startGame(2);
});
document.getElementById('lvl3Btn').addEventListener('click', function() {
startGame(3);
});
document.getElementById('lvl4Btn').addEventListener('click', function() {
startGame(4);
});
document.getElementById('lvl5Btn').addEventListener('click', function() {
startGame(5);
});
document.getElementById('lvl6Btn').addEventListener('click', function() {
startGame(6);
});
function startGame(level) {
// Resume audio context (needed for browsers that require user interaction)
if (audioContext.state === 'suspended') {
audioContext.resume();
}
document.getElementById('startScreen').style.display = 'none';
score = 0;
lives = 3;
currentLevel = level;
createLevel(currentLevel);
resetPlayer();
gameRunning = true;
gameLoop();
}
document.getElementById('restartButton').addEventListener('click', function() {
if (audioContext.state === 'suspended') {
audioContext.resume();
}
document.getElementById('gameOver').classList.add('hidden');
score = 0;
lives = 3;
createLevel(currentLevel);
resetPlayer();
gameRunning = true;
gameLoop();
});
document.getElementById('menuButton').addEventListener('click', function() {
document.getElementById('gameOver').classList.add('hidden');
document.getElementById('startScreen').style.display = 'flex';
});
document.getElementById('playAgainButton').addEventListener('click', function() {
if (audioContext.state === 'suspended') {
audioContext.resume();
}
document.getElementById('gameComplete').classList.add('hidden');
score = 0;
lives = 3;
currentLevel = 1;
createLevel(currentLevel);
resetPlayer();
gameRunning = true;
gameLoop();
});
document.getElementById('winMenuButton').addEventListener('click', function() {
document.getElementById('gameComplete').classList.add('hidden');
document.getElementById('startScreen').style.display = 'flex';
});
function resetPlayer() {
player.x = 50;
player.y = 250;
player.velX = 0;
player.velY = 0;
player.jumping = false;
player.grounded = false;
player.facingRight = true;
player.invincible = false;
player.invincibleTimer = 0;
}
</script>
</body>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.