A fun game created with AI
Created by FunDesigner0263 • November 23, 2025
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Alpine Racer - Ski Slalom</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(to bottom, #1a2a4a 0%, #2d4a6f 100%);
font-family: 'Arial', sans-serif;
overflow: hidden;
touch-action: manipulation;
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
#gameContainer {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100vh;
max-width: 800px;
}
canvas {
border: 3px solid #fff;
background: #e8f4f8;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
touch-action: none;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
color: white;
font-size: clamp(14px, 3vw, 18px);
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
pointer-events: none;
z-index: 10;
}
.stat {
background: rgba(0,0,0,0.6);
padding: 8px 12px;
border-radius: 8px;
margin-bottom: 5px;
display: inline-block;
margin-right: 10px;
}
.stat.highlight {
background: rgba(255,215,0,0.8);
animation: pulse 0.5s ease-in-out;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: clamp(12px, 2.5vw, 14px);
text-align: center;
background: rgba(0,0,0,0.6);
padding: 10px 20px;
border-radius: 10px;
pointer-events: none;
}
#gameOver, #levelSelect {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9);
padding: 30px;
border-radius: 15px;
text-align: center;
color: white;
display: none;
z-index: 100;
max-width: 90%;
}
#levelSelect {
display: block;
}
#gameOver h2, #levelSelect h2 {
font-size: clamp(24px, 5vw, 36px);
margin-bottom: 20px;
color: #4af;
}
#gameOver p, #levelSelect p {
font-size: clamp(16px, 3vw, 20px);
margin: 10px 0;
}
.btn {
margin: 10px 5px;
padding: 15px 30px;
font-size: clamp(16px, 3vw, 20px);
background: #4af;
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
font-weight: bold;
touch-action: manipulation;
display: inline-block;
}
.btn:active {
background: #39e;
transform: scale(0.95);
}
.btn.easy { background: #4af; }
.btn.medium { background: #fa4; }
.btn.hard { background: #f44; }
.level-info {
font-size: clamp(12px, 2.5vw, 14px);
color: #aaa;
margin-top: 5px;
}
#jumpIndicator {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: clamp(16px, 3vw, 24px);
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
background: rgba(0,0,0,0.6);
padding: 10px 20px;
border-radius: 10px;
pointer-events: none;
display: none;
z-index: 10;
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="ui">
<div class="stat">⏱️ Time: <span id="time">0.0</span>s</div>
<div class="stat">🎯 Gates: <span id="gates">0</span>/<span id="totalGates">20</span></div>
<div class="stat">💨 Speed: <span id="speed">0</span></div>
<div class="stat">❤️ Lives: <span id="lives">3</span></div>
<div class="stat" id="coinStat">🪙 Coins: <span id="coins">0</span></div>
<div class="stat" id="gemStat">💎 Gems: <span id="gems">0</span></div>
<div class="stat">📊 Level: <span id="currentLevel">1</span></div>
</div>
<canvas id="game"></canvas>
<div id="controls">🎮 Arrow Keys/WASD: Steer & Brake | SPACE: Jump (2x) | 📱 Swipe to turn, Tap to jump</div>
<div id="jumpIndicator">🚀 Double Jump Available!</div>
<div id="levelSelect">
<h2>🎿 Select Your Level</h2>
<p>Choose your difficulty and start racing!</p>
<button class="btn easy" onclick="startLevel(1)">⛷️ Easy Slope</button>
<div class="level-info">Slower speed • More gates spacing • 3 lives</div>
<button class="btn medium" onclick="startLevel(2)">🏔️ Medium Mountain</button>
<div class="level-info">Normal speed • Standard spacing • 3 lives</div>
<button class="btn hard" onclick="startLevel(3)">🔥 Expert Peak</button>
<div class="level-info">Fast speed • Tight spacing • 2 lives</div>
</div>
<div id="gameOver">
<h2 id="resultTitle">Race Complete!</h2>
<p id="finalTime"></p>
<p id="finalGates"></p>
<p id="finalCoins"></p>
<p id="finalGems"></p>
<p id="finalScore"></p>
<button class="btn" id="restartBtn">Race Again</button>
<button class="btn" id="levelSelectBtn">Change Level</button>
</div>
</div>
<script>
window.addEventListener('load', function() {
try {
const canvas = document.getElementById('game');
if (!canvas) { alert('Canvas not found'); return; }
const ctx = canvas.getContext('2d');
if (!ctx) { alert('Canvas context failed'); return; }
// Responsive canvas setup
function resizeCanvas() {
const container = document.getElementById('gameContainer');
if (!container) return;
const maxWidth = Math.min(800, window.innerWidth - 20);
const maxHeight = window.innerHeight - 100;
const size = Math.min(maxWidth, maxHeight * 0.7);
canvas.style.width = size + 'px';
canvas.style.height = (size * 1.2) + 'px';
canvas.width = 800;
canvas.height = 960;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
window.addEventListener('orientationchange', () => setTimeout(resizeCanvas, 100));
// Game constants
const WIDTH = 800;
const HEIGHT = 960;
// Level configurations
const LEVELS = {
1: { // Easy
name: 'Easy Slope',
baseSpeed: 2,
maxSpeed: 8,
gateSpacing: 400,
totalGates: 15,
lives: 3,
obstacleCount: 10,
coinFrequency: 0.8,
gemFrequency: 0.3
},
2: { // Medium
name: 'Medium Mountain',
baseSpeed: 3.5,
maxSpeed: 12,
gateSpacing: 300,
totalGates: 20,
lives: 3,
obstacleCount: 15,
coinFrequency: 0.6,
gemFrequency: 0.2
},
3: { // Hard
name: 'Expert Peak',
baseSpeed: 5,
maxSpeed: 15,
gateSpacing: 250,
totalGates: 25,
lives: 2,
obstacleCount: 20,
coinFrequency: 0.4,
gemFrequency: 0.15
}
};
// Game state
let gameRunning = false;
let gameTime = 0;
let lastFrameTime = Date.now();
let currentLevelNum = 1;
let currentLevelConfig = LEVELS[1];
// Player
const player = {
x: WIDTH / 2,
y: HEIGHT - 150,
width: 30,
height: 40,
speed: 0,
maxSpeed: 12,
acceleration: 0.2,
friction: 0.98,
turnSpeed: 0,
maxTurnSpeed: 8,
angle: 0,
lives: 3,
invincible: 0,
carving: false,
jumping: false,
jumpHeight: 0,
jumpVelocity: 0,
jumpsRemaining: 2,
maxJumps: 2,
coins: 0,
gems: 0
};
// Course
let scrollSpeed = 3.5;
let gatesCleared = 0;
let totalGates = 20;
let courseOffset = 0;
// Game objects
const gates = [];
const obstacles = [];
const trees = [];
const collectibles = [];
const snowParticles = [];
const sprayParticles = [];
const collectParticles = [];
// Keyboard
const keys = {};
window.addEventListener('keydown', (e) => {
if (!keys[e.key]) {
keys[e.key] = true;
if (e.key === ' ' || e.key === 'Spacebar') {
handleJump();
}
}
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key)) {
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Touch controls
let touchStartX = 0;
let touchStartY = 0;
let touchActive = false;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
touchActive = true;
handleJump();
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (touchActive) {
const deltaX = e.touches[0].clientX - touchStartX;
if (Math.abs(deltaX) > 10) {
if (deltaX > 0) {
keys['ArrowRight'] = true;
keys['ArrowLeft'] = false;
} else {
keys['ArrowLeft'] = true;
keys['ArrowRight'] = false;
}
}
}
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
touchActive = false;
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
}, { passive: false });
// Jump handler
function handleJump() {
if (!gameRunning) return;
if (player.jumpsRemaining > 0) {
player.jumping = true;
player.jumpVelocity = -12;
player.jumpsRemaining--;
// Show double jump indicator
if (player.jumpsRemaining === 1) {
const indicator = document.getElementById('jumpIndicator');
if (indicator) {
indicator.style.display = 'block';
setTimeout(() => {
indicator.style.display = 'none';
}, 1000);
}
}
}
}
// Initialize course
function initCourse() {
gates.length = 0;
obstacles.length = 0;
trees.length = 0;
collectibles.length = 0;
snowParticles.length = 0;
sprayParticles.length = 0;
collectParticles.length = 0;
const config = currentLevelConfig;
totalGates = config.totalGates;
// Create gates
for (let i = 0; i < totalGates; i++) {
const y = -200 - (i * config.gateSpacing);
const side = i % 2 === 0 ? -1 : 1;
const offset = side * (Math.random() * 100 + 50);
gates.push({
x: WIDTH / 2 + offset,
y: y,
width: 150,
cleared: false,
side: side,
type: Math.random() > 0.8 ? 'jump' : 'normal'
});
// Add collectibles near gates
if (Math.random() < config.coinFrequency) {
const coinX = WIDTH / 2 + offset + (Math.random() - 0.5) * 100;
collectibles.push({
x: coinX,
y: y - 100,
type: 'coin',
collected: false,
rotation: 0
});
}
if (Math.random() < config.gemFrequency) {
const gemX = WIDTH / 2 + offset + (Math.random() - 0.5) * 120;
collectibles.push({
x: gemX,
y: y - 150,
type: 'gem',
collected: false,
rotation: 0,
pulse: 0
});
}
}
// Add bonus collectibles between gates
for (let i = 0; i < totalGates * 2; i++) {
if (Math.random() < 0.3) {
collectibles.push({
x: 150 + Math.random() * (WIDTH - 300),
y: -Math.random() * totalGates * config.gateSpacing,
type: 'coin',
collected: false,
rotation: 0
});
}
}
// Create trees
for (let i = 0; i < 50; i++) {
const side = Math.random() > 0.5 ? 1 : -1;
trees.push({
x: side > 0 ? WIDTH - 80 + Math.random() * 60 : 20 + Math.random() * 60,
y: -Math.random() * totalGates * config.gateSpacing,
size: 30 + Math.random() * 20
});
}
// Create obstacles
for (let i = 0; i < config.obstacleCount; i++) {
obstacles.push({
x: 150 + Math.random() * (WIDTH - 300),
y: -Math.random() * totalGates * config.gateSpacing * 0.8,
width: 40,
height: 40,
type: 'rock'
});
}
// Initialize snow particles
for (let i = 0; i < 100; i++) {
snowParticles.push({
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
size: Math.random() * 3 + 1,
speed: Math.random() * 2 + 1
});
}
}
// Draw functions
function drawSnow() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
snowParticles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
function drawTrees() {
trees.forEach(tree => {
const screenY = tree.y + courseOffset;
if (screenY > -100 && screenY < HEIGHT + 100) {
ctx.fillStyle = '#4a3520';
ctx.fillRect(tree.x - 8, screenY, 16, tree.size);
ctx.fillStyle = '#1a5c3a';
ctx.beginPath();
ctx.moveTo(tree.x, screenY - tree.size * 0.5);
ctx.lineTo(tree.x - tree.size * 0.6, screenY + tree.size * 0.3);
ctx.lineTo(tree.x + tree.size * 0.6, screenY + tree.size * 0.3);
ctx.closePath();
ctx.fill();
}
});
}
function drawGate(gate) {
const screenY = gate.y + courseOffset;
if (screenY > -100 && screenY < HEIGHT + 100) {
const leftX = gate.x - gate.width / 2;
const rightX = gate.x + gate.width / 2;
const poleColor = gate.cleared ? '#4a4a4a' : (gate.side > 0 ? '#ff3333' : '#3333ff');
ctx.fillStyle = poleColor;
ctx.fillRect(leftX - 10, screenY - 60, 20, 80);
ctx.fillStyle = gate.cleared ? '#666' : '#ffff00';
ctx.fillRect(leftX - 15, screenY - 65, 30, 15);
ctx.fillStyle = poleColor;
ctx.fillRect(rightX - 10, screenY - 60, 20, 80);
ctx.fillStyle = gate.cleared ? '#666' : '#ffff00';
ctx.fillRect(rightX - 15, screenY - 65, 30, 15);
if (gate.type === 'jump' && !gate.cleared) {
ctx.fillStyle = '#ffa500';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.fillText('🚀', gate.x, screenY - 80);
}
}
}
function drawObstacle(obs) {
const screenY = obs.y + courseOffset;
if (screenY > -100 && screenY < HEIGHT + 100) {
ctx.fillStyle = '#666';
ctx.beginPath();
ctx.arc(obs.x, screenY, obs.width / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#888';
ctx.beginPath();
ctx.arc(obs.x - 5, screenY - 5, obs.width / 3, 0, Math.PI * 2);
ctx.fill();
}
}
function drawCollectibles() {
collectibles.forEach(item => {
if (item.collected) return;
const screenY = item.y + courseOffset;
if (screenY > -100 && screenY < HEIGHT + 100) {
ctx.save();
ctx.translate(item.x, screenY);
ctx.rotate(item.rotation);
if (item.type === 'coin') {
// Gold coin
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.arc(0, 0, 15, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffed4e';
ctx.beginPath();
ctx.arc(-3, -3, 8, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#b8860b';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, 15, 0, Math.PI * 2);
ctx.stroke();
} else if (item.type === 'gem') {
// Diamond gem
const size = 12 + Math.sin(item.pulse) * 2;
ctx.fillStyle = '#00ffff';
ctx.beginPath();
ctx.moveTo(0, -size);
ctx.lineTo(size * 0.7, 0);
ctx.lineTo(0, size);
ctx.lineTo(-size * 0.7, 0);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(0, -size * 0.5);
ctx.lineTo(size * 0.3, 0);
ctx.lineTo(0, size * 0.5);
ctx.lineTo(-size * 0.3, 0);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
});
}
function drawPlayer() {
ctx.save();
const drawY = player.y - player.jumpHeight;
ctx.translate(player.x, drawY);
ctx.rotate(player.angle);
if (player.invincible > 0 && Math.floor(Date.now() / 100) % 2 === 0) {
ctx.globalAlpha = 0.5;
}
// Shadow when jumping
if (player.jumping) {
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.beginPath();
ctx.ellipse(0, player.jumpHeight + 30, 20, 8, 0, 0, Math.PI * 2);
ctx.fill();
}
ctx.fillStyle = '#ff6600';
ctx.fillRect(-18, 10, 12, 30);
ctx.fillRect(6, 10, 12, 30);
ctx.fillStyle = '#0066cc';
ctx.fillRect(-12, -10, 24, 25);
ctx.fillStyle = '#ffcc99';
ctx.beginPath();
ctx.arc(0, -20, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#cc0000';
ctx.beginPath();
ctx.arc(0, -23, 11, Math.PI, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-15, 0);
ctx.lineTo(-20, 20);
ctx.moveTo(15, 0);
ctx.lineTo(20, 20);
ctx.stroke();
ctx.restore();
if (player.carving && Math.abs(player.turnSpeed) > 3 && !player.jumping) {
for (let i = 0; i < 3; i++) {
sprayParticles.push({
x: player.x + (Math.random() - 0.5) * 40,
y: player.y + 20,
vx: (Math.random() - 0.5) * 4 - player.turnSpeed * 0.5,
vy: Math.random() * 2 + 2,
life: 30,
size: Math.random() * 4 + 2
});
}
}
}
function drawSpray() {
sprayParticles.forEach(p => {
ctx.fillStyle = `rgba(255, 255, 255, ${p.life / 30})`;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
function drawCollectParticles() {
collectParticles.forEach(p => {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 30;
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'center';
ctx.fillText(p.text, p.x, p.y);
ctx.globalAlpha = 1;
});
}
// Update functions
function updatePlayer() {
if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
player.turnSpeed = Math.max(player.turnSpeed - 0.5, -player.maxTurnSpeed);
player.carving = true;
} else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
player.turnSpeed = Math.min(player.turnSpeed + 0.5, player.maxTurnSpeed);
player.carving = true;
} else {
player.turnSpeed *= 0.9;
player.carving = false;
}
if (keys['ArrowDown'] || keys['s'] || keys['S']) {
player.speed *= 0.95;
} else {
player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed);
}
player.x += player.turnSpeed;
player.angle = player.turnSpeed * 0.05;
player.x = Math.max(50, Math.min(WIDTH - 50, player.x));
// Jump physics
if (player.jumping) {
player.jumpVelocity += 0.6;
player.jumpHeight += player.jumpVelocity;
if (player.jumpHeight >= 0) {
player.jumpHeight = 0;
player.jumpVelocity = 0;
player.jumping = false;
player.jumpsRemaining = player.maxJumps;
}
}
if (player.invincible > 0) player.invincible--;
}
function updateCourse() {
courseOffset += scrollSpeed + player.speed * 0.3;
snowParticles.forEach(p => {
p.y += p.speed + scrollSpeed * 0.5;
if (p.y > HEIGHT) {
p.y = -10;
p.x = Math.random() * WIDTH;
}
});
for (let i = sprayParticles.length - 1; i >= 0; i--) {
const p = sprayParticles[i];
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) sprayParticles.splice(i, 1);
}
for (let i = collectParticles.length - 1; i >= 0; i--) {
const p = collectParticles[i];
p.y -= 2;
p.life--;
if (p.life <= 0) collectParticles.splice(i, 1);
}
collectibles.forEach(item => {
item.rotation += 0.05;
if (item.type === 'gem') {
item.pulse += 0.1;
}
});
}
function checkCollisions() {
// Check gates
gates.forEach(gate => {
if (!gate.cleared) {
const screenY = gate.y + courseOffset;
if (screenY > player.y - 50 && screenY < player.y + 50) {
const leftX = gate.x - gate.width / 2;
const rightX = gate.x + gate.width / 2;
if (player.x > leftX && player.x < rightX) {
gate.cleared = true;
gatesCleared++;
if (gate.type === 'jump') {
player.speed = Math.min(player.speed + 3, player.maxSpeed + 5);
}
} else if (screenY > player.y) {
if (player.invincible === 0 && !player.jumping) {
player.lives--;
player.invincible = 60;
if (player.lives <= 0) {
endGame(false);
}
}
}
}
}
});
// Check obstacles (can jump over them)
if (player.invincible === 0 && !player.jumping) {
obstacles.forEach(obs => {
const screenY = obs.y + courseOffset;
if (screenY > player.y - 30 && screenY < player.y + 30) {
const dx = player.x - obs.x;
const dy = player.y - screenY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 35) {
player.lives--;
player.invincible = 60;
player.speed *= 0.5;
if (player.lives <= 0) {
endGame(false);
}
}
}
});
}
// Check collectibles
collectibles.forEach(item => {
if (!item.collected) {
const screenY = item.y + courseOffset;
const dx = player.x - item.x;
const dy = (player.y - player.jumpHeight) - screenY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 30) {
item.collected = true;
if (item.type === 'coin') {
player.coins++;
highlightStat('coinStat');
collectParticles.push({
x: item.x,
y: screenY,
text: '+10',
color: '#ffd700',
life: 30
});
} else if (item.type === 'gem') {
player.gems++;
highlightStat('gemStat');
collectParticles.push({
x: item.x,
y: screenY,
text: '+50',
color: '#00ffff',
life: 30
});
}
}
}
});
if (gatesCleared >= totalGates) {
endGame(true);
}
}
function highlightStat(statId) {
const stat = document.getElementById(statId);
if (stat) {
stat.classList.add('highlight');
setTimeout(() => stat.classList.remove('highlight'), 500);
}
}
function endGame(completed) {
gameRunning = false;
const gameOverDiv = document.getElementById('gameOver');
const resultTitle = document.getElementById('resultTitle');
const finalTime = document.getElementById('finalTime');
const finalGates = document.getElementById('finalGates');
const finalCoins = document.getElementById('finalCoins');
const finalGems = document.getElementById('finalGems');
const finalScore = document.getElementById('finalScore');
if (!gameOverDiv || !resultTitle || !finalTime || !finalGates || !finalCoins || !finalGems || !finalScore) return;
if (completed) {
resultTitle.textContent = '🏆 Race Complete!';
resultTitle.style.color = '#4af';
} else {
resultTitle.textContent = '💥 Race Over';
resultTitle.style.color = '#f44';
}
finalTime.textContent = `⏱️ Time: ${gameTime.toFixed(1)}s`;
finalGates.textContent = `🎯 Gates Cleared: ${gatesCleared}/${totalGates}`;
finalCoins.textContent = `🪙 Coins Collected: ${player.coins}`;
finalGems.textContent = `💎 Gems Collected: ${player.gems}`;
const score = Math.floor(
(gatesCleared * 1000) -
(gameTime * 10) +
(player.lives * 500) +
(player.coins * 10) +
(player.gems * 50)
);
finalScore.textContent = `⭐ Total Score: ${Math.max(0, score)}`;
gameOverDiv.style.display = 'block';
}
function updateUI() {
const timeEl = document.getElementById('time');
const gatesEl = document.getElementById('gates');
const totalGatesEl = document.getElementById('totalGates');
const speedEl = document.getElementById('speed');
const livesEl = document.getElementById('lives');
const coinsEl = document.getElementById('coins');
const gemsEl = document.getElementById('gems');
const levelEl = document.getElementById('currentLevel');
if (timeEl) timeEl.textContent = gameTime.toFixed(1);
if (gatesEl) gatesEl.textContent = gatesCleared;
if (totalGatesEl) totalGatesEl.textContent = totalGates;
if (speedEl) speedEl.textContent = Math.floor(player.speed * 10);
if (livesEl) livesEl.textContent = player.lives;
if (coinsEl) coinsEl.textContent = player.coins;
if (gemsEl) gemsEl.textContent = player.gems;
if (levelEl) levelEl.textContent = currentLevelNum;
}
function gameLoop() {
if (!gameRunning) return;
const now = Date.now();
const deltaTime = (now - lastFrameTime) / 1000;
lastFrameTime = now;
gameTime += deltaTime;
ctx.fillStyle = '#e8f4f8';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
ctx.fillStyle = '#c8e0e8';
ctx.fillRect(0, 0, 100, HEIGHT);
ctx.fillRect(WIDTH - 100, 0, 100, HEIGHT);
updatePlayer();
updateCourse();
checkCollisions();
drawTrees();
obstacles.forEach(drawObstacle);
gates.forEach(drawGate);
drawCollectibles();
drawSpray();
drawPlayer();
drawCollectParticles();
drawSnow();
updateUI();
requestAnimationFrame(gameLoop);
}
// Level selection
window.startLevel = function(level) {
currentLevelNum = level;
currentLevelConfig = LEVELS[level];
const levelSelectDiv = document.getElementById('levelSelect');
if (levelSelectDiv) levelSelectDiv.style.display = 'none';
scrollSpeed = currentLevelConfig.baseSpeed;
player.maxSpeed = currentLevelConfig.maxSpeed;
player.lives = currentLevelConfig.lives;
player.acceleration = currentLevelConfig.baseSpeed * 0.06;
resetGame();
gameLoop();
};
function resetGame() {
const gameOverDiv = document.getElementById('gameOver');
if (gameOverDiv) gameOverDiv.style.display = 'none';
gameRunning = true;
gameTime = 0;
gatesCleared = 0;
courseOffset = 0;
player.x = WIDTH / 2;
player.speed = 0;
player.turnSpeed = 0;
player.lives = currentLevelConfig.lives;
player.invincible = 0;
player.jumping = false;
player.jumpHeight = 0;
player.jumpVelocity = 0;
player.jumpsRemaining = 2;
player.coins = 0;
player.gems = 0;
sprayParticles.length = 0;
collectParticles.length = 0;
lastFrameTime = Date.now();
initCourse();
}
// Restart button
const restartBtn = document.getElementById('restartBtn');
if (restartBtn) {
restartBtn.addEventListener('click', function() {
resetGame();
gameLoop();
});
}
// Level select button
const levelSelectBtn = document.getElementById('levelSelectBtn');
if (levelSelectBtn) {
levelSelectBtn.addEventListener('click', function() {
const gameOverDiv = document.getElementById('gameOver');
const levelSelectDiv = document.getElementById('levelSelect');
if (gameOverDiv) gameOverDiv.style.display = 'none';
if (levelSelectDiv) levelSelectDiv.style.display = 'block';
});
}
// Show level select on load
const levelSelectDiv = document.getElementById('levelSelect');
if (levelSelectDiv) levelSelectDiv.style.display = 'block';
} catch(e) {
document.body.innerHTML = '<div style="color:white;padding:20px;text-align:center;">Game error: ' + e.message + '<br><br>Please refresh the page.</div>';
console.error(e);
}
});
</script>
</body>
</html>
Install GB3A on your device for a better experience!