/* ═══════════════════════════════════════════════════════════════ GAME ENTITIES - Player, Platforms, Coins, Enemies, etc. ═══════════════════════════════════════════════════════════════ */ // ═══════════════════════════════════════════════════════════════ // PLAYER // ═══════════════════════════════════════════════════════════════ const Player = { x: 60, y: 400, width: 28, height: 38, color: '#00d9ff', velocityX: 0, velocityY: 0, speed: 6, baseSpeed: 6, sprintSpeed: 9, jumpPower: -14, baseJumpPower: -14, doubleJumpPower: -12, gravity: 0.65, grounded: false, facingRight: true, invincible: false, invincibleTimer: 0, hasDoubleJump: false, hasUsedDoubleJump: false, hasSpeedBoost: false, speedBoostTimer: 0, reset(x = 60, y = 400) { this.x = x; this.y = y; this.velocityX = 0; this.velocityY = 0; this.grounded = false; this.invincible = false; this.invincibleTimer = 0; this.hasUsedDoubleJump = false; this.hasSpeedBoost = false; this.speedBoostTimer = 0; }, update() { // Speed boost timer if (this.hasSpeedBoost) { this.speedBoostTimer--; if (this.speedBoostTimer <= 0) { this.hasSpeedBoost = false; this.speed = this.baseSpeed; } } // Invincibility timer if (this.invincible) { this.invincibleTimer--; if (this.invincibleTimer <= 0) { this.invincible = false; } } }, jump() { if (this.grounded) { this.velocityY = this.jumpPower; this.grounded = false; this.hasUsedDoubleJump = false; return true; } else if (this.hasDoubleJump && !this.hasUsedDoubleJump) { this.velocityY = this.doubleJumpPower; this.hasUsedDoubleJump = true; return true; } return false; }, takeDamage() { if (this.invincible) return false; this.invincible = true; this.invincibleTimer = 90; // 1.5 seconds at 60fps return true; }, applyPowerUp(type) { switch(type) { case 'doubleJump': this.hasDoubleJump = true; break; case 'speedBoost': this.hasSpeedBoost = true; this.speed = this.sprintSpeed; this.speedBoostTimer = 300; // 5 seconds break; case 'invincibility': this.invincible = true; this.invincibleTimer = 300; break; } } }; // Boss Enemy const Boss = { x: 0, y: 0, width: 60, height: 60, health: 5, maxHealth: 5, velocityX: 2, velocityY: 0, gravity: 0.5, patrolLeft: 0, patrolRight: 0, hitTimer: 0, init(x, y, left, right) { this.x = x; this.y = y; this.patrolLeft = left; this.patrolRight = right; this.health = this.maxHealth; }, update() { if (this.hitTimer > 0) this.hitTimer--; this.x += this.velocityX; if (this.x <= this.patrolLeft || this.x + this.width >= this.patrolRight) this.velocityX *= -1; }, draw() { const pulse = Math.sin(frameCount * 0.1) * 0.2 + 0.8; ctx.shadowColor = this.hitTimer > 0 ? '#ff0000' : '#ff00ff'; ctx.shadowBlur = 20 * pulse; ctx.fillStyle = this.hitTimer > 0 ? '#ff4444' : '#ff00ff'; ctx.beginPath(); ctx.arc(this.x + 30, this.y + 30, 25, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(this.x + 20, this.y + 25, 8, 0, Math.PI * 2); ctx.arc(this.x + 40, this.y + 25, 8, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; }, checkCollision(player) { return player.x < this.x + this.width && player.x + player.width > this.x && player.y < this.y + this.height && player.y + player.height > this.y; }, takeDamage() { this.health--; this.hitTimer = 15; screenShake = 10; createParticles(this.x + 30, this.y + 30, '#ff00ff', 15); return this.health <= 0; } }; // Secret Gem const SecretGem = { x: 0, y: 0, width: 20, height: 20, collected: false, bobOffset: 0, init(x, y) { this.x = x; this.y = y; this.collected = false; }, update() { this.bobOffset = Math.sin(frameCount * 0.08) * 5; }, draw() { if (this.collected) return; const y = this.y + this.bobOffset; const glow = Math.sin(frameCount * 0.15) * 0.3 + 0.7; ctx.shadowColor = '#ffd700'; ctx.shadowBlur = 20 * glow; ctx.fillStyle = '#ffd700'; ctx.beginPath(); ctx.moveTo(this.x + 10, y); ctx.lineTo(this.x + 20, y + 10); ctx.lineTo(this.x + 10, y + 20); ctx.lineTo(this.x, y + 10); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(this.x + 7, y + 7, 3, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; }, checkCollision(player) { if (this.collected) return false; return player.x < this.x + this.width && player.x + player.width > this.x && player.y < this.y + this.height && player.y + player.height > this.y; } }; // ═══════════════════════════════════════════════════════════════ // PLATFORMS // ═══════════════════════════════════════════════════════════════ class Platform { constructor(x, y, width, height, color = '#3a3a5a', isMoving = false, moveData = null) { this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; this.isMoving = isMoving; if (isMoving && moveData) { this.startX = moveData.startX; this.endX = moveData.endX; this.startY = moveData.startY; this.endY = moveData.endY; this.speed = moveData.speed || 2; this.direction = 1; this.moveType = moveData.moveType || 'horizontal'; // 'horizontal', 'vertical', 'circular' } } update() { if (!this.isMoving) return; switch(this.moveType) { case 'horizontal': this.x += this.speed * this.direction; if (this.x >= this.endX || this.x <= this.startX) { this.direction *= -1; } break; case 'vertical': this.y += this.speed * this.direction; if (this.y >= this.endY || this.y <= this.startY) { this.direction *= -1; } break; case 'circular': const time = Date.now() / 1000; this.x = this.startX + Math.sin(time * this.speed) * (this.endX - this.startX) / 2; this.y = this.startY + Math.cos(time * this.speed) * (this.endY - this.startY) / 2; break; } } draw(ctx, frameCount) { // Main platform body with gradient const platformGradient = ctx.createLinearGradient(this.x, this.y, this.x, this.y + this.height); platformGradient.addColorStop(0, this.color); platformGradient.addColorStop(1, this.darkenColor(this.color, 30)); ctx.fillStyle = platformGradient; ctx.fillRect(this.x, this.y, this.width, this.height); // Top highlight (bevel effect) ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; ctx.fillRect(this.x, this.y, this.width, 4); // Bottom shadow ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; ctx.fillRect(this.x, this.y + this.height - 3, this.width, 3); // Side highlights ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.fillRect(this.x, this.y, 2, this.height); ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; ctx.fillRect(this.x + this.width - 2, this.y, 2, this.height); // Edge glow for moving platforms if (this.isMoving) { ctx.shadowColor = this.color; ctx.shadowBlur = 15; ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; ctx.lineWidth = 2; ctx.strokeRect(this.x, this.y, this.width, this.height); ctx.shadowBlur = 0; } } darkenColor(color, percent) { // Simple color darkening const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.max((num >> 16) - amt, 0); const G = Math.max((num >> 8 & 0x00FF) - amt, 0); const B = Math.max((num & 0x0000FF) - amt, 0); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); } } // ═══════════════════════════════════════════════════════════════ // COINS // ═══════════════════════════════════════════════════════════════ class Coin { constructor(x, y) { this.x = x; this.y = y; this.width = 20; this.height = 20; this.collected = false; this.rotation = Math.random() * Math.PI * 2; } update(frameCount) { if (!this.collected) { this.rotation += 0.05; } } draw(ctx, frameCount) { if (this.collected) return; const bobOffset = Math.sin(frameCount * 0.08) * 3; const scale = Math.abs(Math.cos(this.rotation)); ctx.save(); ctx.translate(this.x + this.width / 2, this.y + this.height / 2 + bobOffset); ctx.scale(scale, 1); // Glow ctx.shadowColor = '#ffd700'; ctx.shadowBlur = 20; // Coin body const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 10); gradient.addColorStop(0, '#fff8dc'); gradient.addColorStop(0.5, '#ffd700'); gradient.addColorStop(1, '#b8860b'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2); ctx.fill(); // Inner detail ctx.fillStyle = '#daa520'; ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI * 2); ctx.fill(); ctx.restore(); ctx.shadowBlur = 0; } } // ═══════════════════════════════════════════════════════════════ // ENEMIES - Spikes // ═══════════════════════════════════════════════════════════════ class Spike { constructor(x, y, width = 30, height = 22) { this.x = x; this.y = y; this.width = width; this.height = height; } draw(ctx) { ctx.fillStyle = '#ff4757'; const spikeCount = Math.floor(this.width / 15); const spikeWidth = this.width / spikeCount; for (let i = 0; i < spikeCount; i++) { ctx.beginPath(); ctx.moveTo(this.x + i * spikeWidth, this.y + this.height); ctx.lineTo(this.x + i * spikeWidth + spikeWidth / 2, this.y); ctx.lineTo(this.x + (i + 1) * spikeWidth, this.y + this.height); ctx.fill(); } // Glow effect ctx.shadowColor = '#ff4757'; ctx.shadowBlur = 10; ctx.fillStyle = 'rgba(255, 71, 87, 0.5)'; for (let i = 0; i < spikeCount; i++) { ctx.beginPath(); ctx.moveTo(this.x + i * spikeWidth + spikeWidth / 2, this.y); ctx.lineTo(this.x + i * spikeWidth + spikeWidth / 2, this.y + 5); ctx.stroke(); } ctx.shadowBlur = 0; } } // ═══════════════════════════════════════════════════════════════ // ENEMIES - Moving Patrol Enemy // ═══════════════════════════════════════════════════════════════ class PatrolEnemy { constructor(x, y, patrolRange = 100, speed = 1.5) { this.x = x; this.y = y; this.width = 25; this.height = 30; this.startX = x; this.endX = x + patrolRange; this.speed = speed; this.direction = 1; this.color = '#ff6b6b'; } update() { this.x += this.speed * this.direction; if (this.x >= this.endX || this.x <= this.startX) { this.direction *= -1; } } draw(ctx, frameCount) { // Body ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.width, this.height); // Gradient overlay const gradient = ctx.createLinearGradient(this.x, this.y, this.x, this.y + this.height); gradient.addColorStop(0, 'rgba(255, 255, 255, 0.2)'); gradient.addColorStop(1, 'rgba(0, 0, 0, 0.3)'); ctx.fillStyle = gradient; ctx.fillRect(this.x, this.y, this.width, this.height); // Eyes const eyeY = this.y + 8; ctx.fillStyle = '#fff'; ctx.fillRect(this.x + 4, eyeY, 6, 6); ctx.fillRect(this.x + 15, eyeY, 6, 6); // Pupils (follow player direction) ctx.fillStyle = '#000'; const pupilOffset = this.direction > 0 ? 2 : 0; ctx.fillRect(this.x + 5 + pupilOffset, eyeY + 2, 3, 3); ctx.fillRect(this.x + 16 + pupilOffset, eyeY + 2, 3, 3); // Angry eyebrows ctx.fillStyle = '#333'; ctx.beginPath(); ctx.moveTo(this.x + 3, eyeY - 3); ctx.lineTo(this.x + 10, eyeY); ctx.lineTo(this.x + 10, eyeY - 2); ctx.lineTo(this.x + 5, eyeY - 4); ctx.fill(); ctx.beginPath(); ctx.moveTo(this.x + 22, eyeY - 3); ctx.lineTo(this.x + 15, eyeY); ctx.lineTo(this.x + 15, eyeY - 2); ctx.lineTo(this.x + 20, eyeY - 4); ctx.fill(); // Glow ctx.shadowColor = this.color; ctx.shadowBlur = 10; ctx.strokeStyle = 'rgba(255, 107, 107, 0.5)'; ctx.lineWidth = 2; ctx.strokeRect(this.x, this.y, this.width, this.height); ctx.shadowBlur = 0; } } // ═══════════════════════════════════════════════════════════════ // FLAG (Goal) // ═══════════════════════════════════════════════════════════════ class Flag { constructor(x, y) { this.x = x; this.y = y; this.width = 30; this.height = 40; } draw(ctx, frameCount) { // Pole ctx.fillStyle = '#8b4513'; ctx.fillRect(this.x, this.y, 6, this.height); // Pole gradient const poleGradient = ctx.createLinearGradient(this.x, this.y, this.x + 6, this.y); poleGradient.addColorStop(0, '#a0522d'); poleGradient.addColorStop(0.5, '#8b4513'); poleGradient.addColorStop(1, '#5d3a1a'); ctx.fillStyle = poleGradient; ctx.fillRect(this.x, this.y, 6, this.height); // Flag cloth with wave animation const waveOffset = Math.sin(frameCount * 0.08) * 8; ctx.fillStyle = '#ff6b6b'; ctx.beginPath(); ctx.moveTo(this.x + 6, this.y + 2); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 8, this.x + 36, this.y + 12); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 20, this.x + 36, this.y + 28); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 32, this.x + 6, this.y + 35); ctx.closePath(); ctx.fill(); // Flag highlight const flagGradient = ctx.createLinearGradient(this.x + 6, this.y, this.x + 36, this.y + 35); flagGradient.addColorStop(0, 'rgba(255, 255, 255, 0.3)'); flagGradient.addColorStop(1, 'rgba(0, 0, 0, 0.2)'); ctx.fillStyle = flagGradient; ctx.beginPath(); ctx.moveTo(this.x + 6, this.y + 2); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 8, this.x + 36, this.y + 12); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 20, this.x + 36, this.y + 28); ctx.quadraticCurveTo(this.x + 20 + waveOffset, this.y + 32, this.x + 6, this.y + 35); ctx.closePath(); ctx.fill(); // Gold orb on top ctx.fillStyle = '#ffd700'; ctx.beginPath(); ctx.arc(this.x + 3, this.y, 8, 0, Math.PI * 2); ctx.fill(); // Orb shine ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; ctx.beginPath(); ctx.arc(this.x + 1, this.y - 2, 3, 0, Math.PI * 2); ctx.fill(); // Glow ctx.shadowColor = '#ffd700'; ctx.shadowBlur = 20; ctx.strokeStyle = 'rgba(255, 215, 0, 0.5)'; ctx.lineWidth = 2; ctx.strokeRect(this.x + 6, this.y + 2, 30, 33); ctx.shadowBlur = 0; } } // ═══════════════════════════════════════════════════════════════ // POWER-UPS // ═══════════════════════════════════════════════════════════════ class PowerUp { constructor(x, y, type) { this.x = x; this.y = y; this.width = 24; this.height = 24; this.type = type; // 'doubleJump', 'speedBoost', 'invincibility' this.collected = false; switch(type) { case 'doubleJump': this.color = '#00ff88'; this.icon = '⬆️'; break; case 'speedBoost': this.color = '#ff00ff'; this.icon = '⚡'; break; case 'invincibility': this.color = '#ffff00'; this.icon = '🛡️'; break; } } update(frameCount) { if (this.collected) return; } draw(ctx, frameCount) { if (this.collected) return; const floatOffset = Math.sin(frameCount * 0.1) * 5; const rotation = frameCount * 0.02; ctx.save(); ctx.translate(this.x + this.width / 2, this.y + this.height / 2 + floatOffset); ctx.rotate(rotation); // Glow ctx.shadowColor = this.color; ctx.shadowBlur = 20; // Body ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(0, 0, 12, 0, Math.PI * 2); ctx.fill(); // Inner circle ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; ctx.beginPath(); ctx.arc(0, 0, 8, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // Icon (non-rotating) ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText(this.icon, this.x + this.width / 2, this.y + this.height / 2 + floatOffset + 5); ctx.shadowBlur = 0; } } // ═══════════════════════════════════════════════════════════════ // CHECKPOINTS // ═══════════════════════════════════════════════════════════════ class Checkpoint { constructor(x, y) { this.x = x; this.y = y; this.width = 30; this.height = 40; this.activated = false; } draw(ctx, frameCount) { // Pole ctx.fillStyle = this.activated ? '#00ff88' : '#666'; ctx.fillRect(this.x + 12, this.y, 6, this.height); // Flag const waveOffset = Math.sin(frameCount * 0.1) * 5; ctx.fillStyle = this.activated ? '#00ff88' : '#888'; ctx.beginPath(); ctx.moveTo(this.x + 18, this.y + 2); ctx.quadraticCurveTo(this.x + 28 + waveOffset, this.y + 10, this.x + 35, this.y + 12); ctx.quadraticCurveTo(this.x + 28 + waveOffset, this.y + 22, this.x + 18, this.y + 25); ctx.closePath(); ctx.fill(); // Glow when activated if (this.activated) { ctx.shadowColor = '#00ff88'; ctx.shadowBlur = 15; ctx.strokeStyle = 'rgba(0, 255, 136, 0.5)'; ctx.lineWidth = 2; ctx.strokeRect(this.x, this.y, this.width, this.height); ctx.shadowBlur = 0; } } } // ═══════════════════════════════════════════════════════════════ // PORTAL (Level Exit) // ═══════════════════════════════════════════════════════════════ class Portal { constructor(x, y) { this.x = x; this.y = y; this.width = 50; this.height = 60; } draw(ctx, frameCount) { const rotation = frameCount * 0.03; // Outer ring ctx.save(); ctx.translate(this.x + this.width / 2, this.y + this.height / 2); ctx.rotate(rotation); // Glow ctx.shadowColor = '#aa00ff'; ctx.shadowBlur = 30; // Multiple rings for (let i = 0; i < 3; i++) { ctx.strokeStyle = `rgba(170, 0, 255, ${0.8 - i * 0.2})`; ctx.lineWidth = 3 - i; ctx.beginPath(); ctx.arc(0, 0, 25 - i * 5, 0, Math.PI * 2); ctx.stroke(); } // Inner swirl ctx.fillStyle = 'rgba(100, 0, 200, 0.5)'; ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.fill(); ctx.restore(); ctx.shadowBlur = 0; } }