Spaces:
Running
Running
import gradio as gr | |
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>""") |