Spaces:
Running
Running
| import gradio as gr | |
| demo = gr.Blocks() | |
| with demo: | |
| gr.HTML("""<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Hugging Face Temple Run Mobile/Desktop - Tap/Space to Jump</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background: #87ceeb; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| touch-action: none; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100vw !important; | |
| height: 100vh !important; | |
| } | |
| #game-over-message { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 8vw; | |
| color: white; | |
| background: rgba(0, 0, 0, 0.7); | |
| padding: 5vw; | |
| border-radius: 2vw; | |
| display: none; | |
| font-family: Arial, sans-serif; | |
| text-align: center; | |
| z-index: 10; | |
| } | |
| #gpu-counter { | |
| position: absolute; | |
| top: 2vw; | |
| left: 2vw; | |
| font-size: 6vw; | |
| color: white; | |
| background: rgba(0, 0, 0, 0.7); | |
| padding: 2vw; | |
| border-radius: 1vw; | |
| font-family: Arial, sans-serif; | |
| z-index: 10; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-over-message">Game Over!</div> | |
| <div id="gpu-counter">Score: 0</div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> | |
| <script> | |
| // Scene setup | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| // Lighting | |
| const directionalLight = new THREE.DirectionalLight(0xffe0b2, 1); | |
| directionalLight.position.set(1, 1, 0.5).normalize(); | |
| scene.add(directionalLight); | |
| const ambientLight = new THREE.AmbientLight(0x87ceeb, 0.5); | |
| scene.add(ambientLight); | |
| // Background | |
| scene.background = new THREE.Color(0x87ceeb); | |
| scene.fog = new THREE.Fog(0x87ceeb, 10, 100); | |
| // Ground | |
| const groundGeometry = new THREE.PlaneGeometry(12, 1000); | |
| const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x8c8c8c }); | |
| const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
| ground.rotation.x = -Math.PI / 2; | |
| ground.position.z = -500; | |
| scene.add(ground); | |
| // Player | |
| const emojiCanvas = document.createElement('canvas'); | |
| emojiCanvas.width = 256; | |
| emojiCanvas.height = 256; | |
| const emojiCtx = emojiCanvas.getContext('2d'); | |
| emojiCtx.font = '128px Arial'; | |
| emojiCtx.textAlign = 'center'; | |
| emojiCtx.textBaseline = 'middle'; | |
| emojiCtx.fillText('🤗', 128, 128); | |
| const emojiTexture = new THREE.CanvasTexture(emojiCanvas); | |
| emojiTexture.flipY = false; // Ensure correct orientation | |
| const faceGeometry = new THREE.SphereGeometry(0.8, 32, 32); | |
| const faceMaterial = new THREE.MeshLambertMaterial({ map: emojiTexture }); | |
| const face = new THREE.Mesh(faceGeometry, faceMaterial); | |
| face.position.set(0, 0.8, 0); | |
| scene.add(face); | |
| face.velocity = new THREE.Vector3(0, 0, -12); | |
| face.lane = 0; // -1, 0, 1 | |
| face.jumping = false; | |
| // Ensure initial rotation faces the user (backward, toward camera) | |
| face.rotation.y = Math.PI; // Rotate 180 degrees around y-axis to face backward | |
| // Collectibles | |
| const gpuCanvas = document.createElement('canvas'); | |
| gpuCanvas.width = 128; | |
| gpuCanvas.height = 128; | |
| const gpuCtx = gpuCanvas.getContext('2d'); | |
| gpuCtx.font = '48px Arial'; | |
| gpuCtx.fillStyle = 'white'; | |
| gpuCtx.textAlign = 'center'; | |
| gpuCtx.textBaseline = 'middle'; | |
| gpuCtx.fillText('GPU', 64, 64); | |
| const gpuTexture = new THREE.CanvasTexture(gpuCanvas); | |
| const gpuSprites = []; | |
| let gpuScore = 0; | |
| // Obstacles | |
| const obstacles = []; | |
| const obstacleGeometry = new THREE.BoxGeometry(2, 2, 2); | |
| const obstacleMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); | |
| // Audio setup | |
| const listener = new THREE.AudioListener(); | |
| camera.add(listener); | |
| // Background music | |
| const bgSound = new THREE.Audio(listener); | |
| const audioLoader = new THREE.AudioLoader(); | |
| audioLoader.load('https://threejs.org/examples/sounds/358232_j_s_song.mp3', function(buffer) { | |
| bgSound.setBuffer(buffer); | |
| bgSound.setLoop(true); | |
| bgSound.setVolume(0.2); | |
| bgSound.play(); // Attempt to play immediately | |
| }); | |
| // Game state | |
| const clock = new THREE.Clock(); | |
| const gpuCounter = document.getElementById('gpu-counter'); | |
| const gameOverMessage = document.getElementById('game-over-message'); | |
| let gameOver = false; | |
| // Keyboard and touch controls | |
| const keys = {}; | |
| let touchStartX = 0; | |
| let touchMoved = false; | |
| const swipeThreshold = 30; | |
| // Keyboard event listeners (for desktop) | |
| window.addEventListener('keydown', (e) => { | |
| if (!gameOver) keys[e.key] = true; | |
| }); | |
| window.addEventListener('keyup', (e) => { | |
| keys[e.key] = false; | |
| }); | |
| // Touch event listeners (for mobile) | |
| document.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| touchStartX = e.touches[0].clientX; | |
| touchMoved = false; | |
| if (!gameOver && !face.jumping) { | |
| face.jumping = true; | |
| face.velocity.y = 15; // Tap to jump | |
| } | |
| }, { passive: false }); | |
| document.addEventListener('touchmove', (e) => { | |
| e.preventDefault(); | |
| if (gameOver) return; | |
| touchMoved = true; | |
| const deltaX = e.touches[0].clientX - touchStartX; | |
| if (Math.abs(deltaX) > swipeThreshold) { | |
| if (deltaX > 0 && face.lane < 1) face.lane++; // Swipe right | |
| else if (deltaX < 0 && face.lane > -1) face.lane--; // Swipe left | |
| touchStartX = e.touches[0].clientX; | |
| } | |
| }, { passive: false }); | |
| document.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| }, { passive: false }); | |
| // Reset game function | |
| function resetGame() { | |
| gameOver = false; | |
| gameOverMessage.style.display = 'none'; | |
| face.position.set(0, 0.8, 0); | |
| face.velocity.set(0, 0, -12); | |
| face.lane = 0; | |
| face.jumping = false; | |
| face.rotation.y = Math.PI; // Reset rotation to face user | |
| gpuScore = 0; | |
| gpuCounter.textContent = `Score: ${gpuScore}`; | |
| gpuSprites.forEach(gpu => scene.remove(gpu)); | |
| obstacles.forEach(obstacle => scene.remove(obstacle)); | |
| gpuSprites.length = 0; | |
| obstacles.length = 0; | |
| bgSound.play(); // Restart music | |
| } | |
| // Spawn items | |
| function spawnItems() { | |
| if (Math.random() < 0.05) { // GPU | |
| const spriteMaterial = new THREE.SpriteMaterial({ map: gpuTexture }); | |
| const sprite = new THREE.Sprite(spriteMaterial); | |
| sprite.scale.set(1.5, 1.5, 1); | |
| const lane = Math.floor(Math.random() * 3) - 1; | |
| sprite.position.set(lane * 3, 0.75, face.position.z - 40); | |
| scene.add(sprite); | |
| gpuSprites.push(sprite); | |
| } | |
| if (Math.random() < 0.03) { // Obstacle | |
| const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial); | |
| const lane = Math.floor(Math.random() * 3) - 1; | |
| obstacle.position.set(lane * 3, 1, face.position.z - 40); | |
| scene.add(obstacle); | |
| obstacles.push(obstacle); | |
| } | |
| } | |
| // Handle controls in animation loop | |
| function handleControls(deltaTime) { | |
| if (!gameOver) { | |
| // Keyboard controls (desktop) | |
| if (keys['ArrowLeft'] && face.lane > -1) face.lane--; | |
| if (keys['ArrowRight'] && face.lane < 1) face.lane++; | |
| if ((keys[' '] || keys['ArrowUp']) && !face.jumping) { | |
| face.jumping = true; | |
| face.velocity.y = 15; // Spacebar or Up Arrow to jump | |
| } | |
| // Ensure lane position is applied | |
| face.position.x = face.lane * 3; | |
| } | |
| } | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const deltaTime = clock.getDelta(); | |
| if (!gameOver) { | |
| // Move player | |
| face.position.z += face.velocity.z * deltaTime; | |
| face.position.x = face.lane * 3; // Snap to lane | |
| if (face.jumping) { | |
| face.position.y += face.velocity.y * deltaTime; | |
| face.velocity.y -= 30 * deltaTime; // Gravity | |
| if (face.position.y <= 0.8) { | |
| face.position.y = 0.8; | |
| face.jumping = false; | |
| face.velocity.y = 0; | |
| } | |
| } | |
| // Face the user (camera) explicitly | |
| const targetPosition = camera.position.clone(); | |
| targetPosition.y = face.position.y; // Match height to avoid tilting | |
| face.lookAt(targetPosition); | |
| // Handle controls | |
| handleControls(deltaTime); | |
| // Spawn items | |
| spawnItems(); | |
| // Update collectibles | |
| gpuSprites.forEach((gpu, index) => { | |
| gpu.position.z += 12 * deltaTime; | |
| if (face.position.distanceTo(gpu.position) < 1.5) { | |
| scene.remove(gpu); | |
| gpuSprites.splice(index, 1); | |
| gpuScore++; | |
| gpuCounter.textContent = `Score: ${gpuScore}`; | |
| } | |
| if (gpu.position.z > face.position.z + 10) { | |
| scene.remove(gpu); | |
| gpuSprites.splice(index, 1); | |
| } | |
| }); | |
| // Update obstacles | |
| obstacles.forEach((obstacle, index) => { | |
| obstacle.position.z += 12 * deltaTime; | |
| if (face.position.distanceTo(obstacle.position) < 1.5) { | |
| gameOver = true; | |
| gameOverMessage.style.display = 'block'; | |
| bgSound.stop(); // Stop music on game over | |
| setTimeout(resetGame, 1000); // Reset after 1-second delay | |
| } | |
| if (obstacle.position.z > face.position.z + 10) { | |
| scene.remove(obstacle); | |
| obstacles.splice(index, 1); | |
| } | |
| }); | |
| // Update camera | |
| camera.position.set(face.position.x, face.position.y + 4, face.position.z + 8); | |
| camera.lookAt(face.position); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| // Fullscreen on mobile | |
| document.body.addEventListener('touchstart', () => { | |
| if (document.documentElement.requestFullscreen) { | |
| document.documentElement.requestFullscreen(); | |
| } | |
| }, { once: true }); | |
| animate(); | |
| // Handle resize/orientation change | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| </script> | |
| </body> | |
| </html>""") | |
| demo.launch() |