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"> | |
<title>Hugging Face Temple Run</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { display: block; } | |
#game-over-message { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
font-size: 48px; | |
color: white; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 20px; | |
display: none; | |
font-family: Arial, sans-serif; | |
} | |
#gpu-counter { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
font-size: 24px; | |
color: white; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 10px; | |
font-family: Arial, sans-serif; | |
} | |
#controls { | |
position: absolute; | |
top: 0; | |
right: 0; | |
width: 200px; | |
font-size: 16px; | |
color: white; | |
background: rgba(0, 0, 0, 0.8); | |
padding: 15px; | |
font-family: Arial, sans-serif; | |
text-align: left; | |
height: 100%; | |
box-sizing: border-box; | |
} | |
#controls p { margin: 5px 0; } | |
#controls strong { color: #ffcc00; } | |
</style> | |
</head> | |
<body> | |
<div id="game-over-message">Game Over!<br>Click to Restart</div> | |
<div id="gpu-counter">GPUs Collected: 0</div> | |
<div id="controls"> | |
<p><strong>Controls:</strong></p> | |
<p>A / Left / Swipe Left: Move Left</p> | |
<p>D / Right / Swipe Right: Move Right</p> | |
<p>Space / Swipe Up: Jump</p> | |
<p>Avoid obstacles, collect GPUs!</p> | |
</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); | |
// Morning 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 (endless path) | |
const groundGeometry = new THREE.PlaneGeometry(20, 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; // Extend far into distance | |
scene.add(ground); | |
// Player (Hugging Face Emoji) | |
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); | |
const faceGeometry = new THREE.SphereGeometry(1, 32, 32); | |
const faceMaterial = new THREE.MeshLambertMaterial({ map: emojiTexture }); | |
const face = new THREE.Mesh(faceGeometry, faceMaterial); | |
face.position.set(0, 1, 0); | |
scene.add(face); | |
face.velocity = new THREE.Vector3(0, 0, -10); // Constant forward speed | |
face.lane = 0; // -1 (left), 0 (center), 1 (right) | |
face.jumping = false; | |
// Collectibles (GPUs) | |
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 collectedGPUs = 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); | |
const collectSound = new THREE.PositionalAudio(listener); | |
face.add(collectSound); | |
const audioLoader = new THREE.AudioLoader(); | |
audioLoader.load('https://threejs.org/examples/sounds/376737_Skullbeatz___Bad_Cat_Maste.mp3', function(buffer) { | |
collectSound.setBuffer(buffer); | |
collectSound.setRefDistance(10); | |
collectSound.setVolume(0.5); | |
}); | |
// Clock and 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 = {}; | |
window.addEventListener('keydown', (e) => { if (!gameOver) keys[e.key] = true; }); | |
window.addEventListener('keyup', (e) => { keys[e.key] = false; }); | |
let touchStartX = 0; | |
let touchStartY = 0; | |
window.addEventListener('touchstart', (e) => { | |
touchStartX = e.touches[0].clientX; | |
touchStartY = e.touches[0].clientY; | |
}); | |
window.addEventListener('touchmove', (e) => { | |
if (gameOver) return; | |
const deltaX = e.touches[0].clientX - touchStartX; | |
const deltaY = e.touches[0].clientY - touchStartY; | |
if (Math.abs(deltaX) > 50) { | |
if (deltaX > 0 && face.lane < 1) face.lane++; | |
else if (deltaX < 0 && face.lane > -1) face.lane--; | |
touchStartX = e.touches[0].clientX; | |
} | |
if (deltaY < -50 && !face.jumping) { | |
face.jumping = true; | |
face.velocity.y = 15; | |
touchStartY = e.touches[0].clientY; | |
} | |
}); | |
// Spawn collectibles and obstacles | |
function spawnItems() { | |
if (Math.random() < 0.05) { // Spawn GPU | |
const spriteMaterial = new THREE.SpriteMaterial({ map: gpuTexture }); | |
const sprite = new THREE.Sprite(spriteMaterial); | |
sprite.scale.set(2, 2, 1); | |
const lane = Math.floor(Math.random() * 3) - 1; | |
sprite.position.set(lane * 4, 1, face.position.z - 50); | |
scene.add(sprite); | |
gpuSprites.push(sprite); | |
} | |
if (Math.random() < 0.03) { // Spawn obstacle | |
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial); | |
const lane = Math.floor(Math.random() * 3) - 1; | |
obstacle.position.set(lane * 4, 1, face.position.z - 50); | |
scene.add(obstacle); | |
obstacles.push(obstacle); | |
} | |
} | |
// 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 * 4; // Snap to lane | |
if (face.jumping) { | |
face.position.y += face.velocity.y * deltaTime; | |
face.velocity.y -= 30 * deltaTime; // Gravity | |
if (face.position.y <= 1) { | |
face.position.y = 1; | |
face.jumping = false; | |
face.velocity.y = 0; | |
} | |
} | |
// Handle controls | |
if (keys['a'] || keys['ArrowLeft']) if (face.lane > -1) face.lane--; | |
if (keys['d'] || keys['ArrowRight']) if (face.lane < 1) face.lane++; | |
if ((keys[' '] || keys['ArrowUp']) && !face.jumping) { | |
face.jumping = true; | |
face.velocity.y = 15; | |
} | |
// Spawn items | |
spawnItems(); | |
// Update collectibles | |
gpuSprites.forEach((gpu, index) => { | |
gpu.position.z += 10 * deltaTime; // Move toward player | |
if (face.position.distanceTo(gpu.position) < 2) { | |
scene.remove(gpu); | |
gpuSprites.splice(index, 1); | |
collectedGPUs++; | |
gpuCounter.textContent = `GPUs Collected: ${collectedGPUs}`; | |
collectSound.play(); | |
} | |
if (gpu.position.z > face.position.z + 10) { | |
scene.remove(gpu); | |
gpuSprites.splice(index, 1); | |
} | |
}); | |
// Update obstacles | |
obstacles.forEach((obstacle, index) => { | |
obstacle.position.z += 10 * deltaTime; | |
if (face.position.distanceTo(obstacle.position) < 2) { | |
gameOver = true; | |
gameOverMessage.style.display = 'block'; | |
face.velocity.z = 0; | |
} | |
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 + 5, face.position.z + 10); | |
camera.lookAt(face.position); | |
} | |
renderer.render(scene, camera); | |
} | |
// Restart game | |
gameOverMessage.addEventListener('click', () => { | |
if (gameOver) { | |
gameOver = false; | |
gameOverMessage.style.display = 'none'; | |
face.position.set(0, 1, 0); | |
face.velocity.z = -10; | |
collectedGPUs = 0; | |
gpuCounter.textContent = `GPUs Collected: 0`; | |
gpuSprites.forEach(gpu => scene.remove(gpu)); | |
obstacles.forEach(obstacle => scene.remove(obstacle)); | |
gpuSprites.length = 0; | |
obstacles.length = 0; | |
} | |
}); | |
animate(); | |
// Handle window resize | |
window.addEventListener('resize', () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
</script> | |
</body> | |
</html>""") | |
demo.launch() |