Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Quantum Flipper - The Backflipping Bird</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
body { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
font-family: 'Press Start 2P', cursive; | |
background-color: #0f0f1a; | |
color: #e0e0ff; | |
} | |
#gameCanvas { | |
display: block; | |
background: linear-gradient(135deg, #000428, #004e92); | |
} | |
.universe-portal { | |
position: absolute; | |
width: 100px; | |
height: 100px; | |
border-radius: 50%; | |
background: radial-gradient(circle, rgba(0,255,255,0.8) 0%, rgba(0,0,255,0) 70%); | |
box-shadow: 0 0 30px cyan; | |
animation: pulse 2s infinite alternate; | |
z-index: 10; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); opacity: 0.8; } | |
100% { transform: scale(1.2); opacity: 1; } | |
} | |
.streak-effect { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); | |
pointer-events: none; | |
z-index: 5; | |
} | |
.turtle { | |
position: absolute; | |
width: 40px; | |
height: 30px; | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80"><path fill="%234CAF50" d="M20,40 Q50,10 80,40 Q50,70 20,40 Z"/><circle cx="35" cy="35" r="5" fill="black"/><circle cx="65" cy="35" r="5" fill="black"/></svg>'); | |
background-size: contain; | |
background-repeat: no-repeat; | |
z-index: 2; | |
} | |
.bird { | |
position: absolute; | |
width: 50px; | |
height: 40px; | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80"><path fill="%23FF5722" d="M10,40 L40,20 L90,40 L40,60 Z"/><circle cx="70" cy="40" r="10" fill="%23FF9800"/><circle cx="75" cy="35" r="3" fill="white"/><circle cx="75" cy="35" r="1" fill="black"/></svg>'); | |
background-size: contain; | |
background-repeat: no-repeat; | |
z-index: 3; | |
transition: transform 0.2s ease; | |
} | |
.flipping { | |
animation: backflip 0.5s linear; | |
} | |
@keyframes backflip { | |
0% { transform: rotateY(0deg); } | |
100% { transform: rotateY(360deg); } | |
} | |
.game-ui { | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
z-index: 100; | |
} | |
.universe-indicator { | |
position: absolute; | |
bottom: 20px; | |
right: 20px; | |
font-size: 14px; | |
background: rgba(0,0,0,0.7); | |
padding: 10px; | |
border-radius: 10px; | |
border: 2px solid cyan; | |
} | |
.streak-counter { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
font-size: 18px; | |
color: gold; | |
text-shadow: 0 0 5px rgba(255,215,0,0.7); | |
} | |
.game-over { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,0.8); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 200; | |
} | |
.start-screen { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(135deg, #000428, #004e92); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 300; | |
} | |
.title { | |
font-size: 3rem; | |
color: #FF5722; | |
text-shadow: 0 0 10px rgba(255,87,34,0.7); | |
margin-bottom: 2rem; | |
animation: glow 1.5s infinite alternate; | |
} | |
@keyframes glow { | |
from { text-shadow: 0 0 5px #FF5722; } | |
to { text-shadow: 0 0 20px #FF9800, 0 0 30px #FF5722; } | |
} | |
.instructions { | |
background: rgba(0,0,0,0.7); | |
padding: 20px; | |
border-radius: 10px; | |
margin-bottom: 2rem; | |
max-width: 500px; | |
text-align: center; | |
line-height: 1.6; | |
} | |
.btn { | |
background: linear-gradient(135deg, #FF5722, #FF9800); | |
border: none; | |
color: white; | |
padding: 15px 30px; | |
font-size: 1.2rem; | |
border-radius: 50px; | |
cursor: pointer; | |
font-family: 'Press Start 2P', cursive; | |
transition: all 0.3s; | |
box-shadow: 0 5px 15px rgba(0,0,0,0.3); | |
} | |
.btn:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 8px 20px rgba(0,0,0,0.4); | |
} | |
.btn:active { | |
transform: translateY(1px); | |
} | |
.particle { | |
position: absolute; | |
width: 5px; | |
height: 5px; | |
background-color: cyan; | |
border-radius: 50%; | |
pointer-events: none; | |
z-index: 1; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="gameCanvas"></canvas> | |
<div class="game-ui"> | |
<div id="score" class="text-white text-xl">Score: 0</div> | |
</div> | |
<div id="streakCounter" class="streak-counter hidden">Streak: 0</div> | |
<div id="universeIndicator" class="universe-indicator hidden">Universe: 1</div> | |
<div id="startScreen" class="start-screen"> | |
<h1 class="title">QUANTUM FLIPPER</h1> | |
<div class="instructions"> | |
<p>Control the quantum bird with arrow keys</p> | |
<p>Press SPACE to backflip</p> | |
<p>3 backflips in a row opens a portal to a parallel universe</p> | |
<p>Avoid the turtles - they'll break your streak!</p> | |
</div> | |
<button id="startBtn" class="btn">START ADVENTURE</button> | |
</div> | |
<div id="gameOverScreen" class="game-over hidden"> | |
<h1 class="title">GAME OVER</h1> | |
<div id="finalScore" class="text-white text-2xl mb-8">Score: 0</div> | |
<div id="universesVisited" class="text-cyan-300 text-xl mb-8">Universes Visited: 0</div> | |
<button id="restartBtn" class="btn">FLIP AGAIN</button> | |
</div> | |
<script> | |
// Game variables | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const startScreen = document.getElementById('startScreen'); | |
const gameOverScreen = document.getElementById('gameOverScreen'); | |
const startBtn = document.getElementById('startBtn'); | |
const restartBtn = document.getElementById('restartBtn'); | |
const scoreDisplay = document.getElementById('score'); | |
const finalScoreDisplay = document.getElementById('finalScore'); | |
const universesVisitedDisplay = document.getElementById('universesVisited'); | |
const streakCounter = document.getElementById('streakCounter'); | |
const universeIndicator = document.getElementById('universeIndicator'); | |
// Set canvas size | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
// Game state | |
let gameRunning = false; | |
let score = 0; | |
let streak = 0; | |
let universe = 1; | |
let bird = { | |
x: 100, | |
y: 100, | |
width: 50, | |
height: 40, | |
speed: 5, | |
isFlipping: false | |
}; | |
let turtles = []; | |
let portals = []; | |
let particles = []; | |
let keys = {}; | |
let lastFlipTime = 0; | |
let flipCooldown = 500; // milliseconds | |
let gameLoopInterval; | |
let turtleSpawnInterval; | |
let backgroundHue = 200; | |
// Event listeners | |
window.addEventListener('keydown', (e) => { | |
keys[e.key] = true; | |
if (e.key === ' ' && gameRunning && !bird.isFlipping && Date.now() - lastFlipTime > flipCooldown) { | |
performBackflip(); | |
} | |
}); | |
window.addEventListener('keyup', (e) => { | |
keys[e.key] = false; | |
}); | |
startBtn.addEventListener('click', startGame); | |
restartBtn.addEventListener('click', startGame); | |
window.addEventListener('resize', () => { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
}); | |
function startGame() { | |
// Reset game state | |
gameRunning = true; | |
score = 0; | |
streak = 0; | |
universe = 1; | |
bird.x = 100; | |
bird.y = 100; | |
turtles = []; | |
portals = []; | |
particles = []; | |
// Update UI | |
scoreDisplay.textContent = `Score: ${score}`; | |
streakCounter.classList.add('hidden'); | |
universeIndicator.textContent = `Universe: ${universe}`; | |
universeIndicator.classList.remove('hidden'); | |
// Hide screens | |
startScreen.classList.add('hidden'); | |
gameOverScreen.classList.add('hidden'); | |
// Start game loops | |
clearInterval(gameLoopInterval); | |
clearInterval(turtleSpawnInterval); | |
gameLoopInterval = setInterval(gameLoop, 16); | |
turtleSpawnInterval = setInterval(spawnTurtle, 2000); | |
} | |
function gameLoop() { | |
update(); | |
render(); | |
} | |
function update() { | |
// Move bird based on key presses | |
if (keys['ArrowUp'] && bird.y > 0) { | |
bird.y -= bird.speed; | |
} | |
if (keys['ArrowDown'] && bird.y < canvas.height - bird.height) { | |
bird.y += bird.speed; | |
} | |
if (keys['ArrowLeft'] && bird.x > 0) { | |
bird.x -= bird.speed; | |
} | |
if (keys['ArrowRight'] && bird.x < canvas.width - bird.width) { | |
bird.x += bird.speed; | |
} | |
// Update turtles | |
turtles.forEach((turtle, index) => { | |
turtle.x += turtle.speedX; | |
turtle.y += turtle.speedY; | |
// Bounce off walls | |
if (turtle.x <= 0 || turtle.x >= canvas.width - turtle.width) { | |
turtle.speedX *= -1; | |
} | |
if (turtle.y <= 0 || turtle.y >= canvas.height - turtle.height) { | |
turtle.speedY *= -1; | |
} | |
// Check collision with bird | |
if ( | |
bird.x < turtle.x + turtle.width && | |
bird.x + bird.width > turtle.x && | |
bird.y < turtle.y + turtle.height && | |
bird.y + bird.height > turtle.y | |
) { | |
// If bird is flipping, turtle is destroyed | |
if (bird.isFlipping) { | |
turtles.splice(index, 1); | |
score += 50; | |
createParticles(turtle.x + turtle.width/2, turtle.y + turtle.height/2, 10, 'lime'); | |
} else { | |
// Otherwise, streak is broken | |
if (streak > 0) { | |
streak = 0; | |
streakCounter.textContent = `Streak: ${streak}`; | |
streakCounter.classList.add('hidden'); | |
createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 15, 'red'); | |
} | |
} | |
} | |
}); | |
// Update particles | |
particles.forEach((particle, index) => { | |
particle.x += particle.speedX; | |
particle.y += particle.speedY; | |
particle.lifetime--; | |
if (particle.lifetime <= 0) { | |
particles.splice(index, 1); | |
} | |
}); | |
// Update score display | |
scoreDisplay.textContent = `Score: ${score}`; | |
} | |
function render() { | |
// Clear canvas | |
ctx.fillStyle = `hsl(${backgroundHue}, 80%, 10%)`; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Draw stars | |
drawStars(); | |
// Draw portals | |
portals.forEach(portal => { | |
ctx.beginPath(); | |
ctx.arc(portal.x, portal.y, portal.radius, 0, Math.PI * 2); | |
const gradient = ctx.createRadialGradient( | |
portal.x, portal.y, portal.radius * 0.3, | |
portal.x, portal.y, portal.radius | |
); | |
gradient.addColorStop(0, 'rgba(0, 255, 255, 0.8)'); | |
gradient.addColorStop(1, 'rgba(0, 0, 255, 0)'); | |
ctx.fillStyle = gradient; | |
ctx.fill(); | |
// Draw event horizon | |
ctx.beginPath(); | |
ctx.arc(portal.x, portal.y, portal.radius * 0.7, 0, Math.PI * 2); | |
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; | |
ctx.fill(); | |
// Draw spiral | |
for (let i = 0; i < 8; i++) { | |
const angle = i * (Math.PI / 4) + Date.now() / 500; | |
const startRadius = portal.radius * 0.7; | |
const endRadius = portal.radius; | |
ctx.beginPath(); | |
ctx.moveTo( | |
portal.x + Math.cos(angle) * startRadius, | |
portal.y + Math.sin(angle) * startRadius | |
); | |
ctx.lineTo( | |
portal.x + Math.cos(angle) * endRadius, | |
portal.y + Math.sin(angle) * endRadius | |
); | |
ctx.strokeStyle = `rgba(0, 255, 255, ${0.5 + 0.5 * Math.sin(Date.now()/200 + i)})`; | |
ctx.lineWidth = 2; | |
ctx.stroke(); | |
} | |
}); | |
// Draw particles | |
particles.forEach(particle => { | |
ctx.fillStyle = particle.color; | |
ctx.beginPath(); | |
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); | |
ctx.fill(); | |
}); | |
// Draw turtles | |
turtles.forEach(turtle => { | |
ctx.save(); | |
ctx.translate(turtle.x + turtle.width/2, turtle.y + turtle.height/2); | |
// Add slight rotation based on movement direction | |
if (Math.abs(turtle.speedX) > 0.1) { | |
ctx.rotate(Math.atan2(turtle.speedY, turtle.speedX)); | |
} | |
// Turtle shell | |
ctx.beginPath(); | |
ctx.moveTo(-15, 0); | |
ctx.quadraticCurveTo(0, -25, 15, 0); | |
ctx.quadraticCurveTo(0, 25, -15, 0); | |
ctx.fillStyle = '#4CAF50'; | |
ctx.fill(); | |
// Turtle head | |
ctx.beginPath(); | |
ctx.arc(-20, -5, 8, 0, Math.PI * 2); | |
ctx.fillStyle = '#4CAF50'; | |
ctx.fill(); | |
// Eyes | |
ctx.beginPath(); | |
ctx.arc(-15, -5, 3, 0, Math.PI * 2); | |
ctx.fillStyle = 'black'; | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(-25, -5, 3, 0, Math.PI * 2); | |
ctx.fillStyle = 'black'; | |
ctx.fill(); | |
// Legs (simplified) | |
ctx.fillStyle = '#388E3C'; | |
ctx.fillRect(10, -10, 5, 5); | |
ctx.fillRect(10, 5, 5, 5); | |
ctx.fillRect(-5, -15, 5, 5); | |
ctx.fillRect(-5, 10, 5, 5); | |
ctx.restore(); | |
}); | |
// Draw bird | |
ctx.save(); | |
ctx.translate(bird.x + bird.width/2, bird.y + bird.height/2); | |
// Apply flip rotation if flipping | |
if (bird.isFlipping) { | |
const flipProgress = (Date.now() - lastFlipTime) / flipCooldown; | |
ctx.rotate(flipProgress * Math.PI * 2); | |
} | |
// Bird body | |
ctx.beginPath(); | |
ctx.moveTo(-20, 0); | |
ctx.lineTo(10, -15); | |
ctx.lineTo(30, 0); | |
ctx.lineTo(10, 15); | |
ctx.closePath(); | |
ctx.fillStyle = '#FF5722'; | |
ctx.fill(); | |
// Bird head | |
ctx.beginPath(); | |
ctx.arc(25, 0, 10, 0, Math.PI * 2); | |
ctx.fillStyle = '#FF9800'; | |
ctx.fill(); | |
// Eye | |
ctx.beginPath(); | |
ctx.arc(27, -2, 3, 0, Math.PI * 2); | |
ctx.fillStyle = 'white'; | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(27, -2, 1, 0, Math.PI * 2); | |
ctx.fillStyle = 'black'; | |
ctx.fill(); | |
// Beak | |
ctx.beginPath(); | |
ctx.moveTo(35, 0); | |
ctx.lineTo(40, -5); | |
ctx.lineTo(40, 5); | |
ctx.closePath(); | |
ctx.fillStyle = '#FFC107'; | |
ctx.fill(); | |
ctx.restore(); | |
// Check if bird entered a portal | |
portals.forEach((portal, index) => { | |
const dx = bird.x + bird.width/2 - portal.x; | |
const dy = bird.y + bird.height/2 - portal.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < portal.radius) { | |
// Travel to new universe | |
universe++; | |
universeIndicator.textContent = `Universe: ${universe}`; | |
backgroundHue = (backgroundHue + 60) % 360; | |
// Clear turtles and portals | |
turtles = []; | |
portals = []; | |
// Create celebration particles | |
createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 50, 'cyan'); | |
// Reset streak | |
streak = 0; | |
streakCounter.classList.add('hidden'); | |
} | |
}); | |
} | |
function drawStars() { | |
// Create a starfield effect | |
ctx.fillStyle = 'white'; | |
for (let i = 0; i < 200; i++) { | |
// Use a simple hash to create consistent star positions per universe | |
const x = (Math.sin(i * 100 + universe * 10) * 0.5 + 0.5) * canvas.width; | |
const y = (Math.cos(i * 100 + universe * 10) * 0.5 + 0.5) * canvas.height; | |
const size = Math.random() * 1.5; | |
// Make some stars twinkle | |
const opacity = 0.5 + 0.5 * Math.sin(Date.now() / 1000 + i); | |
ctx.globalAlpha = opacity; | |
ctx.beginPath(); | |
ctx.arc(x, y, size, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
ctx.globalAlpha = 1; | |
} | |
function performBackflip() { | |
bird.isFlipping = true; | |
lastFlipTime = Date.now(); | |
score += 20; | |
// Create flip particles | |
createParticles(bird.x + bird.width/2, bird.y + bird.height/2, 15, 'orange'); | |
// End flip animation after cooldown | |
setTimeout(() => { | |
bird.isFlipping = false; | |
// Increase streak | |
streak++; | |
streakCounter.textContent = `Streak: ${streak}`; | |
streakCounter.classList.remove('hidden'); | |
// Check for streak milestone | |
if (streak >= 3) { | |
createPortal(); | |
streak = 0; | |
streakCounter.classList.add('hidden'); | |
} | |
}, flipCooldown); | |
} | |
function spawnTurtle() { | |
if (!gameRunning) return; | |
const side = Math.floor(Math.random() * 4); | |
let x, y, speedX, speedY; | |
switch (side) { | |
case 0: // top | |
x = Math.random() * canvas.width; | |
y = -40; | |
speedX = (Math.random() - 0.5) * 3; | |
speedY = Math.random() * 2 + 1; | |
break; | |
case 1: // right | |
x = canvas.width + 40; | |
y = Math.random() * canvas.height; | |
speedX = -(Math.random() * 2 + 1); | |
speedY = (Math.random() - 0.5) * 3; | |
break; | |
case 2: // bottom | |
x = Math.random() * canvas.width; | |
y = canvas.height + 40; | |
speedX = (Math.random() - 0.5) * 3; | |
speedY = -(Math.random() * 2 + 1); | |
break; | |
case 3: // left | |
x = -40; | |
y = Math.random() * canvas.height; | |
speedX = Math.random() * 2 + 1; | |
speedY = (Math.random() - 0.5) * 3; | |
break; | |
} | |
turtles.push({ | |
x, | |
y, | |
width: 40, | |
height: 30, | |
speedX, | |
speedY | |
}); | |
// Randomly spawn between 1-3 turtles | |
if (Math.random() > 0.7) { | |
setTimeout(spawnTurtle, Math.random() * 500); | |
} | |
} | |
function createPortal() { | |
const x = Math.random() * (canvas.width - 200) + 100; | |
const y = Math.random() * (canvas.height - 200) + 100; | |
portals.push({ | |
x, | |
y, | |
radius: 60 | |
}); | |
// Create portal particles | |
createParticles(x, y, 30, 'cyan'); | |
} | |
function createParticles(x, y, count, color) { | |
for (let i = 0; i < count; i++) { | |
const angle = Math.random() * Math.PI * 2; | |
const speed = Math.random() * 3 + 1; | |
particles.push({ | |
x, | |
y, | |
size: Math.random() * 3 + 1, | |
speedX: Math.cos(angle) * speed, | |
speedY: Math.sin(angle) * speed, | |
lifetime: Math.random() * 30 + 20, | |
color | |
}); | |
} | |
} | |
function gameOver() { | |
gameRunning = false; | |
clearInterval(gameLoopInterval); | |
clearInterval(turtleSpawnInterval); | |
finalScoreDisplay.textContent = `Score: ${score}`; | |
universesVisitedDisplay.textContent = `Universes Visited: ${universe - 1}`; | |
gameOverScreen.classList.remove('hidden'); | |
} | |
// Start with the start screen | |
startScreen.classList.remove('hidden'); | |
</script> | |
</body> | |
</html> | |