/** * Süper Racing - Game Engine v2.0 Turbo Edition (Keyboard Enter Restart Added) * Deep Upgrade Shop (Engine, Nitro, Armor Shield), Bulletproof Canvas Drawing. */ (function () { 'use strict'; // --- SAFE CANVAS ROUNDRECT POLYFILL --- function drawRoundRect(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } // --- AUDIO SYNTHESIZER --- class SoundManager { constructor() { this.ctx = null; this.enabled = true; this.engineOsc = null; this.engineGain = null; } init() { try { if (!this.ctx) { const AudioContext = window.AudioContext || window.webkitAudioContext; if (AudioContext) { this.ctx = new AudioContext(); } } if (this.ctx && this.ctx.state === 'suspended') { this.ctx.resume().catch(() => {}); } } catch (e) {} } toggleSound() { this.enabled = !this.enabled; if (!this.enabled && this.engineGain && this.ctx) { try { this.engineGain.gain.setValueAtTime(0, this.ctx.currentTime); } catch (e) {} } return this.enabled; } startEngine() { if (!this.enabled) return; this.init(); if (!this.ctx || this.engineOsc) return; try { this.engineOsc = this.ctx.createOscillator(); this.engineGain = this.ctx.createGain(); this.engineOsc.type = 'sawtooth'; this.engineOsc.frequency.setValueAtTime(45, this.ctx.currentTime); this.engineGain.gain.setValueAtTime(0.04, this.ctx.currentTime); const filter = this.ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.setValueAtTime(150, this.ctx.currentTime); this.engineOsc.connect(filter); filter.connect(this.engineGain); this.engineGain.connect(this.ctx.destination); this.engineOsc.start(); } catch (e) {} } updateEngine(speedRatio) { if (!this.enabled || !this.engineOsc || !this.ctx) return; try { const targetFreq = 40 + speedRatio * 110; this.engineOsc.frequency.setTargetAtTime(targetFreq, this.ctx.currentTime, 0.1); this.engineGain.gain.setTargetAtTime(0.03 + speedRatio * 0.04, this.ctx.currentTime, 0.1); } catch (e) {} } stopEngine() { if (this.engineOsc) { try { this.engineOsc.stop(); this.engineOsc.disconnect(); } catch (e) {} this.engineOsc = null; this.engineGain = null; } } playCoin() { if (!this.enabled) return; this.init(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(987.77, now); osc.frequency.setValueAtTime(1318.51, now + 0.08); gain.gain.setValueAtTime(0.12, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.2); } catch (e) {} } playNearMiss() { if (!this.enabled) return; this.init(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'triangle'; osc.frequency.setValueAtTime(320, now); osc.frequency.exponentialRampToValueAtTime(750, now + 0.12); gain.gain.setValueAtTime(0.08, now); gain.gain.linearRampToValueAtTime(0.001, now + 0.12); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.12); } catch (e) {} } playNitro() { if (!this.enabled) return; this.init(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(140, now); osc.frequency.linearRampToValueAtTime(400, now + 0.25); gain.gain.setValueAtTime(0.1, now); gain.gain.linearRampToValueAtTime(0.001, now + 0.25); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.25); } catch (e) {} } playCrash() { if (!this.enabled) return; this.init(); this.stopEngine(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'square'; osc.frequency.setValueAtTime(100, now); osc.frequency.exponentialRampToValueAtTime(25, now + 0.35); gain.gain.setValueAtTime(0.25, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.4); } catch (e) {} } playShield() { if (!this.enabled) return; this.init(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(400, now); osc.frequency.linearRampToValueAtTime(800, now + 0.2); gain.gain.setValueAtTime(0.2, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.25); } catch (e) {} } playNudge() { if (!this.enabled) return; this.init(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(180, now); osc.frequency.setValueAtTime(120, now + 0.07); osc.frequency.setValueAtTime(240, now + 0.14); gain.gain.setValueAtTime(0.2, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.3); } catch (e) {} } playWin() { if (!this.enabled) return; this.init(); this.stopEngine(); if (!this.ctx) return; try { const now = this.ctx.currentTime; const notes = [523.25, 659.25, 783.99]; notes.forEach((freq, i) => { const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, now + i * 0.1); gain.gain.setValueAtTime(0.12, now + i * 0.1); gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.1 + 0.25); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now + i * 0.1); osc.stop(now + i * 0.1 + 0.25); }); } catch (e) {} } } // --- VEHICLES CONFIGURATION --- const VEHICLES = [ { id: 'car_red', name: 'Süper Kırmızı Spor', desc: 'Standart başlangıç yarış aracı.', color: '#ef4444', price: 0, speedBase: 140, accelBase: 50 }, { id: 'car_blue', name: 'Mavi Fırtına Turbo', desc: 'Hızlı ivmelenen şehir yarışçısı.', color: '#0284c7', price: 150, speedBase: 165, accelBase: 65 }, { id: 'car_neon', name: 'Neon Siber Roadster', desc: 'Yüksek maksimum hız ve özel görünüm.', color: '#38bdf8', price: 350, speedBase: 190, accelBase: 80 }, { id: 'car_gold', name: 'Efsanevi Altın Phantom', desc: 'Pistlerin en güçlü ve prestijli efsanesi.', color: '#fbbf24', price: 700, speedBase: 220, accelBase: 100 } ]; // --- LEVELS --- const LEVELS = [ { id: 1, name: 'Sahil Yolu (Başlangıç)', desc: 'Güneşli otobanda 800m sürüş yap.', goalDistance: 800, trafficDensity: 0.015, bgTheme: 'day', asphaltColor: '#334155', starScores: [1000, 2000, 3500] }, { id: 2, name: 'Gece Yağmuru (Orta)', desc: 'Gece yağmurlu yolda 1200m ilerle.', goalDistance: 1200, trafficDensity: 0.02, bgTheme: 'rain', asphaltColor: '#1e293b', starScores: [2000, 3500, 5500] }, { id: 3, name: 'Sonsuz Otoban Şampiyonluğu (Zor)', desc: 'Sınırsız sürüş! En yüksek skoru kır.', goalDistance: 999999, trafficDensity: 0.026, bgTheme: 'cyber', asphaltColor: '#0f172a', starScores: [4000, 10000, 20000] } ]; // --- GAME ENGINE --- class Game { constructor() { this.canvas = document.getElementById('gameCanvas'); this.ctx = this.canvas.getContext('2d'); this.sound = new SoundManager(); this.userData = this.loadUserData(); this.currentLevel = LEVELS[0]; this.selectedCar = VEHICLES[0]; this.state = 'MENU'; this.distance = 0; this.score = 0; this.coinsCollected = 0; this.speed = 0; this.maxSpeed = 150; this.nitro = 100; this.hasShield = false; this.lanes = [240, 340, 440, 540]; this.player = { currentLane: 1, x: 340, targetX: 340, y: 480, width: 42, height: 76 }; this.traffic = []; this.coins = []; this.particles = []; this.roadScroll = 0; this.keys = { left: false, right: false, up: false, down: false, nitro: false }; this.laneCooldown = false; this.initEvents(); this.initGarageView(); this.renderMenuStats(); this.startLoop(); } loadUserData() { const defaultData = { totalCoins: 0, unlockedCars: ['car_red'], selectedCarId: 'car_red', unlockedLevels: [1], levelStars: { 1: 0, 2: 0, 3: 0 }, upgrades: { engineLevel: 1, nitroLevel: 1, armorLevel: 0 } }; try { const stored = localStorage.getItem('super_racing_save'); return stored ? Object.assign(defaultData, JSON.parse(stored)) : defaultData; } catch (e) { return defaultData; } } saveUserData() { try { localStorage.setItem('super_racing_save', JSON.stringify(this.userData)); } catch (e) {} } initEvents() { const unlockAudio = () => this.sound.init(); window.addEventListener('click', unlockAudio, { once: true }); window.addEventListener('keydown', unlockAudio, { once: true }); // Keyboard Controls (INCLUDING ENTER / RESTART SHORTCUTS!) window.addEventListener('keydown', (e) => { // ENTER or SPACE or 'R' Key on Game Over / Win / Menu screens to instantly restart/play! if (e.key === 'Enter' || e.key === 'r' || e.key === 'R') { if (this.state === 'GAMEOVER') { this.startLevel(this.currentLevel.id); e.preventDefault(); return; } else if (this.state === 'WIN') { const nextId = Math.min(3, this.currentLevel.id + 1); this.startLevel(nextId); e.preventDefault(); return; } else if (this.state === 'MENU' && !document.getElementById('menuModal').classList.contains('hidden')) { this.startLevel(1); e.preventDefault(); return; } } if (this.state === 'GAMEOVER' && (e.key === ' ' || e.key === 'Spacebar')) { this.startLevel(this.currentLevel.id); e.preventDefault(); return; } if (this.state === 'PLAYING') { if ((e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') && !this.laneCooldown) { this.changeLane(-1); this.laneCooldown = true; } if ((e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') && !this.laneCooldown) { this.changeLane(1); this.laneCooldown = true; } } if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') this.keys.up = true; if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') this.keys.down = true; if (e.key === ' ' || e.key === 'Spacebar') { this.keys.nitro = true; e.preventDefault(); } }); window.addEventListener('keyup', (e) => { if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A' || e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { this.laneCooldown = false; } if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') this.keys.up = false; if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') this.keys.down = false; if (e.key === ' ' || e.key === 'Spacebar') this.keys.nitro = false; }); // Touch Controls const setupTouchClick = (id, action) => { const btn = document.getElementById(id); if (!btn) return; btn.onclick = (e) => { e.preventDefault(); this.sound.init(); if (action === 'left') this.changeLane(-1); if (action === 'right') this.changeLane(1); }; }; setupTouchClick('btnTouchLeft', 'left'); setupTouchClick('btnTouchRight', 'right'); const setupHoldBtn = (id, keyName) => { const btn = document.getElementById(id); if (!btn) return; btn.onmousedown = () => { this.keys[keyName] = true; }; btn.onmouseup = () => { this.keys[keyName] = false; }; btn.ontouchstart = (e) => { e.preventDefault(); this.keys[keyName] = true; }; btn.ontouchend = (e) => { e.preventDefault(); this.keys[keyName] = false; }; }; setupHoldBtn('btnTouchBrake', 'down'); setupHoldBtn('btnTouchNitro', 'nitro'); // Menu Buttons document.getElementById('btnQuickPlay').onclick = () => this.startLevel(1); document.getElementById('btnSelectLevel').onclick = () => this.showModal('levelModal'); document.getElementById('btnCloseLevel').onclick = () => this.hideModal('levelModal'); document.getElementById('btnGarage').onclick = () => this.showGarage(); document.getElementById('btnCloseGarage').onclick = () => this.hideModal('garageModal'); document.getElementById('btnHowToPlay').onclick = () => this.showModal('howToModal'); document.getElementById('btnCloseHowTo').onclick = () => this.hideModal('howToModal'); document.getElementById('btnRetryLevel').onclick = () => this.startLevel(this.currentLevel.id); document.getElementById('btnBackToMenuFromGO').onclick = () => this.showMenu(); document.getElementById('btnNextLevel').onclick = () => { const nextId = Math.min(3, this.currentLevel.id + 1); this.startLevel(nextId); }; document.getElementById('btnBackToMenuFromWin').onclick = () => this.showMenu(); // Nudge Button document.getElementById('btnNudge').onclick = () => { this.sound.playNudge(); const wrapper = document.getElementById('canvasWrapper'); wrapper.classList.remove('nudge-shake'); void wrapper.offsetWidth; wrapper.classList.add('nudge-shake'); }; // Sound Toggle document.getElementById('btnSound').onclick = (e) => { const isEnabled = this.sound.toggleSound(); e.target.innerText = isEnabled ? '🔊 Ses' : '🔇 Mute'; }; // Fullscreen Toggle document.getElementById('btnFullscreen').onclick = () => { if (!document.fullscreenElement) { document.querySelector('.app-container').requestFullscreen().catch(() => {}); } else { document.exitFullscreen().catch(() => {}); } }; // Shop Upgrades document.getElementById('btnUpgradeEngine').onclick = () => this.buyUpgrade('engine'); document.getElementById('btnUpgradeNitro').onclick = () => this.buyUpgrade('nitro'); document.getElementById('btnUpgradeArmor').onclick = () => this.buyUpgrade('armor'); } changeLane(dir) { const nextLane = Math.max(0, Math.min(3, this.player.currentLane + dir)); this.player.currentLane = nextLane; this.player.targetX = this.lanes[nextLane]; } renderMenuStats() { let totalStars = 0; Object.values(this.userData.levelStars).forEach(s => totalStars += s); document.getElementById('totalStarsText').innerText = `⭐ ${totalStars}/9`; document.getElementById('totalCoinsText').innerText = `🪙 ${this.userData.totalCoins}`; } showModal(id) { document.querySelectorAll('.modal-overlay').forEach(m => m.classList.add('hidden')); document.getElementById(id).classList.remove('hidden'); if (id === 'levelModal') this.renderLevelsList(); } hideModal(id) { document.getElementById(id).classList.add('hidden'); } showMenu() { this.state = 'MENU'; this.sound.stopEngine(); document.getElementById('hudOverlay').classList.add('hidden'); document.getElementById('touchControls').classList.add('hidden'); this.renderMenuStats(); this.showModal('menuModal'); } renderLevelsList() { const container = document.getElementById('levelsGrid'); container.innerHTML = ''; LEVELS.forEach(lvl => { const isUnlocked = this.userData.unlockedLevels.includes(lvl.id); const stars = this.userData.levelStars[lvl.id] || 0; const starStr = '⭐'.repeat(stars) + '☆'.repeat(3 - stars); const item = document.createElement('div'); item.className = `level-item ${isUnlocked ? '' : 'locked'}`; item.innerHTML = `
${lvl.id}. ${lvl.name}

${lvl.desc}

${isUnlocked ? starStr : '🔒 KİLİTLİ'}
`; if (isUnlocked) { item.onclick = () => { this.hideModal('levelModal'); this.startLevel(lvl.id); }; } container.appendChild(item); }); } showGarage() { this.showModal('garageModal'); this.renderGarageCar(VEHICLES.find(c => c.id === this.userData.selectedCarId) || VEHICLES[0]); } renderGarageCar(car) { document.getElementById('garageCarName').innerText = car.name; document.getElementById('garageCarDesc').innerText = car.desc; const engineLvl = this.userData.upgrades.engineLevel || 1; const nitroLvl = this.userData.upgrades.nitroLevel || 1; const armorLvl = this.userData.upgrades.armorLevel || 0; const effectiveSpeed = car.speedBase + (engineLvl - 1) * 15; document.getElementById('specSpeed').style.width = `${Math.min(100, (effectiveSpeed / 250) * 100)}%`; document.getElementById('specAccel').style.width = `${Math.min(100, ((car.accelBase + engineLvl * 10) / 150) * 100)}%`; document.getElementById('garageCoinDisplay').innerText = `Altınınız: 🪙 ${this.userData.totalCoins}`; document.getElementById('lblEngineLvl').innerText = `Lvl ${engineLvl}/5`; document.getElementById('lblNitroLvl').innerText = `Lvl ${nitroLvl}/5`; document.getElementById('lblArmorLvl').innerText = armorLvl > 0 ? `Aktif (${armorLvl} Kalkan)` : 'Devre Dışı'; const engineCost = engineLvl < 5 ? engineLvl * 60 : 'MAX'; const nitroCost = nitroLvl < 5 ? nitroLvl * 50 : 'MAX'; const armorCost = armorLvl < 3 ? (armorLvl + 1) * 100 : 'MAX'; document.getElementById('btnUpgradeEngine').innerText = engineLvl < 5 ? `Yükselt (🪙 ${engineCost})` : 'MAX LEVEL'; document.getElementById('btnUpgradeNitro').innerText = nitroLvl < 5 ? `Yükselt (🪙 ${nitroCost})` : 'MAX LEVEL'; document.getElementById('btnUpgradeArmor').innerText = armorLvl < 3 ? `Kalkan Al (🪙 ${armorCost})` : 'MAX KALKAN'; const btn = document.getElementById('btnSelectOrBuyCar'); const isUnlocked = this.userData.unlockedCars.includes(car.id); const isSelected = this.userData.selectedCarId === car.id; if (isSelected) { btn.innerText = 'SEÇİLDİ ✓'; btn.className = 'btn btn-secondary'; btn.onclick = null; } else if (isUnlocked) { btn.innerText = 'ARACI SEÇ'; btn.className = 'btn btn-primary'; btn.onclick = () => { this.userData.selectedCarId = car.id; this.selectedCar = car; this.saveUserData(); this.renderGarageCar(car); }; } else { btn.innerText = `SATIN AL (🪙 ${car.price})`; btn.className = 'btn btn-primary'; btn.onclick = () => { if (this.userData.totalCoins >= car.price) { this.userData.totalCoins -= car.price; this.userData.unlockedCars.push(car.id); this.userData.selectedCarId = car.id; this.selectedCar = car; this.saveUserData(); this.sound.playCoin(); this.renderGarageCar(car); } else { alert('Yeterli altınınız yok! Bölümleri oynayarak altın kazanın.'); } }; } const strip = document.getElementById('carSelectorStrip'); strip.innerHTML = ''; VEHICLES.forEach(v => { const thumb = document.createElement('div'); const unlocked = this.userData.unlockedCars.includes(v.id); const sel = this.userData.selectedCarId === v.id; thumb.className = `car-thumb ${sel ? 'active' : ''}`; thumb.innerHTML = `${v.name.split(' ')[0]}
${unlocked ? 'Açık' : `🪙 ${v.price}`}`; thumb.onclick = () => this.renderGarageCar(v); strip.appendChild(thumb); }); const gCanvas = document.getElementById('garageCarCanvas'); const gCtx = gCanvas.getContext('2d'); gCtx.clearRect(0, 0, gCanvas.width, gCanvas.height); this.drawCarBody(gCtx, gCanvas.width / 2, gCanvas.height / 2, 46, 84, car.color); } buyUpgrade(type) { const u = this.userData.upgrades; let cost = 0; if (type === 'engine' && u.engineLevel < 5) { cost = u.engineLevel * 60; if (this.userData.totalCoins >= cost) { this.userData.totalCoins -= cost; u.engineLevel++; this.sound.playCoin(); } else { alert('Yeterli altınınız yok!'); } } else if (type === 'nitro' && u.nitroLevel < 5) { cost = u.nitroLevel * 50; if (this.userData.totalCoins >= cost) { this.userData.totalCoins -= cost; u.nitroLevel++; this.sound.playCoin(); } else { alert('Yeterli altınınız yok!'); } } else if (type === 'armor' && u.armorLevel < 3) { cost = (u.armorLevel + 1) * 100; if (this.userData.totalCoins >= cost) { this.userData.totalCoins -= cost; u.armorLevel++; this.sound.playCoin(); } else { alert('Yeterli altınınız yok!'); } } this.saveUserData(); this.renderGarageCar(this.selectedCar); } initGarageView() { const car = VEHICLES.find(c => c.id === this.userData.selectedCarId) || VEHICLES[0]; this.selectedCar = car; } startLevel(levelId) { try { this.currentLevel = LEVELS.find(l => l.id === levelId) || LEVELS[0]; this.selectedCar = VEHICLES.find(c => c.id === this.userData.selectedCarId) || VEHICLES[0]; const engineLvl = this.userData.upgrades.engineLevel || 1; const nitroLvl = this.userData.upgrades.nitroLevel || 1; this.distance = 0; this.score = 0; this.coinsCollected = 0; this.speed = 50; this.maxSpeed = this.selectedCar.speedBase + (engineLvl - 1) * 15; this.nitro = 100; this.hasShield = (this.userData.upgrades.armorLevel || 0) > 0; this.player.currentLane = 1; this.player.x = this.lanes[1]; this.player.targetX = this.lanes[1]; this.traffic = []; this.coins = []; this.particles = []; this.hideModal('menuModal'); this.hideModal('levelModal'); this.hideModal('gameOverModal'); this.hideModal('levelWinModal'); document.getElementById('hudOverlay').classList.remove('hidden'); document.getElementById('touchControls').classList.remove('hidden'); document.getElementById('hudLevelName').innerText = `${this.currentLevel.id}: ${this.currentLevel.name}`; document.getElementById('hudProgressText').innerText = `0 / ${this.currentLevel.goalDistance === 999999 ? '∞' : this.currentLevel.goalDistance + 'm'}`; this.state = 'PLAYING'; this.sound.startEngine(); } catch (err) { console.error('Start level error:', err); } } startLoop() { let lastTime = performance.now(); const loop = (now) => { try { const dt = Math.min((now - lastTime) / 1000, 0.1); lastTime = now; this.update(dt); this.render(); } catch (err) { console.error('Loop error:', err); } requestAnimationFrame(loop); }; requestAnimationFrame(loop); } update(dt) { if (this.state !== 'PLAYING') return; this.player.x += (this.player.targetX - this.player.x) * 18 * dt; const engineLvl = this.userData.upgrades.engineLevel || 1; const accel = this.selectedCar.accelBase + engineLvl * 10; if (this.keys.up) { this.speed = Math.min(this.speed + accel * dt, this.maxSpeed); } else if (this.keys.down) { this.speed = Math.max(this.speed - accel * 1.5 * dt, 20); } else { if (this.speed > 70) this.speed -= 15 * dt; if (this.speed < 70) this.speed += 15 * dt; } const nitroLvl = this.userData.upgrades.nitroLevel || 1; if (this.keys.nitro && this.nitro > 5) { this.speed = Math.min(this.speed + accel * 2 * dt, this.maxSpeed * (1.25 + nitroLvl * 0.05)); this.nitro -= (35 - nitroLvl * 3) * dt; this.sound.playNitro(); this.createNitroParticles(); } else { if (this.nitro < 100) this.nitro += (8 + nitroLvl * 2) * dt; } const distDelta = (this.speed * dt); this.distance += distDelta * 0.2; this.score += Math.floor(distDelta * 0.5); this.roadScroll += this.speed * dt * 8; this.sound.updateEngine(this.speed / this.maxSpeed); if (Math.random() < this.currentLevel.trafficDensity) { this.spawnTraffic(); } if (Math.random() < 0.018) { this.spawnCoin(); } for (let i = this.traffic.length - 1; i >= 0; i--) { const car = this.traffic[i]; car.y += (this.speed - car.speed) * dt * 6; if (!car.nearMissed && Math.abs(car.x - this.player.x) < 48 && Math.abs(car.y - this.player.y) < 65 && car.y < this.player.y) { car.nearMissed = true; this.score += 150; this.sound.playNearMiss(); this.showNearMissBanner(); } if (Math.abs(car.x - this.player.x) < 30 && Math.abs(car.y - this.player.y) < 58) { if (this.hasShield) { this.hasShield = false; this.userData.upgrades.armorLevel = Math.max(0, (this.userData.upgrades.armorLevel || 1) - 1); this.saveUserData(); this.sound.playShield(); this.traffic.splice(i, 1); this.createSparkles(this.player.x, this.player.y); continue; } else { this.handleCrash(); return; } } if (car.y > 700 || car.y < -300) { this.traffic.splice(i, 1); } } for (let i = this.coins.length - 1; i >= 0; i--) { const coin = this.coins[i]; coin.y += this.speed * dt * 6; if (Math.abs(coin.x - this.player.x) < 30 && Math.abs(coin.y - this.player.y) < 38) { this.coinsCollected++; this.score += 100; this.sound.playCoin(); this.createSparkles(coin.x, coin.y); this.coins.splice(i, 1); continue; } if (coin.y > 650) { this.coins.splice(i, 1); } } for (let i = this.particles.length - 1; i >= 0; i--) { const p = this.particles[i]; p.x += p.vx; p.y += p.vy; p.life -= dt; if (p.life <= 0) this.particles.splice(i, 1); } document.getElementById('hudScore').innerText = this.score; document.getElementById('hudCoins').innerText = `🪙 ${this.coinsCollected}`; document.getElementById('hudSpeed').innerText = Math.floor(this.speed); document.getElementById('hudNitroBar').style.width = `${this.nitro}%`; const distMeter = Math.floor(this.distance); const goal = this.currentLevel.goalDistance; document.getElementById('hudProgressText').innerText = `${distMeter} / ${goal === 999999 ? '∞' : goal + 'm'}`; document.getElementById('hudProgressBar').style.width = goal === 999999 ? '100%' : `${Math.min(100, (distMeter / goal) * 100)}%`; if (distMeter >= goal && goal !== 999999) { this.handleLevelWin(); } } spawnTraffic() { const laneIndex = Math.floor(Math.random() * 4); const laneX = this.lanes[laneIndex]; const existingInLane = this.traffic.some(c => Math.abs(c.x - laneX) < 20 && c.y < 0 && c.y > -220); if (existingInLane) return; const carTypes = [ { color: '#e11d48', speed: 55 }, { color: '#f59e0b', speed: 45 }, { color: '#16a34a', speed: 65 } ]; const type = carTypes[Math.floor(Math.random() * carTypes.length)]; this.traffic.push({ x: laneX, y: -100, width: 40, height: 72, color: type.color, speed: type.speed, nearMissed: false }); } spawnCoin() { const laneIndex = Math.floor(Math.random() * 4); this.coins.push({ x: this.lanes[laneIndex], y: -40 }); } createNitroParticles() { for (let i = 0; i < 2; i++) { this.particles.push({ x: this.player.x + (Math.random() - 0.5) * 14, y: this.player.y + 38, vx: (Math.random() - 0.5) * 2, vy: Math.random() * 3 + 3, color: Math.random() > 0.5 ? '#f59e0b' : '#38bdf8', size: Math.random() * 5 + 3, life: 0.25 }); } } createSparkles(x, y) { for (let i = 0; i < 6; i++) { this.particles.push({ x: x, y: y, vx: (Math.random() - 0.5) * 5, vy: (Math.random() - 0.5) * 5, color: '#fbbf24', size: 4, life: 0.35 }); } } showNearMissBanner() { const banner = document.getElementById('hudNearMiss'); if (banner) { banner.classList.remove('hidden'); clearTimeout(this.nearMissTimeout); this.nearMissTimeout = setTimeout(() => banner.classList.add('hidden'), 700); } } handleCrash() { this.state = 'GAMEOVER'; this.sound.playCrash(); for (let i = 0; i < 30; i++) { this.particles.push({ x: this.player.x, y: this.player.y, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8, color: Math.random() > 0.5 ? '#ef4444' : '#f59e0b', size: Math.random() * 6 + 4, life: 0.6 }); } this.userData.totalCoins += this.coinsCollected; this.saveUserData(); document.getElementById('goDistance').innerText = `${Math.floor(this.distance)}m`; document.getElementById('goCoins').innerText = `🪙 ${this.coinsCollected}`; document.getElementById('goScore').innerText = this.score; setTimeout(() => { document.getElementById('hudOverlay').classList.add('hidden'); document.getElementById('touchControls').classList.add('hidden'); this.showModal('gameOverModal'); }, 400); } handleLevelWin() { this.state = 'WIN'; this.sound.playWin(); let stars = 1; if (this.score >= this.currentLevel.starScores[1]) stars = 2; if (this.score >= this.currentLevel.starScores[2]) stars = 3; const currentStars = this.userData.levelStars[this.currentLevel.id] || 0; if (stars > currentStars) { this.userData.levelStars[this.currentLevel.id] = stars; } const rewardCoins = 100 + stars * 50; this.userData.totalCoins += this.coinsCollected + rewardCoins; const nextLevelId = this.currentLevel.id + 1; if (nextLevelId <= 3 && !this.userData.unlockedLevels.includes(nextLevelId)) { this.userData.unlockedLevels.push(nextLevelId); } this.saveUserData(); const starsDisplay = document.getElementById('winStarsDisplay'); starsDisplay.innerHTML = '⭐'.repeat(stars) + '☆'.repeat(3 - stars); document.getElementById('winRewardCoins').innerText = `🪙 +${rewardCoins}`; document.getElementById('winScore').innerText = this.score; document.getElementById('hudOverlay').classList.add('hidden'); document.getElementById('touchControls').classList.add('hidden'); this.showModal('levelWinModal'); } render() { this.ctx.clearRect(0, 0, 800, 600); this.drawEnvironment(); this.drawRoad(); this.drawCoins(); this.drawTraffic(); this.drawPlayer(); this.drawParticles(); } drawEnvironment() { const theme = this.currentLevel.bgTheme; if (theme === 'day') { this.ctx.fillStyle = '#15803d'; } else if (theme === 'rain') { this.ctx.fillStyle = '#0f172a'; } else { this.ctx.fillStyle = '#2e1065'; } this.ctx.fillRect(0, 0, 800, 600); } drawRoad() { this.ctx.fillStyle = this.currentLevel.asphaltColor; this.ctx.fillRect(180, 0, 440, 600); this.ctx.fillStyle = '#f59e0b'; this.ctx.fillRect(185, 0, 6, 600); this.ctx.fillRect(609, 0, 6, 600); this.ctx.fillStyle = '#ffffff'; const offset = this.roadScroll % 50; const dividers = [290, 390, 490]; for (let x of dividers) { for (let y = -50 + offset; y < 600; y += 50) { this.ctx.fillRect(x - 2, y, 4, 26); } } } drawPlayer() { this.drawCarBody(this.ctx, this.player.x, this.player.y, this.player.width, this.player.height, this.selectedCar.color); if (this.hasShield) { this.ctx.save(); this.ctx.strokeStyle = '#38bdf8'; this.ctx.lineWidth = 3; this.ctx.beginPath(); this.ctx.arc(this.player.x, this.player.y, 48, 0, Math.PI * 2); this.ctx.stroke(); this.ctx.restore(); } } drawTraffic() { this.traffic.forEach(car => { this.drawCarBody(this.ctx, car.x, car.y, car.width, car.height, car.color); }); } drawCoins() { this.ctx.save(); this.coins.forEach(c => { this.ctx.fillStyle = '#fbbf24'; this.ctx.strokeStyle = '#d97706'; this.ctx.lineWidth = 3; this.ctx.beginPath(); this.ctx.arc(c.x, c.y, 13, 0, Math.PI * 2); this.ctx.fill(); this.ctx.stroke(); this.ctx.fillStyle = '#92400e'; this.ctx.font = 'bold 12px sans-serif'; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.fillText('$', c.x, c.y); }); this.ctx.restore(); } drawParticles() { this.particles.forEach(p => { this.ctx.fillStyle = p.color; this.ctx.beginPath(); this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); this.ctx.fill(); }); } drawCarBody(ctx, x, y, width, height, color) { ctx.save(); ctx.translate(x, y); ctx.fillStyle = '#0f172a'; ctx.fillRect(-width / 2 - 4, -height / 2 + 10, 6, 16); ctx.fillRect(width / 2 - 2, -height / 2 + 10, 6, 16); ctx.fillRect(-width / 2 - 4, height / 2 - 24, 6, 16); ctx.fillRect(width / 2 - 2, height / 2 - 24, 6, 16); ctx.fillStyle = color; drawRoundRect(ctx, -width / 2, -height / 2, width, height, 8); ctx.fill(); ctx.fillStyle = '#0f172a'; drawRoundRect(ctx, -width / 2 + 4, -height / 2 + 16, width - 8, height - 34, 5); ctx.fill(); ctx.fillStyle = '#38bdf8'; drawRoundRect(ctx, -width / 2 + 6, -height / 2 + 18, width - 12, 14, 3); ctx.fill(); ctx.fillStyle = '#fef08a'; ctx.fillRect(-width / 2 + 4, -height / 2 + 2, 8, 4); ctx.fillRect(width / 2 - 12, -height / 2 + 2, 8, 4); ctx.fillStyle = '#ef4444'; ctx.fillRect(-width / 2 + 4, height / 2 - 6, 8, 4); ctx.fillRect(width / 2 - 12, height / 2 - 6, 8, 4); ctx.restore(); } } window.addEventListener('DOMContentLoaded', () => { window.gameInstance = new Game(); }); })();