Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<title>Minecraft-like 3D Game</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { width: 100%; height: 100%; } | |
</style> | |
</head> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> | |
<script> | |
// Replace with your Hugging Face Space URL when deployed | |
const BASE_URL = window.location.hostname === "localhost" ? "http://localhost:7860" : "https://broadfield-dev-mind-craft.hf.space"; | |
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); | |
const chunkSize = 5; | |
let playerPos = [0, 0]; | |
const chunks = new Map(); | |
function createChunkMesh(chunkData, chunkX, chunkZ) { | |
const geometry = new THREE.BufferGeometry(); | |
const vertices = []; | |
const indices = []; | |
for (let x = 0; x < chunkSize; x++) { | |
for (let z = 0; z < chunkSize; z++) { | |
const y = chunkData[x][z]; | |
const baseX = chunkX * chunkSize + x; | |
const baseZ = chunkZ * chunkSize + z; | |
vertices.push(baseX, y, baseZ); | |
} | |
} | |
for (let x = 0; x < chunkSize - 1; x++) { | |
for (let z = 0; z < chunkSize - 1; z++) { | |
const a = x + z * chunkSize; | |
const b = (x + 1) + z * chunkSize; | |
const c = x + (z + 1) * chunkSize; | |
const d = (x + 1) + (z + 1) * chunkSize; | |
indices.push(a, b, d); | |
indices.push(a, d, c); | |
} | |
} | |
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); | |
geometry.setIndex(indices); | |
geometry.computeVertexNormals(); | |
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }); | |
const mesh = new THREE.Mesh(geometry, material); | |
scene.add(mesh); | |
return mesh; | |
} | |
async function move(dx, dz) { | |
try { | |
// Gradio doesn’t expose /move, so we fetch the root and simulate | |
const response = await fetch(`${BASE_URL}/api/predict`, { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ | |
"fn_index": 0, // Index of move_player function in Blocks | |
"data": [dx, dz, null] // dx, dz, and null for state | |
}) | |
}); | |
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); | |
const result = await response.json(); | |
const data = JSON.parse(result.data[1]); // Parse the JSON string from output | |
playerPos = data.player_pos; | |
const key = `${data.chunk_coords[0]},${data.chunk_coords[1]}`; | |
if (!chunks.has(key)) { | |
const mesh = createChunkMesh(data.chunk, data.chunk_coords[0], data.chunk_coords[1]); | |
chunks.set(key, mesh); | |
} | |
updateCamera(); | |
} catch (error) { | |
console.error('Fetch error:', error); | |
} | |
} | |
function updateCamera() { | |
camera.position.set(playerPos[0], 10, playerPos[1] + 10); | |
camera.lookAt(playerPos[0], 0, playerPos[1]); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
renderer.render(scene, camera); | |
} | |
animate(); | |
document.addEventListener('keydown', (event) => { | |
switch (event.key) { | |
case 'ArrowUp': move(0, -1); break; | |
case 'ArrowDown': move(0, 1); break; | |
case 'ArrowLeft': move(-1, 0); break; | |
case 'ArrowRight': move(1, 0); break; | |
} | |
}); | |
move(0, 0); // Initial load | |
</script> | |
</body> | |
</html> |