Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| demo = gr.Blocks() | |
| with demo: | |
| gr.HTML("""<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Maze Game Demo</title> | |
| <style> | |
| body { margin: 0; overflow: hidden; } | |
| #debug { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| color: white; | |
| background: rgba(0, 0, 0, 0.7); | |
| padding: 5px; | |
| font-family: monospace; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="debug"></div> | |
| <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.body.appendChild(renderer.domElement); | |
| // Lighting | |
| const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6); | |
| scene.add(hemiLight); | |
| const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| dirLight.position.set(0, 20, 10); | |
| scene.add(dirLight); | |
| // OrbitControls | |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| // Maze generation | |
| const gridSize = 16; | |
| const cellSize = 4; | |
| const maze = []; | |
| const walls = []; | |
| function createTexture(width, height, type) { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = width; | |
| canvas.height = height; | |
| const ctx = canvas.getContext('2d'); | |
| if (type === 'floor') { | |
| ctx.fillStyle = '#8B4513'; | |
| ctx.fillRect(0, 0, width, height); | |
| ctx.strokeStyle = '#654321'; | |
| ctx.lineWidth = 2; | |
| ctx.strokeRect(0, 0, width, height); | |
| } else { | |
| ctx.fillStyle = '#666666'; | |
| ctx.fillRect(0, 0, width, height); | |
| for (let i = 0; i < 10; i++) { | |
| ctx.fillStyle = `rgba(0, 0, 0, ${Math.random() * 0.3})`; | |
| ctx.fillRect( | |
| Math.random() * width, | |
| Math.random() * height, | |
| Math.random() * width/4, | |
| Math.random() * height/4 | |
| ); | |
| } | |
| } | |
| return new THREE.CanvasTexture(canvas); | |
| } | |
| const floorTexture = createTexture(256, 256, 'floor'); | |
| const wallTexture = createTexture(256, 256, 'wall'); | |
| // Generate maze | |
| for (let x = 0; x < gridSize; x++) { | |
| maze[x] = []; | |
| for (let z = 0; z < gridSize; z++) { | |
| const isBoundary = x === 0 || x === gridSize - 1 || z === 0 || z === gridSize - 1; | |
| const isCenter = x >= 6 && x <= 9 && z >= 6 && z <= 9; | |
| const isWall = isBoundary || (!isCenter && Math.random() < 0.15); | |
| maze[x][z] = isWall ? 1 : 0; | |
| if (isWall) { | |
| const wallGeometry = new THREE.BoxGeometry(cellSize, cellSize, cellSize); | |
| const wallMaterial = new THREE.MeshPhongMaterial({ map: wallTexture }); | |
| const wall = new THREE.Mesh(wallGeometry, wallMaterial); | |
| wall.position.set(x * cellSize, cellSize/2, z * cellSize); | |
| scene.add(wall); | |
| wall.boundingBox = new THREE.Box3().setFromObject(wall); | |
| walls.push(wall); | |
| } | |
| } | |
| } | |
| // Floor | |
| const floorGeometry = new THREE.PlaneGeometry(gridSize * cellSize, gridSize * cellSize); | |
| const floorMaterial = new THREE.MeshPhongMaterial({ map: floorTexture }); | |
| const floor = new THREE.Mesh(floorGeometry, floorMaterial); | |
| floor.rotation.x = -Math.PI / 2; | |
| scene.add(floor); | |
| // Soldier setup | |
| const loader = new THREE.GLTFLoader(); | |
| const soldiers = []; | |
| const mixers = []; | |
| function createSoldier(team, startX, startZ) { | |
| loader.load('https://threejs.org/examples/models/gltf/Soldier.glb', (gltf) => { | |
| const soldier = gltf.scene; | |
| soldier.scale.set(2, 2, 2); | |
| soldier.position.set(startX, 0, startZ); | |
| soldier.team = team; | |
| soldier.collisionRadius = 1; | |
| soldier.speed = 0.1; | |
| scene.add(soldier); | |
| soldiers.push(soldier); | |
| const mixer = new THREE.AnimationMixer(soldier); | |
| mixers.push(mixer); | |
| const idleAction = mixer.clipAction(gltf.animations[0]); | |
| const runAction = mixer.clipAction(gltf.animations[3]); | |
| soldier.actions = { idle: idleAction, run: runAction }; | |
| idleAction.play(); | |
| }); | |
| } | |
| // Create two teams | |
| createSoldier(1, 28, 28); // Team 1 | |
| createSoldier(2, 36, 36); // Team 2 | |
| camera.position.set(32, 20, 32); | |
| // Movement controls | |
| const keys = { w: false, a: false, s: false, d: false }; | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key in keys) keys[e.key] = true; | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| if (e.key in keys) keys[e.key] = false; | |
| }); | |
| function checkCollision(newPosition, soldier) { | |
| const soldierBox = new THREE.Sphere(newPosition, soldier.collisionRadius); | |
| for (const wall of walls) { | |
| if (soldierBox.intersectsBox(wall.boundingBox)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Animation loop | |
| const clock = new THREE.Clock(); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const delta = clock.getDelta(); | |
| soldiers.forEach((soldier, index) => { | |
| if (!soldier) return; | |
| const moveDirection = new THREE.Vector3(); | |
| if (index === 0) { // Only control first soldier for demo | |
| const camForward = new THREE.Vector3(); | |
| camera.getWorldDirection(camForward); | |
| camForward.y = 0; | |
| camForward.normalize(); | |
| const camLeft = new THREE.Vector3(-camForward.z, 0, camForward.x); | |
| if (keys.w) moveDirection.add(camForward); | |
| if (keys.s) moveDirection.sub(camForward); | |
| if (keys.a) moveDirection.add(camLeft); | |
| if (keys.d) moveDirection.sub(camLeft); | |
| } | |
| if (moveDirection.length() > 0) { | |
| moveDirection.normalize(); | |
| const newPosition = soldier.position.clone() | |
| .add(moveDirection.multiplyScalar(soldier.speed)); | |
| if (!checkCollision(newPosition, soldier)) { | |
| soldier.position.copy(newPosition); | |
| const angle = Math.atan2(moveDirection.x, moveDirection.z); | |
| soldier.rotation.y = angle + Math.PI; | |
| soldier.actions.idle.fadeOut(0.2); | |
| soldier.actions.run.fadeIn(0.2); | |
| } | |
| } else { | |
| soldier.actions.run.fadeOut(0.2); | |
| soldier.actions.idle.fadeIn(0.2); | |
| } | |
| if (index === 0) { | |
| controls.target.copy(soldier.position); | |
| controls.update(); | |
| // Update debug overlay | |
| document.getElementById('debug').innerHTML = ` | |
| Position: ${soldier.position.x.toFixed(2)}, | |
| ${soldier.position.y.toFixed(2)}, | |
| ${soldier.position.z.toFixed(2)}<br> | |
| Rotation: ${(soldier.rotation.y * 180 / Math.PI).toFixed(2)}° | |
| `; | |
| } | |
| }); | |
| mixers.forEach(mixer => mixer.update(delta)); | |
| renderer.render(scene, camera); | |
| } | |
| // Responsive design | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| animate(); | |
| </script> | |
| </body> | |
| </html>""") | |
| demo.launch() |