gradio-game / app.py
akhaliq's picture
akhaliq HF staff
Update app.py
b9232bd verified
raw
history blame
10.5 kB
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>3D Maze Game Demo</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#container {
position: relative;
}
#debug {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="debug">
<p>Position: <span id="position"></span></p>
<p>Rotation: <span id="rotation"></span></p>
</div>
<!-- Libraries from CDNs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.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.getElementById('container').appendChild(renderer.domElement);
// Lighting
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(0, 20, 10);
scene.add(dirLight);
// Floor Texture
function createFloorTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
context.fillStyle = '#8B4513'; // Brown tone
context.fillRect(0, 0, 256, 256);
context.strokeStyle = '#FFFFFF';
context.lineWidth = 2;
for (let i = 0; i <= 16; i++) {
const pos = i * 16;
context.beginPath();
context.moveTo(pos, 0);
context.lineTo(pos, 256);
context.stroke();
context.beginPath();
context.moveTo(0, pos);
context.lineTo(256, pos);
context.stroke();
}
const texture = new THREE.CanvasTexture(canvas);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(16, 16);
return texture;
}
// Wall Texture
function createWallTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const context = canvas.getContext('2d');
context.fillStyle = '#808080'; // Gray base
context.fillRect(0, 0, 64, 64);
context.fillStyle = '#A0A0A0';
for (let i = 0; i < 100; i++) {
const x = Math.random() * 64;
const y = Math.random() * 64;
context.fillRect(x, y, 2, 2);
}
return new THREE.CanvasTexture(canvas);
}
// Floor
const floorTexture = createFloorTexture();
const floorGeometry = new THREE.PlaneGeometry(64, 64);
const floorMaterial = new THREE.MeshBasicMaterial({ map: floorTexture });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
// Maze Generation
const gridSize = 16;
const cellSize = 4;
const maze = [];
for (let i = 0; i < gridSize; i++) {
maze[i] = [];
for (let j = 0; j < gridSize; j++) {
if (i === 0 || i === gridSize - 1 || j === 0 || j === gridSize - 1) {
maze[i][j] = 1; // Walls on boundaries
} else if (i >= 6 && i <= 9 && j >= 6 && j <= 9) {
maze[i][j] = 0; // Central open area
} else {
maze[i][j] = Math.random() < 0.15 ? 1 : 0; // 15% chance of wall
}
}
}
// Walls and Collision Bounds
const wallTexture = createWallTexture();
const wallGeometry = new THREE.BoxGeometry(cellSize, cellSize, cellSize);
const wallMaterial = new THREE.MeshBasicMaterial({ map: wallTexture });
const walls = [];
const wallBounds = [];
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (maze[i][j] === 1) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
const x = (j - 7.5) * cellSize;
const z = (i - 7.5) * cellSize;
wall.position.set(x, cellSize / 2, z);
scene.add(wall);
walls.push(wall);
wallBounds.push({
minX: x - cellSize / 2,
maxX: x + cellSize / 2,
minZ: z - cellSize / 2,
maxZ: z + cellSize / 2
});
}
}
}
// Soldier Setup
const loader = new THREE.GLTFLoader();
let soldier, mixer, idleAction, runAction, activeAction;
loader.load(
'https://threejs.org/examples/models/gltf/Soldier.glb',
(gltf) => {
soldier = gltf.scene;
soldier.scale.set(2, 2, 2);
soldier.position.set(0, 0, 0);
scene.add(soldier);
mixer = new THREE.AnimationMixer(soldier);
const animations = gltf.animations;
idleAction = mixer.clipAction(THREE.AnimationClip.findByName(animations, 'Idle'));
runAction = mixer.clipAction(THREE.AnimationClip.findByName(animations, 'Run'));
activeAction = idleAction;
activeAction.play();
},
undefined,
(error) => console.error('Error loading soldier:', error)
);
// Camera and Controls
camera.position.set(0, 5, 10);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
// Movement Controls
const keys = new Set();
window.addEventListener('keydown', (e) => keys.add(e.key.toLowerCase()));
window.addEventListener('keyup', (e) => keys.delete(e.key.toLowerCase()));
const moveSpeed = 10; // Units per second
const collisionRadius = 0.5;
function updateSoldier(delta) {
if (!soldier) return;
const forward = new THREE.Vector3();
camera.getWorldDirection(forward);
forward.y = 0;
forward.normalize();
const right = new THREE.Vector3();
right.crossVectors(forward, new THREE.Vector3(0, 1, 0));
const moveDirection = new THREE.Vector3();
if (keys.has('w')) moveDirection.add(forward);
if (keys.has('s')) moveDirection.add(forward.clone().negate());
if (keys.has('a')) moveDirection.add(right.clone().negate());
if (keys.has('d')) moveDirection.add(right);
if (moveDirection.lengthSq() > 0) {
moveDirection.normalize();
const newPosition = soldier.position.clone().add(
moveDirection.multiplyScalar(moveSpeed * delta)
);
// Collision Detection
let colliding = false;
for (const wall of wallBounds) {
if (
newPosition.x > wall.minX - collisionRadius &&
newPosition.x < wall.maxX + collisionRadius &&
newPosition.z > wall.minZ - collisionRadius &&
newPosition.z < wall.maxZ + collisionRadius
) {
colliding = true;
break;
}
}
if (!colliding) {
soldier.position.copy(newPosition);
const angle = Math.atan2(moveDirection.x, moveDirection.z);
soldier.rotation.y = angle + Math.PI; // Orientation fix
}
// Animation Transition to Run
if (activeAction !== runAction) {
activeAction.fadeOut(0.2);
runAction.reset().fadeIn(0.2).play();
activeAction = runAction;
}
} else {
// Animation Transition to Idle
if (activeAction !== idleAction) {
activeAction.fadeOut(0.2);
idleAction.reset().fadeIn(0.2).play();
activeAction = idleAction;
}
}
}
// Animation Loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
updateSoldier(delta);
if (soldier) {
controls.target.copy(soldier.position);
const pos = soldier.position;
document.getElementById('position').textContent =
`(${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})`;
document.getElementById('rotation').textContent =
soldier.rotation.y.toFixed(2);
}
controls.update();
renderer.render(scene, camera);
}
animate();
// Responsive Design
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>""")
demo.launch()