paperdash-express / index.html
Tagss's picture
Paper Dash
aa087a6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>PaperDash Express</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
font-family: 'Press Start 2P', cursive;
overflow: hidden;
touch-action: none;
background-color: #0f172a;
color: #f8fafc;
}
.game-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.player {
position: absolute;
transition: transform 0.1s ease-out;
z-index: 10;
}
.obstacle {
position: absolute;
z-index: 5;
}
.paper {
position: absolute;
z-index: 8;
transition: transform 0.5s linear, opacity 0.5s ease-out;
}
.house {
position: absolute;
z-index: 3;
}
.parallax-bg {
position: absolute;
width: 300%;
height: 100%;
background-repeat: repeat-x;
z-index: 1;
}
.score-popup {
position: absolute;
animation: floatUp 1s forwards;
opacity: 0;
z-index: 20;
}
@keyframes floatUp {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(-100px); opacity: 0; }
}
.neon-text {
text-shadow: 0 0 10px #3b82f6, 0 0 20px #3b82f6, 0 0 30px #3b82f6;
}
.menu-btn {
transition: all 0.2s ease;
}
.menu-btn:hover {
transform: scale(1.05);
text-shadow: 0 0 10px #3b82f6;
}
</style>
</head>
<body class="select-none">
<div id="main-menu" class="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-b from-blue-900 to-gray-900 z-50 transition-opacity duration-500">
<div class="text-center mb-12">
<h1 class="text-5xl md:text-6xl lg:text-7xl mb-4 neon-text">PAPERDASH</h1>
<p class="text-xl text-blue-300">Express Delivery Challenge</p>
</div>
<div class="w-full max-w-md space-y-4 px-4">
<button id="start-btn" class="menu-btn w-full py-4 bg-blue-600 hover:bg-blue-500 rounded-lg text-xl font-bold transition-all">
START DELIVERY
</button>
<button id="settings-btn" class="menu-btn w-full py-4 bg-gray-700 hover:bg-gray-600 rounded-lg text-lg flex items-center justify-center gap-2">
<i data-feather="settings"></i> OPTIONS
</button>
<button id="about-btn" class="menu-btn w-full py-4 bg-gray-700 hover:bg-gray-600 rounded-lg text-lg flex items-center justify-center gap-2">
<i data-feather="info"></i> ABOUT
</button>
</div>
</div>
<div id="difficulty-menu" class="fixed inset-0 flex flex-col items-center justify-center bg-gray-900 bg-opacity-90 z-40 hidden">
<div class="text-center mb-8">
<h2 class="text-3xl mb-2 neon-text">SELECT DIFFICULTY</h2>
<p class="text-blue-300">Choose your delivery challenge</p>
</div>
<div class="w-full max-w-md space-y-3 px-4">
<button data-difficulty="easy" class="difficulty-btn w-full py-3 bg-green-600 hover:bg-green-500 rounded-lg text-lg font-bold transition-all">
EASY - Suburban Route
</button>
<button data-difficulty="normal" class="difficulty-btn w-full py-3 bg-yellow-600 hover:bg-yellow-500 rounded-lg text-lg font-bold transition-all">
NORMAL - City Streets
</button>
<button data-difficulty="hard" class="difficulty-btn w-full py-3 bg-orange-600 hover:bg-orange-500 rounded-lg text-lg font-bold transition-all">
HARD - Downtown Rush
</button>
<button data-difficulty="extreme" class="difficulty-btn w-full py-3 bg-red-600 hover:bg-red-500 rounded-lg text-lg font-bold transition-all">
EXTREME - Neon Challenge
</button>
<button id="back-btn" class="menu-btn w-full py-3 bg-gray-700 hover:bg-gray-600 rounded-lg mt-4 flex items-center justify-center gap-2">
<i data-feather="arrow-left"></i> BACK
</button>
</div>
</div>
<div id="game-container" class="game-container hidden">
<div class="parallax-bg bg-gray-900" id="bg1"></div>
<div class="parallax-bg bg-gray-800" id="bg2"></div>
<div class="parallax-bg bg-gray-700" id="bg3"></div>
<div id="player" class="player">
<img src="http://static.photos/technology/200x200/1" alt="Player" class="w-16 h-16 object-contain">
</div>
<div id="hud" class="absolute top-0 left-0 right-0 p-4 flex justify-between items-center z-20">
<div class="flex items-center gap-2">
<span id="score" class="text-xl font-bold">0</span>
<span class="text-blue-400">PTS</span>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-1">
<i data-feather="clock" class="text-yellow-400"></i>
<span id="timer" class="text-xl font-bold">60</span>
</div>
<div class="flex items-center gap-1">
<i data-feather="heart" class="text-red-400"></i>
<span id="lives" class="text-xl font-bold">3</span>
</div>
<div class="flex items-center gap-1">
<i data-feather="mail" class="text-green-400"></i>
<span id="papers" class="text-xl font-bold">30</span>
</div>
</div>
</div>
<div id="controls" class="absolute bottom-4 left-0 right-0 flex justify-around items-center px-4 z-20">
<button id="left-btn" class="control-btn bg-blue-600 bg-opacity-70 rounded-full w-16 h-16 flex items-center justify-center">
<i data-feather="arrow-left" class="text-2xl"></i>
</button>
<button id="jump-btn" class="control-btn bg-green-600 bg-opacity-70 rounded-full w-16 h-16 flex items-center justify-center">
<i data-feather="arrow-up" class="text-2xl"></i>
</button>
<button id="throw-btn" class="control-btn bg-red-600 bg-opacity-70 rounded-full w-16 h-16 flex items-center justify-center">
<i data-feather="arrow-right" class="text-2xl"></i>
</button>
</div>
</div>
<div id="game-over" class="fixed inset-0 flex flex-col items-center justify-center bg-black bg-opacity-80 z-30 hidden">
<div class="text-center mb-8">
<h2 class="text-4xl mb-4 text-red-500 neon-text">DELIVERY FAILED!</h2>
<p class="text-xl mb-2">Your final score:</p>
<p id="final-score" class="text-5xl font-bold text-blue-400">0</p>
</div>
<div class="w-full max-w-md space-y-4 px-4">
<button id="retry-btn" class="menu-btn w-full py-4 bg-blue-600 hover:bg-blue-500 rounded-lg text-xl font-bold">
RETRY DELIVERY
</button>
<button id="menu-btn" class="menu-btn w-full py-4 bg-gray-700 hover:bg-gray-600 rounded-lg text-lg flex items-center justify-center gap-2">
<i data-feather="home"></i> MAIN MENU
</button>
</div>
</div>
<div id="level-complete" class="fixed inset-0 flex flex-col items-center justify-center bg-black bg-opacity-80 z-30 hidden">
<div class="text-center mb-8">
<h2 class="text-4xl mb-4 text-green-500 neon-text">DELIVERY COMPLETE!</h2>
<p class="text-xl mb-2">You scored:</p>
<p id="level-score" class="text-5xl font-bold text-blue-400">0</p>
<p id="bonus-text" class="text-yellow-400 mt-2">+500 Time Bonus!</p>
</div>
<div class="w-full max-w-md space-y-4 px-4">
<button id="next-level-btn" class="menu-btn w-full py-4 bg-green-600 hover:bg-green-500 rounded-lg text-xl font-bold">
NEXT DELIVERY
</button>
<button id="level-menu-btn" class="menu-btn w-full py-4 bg-gray-700 hover:bg-gray-600 rounded-lg text-lg flex items-center justify-center gap-2">
<i data-feather="home"></i> MAIN MENU
</button>
</div>
</div>
<script>
feather.replace();
// Game state
const gameState = {
score: 0,
lives: 3,
papers: 30,
time: 60,
difficulty: 'normal',
level: 1,
isGameOver: false,
isPaused: false,
player: {
x: 0,
y: 0,
lane: 1, // 0: left, 1: middle, 2: right
isJumping: false
},
obstacles: [],
houses: [],
papersThrown: [],
combo: 0,
bgPos: 0
};
// DOM elements
const elements = {
mainMenu: document.getElementById('main-menu'),
difficultyMenu: document.getElementById('difficulty-menu'),
gameContainer: document.getElementById('game-container'),
gameOver: document.getElementById('game-over'),
levelComplete: document.getElementById('level-complete'),
player: document.getElementById('player'),
score: document.getElementById('score'),
timer: document.getElementById('timer'),
lives: document.getElementById('lives'),
papers: document.getElementById('papers'),
finalScore: document.getElementById('final-score'),
levelScore: document.getElementById('level-score'),
bonusText: document.getElementById('bonus-text'),
bg1: document.getElementById('bg1'),
bg2: document.getElementById('bg2'),
bg3: document.getElementById('bg3'),
startBtn: document.getElementById('start-btn'),
settingsBtn: document.getElementById('settings-btn'),
aboutBtn: document.getElementById('about-btn'),
backBtn: document.getElementById('back-btn'),
retryBtn: document.getElementById('retry-btn'),
menuBtn: document.getElementById('menu-btn'),
nextLevelBtn: document.getElementById('next-level-btn'),
levelMenuBtn: document.getElementById('level-menu-btn'),
leftBtn: document.getElementById('left-btn'),
jumpBtn: document.getElementById('jump-btn'),
throwBtn: document.getElementById('throw-btn')
};
// Button event listeners
elements.startBtn.addEventListener('click', () => {
elements.mainMenu.classList.add('hidden');
elements.difficultyMenu.classList.remove('hidden');
});
elements.backBtn.addEventListener('click', () => {
elements.difficultyMenu.classList.add('hidden');
elements.mainMenu.classList.remove('hidden');
});
document.querySelectorAll('.difficulty-btn').forEach(btn => {
btn.addEventListener('click', function() {
gameState.difficulty = this.dataset.difficulty;
startGame();
});
});
elements.retryBtn.addEventListener('click', () => {
elements.gameOver.classList.add('hidden');
startGame();
});
elements.menuBtn.addEventListener('click', () => {
elements.gameOver.classList.add('hidden');
elements.mainMenu.classList.remove('hidden');
});
elements.nextLevelBtn.addEventListener('click', () => {
gameState.level++;
elements.levelComplete.classList.add('hidden');
startGame();
});
elements.levelMenuBtn.addEventListener('click', () => {
elements.levelComplete.classList.add('hidden');
elements.mainMenu.classList.remove('hidden');
});
// Control event listeners
elements.leftBtn.addEventListener('click', () => movePlayer(-1));
elements.jumpBtn.addEventListener('click', jump);
elements.throwBtn.addEventListener('click', throwPaper);
// Touch events for swipe controls
let touchStartX = 0;
let touchStartY = 0;
document.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
touchStartY = e.changedTouches[0].screenY;
});
document.addEventListener('touchend', (e) => {
const touchEndX = e.changedTouches[0].screenX;
const touchEndY = e.changedTouches[0].screenY;
const diffX = touchEndX - touchStartX;
const diffY = touchEndY - touchStartY;
// Horizontal swipe
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 50) { // Right swipe
throwPaper();
} else if (diffX < -50) { // Left swipe
movePlayer(-1);
}
} else { // Vertical swipe
if (diffY < -50) { // Up swipe
jump();
}
}
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (!elements.gameContainer.classList.contains('hidden')) {
switch(e.key) {
case 'ArrowLeft':
movePlayer(-1);
break;
case 'ArrowRight':
movePlayer(1);
break;
case 'ArrowUp':
jump();
break;
case ' ':
case 'ArrowDown':
throwPaper();
break;
}
}
});
// Device tilt controls
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', (e) => {
if (elements.gameContainer.classList.contains('hidden') || gameState.isPaused) return;
const gamma = e.gamma; // Left/right tilt
if (gamma > 15 && gameState.player.lane < 2) {
movePlayer(1);
} else if (gamma < -15 && gameState.player.lane > 0) {
movePlayer(-1);
}
});
}
// Game functions
function startGame() {
// Reset game state
gameState.score = 0;
gameState.lives = 3;
gameState.papers = 30;
gameState.time = getTimeForDifficulty();
gameState.isGameOver = false;
gameState.isPaused = false;
gameState.player = {
x: 0,
y: 0,
lane: 1,
isJumping: false
};
gameState.obstacles = [];
gameState.houses = [];
gameState.papersThrown = [];
gameState.combo = 0;
gameState.bgPos = 0;
// Update UI
elements.score.textContent = gameState.score;
elements.lives.textContent = gameState.lives;
elements.papers.textContent = gameState.papers;
elements.timer.textContent = gameState.time;
// Show game container
elements.mainMenu.classList.add('hidden');
elements.difficultyMenu.classList.add('hidden');
elements.gameContainer.classList.remove('hidden');
// Set up game area
setupGameArea();
// Start game loop
gameLoop();
// Start timer
startTimer();
}
function getTimeForDifficulty() {
switch(gameState.difficulty) {
case 'easy': return 90;
case 'normal': return 60;
case 'hard': return 45;
case 'extreme': return 30;
default: return 60;
}
}
function setupGameArea() {
const containerWidth = elements.gameContainer.clientWidth;
const containerHeight = elements.gameContainer.clientHeight;
// Set up lanes
const laneWidth = containerWidth / 3;
gameState.player.x = laneWidth * 1.5;
gameState.player.y = containerHeight * 0.7;
// Position player
elements.player.style.left = `${gameState.player.x - 32}px`;
elements.player.style.top = `${gameState.player.y - 32}px`;
// Generate initial houses
generateHouses(containerWidth, containerHeight);
}
function generateHouses(width, height) {
// Clear existing houses
gameState.houses = [];
document.querySelectorAll('.house').forEach(el => el.remove());
// Generate new houses based on difficulty
const houseCount = getHouseCountForDifficulty();
for (let i = 0; i < houseCount; i++) {
const house = {
id: i,
x: width + (i * getHouseSpacingForDifficulty()),
lane: Math.floor(Math.random() * 3),
delivered: false
};
gameState.houses.push(house);
const houseEl = document.createElement('div');
houseEl.className = 'house';
houseEl.dataset.id = i;
houseEl.style.width = '64px';
houseEl.style.height = '64px';
houseEl.style.left = `${house.x}px`;
houseEl.style.top = `${height * 0.6}px`;
// Different house styles based on lane
const colors = ['#ef4444', '#3b82f6', '#10b981'];
houseEl.style.backgroundColor = colors[house.lane];
// Mailbox indicator
const mailbox = document.createElement('div');
mailbox.style.width = '16px';
mailbox.style.height = '16px';
mailbox.style.backgroundColor = '#f59e0b';
mailbox.style.position = 'absolute';
mailbox.style.bottom = '-8px';
mailbox.style.left = '24px';
mailbox.style.borderRadius = '4px';
houseEl.appendChild(mailbox);
elements.gameContainer.appendChild(houseEl);
}
}
function getHouseCountForDifficulty() {
switch(gameState.difficulty) {
case 'easy': return 15;
case 'normal': return 20;
case 'hard': return 25;
case 'extreme': return 30;
default: return 20;
}
}
function getHouseSpacingForDifficulty() {
switch(gameState.difficulty) {
case 'easy': return 300;
case 'normal': return 250;
case 'hard': return 200;
case 'extreme': return 150;
default: return 250;
}
}
function movePlayer(direction) {
if (gameState.isPaused) return;
const containerWidth = elements.gameContainer.clientWidth;
const laneWidth = containerWidth / 3;
gameState.player.lane = Math.max(0, Math.min(2, gameState.player.lane + direction));
gameState.player.x = laneWidth * (gameState.player.lane + 0.5);
elements.player.style.left = `${gameState.player.x - 32}px`;
}
function jump() {
if (gameState.isPaused || gameState.player.isJumping) return;
gameState.player.isJumping = true;
const jumpHeight = 50;
const startY = gameState.player.y;
// Jump up
const jumpUp = setInterval(() => {
gameState.player.y -= 2;
elements.player.style.top = `${gameState.player.y - 32}px`;
if (gameState.player.y <= startY - jumpHeight) {
clearInterval(jumpUp);
// Fall down
const fallDown = setInterval(() => {
gameState.player.y += 2;
elements.player.style.top = `${gameState.player.y - 32}px`;
if (gameState.player.y >= startY) {
gameState.player.y = startY;
elements.player.style.top = `${gameState.player.y - 32}px`;
gameState.player.isJumping = false;
clearInterval(fallDown);
}
}, 16);
}
}, 16);
}
function throwPaper() {
if (gameState.isPaused || gameState.papers <= 0) return;
gameState.papers--;
elements.papers.textContent = gameState.papers;
const paperEl = document.createElement('div');
paperEl.className = 'paper';
paperEl.style.width = '24px';
paperEl.style.height = '24px';
paperEl.style.left = `${gameState.player.x - 12}px`;
paperEl.style.top = `${gameState.player.y - 12}px`;
paperEl.style.backgroundColor = '#ffffff';
paperEl.style.borderRadius = '2px';
elements.gameContainer.appendChild(paperEl);
const paper = {
id: Date.now(),
x: gameState.player.x,
y: gameState.player.y,
element: paperEl
};
gameState.papersThrown.push(paper);
// Animate paper throw
const throwDistance = 200;
let distance = 0;
const throwInterval = setInterval(() => {
distance += 8;
paper.x += 8;
paperEl.style.left = `${paper.x - 12}px`;
// Parabolic trajectory
const height = Math.sin(distance / throwDistance * Math.PI) * 50;
paperEl.style.top = `${gameState.player.y - 12 - height}px`;
// Check for collisions with houses
checkPaperCollision(paper);
if (distance >= throwDistance) {
clearInterval(throwInterval);
paperEl.remove();
gameState.papersThrown = gameState.papersThrown.filter(p => p.id !== paper.id);
}
}, 16);
// Play sound
playSound('throw');
}
function checkPaperCollision(paper) {
const paperRect = {
x: paper.x - 12,
y: paper.y - 12,
width: 24,
height: 24
};
for (const house of gameState.houses) {
if (house.delivered) continue;
const houseEl = document.querySelector(`.house[data-id="${house.id}"]`);
if (!houseEl) continue;
const houseRect = {
x: parseInt(houseEl.style.left),
y: parseInt(houseEl.style.top),
width: 64,
height: 64
};
// Simple collision detection
if (paperRect.x < houseRect.x + houseRect.width &&
paperRect.x + paperRect.width > houseRect.x &&
paperRect.y < houseRect.y + houseRect.height &&
paperRect.y + paperRect.height > houseRect.y) {
// Check if it's the correct lane
if (gameState.player.lane === house.lane) {
// Successful delivery
house.delivered = true;
houseEl.style.opacity = '0.5';
// Increment combo
gameState.combo++;
// Calculate points
const basePoints = 100;
const comboBonus = Math.min(5, gameState.combo) * 20;
const points = basePoints + comboBonus;
gameState.score += points;
elements.score.textContent = gameState.score;
// Show score popup
showScorePopup(points, houseRect.x + 32, houseRect.y);
// Play sound
playSound('ding');
// Check if all houses are delivered
if (gameState.houses.every(h => h.delivered)) {
levelComplete();
}
} else {
// Wrong lane - penalty
gameState.combo = 0;
gameState.score -= 50;
elements.score.textContent = Math.max(0, gameState.score);
// Show penalty popup
showScorePopup(-50, houseRect.x + 32, houseRect.y);
// Play sound
playSound('error');
}
// Remove paper
paper.element.remove();
gameState.papersThrown = gameState.papersThrown.filter(p => p.id !== paper.id);
break;
}
}
}
function showScorePopup(points, x, y) {
const popup = document.createElement('div');
popup.className = 'score-popup';
popup.textContent = points > 0 ? `+${points}` : points;
popup.style.left = `${x - 20}px`;
popup.style.top = `${y - 20}px`;
popup.style.color = points > 0 ? '#10b981' : '#ef4444';
popup.style.fontSize = '24px';
popup.style.fontWeight = 'bold';
elements.gameContainer.appendChild(popup);
// Remove after animation
setTimeout(() => {
popup.remove();
}, 1000);
}
function generateObstacle() {
const containerWidth = elements.gameContainer.clientWidth;
const containerHeight = elements.gameContainer.clientHeight;
const obstacle = {
id: Date.now(),
x: containerWidth,
lane: Math.floor(Math.random() * 3),
type: Math.random() > 0.5 ? 'cone' : 'dog'
};
gameState.obstacles.push(obstacle);
const obstacleEl = document.createElement('div');
obstacleEl.className = 'obstacle';
obstacleEl.dataset.id = obstacle.id;
obstacleEl.style.width = '32px';
obstacleEl.style.height = '32px';
obstacleEl.style.left = `${obstacle.x}px`;
obstacleEl.style.top = `${containerHeight * 0.7}px`;
if (obstacle.type === 'cone') {
obstacleEl.style.backgroundColor = '#f59e0b';
obstacleEl.style.borderRadius = '4px';
} else {
obstacleEl.style.backgroundColor = '#ef4444';
obstacleEl.style.borderRadius = '50%';
}
elements.gameContainer.appendChild(obstacleEl);
}
function checkPlayerCollision() {
const playerRect = {
x: gameState.player.x - 32,
y: gameState.player.y - 32,
width: 64,
height: 64
};
for (const obstacle of gameState.obstacles) {
const obstacleEl = document.querySelector(`.obstacle[data-id="${obstacle.id}"]`);
if (!obstacleEl) continue;
const obstacleRect = {
x: parseInt(obstacleEl.style.left),
y: parseInt(obstacleEl.style.top),
width: 32,
height: 32
};
// Simple collision detection
if (playerRect.x < obstacleRect.x + obstacleRect.width &&
playerRect.x + playerRect.width > obstacleRect.x &&
playerRect.y < obstacleRect.y + obstacleRect.height &&
playerRect.y + playerRect.height > obstacleRect.y) {
// Handle collision
gameState.lives--;
elements.lives.textContent = gameState.lives;
// Show hit effect
elements.player.style.filter = 'brightness(2)';
setTimeout(() => {
elements.player.style.filter = 'none';
}, 200);
// Remove obstacle
obstacleEl.remove();
gameState.obstacles = gameState.obstacles.filter(o => o.id !== obstacle.id);
// Reset combo
gameState.combo = 0;
// Play sound
playSound('crash');
// Check if game over
if (gameState.lives <= 0) {
gameOver();
}
break;
}
}
}
function startTimer() {
gameState.timerInterval = setInterval(() => {
gameState.time--;
elements.timer.textContent = gameState.time;
if (gameState.time <= 10) {
elements.timer.style.color = '#ef4444';
}
if (gameState.time <= 0) {
gameOver();
}
}, 1000);
}
function gameOver() {
clearInterval(gameState.timerInterval);
gameState.isGameOver = true;
gameState.isPaused = true;
// Show game over screen
elements.finalScore.textContent = gameState.score;
elements.gameOver.classList.remove('hidden');
// Play sound
playSound('gameover');
}
function levelComplete() {
clearInterval(gameState.timerInterval);
gameState.isPaused = true;
// Calculate time bonus
const timeBonus = gameState.time * 10;
gameState.score += timeBonus;
// Show level complete screen
elements.levelScore.textContent = gameState.score;
elements.bonusText.textContent = `+${timeBonus} Time Bonus!`;
elements.levelComplete.classList.remove('hidden');
// Play sound
playSound('win');
}
function playSound(type) {
// In a real implementation, you would use Howler.js or similar
// This is a placeholder for sound effects
console.log(`Play sound: ${type}`);
}
function gameLoop() {
if (gameState.isPaused) return;
const containerWidth = elements.gameContainer.clientWidth;
const containerHeight = elements.gameContainer.clientHeight;
// Move background (parallax effect)
gameState.bgPos -= getScrollSpeedForDifficulty();
elements.bg1.style.backgroundPositionX = `${gameState.bgPos * 0.2}px`;
elements.bg2.style.backgroundPositionX = `${gameState.bgPos * 0.5}px`;
elements.bg3.style.backgroundPositionX = `${gameState.bgPos}px`;
// Move houses
for (const house of gameState.houses) {
const houseEl = document.querySelector(`.house[data-id="${house.id}"]`);
if (houseEl) {
house.x -= getScrollSpeedForDifficulty();
houseEl.style.left = `${house.x}px`;
// Remove if off screen
if (house.x < -64) {
houseEl.remove();
gameState.houses = gameState.houses.filter(h => h.id !== house.id);
}
}
}
// Move obstacles
for (const obstacle of gameState.obstacles) {
const obstacleEl = document.querySelector(`.obstacle[data-id="${obstacle.id}"]`);
if (obstacleEl) {
obstacle.x -= getScrollSpeedForDifficulty() * 1.2;
obstacleEl.style.left = `${obstacle.x}px`;
// Remove if off screen
if (obstacle.x < -32) {
obstacleEl.remove();
gameState.obstacles = gameState.obstacles.filter(o => o.id !== obstacle.id);
}
}
}
// Check for collisions
checkPlayerCollision();
// Randomly generate obstacles
if (Math.random() < getObstacleFrequencyForDifficulty()) {
generateObstacle();
}
// Continue game loop
requestAnimationFrame(gameLoop);
}
function getScrollSpeedForDifficulty() {
switch(gameState.difficulty) {
case 'easy': return 2;
case 'normal': return 3;
case 'hard': return 4;
case 'extreme': return 5;
default: return 3;
}
}
function getObstacleFrequencyForDifficulty() {
switch(gameState.difficulty) {
case 'easy': return 0.005;
case 'normal': return 0.01;
case 'hard': return 0.015;
case 'extreme': return 0.02;
default: return 0.01;
}
}
</script>
</body>
</html>