A fun game created with AI
Created by FunGamer3686 • November 25, 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 Ski Racing</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(180deg, #87CEEB 0%, #E0F6FF 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;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
border: 3px solid #2C3E50;
background: linear-gradient(180deg, #E8F4F8 0%, #FFFFFF 100%);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
touch-action: none;
max-width: 100vw;
max-height: 100vh;
display: block;
}
#ui {
position: absolute;
top: max(10px, env(safe-area-inset-top, 10px) + 10px);
left: max(10px, env(safe-area-inset-left, 10px) + 10px);
right: max(10px, env(safe-area-inset-right, 10px) + 10px);
color: white;
font-weight: bold;
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: clamp(8px, 2vw, 12px);
border-radius: 8px;
margin-bottom: 8px;
font-size: clamp(14px, 3vw, 18px);
}
#controls {
position: absolute;
bottom: max(20px, env(safe-area-inset-bottom, 20px) + 20px);
left: 50%;
transform: translateX(-50%);
color: white;
text-align: center;
font-size: clamp(12px, 2.5vw, 14px);
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
background: rgba(0,0,0,0.5);
padding: clamp(8px, 2vw, 12px);
border-radius: 8px;
}
.particle {
position: absolute;
pointer-events: none;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="game"></canvas>
<div id="ui">
<div class="stat" id="score">Score: 0</div>
<div class="stat" id="speed">Speed: 0 km/h</div>
<div class="stat" id="gates">Gates: 0/0</div>
<div class="stat" id="coins">💰 Coins: 0</div>
<div class="stat" id="gems">💎 Gems: 0</div>
<div class="stat" id="level">Level: 1</div>
<div class="stat" id="slope">Slope: Beginner</div>
</div>
<div id="controls">⬅️➡️ Swipe to Turn | Space/Tap Brake | W/↑ Double Jump</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 maxWidth = Math.min(window.innerWidth * 0.95, 500);
const maxHeight = Math.min(window.innerHeight * 0.85, 700);
canvas.style.width = maxWidth + 'px';
canvas.style.height = maxHeight + 'px';
const dpr = window.devicePixelRatio || 1;
canvas.width = maxWidth * dpr;
canvas.height = maxHeight * dpr;
ctx.scale(dpr, dpr);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
window.addEventListener('orientationchange', () => setTimeout(resizeCanvas, 100));
// Game constants
const GAME_WIDTH = parseInt(canvas.style.width);
const GAME_HEIGHT = parseInt(canvas.style.height);
// Keyboard controls
const keys = {};
window.addEventListener('keydown', (e) => {
if (!keys[e.key]) {
keys[e.key] = true;
if(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', ' ', 'w', 'W'].includes(e.key)) {
e.preventDefault();
}
}
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Touch controls
let touchStartX = 0;
let touchStartY = 0;
let isTouching = false;
let touchJumpUsed = false;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
isTouching = true;
touchJumpUsed = false;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!isTouching) return;
const deltaX = e.touches[0].clientX - touchStartX;
const deltaY = touchStartY - e.touches[0].clientY;
// Horizontal swipe for turning
if (Math.abs(deltaX) > 10) {
if (deltaX > 0) {
keys['ArrowRight'] = true;
keys['ArrowLeft'] = false;
} else {
keys['ArrowLeft'] = true;
keys['ArrowRight'] = false;
}
}
// Upward swipe for jump
if (deltaY > 30 && !touchJumpUsed) {
keys['ArrowUp'] = true;
touchJumpUsed = true;
setTimeout(() => keys['ArrowUp'] = false, 100);
}
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
isTouching = false;
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
touchJumpUsed = false;
}, { passive: false });
// Tap to brake
let lastTapTime = 0;
canvas.addEventListener('click', (e) => {
const now = Date.now();
if (now - lastTapTime < 300) return; // Prevent double-tap issues
lastTapTime = now;
keys[' '] = true;
setTimeout(() => keys[' '] = false, 100);
});
// Game state
let gameState = {
score: 0,
speed: 0,
gatesPassed: 0,
totalGates: 15,
coins: 0,
gems: 0,
level: 1,
slopeLevel: 0,
slopeNames: ['Beginner', 'Intermediate', 'Advanced', 'Expert'],
gameOver: false,
raceComplete: false,
invincible: false,
invincibleTimer: 0,
speedBoost: false,
speedBoostTimer: 0
};
// Player (skier)
const player = {
x: GAME_WIDTH / 2,
y: GAME_HEIGHT * 0.75,
width: 20,
height: 30,
vx: 0,
baseSpeed: 3,
maxSpeed: 8,
turnSpeed: 0.3,
carving: false,
jumpHeight: 0,
isJumping: false,
jumpVelocity: 0,
jumpsAvailable: 2,
maxJumps: 2,
onGround: true
};
// Slope elements
let gates = [];
let obstacles = [];
let jumps = [];
let snowflakes = [];
let trees = [];
let enemies = [];
let coins = [];
let gems = [];
let powerUps = [];
let particles = [];
let scrollSpeed = 3;
// Weather
let weather = 'clear';
// Helper functions
function createParticle(x, y, color, count = 5) {
for (let i = 0; i < count; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4 - 2,
life: 30,
color: color,
size: 3 + Math.random() * 3
});
}
}
function checkCollision(obj1, obj2, radius1, radius2) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const dist = Math.sqrt(dx * dx + dy * dy);
return dist < radius1 + radius2;
}
// Initialize level
function initLevel() {
gates = [];
obstacles = [];
jumps = [];
trees = [];
enemies = [];
coins = [];
gems = [];
powerUps = [];
particles = [];
gameState.gatesPassed = 0;
gameState.raceComplete = false;
gameState.gameOver = false;
player.x = GAME_WIDTH / 2;
player.vx = 0;
player.jumpHeight = 0;
player.isJumping = false;
player.jumpsAvailable = player.maxJumps;
player.onGround = true;
const levelLength = gameState.totalGates * 200;
// Create gates
for (let i = 0; i < gameState.totalGates; i++) {
const y = -100 - i * 200;
const gateWidth = 120 - gameState.slopeLevel * 15;
const centerX = GAME_WIDTH / 2 + (Math.random() - 0.5) * 100;
gates.push({
x: centerX,
y: y,
width: gateWidth,
passed: false,
side: i % 2 === 0 ? 'left' : 'right'
});
}
// Create coins (frequent)
const coinCount = 30 + gameState.level * 5;
for (let i = 0; i < coinCount; i++) {
coins.push({
x: 50 + Math.random() * (GAME_WIDTH - 100),
y: -200 - Math.random() * levelLength,
radius: 10,
collected: false,
rotation: Math.random() * Math.PI * 2
});
}
// Create gems (rare, higher value)
const gemCount = 5 + gameState.level * 2;
for (let i = 0; i < gemCount; i++) {
gems.push({
x: 50 + Math.random() * (GAME_WIDTH - 100),
y: -200 - Math.random() * levelLength,
radius: 12,
collected: false,
sparkle: Math.random() * Math.PI * 2
});
}
// Create power-ups
const powerUpCount = 3 + gameState.level;
for (let i = 0; i < powerUpCount; i++) {
const types = ['invincible', 'speedBoost'];
powerUps.push({
x: 50 + Math.random() * (GAME_WIDTH - 100),
y: -200 - Math.random() * levelLength,
radius: 15,
collected: false,
type: types[Math.floor(Math.random() * types.length)],
pulse: 0
});
}
// Create jumps
for (let i = 0; i < 3 + gameState.slopeLevel; i++) {
jumps.push({
x: GAME_WIDTH / 2 + (Math.random() - 0.5) * 100,
y: -200 - Math.random() * levelLength,
width: 80,
height: 15
});
}
// Create obstacles (rocks)
for (let i = 0; i < 5 + gameState.slopeLevel * 2; i++) {
obstacles.push({
x: 30 + Math.random() * (GAME_WIDTH - 60),
y: -200 - Math.random() * levelLength,
radius: 15 + Math.random() * 10
});
}
// Create enemies (snowboarders)
const enemyCount = 3 + gameState.slopeLevel * 2 + Math.floor(gameState.level / 2);
for (let i = 0; i < enemyCount; i++) {
enemies.push({
x: 50 + Math.random() * (GAME_WIDTH - 100),
y: -300 - Math.random() * levelLength,
vx: (Math.random() - 0.5) * 2,
width: 20,
height: 25,
zigzag: Math.random() > 0.5,
zigzagTimer: 0,
color: ['#FF1493', '#00CED1', '#FFD700', '#9370DB'][Math.floor(Math.random() * 4)]
});
}
// Create trees
for (let i = 0; i < 20; i++) {
const side = Math.random() > 0.5 ? 'left' : 'right';
trees.push({
x: side === 'left' ? Math.random() * 40 : GAME_WIDTH - Math.random() * 40,
y: -Math.random() * levelLength,
size: 20 + Math.random() * 15
});
}
// Weather
weather = Math.random() > 0.7 ? 'snowing' : 'clear';
if (weather === 'snowing') {
snowflakes = [];
for (let i = 0; i < 100; i++) {
snowflakes.push({
x: Math.random() * GAME_WIDTH,
y: Math.random() * GAME_HEIGHT,
speed: 1 + Math.random() * 2,
size: 2 + Math.random() * 3
});
}
}
}
initLevel();
// Update function
function update() {
if (gameState.gameOver || gameState.raceComplete) return;
// Update power-up timers
if (gameState.invincible) {
gameState.invincibleTimer--;
if (gameState.invincibleTimer <= 0) {
gameState.invincible = false;
}
}
if (gameState.speedBoost) {
gameState.speedBoostTimer--;
if (gameState.speedBoostTimer <= 0) {
gameState.speedBoost = false;
}
}
// Player movement
if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
player.vx -= player.turnSpeed;
player.carving = true;
} else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
player.vx += player.turnSpeed;
player.carving = true;
} else {
player.carving = false;
}
// Double jump mechanic
if ((keys['ArrowUp'] || keys['w'] || keys['W']) && player.jumpsAvailable > 0) {
if (!player.isJumping || (player.jumpsAvailable === 1 && player.jumpVelocity < 0)) {
player.isJumping = true;
player.jumpVelocity = -8;
player.jumpsAvailable--;
createParticle(player.x, player.y + 15, '#FFFFFF', 8);
keys['ArrowUp'] = false;
keys['w'] = false;
keys['W'] = false;
}
}
// Braking
if (keys[' ']) {
scrollSpeed = Math.max(1, scrollSpeed - 0.2);
} else {
const baseSpeed = player.baseSpeed + gameState.slopeLevel;
const maxSpeed = gameState.speedBoost ? baseSpeed * 1.5 : baseSpeed;
scrollSpeed = Math.min(maxSpeed, scrollSpeed + 0.1);
}
// Friction
player.vx *= 0.9;
player.vx = Math.max(-5, Math.min(5, player.vx));
// Move player
player.x += player.vx;
player.x = Math.max(20, Math.min(GAME_WIDTH - 20, player.x));
// Calculate speed
gameState.speed = Math.round(scrollSpeed * 15);
// Update jumping physics
if (player.isJumping) {
player.jumpVelocity += 0.5; // Gravity
player.jumpHeight -= player.jumpVelocity;
if (player.jumpHeight <= 0) {
player.jumpHeight = 0;
player.isJumping = false;
player.jumpVelocity = 0;
player.jumpsAvailable = player.maxJumps;
player.onGround = true;
} else {
player.onGround = false;
}
}
// Scroll elements
gates.forEach(gate => gate.y += scrollSpeed);
obstacles.forEach(obs => obs.y += scrollSpeed);
jumps.forEach(jump => jump.y += scrollSpeed);
trees.forEach(tree => tree.y += scrollSpeed);
enemies.forEach(enemy => enemy.y += scrollSpeed);
coins.forEach(coin => coin.y += scrollSpeed);
gems.forEach(gem => gem.y += scrollSpeed);
powerUps.forEach(powerUp => powerUp.y += scrollSpeed);
// Update enemies
enemies.forEach(enemy => {
if (enemy.zigzag) {
enemy.zigzagTimer++;
if (enemy.zigzagTimer > 60) {
enemy.vx = -enemy.vx;
enemy.zigzagTimer = 0;
}
}
enemy.x += enemy.vx;
enemy.x = Math.max(30, Math.min(GAME_WIDTH - 30, enemy.x));
if (enemy.x <= 30 || enemy.x >= GAME_WIDTH - 30) {
enemy.vx = -enemy.vx;
}
});
// Check gate collision
gates.forEach(gate => {
if (!gate.passed && gate.y > player.y - 50 && gate.y < player.y + 50) {
const gateLeft = gate.x - gate.width / 2;
const gateRight = gate.x + gate.width / 2;
if (player.x > gateLeft && player.x < gateRight) {
gate.passed = true;
gameState.gatesPassed++;
gameState.score += 100 + gameState.slopeLevel * 50;
createParticle(gate.x, gate.y, '#00FF00', 10);
} else if (gate.y > player.y) {
gate.passed = true;
gameState.score = Math.max(0, gameState.score - 50);
}
}
});
// Check coin collection
coins.forEach(coin => {
if (!coin.collected && checkCollision(player, coin, 15, coin.radius)) {
coin.collected = true;
gameState.coins++;
gameState.score += 50;
createParticle(coin.x, coin.y, '#FFD700', 8);
}
coin.rotation += 0.1;
});
// Check gem collection
gems.forEach(gem => {
if (!gem.collected && checkCollision(player, gem, 15, gem.radius)) {
gem.collected = true;
gameState.gems++;
gameState.score += 200;
createParticle(gem.x, gem.y, '#FF00FF', 12);
}
gem.sparkle += 0.15;
});
// Check power-up collection
powerUps.forEach(powerUp => {
if (!powerUp.collected && checkCollision(player, powerUp, 15, powerUp.radius)) {
powerUp.collected = true;
if (powerUp.type === 'invincible') {
gameState.invincible = true;
gameState.invincibleTimer = 300; // 5 seconds at 60fps
createParticle(powerUp.x, powerUp.y, '#00FFFF', 15);
} else if (powerUp.type === 'speedBoost') {
gameState.speedBoost = true;
gameState.speedBoostTimer = 240; // 4 seconds
createParticle(powerUp.x, powerUp.y, '#FF6600', 15);
}
gameState.score += 100;
}
powerUp.pulse += 0.1;
});
// Check jump collision
jumps.forEach(jump => {
if (player.onGround && Math.abs(player.x - jump.x) < jump.width / 2 &&
Math.abs(player.y - jump.y) < 20) {
player.isJumping = true;
player.jumpVelocity = -10;
player.onGround = false;
gameState.score += 50;
createParticle(jump.x, jump.y, '#FFFFFF', 10);
}
});
// Check obstacle collision
if (!gameState.invincible && player.jumpHeight === 0) {
obstacles.forEach(obs => {
if (checkCollision(player, obs, 15, obs.radius)) {
gameState.gameOver = true;
createParticle(player.x, player.y, '#FF0000', 20);
}
});
}
// Check enemy collision
if (!gameState.invincible) {
enemies.forEach(enemy => {
if (Math.abs(player.x - enemy.x) < 25 && Math.abs(player.y - enemy.y) < 30 && player.jumpHeight === 0) {
gameState.gameOver = true;
createParticle(player.x, player.y, '#FF0000', 20);
}
});
}
// Update particles
particles = particles.filter(p => {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.2;
p.life--;
return p.life > 0;
});
// Update snowflakes
if (weather === 'snowing') {
snowflakes.forEach(flake => {
flake.y += flake.speed + scrollSpeed;
flake.x += Math.sin(flake.y * 0.01) * 0.5;
if (flake.y > GAME_HEIGHT) {
flake.y = -10;
flake.x = Math.random() * GAME_WIDTH;
}
});
}
// Check race completion
if (gameState.gatesPassed >= gameState.totalGates) {
gameState.raceComplete = true;
gameState.score += 500;
gameState.score += gameState.coins * 10;
gameState.score += gameState.gems * 50;
}
// Remove off-screen elements
gates = gates.filter(g => g.y < GAME_HEIGHT + 100);
obstacles = obstacles.filter(o => o.y < GAME_HEIGHT + 100);
jumps = jumps.filter(j => j.y < GAME_HEIGHT + 100);
trees = trees.filter(t => t.y < GAME_HEIGHT + 100);
enemies = enemies.filter(e => e.y < GAME_HEIGHT + 100);
coins = coins.filter(c => !c.collected && c.y < GAME_HEIGHT + 100);
gems = gems.filter(g => !g.collected && g.y < GAME_HEIGHT + 100);
powerUps = powerUps.filter(p => !p.collected && p.y < GAME_HEIGHT + 100);
}
// Draw function
function draw() {
// Clear canvas
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
// Draw slope lines
ctx.strokeStyle = '#D0D0D0';
ctx.lineWidth = 2;
for (let i = 0; i < 10; i++) {
const y = (i * 100 + (Date.now() / 20) % 100) % GAME_HEIGHT;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(GAME_WIDTH, y);
ctx.stroke();
}
// Draw trees
trees.forEach(tree => {
ctx.fillStyle = '#2D5016';
ctx.beginPath();
ctx.moveTo(tree.x, tree.y - tree.size);
ctx.lineTo(tree.x - tree.size / 2, tree.y);
ctx.lineTo(tree.x + tree.size / 2, tree.y);
ctx.fill();
ctx.fillStyle = '#4A3C28';
ctx.fillRect(tree.x - 3, tree.y, 6, tree.size / 2);
});
// Draw gates
gates.forEach(gate => {
const color = gate.side === 'left' ? '#FF0000' : '#0000FF';
ctx.fillStyle = gate.passed ? '#888888' : color;
ctx.fillRect(gate.x - gate.width / 2 - 5, gate.y - 40, 10, 80);
ctx.fillRect(gate.x + gate.width / 2 - 5, gate.y - 40, 10, 80);
ctx.fillStyle = gate.passed ? '#AAAAAA' : (gate.side === 'left' ? '#FF6666' : '#6666FF');
ctx.fillRect(gate.x - gate.width / 2 + 5, gate.y - 30, gate.width - 10, 20);
});
// Draw coins
coins.forEach(coin => {
if (!coin.collected) {
ctx.save();
ctx.translate(coin.x, coin.y);
ctx.rotate(coin.rotation);
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(0, 0, coin.radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFA500';
ctx.beginPath();
ctx.arc(0, 0, coin.radius * 0.6, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
});
// Draw gems
gems.forEach(gem => {
if (!gem.collected) {
ctx.save();
ctx.translate(gem.x, gem.y);
const sparkleSize = Math.abs(Math.sin(gem.sparkle)) * 5;
ctx.fillStyle = '#FF00FF';
ctx.beginPath();
ctx.moveTo(0, -gem.radius);
for (let i = 0; i < 8; i++) {
const angle = (i * Math.PI / 4);
const r = i % 2 === 0 ? gem.radius : gem.radius * 0.5;
ctx.lineTo(Math.cos(angle) * r, Math.sin(angle) * r);
}
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(-sparkleSize/2, -1, sparkleSize, 2);
ctx.fillRect(-1, -sparkleSize/2, 2, sparkleSize);
ctx.restore();
}
});
// Draw power-ups
powerUps.forEach(powerUp => {
if (!powerUp.collected) {
const pulseSize = Math.abs(Math.sin(powerUp.pulse)) * 3;
ctx.save();
ctx.translate(powerUp.x, powerUp.y);
if (powerUp.type === 'invincible') {
ctx.fillStyle = '#00FFFF';
ctx.strokeStyle = '#0088FF';
} else {
ctx.fillStyle = '#FF6600';
ctx.strokeStyle = '#FF0000';
}
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, powerUp.radius + pulseSize, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(powerUp.type === 'invincible' ? '⭐' : '⚡', 0, 0);
ctx.restore();
}
});
// Draw jumps
jumps.forEach(jump => {
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.moveTo(jump.x - jump.width / 2, jump.y);
ctx.lineTo(jump.x, jump.y - jump.height);
ctx.lineTo(jump.x + jump.width / 2, jump.y);
ctx.fill();
});
// Draw obstacles
obstacles.forEach(obs => {
ctx.fillStyle = '#666666';
ctx.beginPath();
ctx.arc(obs.x, obs.y, obs.radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#999999';
ctx.beginPath();
ctx.arc(obs.x - obs.radius / 3, obs.y - obs.radius / 3, obs.radius / 3, 0, Math.PI * 2);
ctx.fill();
});
// Draw enemies (snowboarders)
enemies.forEach(enemy => {
ctx.fillStyle = enemy.color;
ctx.fillRect(enemy.x - enemy.width / 2, enemy.y - enemy.height / 2, enemy.width, enemy.height);
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(enemy.x, enemy.y - enemy.height / 2 - 5, 6, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#333333';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(enemy.x - 12, enemy.y + enemy.height / 2 + 2);
ctx.lineTo(enemy.x + 12, enemy.y + enemy.height / 2 + 2);
ctx.stroke();
});
// Draw particles
particles.forEach(p => {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 30;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
});
// Draw player (skier)
const playerDrawY = player.y - player.jumpHeight;
// Shadow when jumping
if (player.jumpHeight > 0) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.beginPath();
ctx.ellipse(player.x, player.y, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
}
// Invincible glow
if (gameState.invincible) {
ctx.strokeStyle = '#00FFFF';
ctx.lineWidth = 3;
ctx.globalAlpha = 0.5 + Math.abs(Math.sin(Date.now() / 100)) * 0.5;
ctx.beginPath();
ctx.arc(player.x, playerDrawY, 25, 0, Math.PI * 2);
ctx.stroke();
ctx.globalAlpha = 1;
}
// Speed boost trail
if (gameState.speedBoost) {
ctx.strokeStyle = '#FF6600';
ctx.lineWidth = 4;
ctx.globalAlpha = 0.3;
ctx.beginPath();
ctx.moveTo(player.x - 10, playerDrawY + 20);
ctx.lineTo(player.x - 10, playerDrawY + 40);
ctx.moveTo(player.x + 10, playerDrawY + 20);
ctx.lineTo(player.x + 10, playerDrawY + 40);
ctx.stroke();
ctx.globalAlpha = 1;
}
// Skier body
ctx.fillStyle = '#FF6600';
ctx.fillRect(player.x - 10, playerDrawY - 15, 20, 25);
// Helmet
ctx.fillStyle = '#0066CC';
ctx.beginPath();
ctx.arc(player.x, playerDrawY - 20, 8, 0, Math.PI * 2);
ctx.fill();
// Skis
ctx.fillStyle = '#333333';
const skiAngle = player.vx * 0.1;
ctx.save();
ctx.translate(player.x, playerDrawY + 15);
ctx.rotate(skiAngle);
ctx.fillRect(-15, -2, 30, 4);
ctx.restore();
// Carving effect
if (player.carving && player.jumpHeight === 0) {
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(player.x - 15, player.y + 20);
ctx.lineTo(player.x - 15, player.y + 40);
ctx.moveTo(player.x + 15, player.y + 20);
ctx.lineTo(player.x + 15, player.y + 40);
ctx.stroke();
}
// Jump indicator
if (player.jumpsAvailable < player.maxJumps) {
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.fillText('Jumps: ' + player.jumpsAvailable, player.x, playerDrawY - 35);
}
// Draw snowflakes
if (weather === 'snowing') {
ctx.fillStyle = '#FFFFFF';
snowflakes.forEach(flake => {
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
});
}
// Game over / Race complete
if (gameState.gameOver) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
ctx.fillStyle = '#FF0000';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('CRASHED!', GAME_WIDTH / 2, GAME_HEIGHT / 2 - 60);
ctx.fillStyle = '#FFFFFF';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + gameState.score, GAME_WIDTH / 2, GAME_HEIGHT / 2 - 20);
ctx.fillText('💰 ' + gameState.coins + ' 💎 ' + gameState.gems, GAME_WIDTH / 2, GAME_HEIGHT / 2 + 10);
ctx.fillText('Tap/Press R to Restart', GAME_WIDTH / 2, GAME_HEIGHT / 2 + 50);
}
if (gameState.raceComplete) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
ctx.fillStyle = '#00FF00';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('LEVEL COMPLETE!', GAME_WIDTH / 2, GAME_HEIGHT / 2 - 80);
ctx.fillStyle = '#FFFFFF';
ctx.font = '18px Arial';
ctx.fillText('Score: ' + gameState.score, GAME_WIDTH / 2, GAME_HEIGHT / 2 - 40);
ctx.fillText('Gates: ' + gameState.gatesPassed + '/' + gameState.totalGates, GAME_WIDTH / 2, GAME_HEIGHT / 2 - 10);
ctx.fillText('💰 ' + gameState.coins + ' 💎 ' + gameState.gems, GAME_WIDTH / 2, GAME_HEIGHT / 2 + 20);
if (gameState.slopeLevel < 3) {
ctx.fillText('Tap/Press N for Next Level', GAME_WIDTH / 2, GAME_HEIGHT / 2 + 60);
} else {
ctx.fillText('YOU CONQUERED ALL SLOPES!', GAME_WIDTH / 2, GAME_HEIGHT / 2 + 60);
}
ctx.fillText('Tap/Press R to Restart', GAME_WIDTH / 2, GAME_HEIGHT / 2 + 90);
}
// Update UI
const scoreEl = document.getElementById('score');
const speedEl = document.getElementById('speed');
const gatesEl = document.getElementById('gates');
const coinsEl = document.getElementById('coins');
const gemsEl = document.getElementById('gems');
const levelEl = document.getElementById('level');
const slopeEl = document.getElementById('slope');
if (scoreEl) scoreEl.textContent = 'Score: ' + gameState.score;
if (speedEl) speedEl.textContent = 'Speed: ' + gameState.speed + ' km/h';
if (gatesEl) gatesEl.textContent = 'Gates: ' + gameState.gatesPassed + '/' + gameState.totalGates;
if (coinsEl) coinsEl.textContent = '💰 Coins: ' + gameState.coins;
if (gemsEl) gemsEl.textContent = '💎 Gems: ' + gameState.gems;
if (levelEl) levelEl.textContent = 'Level: ' + gameState.level;
if (slopeEl) slopeEl.textContent = 'Slope: ' + gameState.slopeNames[gameState.slopeLevel];
}
// Restart
function restart() {
gameState.score = 0;
gameState.coins = 0;
gameState.gems = 0;
gameState.level = 1;
gameState.slopeLevel = 0;
gameState.invincible = false;
gameState.speedBoost = false;
initLevel();
}
// Next level
function nextLevel() {
if (gameState.slopeLevel < 3) {
gameState.slopeLevel++;
}
gameState.level++;
gameState.totalGates += 5;
player.baseSpeed = 3 + gameState.slopeLevel * 0.5;
initLevel();
}
// Keyboard restart/next
window.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
restart();
}
if ((e.key === 'n' || e.key === 'N') && gameState.raceComplete) {
nextLevel();
}
});
// Touch restart/next
canvas.addEventListener('touchend', (e) => {
if (gameState.gameOver) {
restart();
} else if (gameState.raceComplete) {
nextLevel();
}
});
// Game loop
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
} catch(e) {
document.body.innerHTML = '<div style="color:white;padding:20px;text-align:center;">Game error: ' + e.message + '</div>';
console.error(e);
}
});
</script>
</body>
</html>
Install GB3A on your device for a better experience!