Spaces:
Running
Running
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>بازی ماشینی سرعتی (Top Down)</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700;900&display=swap'); | |
| :root { | |
| --primary-color: #00f2ff; | |
| --accent-color: #ff0055; | |
| --road-color: #2a2a2a; | |
| --grass-color: #1a4a1a; | |
| --ui-bg: rgba(0, 0, 0, 0.85); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| user-select: none; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| background-color: #0f0f0f; | |
| color: white; | |
| font-family: 'Vazirmatn', sans-serif; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100vh; | |
| } | |
| /* Header & Credit */ | |
| header { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| padding: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| z-index: 100; | |
| pointer-events: none; | |
| } | |
| .game-title { | |
| font-size: 1.5rem; | |
| font-weight: 900; | |
| text-transform: uppercase; | |
| color: var(--primary-color); | |
| text-shadow: 0 0 10px var(--primary-color); | |
| } | |
| .credit-link { | |
| pointer-events: auto; | |
| font-size: 0.8rem; | |
| color: #aaa; | |
| text-decoration: none; | |
| background: rgba(255,255,255,0.1); | |
| padding: 5px 10px; | |
| border-radius: 15px; | |
| transition: all 0.3s ease; | |
| } | |
| .credit-link:hover { | |
| background: var(--primary-color); | |
| color: #000; | |
| box-shadow: 0 0 15px var(--primary-color); | |
| } | |
| /* Game Container */ | |
| #game-container { | |
| position: relative; | |
| width: 100%; | |
| max-width: 450px; | |
| height: 100vh; | |
| max-height: 800px; | |
| background-color: var(--road-color); | |
| overflow: hidden; | |
| box-shadow: 0 0 50px rgba(0,0,0,0.5); | |
| border-left: 5px solid #111; | |
| border-right: 5px solid #111; | |
| } | |
| /* Road Markings */ | |
| .road-line { | |
| position: absolute; | |
| width: 10px; | |
| height: 100px; | |
| background: rgba(255, 255, 255, 0.5); | |
| left: 50%; | |
| transform: translateX(-50%); | |
| animation: moveRoad 1s linear infinite; | |
| } | |
| @keyframes moveRoad { | |
| 0% { top: -100px; } | |
| 100% { top: 100%; } | |
| } | |
| /* Player Car */ | |
| #player-car { | |
| position: absolute; | |
| bottom: 100px; | |
| left: 50%; | |
| width: 50px; | |
| height: 90px; | |
| background: linear-gradient(180deg, #ffcc00, #ff9900); | |
| border-radius: 10px; | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.5); | |
| z-index: 10; | |
| transition: left 0.1s ease-out; | |
| transform: translateX(-50%); | |
| } | |
| /* Car Details (Windshield, Lights) */ | |
| #player-car::before { | |
| content: ''; | |
| position: absolute; | |
| top: 20%; | |
| left: 10%; | |
| width: 80%; | |
| height: 20%; | |
| background: #333; | |
| border-radius: 2px; | |
| } | |
| #player-car::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 5%; | |
| width: 90%; | |
| height: 10px; | |
| background: #ff3333; | |
| box-shadow: 0 0 10px #ff0000; | |
| border-radius: 5px 5px 0 0; | |
| } | |
| /* Enemy Cars */ | |
| .enemy { | |
| position: absolute; | |
| width: 50px; | |
| height: 90px; | |
| border-radius: 10px; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.5); | |
| z-index: 9; | |
| top: -100px; | |
| } | |
| .enemy::before { | |
| content: ''; | |
| position: absolute; | |
| bottom: 20%; | |
| left: 10%; | |
| width: 80%; | |
| height: 20%; | |
| background: #222; | |
| border-radius: 2px; | |
| } | |
| /* UI Overlays */ | |
| .overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: var(--ui-bg); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 50; | |
| backdrop-filter: blur(5px); | |
| transition: opacity 0.3s; | |
| } | |
| .hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| margin-bottom: 10px; | |
| color: var(--primary-color); | |
| text-shadow: 2px 2px 0px var(--accent-color); | |
| text-align: center; | |
| } | |
| p { | |
| font-size: 1.2rem; | |
| margin-bottom: 30px; | |
| color: #ddd; | |
| text-align: center; | |
| max-width: 80%; | |
| } | |
| .btn { | |
| background: var(--accent-color); | |
| color: white; | |
| padding: 15px 40px; | |
| font-size: 1.5rem; | |
| font-family: 'Vazirmatn', sans-serif; | |
| border: none; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| box-shadow: 0 0 20px var(--accent-color); | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| font-weight: 700; | |
| } | |
| .btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 30px var(--accent-color); | |
| } | |
| .btn:active { | |
| transform: scale(0.95); | |
| } | |
| /* HUD (Heads Up Display) */ | |
| #hud { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| right: 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| pointer-events: none; | |
| z-index: 20; | |
| } | |
| .stat-box { | |
| background: rgba(0,0,0,0.6); | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| border: 1px solid rgba(255,255,255,0.2); | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| color: white; | |
| } | |
| #score-display span, #speed-display span { | |
| color: var(--primary-color); | |
| } | |
| /* Mobile Controls */ | |
| #mobile-controls { | |
| position: absolute; | |
| bottom: 20px; | |
| width: 100%; | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 0 20px; | |
| z-index: 40; | |
| pointer-events: none; /* Let clicks pass through empty space */ | |
| } | |
| .control-btn { | |
| width: 80px; | |
| height: 80px; | |
| background: rgba(255, 255, 255, 0.15); | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 2rem; | |
| color: white; | |
| pointer-events: auto; | |
| transition: background 0.2s; | |
| touch-action: manipulation; | |
| } | |
| .control-btn:active { | |
| background: rgba(255, 255, 255, 0.4); | |
| } | |
| .control-btn.left:active { border-color: var(--primary-color); } | |
| .control-btn.right:active { border-color: var(--primary-color); } | |
| /* Responsive adjustments */ | |
| @media (min-width: 768px) { | |
| #mobile-controls { display: none; } /* Hide touch controls on desktop */ | |
| h1 { font-size: 4rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="game-title">بازی ماشینی 🏎️</div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="credit-link">Built with anycoder</a> | |
| </header> | |
| <div id="game-container"> | |
| <!-- Road Lines Animation --> | |
| <div class="road-line" style="animation-delay: 0s;"></div> | |
| <div class="road-line" style="animation-delay: 0.5s;"></div> | |
| <!-- HUD --> | |
| <div id="hud"> | |
| <div class="stat-box" id="score-display">امتیاز: <span id="score">0</span></div> | |
| <div class="stat-box" id="speed-display">سرعت: <span id="speed">100</span> کیلومتر</div> | |
| </div> | |
| <!-- Player Car --> | |
| <div id="player-car"></div> | |
| <!-- Start Screen --> | |
| <div id="start-screen" class="overlay"> | |
| <h1>تک سواست</h1> | |
| <p>از موانع عبور کنید و امتیاز بگیرید.<br>برای حرکت از کلیدهای جهتنما یا دکمههای صفحه استفاده کنید.</p> | |
| <button class="btn" onclick="startGame()">شروع بازی</button> | |
| </div> | |
| <!-- Game Over Screen --> | |
| <div id="game-over-screen" class="overlay hidden"> | |
| <h1 style="color: var(--accent-color);">تصادف کردید!</h1> | |
| <p>امتیاز نهایی شما: <span id="final-score" style="color: var(--primary-color); font-weight: bold;">0</span></p> | |
| <button class="btn" onclick="resetGame()">بازی مجدد</button> | |
| </div> | |
| <!-- Mobile Controls --> | |
| <div id="mobile-controls"> | |
| <div class="control-btn left" id="btn-left">◀</div> | |
| <div class="control-btn right" id="btn-right">▶</div> | |
| </div> | |
| </div> | |
| <script> | |
| // --- Game Configuration & State --- | |
| const gameContainer = document.getElementById('game-container'); | |
| const playerCar = document.getElementById('player-car'); | |
| const scoreEl = document.getElementById('score'); | |
| const finalScoreEl = document.getElementById('final-score'); | |
| const speedEl = document.getElementById('speed'); | |
| const startScreen = document.getElementById('start-screen'); | |
| const gameOverScreen = document.getElementById('game-over-screen'); | |
| let gameActive = false; | |
| let score = 0; | |
| let gameSpeed = 5; | |
| let lane = 1; // 0: Left, 1: Center, 2: Right (approximate percentages) | |
| let playerX = 50; // Percentage | |
| let enemies = []; | |
| let roadLines = []; | |
| let animationId; | |
| let spawnInterval; | |
| let speedInterval; | |
| // Constants | |
| const CONTAINER_WIDTH = 450; // Max width in pixels | |
| const CAR_WIDTH = 50; | |
| const LANE_WIDTH_PERCENT = 33.33; | |
| const BASE_SPEED = 5; | |
| const MAX_SPEED = 20; | |
| // --- Input Handling --- | |
| // Keyboard | |
| document.addEventListener('keydown', (e) => { | |
| if (!gameActive) return; | |
| if (e.key === 'ArrowLeft' || e.key === 'a') moveCar(-1); | |
| if (e.key === 'ArrowRight' || e.key === 'd') moveCar(1); | |
| }); | |
| // Touch / Mouse for Mobile Controls | |
| const btnLeft = document.getElementById('btn-left'); | |
| const btnRight = document.getElementById('btn-right'); | |
| const handleInputStart = (direction) => { | |
| if (!gameActive) return; | |
| moveCar(direction); | |
| }; | |
| // Add touch listeners | |
| btnLeft.addEventListener('touchstart', (e) => { e.preventDefault(); handleInputStart(-1); }); | |
| btnRight.addEventListener('touchstart', (e) => { e.preventDefault(); handleInputStart(1); }); | |
| // Add mouse listeners for testing on desktop with mouse | |
| btnLeft.addEventListener('mousedown', () => handleInputStart(-1)); | |
| btnRight.addEventListener('mousedown', () => handleInputStart(1)); | |
| function moveCar(direction) { | |
| // Lane logic: 15% (Left), 50% (Center), 85% (Right) | |
| if (direction === -1 && playerX > 15) { | |
| playerX -= 35; | |
| } else if (direction === 1 && playerX < 85) { | |
| playerX += 35; | |
| } | |
| playerCar.style.left = `${playerX}%`; | |
| } | |
| // --- Game Logic --- | |
| function startGame() { | |
| startScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| // Reset State | |
| gameActive = true; | |
| score = 0; | |
| gameSpeed = BASE_SPEED; | |
| enemies.forEach(enemy => enemy.remove()); | |
| enemies = []; | |
| playerX = 50; | |
| playerCar.style.left = '50%'; | |
| updateHUD(); | |
| // Start Loops | |
| gameLoop(); | |
| spawnEnemies(); | |
| increaseSpeed(); | |
| } | |
| function resetGame() { | |
| startGame(); | |
| } | |
| function gameOver() { | |
| gameActive = false; | |
| cancelAnimationFrame(animationId); | |
| clearInterval(spawnInterval); | |
| clearInterval(speedInterval); | |
| finalScoreEl.innerText = score; | |
| gameOverScreen.classList.remove('hidden'); | |
| // Simple crash effect | |
| playerCar.style.background = 'linear-gradient(180deg, #555, #000)'; | |
| playerCar.style.transform = 'translateX(-50%) rotate(15deg)'; | |
| } | |
| function spawnEnemies() { | |
| spawnInterval = setInterval(() => { | |
| if (!gameActive) return; | |
| const enemy = document.createElement('div'); | |
| enemy.classList.add('enemy'); | |
| // Random Color for enemies | |
| const colors = ['#ff0055', '#00f2ff', '#cc00ff', '#00ff66', '#ff9900']; | |
| const randomColor = colors[Math.floor(Math.random() * colors.length)]; | |
| enemy.style.background = `linear-gradient(180deg, ${randomColor}, #000)`; | |
| // Random Lane (15, 50, 85) | |
| const lanes = [15, 50, 85]; | |
| const randomLane = lanes[Math.floor(Math.random() * lanes.length)]; | |
| enemy.style.left = `${randomLane}%`; | |
| enemy.style.top = '-100px'; | |
| // Store data for movement | |
| enemy.dataset.speed = gameSpeed * 1.2; // Enemies move slightly faster than road relative to player | |
| enemy.dataset.lane = randomLane; | |
| gameContainer.appendChild(enemy); | |
| enemies.push(enemy); | |
| }, 1000 - (score * 0.005)); // Spawn faster as score increases (min 400ms) | |
| // Ensure minimum spawn time | |
| if (spawnInterval) { | |
| // This is a bit tricky with setInterval, so we just let it run or clear/restart | |
| // For simplicity in this demo, we'll keep the interval constant or slightly dynamic | |
| // Let's just use a timeout recursion for better control | |
| spawnEnemyRecursive(); | |
| clearInterval(spawnInterval); | |
| } | |
| } | |
| function spawnEnemyRecursive() { | |
| if (!gameActive) return; | |
| const enemy = document.createElement('div'); | |
| enemy.classList.add('enemy'); | |
| const colors = ['#ff0055', '#00f2ff', '#cc00ff', '#00ff66', '#ff9900']; | |
| const randomColor = colors[Math.floor(Math.random() * colors.length)]; | |
| enemy.style.background = `linear-gradient(180deg, ${randomColor}, #000)`; | |
| const lanes = [15, 50, 85]; | |
| const randomLane = lanes[Math.floor(Math.random() * lanes.length)]; | |
| // Prevent spawning on top of another car | |
| const tooClose = enemies.some(e => Math.abs(parseInt(e.style.top) - (-100)) < 150 && Math.abs(parseInt(e.style.left) - randomLane) < 5); | |
| if (!tooClose) { | |
| enemy.style.left = `${randomLane}%`; | |
| enemy.style.top = '-100px'; | |
| enemy.style.zIndex = 9; | |
| gameContainer.appendChild(enemy); | |
| enemies.push(enemy); | |
| } | |
| // Difficulty scaling for spawn rate | |
| let spawnRate = 1000 - (score * 0.01); | |
| if (spawnRate < 400) spawnRate = 400; | |
| setTimeout(spawnEnemyRecursive, spawnRate); | |
| } | |
| function increaseSpeed() { | |
| speedInterval = setInterval(() => { | |
| if (!gameActive) return; | |
| if (gameSpeed < MAX_SPEED) { | |
| gameSpeed += 0.5; | |
| updateHUD(); | |
| } | |
| }, 5000); | |
| } | |
| function gameLoop() { | |
| if (!gameActive) return; | |
| // Move Road Lines (Visual effect) | |
| const roadLines = document.querySelectorAll('.road-line'); | |
| roadLines.forEach(line => { | |
| let top = parseFloat(getComputedStyle(line).top); | |
| if (top > 100) { | |
| line.style.top = '-100px'; | |
| } | |
| line.style.top = `${top + gameSpeed * 2}px`; | |
| }); | |
| // Move Enemies | |
| enemies.forEach((enemy, index) => { | |
| let currentTop = parseFloat(getComputedStyle(enemy).top); | |
| let speed = gameSpeed * 1.5; // Relative speed | |
| // Move enemy down | |
| let newTop = currentTop + speed; | |
| enemy.style.top = `${newTop}px`; | |
| // Collision Detection | |
| if (checkCollision(playerCar, enemy)) { | |
| gameOver(); | |
| return; | |
| } | |
| // Remove if off screen | |
| if (newTop > gameContainer.offsetHeight) { | |
| enemy.remove(); | |
| enemies.splice(index, 1); | |
| score += 10; | |
| updateHUD(); | |
| } | |
| }); | |
| animationId = requestAnimationFrame(gameLoop); | |
| } | |
| function checkCollision(player, enemy) { | |
| const pRect = player.getBoundingClientRect(); | |
| const eRect = enemy.getBoundingClientRect(); | |
| // Shrink hitbox slightly for better gameplay feel | |
| const padding = 5; | |
| return !( | |
| pRect.top + padding > eRect.bottom - padding || | |
| pRect.bottom - padding < eRect.top + padding || | |
| pRect.right - padding < eRect.left + padding || | |
| pRect.left + padding > eRect.right - padding | |
| ); | |
| } | |
| function updateHUD() { | |
| scoreEl.innerText = score; | |
| speedEl.innerText = Math.floor(100 + (gameSpeed * 10)); | |
| // Dynamic color for speed | |
| if (gameSpeed > 15) speedEl.style.color = '#ff0055'; | |
| else if (gameSpeed > 10) speedEl.style.color = '#ffcc00'; | |
| else speedEl.style.color = '#00f2ff'; | |
| } | |
| // Initialize Road Lines | |
| function initRoadLines() { | |
| for(let i=0; i<5; i++) { | |
| const line = document.createElement('div'); | |
| line.classList.add('road-line'); | |
| line.style.top = `${i * 25}%`; | |
| gameContainer.appendChild(line); | |
| roadLines.push(line); | |
| } | |
| } | |
| initRoadLines(); | |
| </script> | |
| </body> | |
| </html> |