gradio-game / app.py
akhaliq's picture
akhaliq HF staff
Update app.py
e7e7de8 verified
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()