|
import gradio as gr |
|
import numpy as np |
|
import json |
|
import random |
|
|
|
|
|
THREE_JS_TEMPLATE = """ |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Webspace Network</title> |
|
<style> |
|
body {{ |
|
margin: 0; |
|
overflow: hidden; |
|
font-family: 'Arial', sans-serif; |
|
}} |
|
canvas {{ display: block; }} |
|
#ui {{ |
|
position: absolute; |
|
top: 10px; |
|
left: 10px; |
|
color: white; |
|
background: rgba(0, 0, 0, 0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
width: 300px; |
|
}} |
|
.bar-container {{ |
|
width: 100{percent}; |
|
background: #333; |
|
border-radius: 5px; |
|
margin: 5px 0; |
|
}} |
|
.bar {{ |
|
height: 20px; |
|
border-radius: 5px; |
|
text-align: center; |
|
line-height: 20px; |
|
color: white; |
|
font-size: 12px; |
|
}} |
|
.health-bar {{ background: linear-gradient(to right, #ff0000, #00ff00); }} |
|
.fuel-bar {{ background: linear-gradient(to right, #0000ff, #00ffff); }} |
|
#resources {{ |
|
margin-top: 10px; |
|
}} |
|
.resource-item {{ |
|
display: flex; |
|
justify-content: space-between; |
|
margin: 5px 0; |
|
}} |
|
#interaction-prompt {{ |
|
position: absolute; |
|
bottom: 20px; |
|
left: 50{percent}; |
|
transform: translateX(-50{percent}); |
|
background: rgba(0, 0, 0, 0.7); |
|
padding: 10px 20px; |
|
border-radius: 5px; |
|
color: white; |
|
display: none; |
|
}} |
|
#planet-info {{ |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
background: rgba(0, 0, 0, 0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
width: 300px; |
|
color: white; |
|
display: none; |
|
}} |
|
#game-menu {{ |
|
position: absolute; |
|
top: 50{percent}; |
|
left: 50{percent}; |
|
transform: translate(-50{percent}, -50{percent}); |
|
background: rgba(0, 0, 0, 0.9); |
|
padding: 20px; |
|
border-radius: 10px; |
|
color: white; |
|
text-align: center; |
|
display: none; |
|
}} |
|
.menu-btn {{ |
|
margin: 10px; |
|
padding: 10px 20px; |
|
background: #333; |
|
border: none; |
|
border-radius: 5px; |
|
color: white; |
|
cursor: pointer; |
|
}} |
|
.menu-btn:hover {{ |
|
background: #555; |
|
}} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="ui"> |
|
<h2>Webspace Network</h2> |
|
<div> |
|
<div>Health: <span id="health-value">100</span>/100</div> |
|
<div class="bar-container"> |
|
<div id="health-bar" class="bar health-bar" style="width: 100{percent}">100{percent}</div> |
|
</div> |
|
</div> |
|
<div> |
|
<div>Fuel: <span id="fuel-value">100</span>/100</div> |
|
<div class="bar-container"> |
|
<div id="fuel-bar" class="bar fuel-bar" style="width: 100{percent}">100{percent}</div> |
|
</div> |
|
</div> |
|
<div id="resources"> |
|
<h3>Resources:</h3> |
|
<div id="resource-list"></div> |
|
</div> |
|
<button id="menu-btn" class="menu-btn">Menu</button> |
|
</div> |
|
|
|
<div id="interaction-prompt">Press [E] to interact</div> |
|
|
|
<div id="planet-info"> |
|
<h3 id="planet-name">Planet Name</h3> |
|
<div>Type: <span id="planet-type">Desert</span></div> |
|
<div>Resources: <span id="planet-resources">Iron, Water</span></div> |
|
<div>Habitability: <span id="planet-habitability">0.75</span></div> |
|
<button id="mine-btn" class="menu-btn">Mine Resources</button> |
|
</div> |
|
|
|
<div id="game-menu"> |
|
<h2>Game Menu</h2> |
|
<button id="resume-btn" class="menu-btn">Resume Game</button> |
|
<button id="save-btn" class="menu-btn">Save Game</button> |
|
<button id="load-btn" class="menu-btn">Load Game</button> |
|
<button id="new-btn" class="menu-btn">New Game</button> |
|
<button id="quit-btn" class="menu-btn">Quit to Desktop</button> |
|
</div> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> |
|
|
|
<script> |
|
// Game state |
|
const gameState = {{ |
|
health: 100, |
|
maxHealth: 100, |
|
fuel: 100, |
|
maxFuel: 100, |
|
resources: {{ |
|
'Iron': 10, |
|
'Water': 5, |
|
'Fuel': 20, |
|
'Gold': 2 |
|
}}, |
|
currentPlanet: null, |
|
inMenu: false |
|
}}; |
|
|
|
// Universe data |
|
const universeData = {universe_json}; |
|
|
|
// Scene setup |
|
const scene = new THREE.Scene(); |
|
scene.background = new THREE.Color(0x000010); |
|
|
|
// Camera |
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); |
|
camera.position.set(0, 5, 15); |
|
|
|
// Renderer |
|
const renderer = new THREE.WebGLRenderer({{ antialias: true }}); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
document.body.appendChild(renderer.domElement); |
|
|
|
// Lighting |
|
const ambientLight = new THREE.AmbientLight(0x404040); |
|
scene.add(ambientLight); |
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); |
|
directionalLight.position.set(1, 1, 1); |
|
scene.add(directionalLight); |
|
|
|
// Stars background |
|
const starGeometry = new THREE.BufferGeometry(); |
|
const starVertices = []; |
|
for (let i = 0; i < 10000; i++) {{ |
|
const x = (Math.random() - 0.5) * 2000; |
|
const y = (Math.random() - 0.5) * 2000; |
|
const z = (Math.random() - 0.5) * 2000; |
|
starVertices.push(x, y, z); |
|
}} |
|
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3)); |
|
const starMaterial = new THREE.PointsMaterial({{ color: 0xffffff, size: 1 }}); |
|
const stars = new THREE.Points(starGeometry, starMaterial); |
|
scene.add(stars); |
|
|
|
// Player ship |
|
const shipGeometry = new THREE.ConeGeometry(1, 3, 8); |
|
const shipMaterial = new THREE.MeshPhongMaterial({{ color: 0x00aaff }}); |
|
const ship = new THREE.Mesh(shipGeometry, shipMaterial); |
|
ship.rotation.x = Math.PI / 2; |
|
scene.add(ship); |
|
|
|
// Planets |
|
const planets = []; |
|
|
|
// Create planets |
|
function createPlanets() {{ |
|
universeData.systems[0].planets.forEach((planet, i) => {{ |
|
const planetGeometry = new THREE.SphereGeometry(planet.size, 32, 32); |
|
|
|
// Planet colors |
|
const colors = {{ |
|
'Lava': [0.8, 0.3, 0.1], |
|
'Ocean': [0.1, 0.3, 0.8], |
|
'Desert': [0.9, 0.8, 0.5], |
|
'Ice': [0.7, 0.8, 0.9], |
|
'Jungle': [0.1, 0.7, 0.2], |
|
'Toxic': [0.5, 0.1, 0.7], |
|
'Radioactive': [0.3, 0.8, 0.1] |
|
}}; |
|
|
|
const planetMaterial = new THREE.MeshStandardMaterial({{ |
|
color: new THREE.Color(...colors[planet.type] || [0.5, 0.5, 0.5]), |
|
roughness: 0.8, |
|
metalness: 0.2 |
|
}}); |
|
|
|
const planetMesh = new THREE.Mesh(planetGeometry, planetMaterial); |
|
|
|
// Position in orbit |
|
const angle = (i / universeData.systems[0].planets.length) * Math.PI * 2; |
|
const distance = 15 + i * 5; |
|
planetMesh.position.set( |
|
Math.cos(angle) * distance, |
|
0, |
|
Math.sin(angle) * distance |
|
); |
|
|
|
planetMesh.userData = planet; |
|
scene.add(planetMesh); |
|
planets.push(planetMesh); |
|
|
|
// Add click handler |
|
planetMesh.addEventListener('click', (event) => {{ |
|
gameState.currentPlanet = planet; |
|
document.getElementById('planet-name').textContent = planet.name; |
|
document.getElementById('planet-type').textContent = planet.type; |
|
document.getElementById('planet-resources').textContent = planet.resources.join(', '); |
|
document.getElementById('planet-habitability').textContent = planet.habitability.toFixed(2); |
|
document.getElementById('planet-info').style.display = 'block'; |
|
event.stopPropagation(); |
|
}}); |
|
}}); |
|
}} |
|
|
|
createPlanets(); |
|
|
|
// Camera controls |
|
const controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
controls.enableDamping = true; |
|
controls.dampingFactor = 0.05; |
|
controls.screenSpacePanning = false; |
|
controls.minDistance = 5; |
|
controls.maxDistance = 50; |
|
controls.maxPolarAngle = Math.PI / 2 - 0.05; |
|
|
|
// Ship movement |
|
const shipSpeed = 0.1; |
|
const keys = {{}}; |
|
|
|
window.addEventListener('keydown', (e) => {{ |
|
keys[e.key.toLowerCase()] = true; |
|
|
|
if (e.key === 'e' && gameState.currentPlanet) {{ |
|
mineResources(); |
|
}} |
|
|
|
if (e.key === 'm') {{ |
|
toggleMenu(); |
|
}} |
|
}}); |
|
|
|
window.addEventListener('keyup', (e) => {{ |
|
keys[e.key.toLowerCase()] = false; |
|
}}); |
|
|
|
// Update UI |
|
function updateUI() {{ |
|
document.getElementById('health-value').textContent = gameState.health; |
|
document.getElementById('health-bar').style.width = `${{(gameState.health / gameState.maxHealth) * 100}}{percent}`; |
|
document.getElementById('health-bar').textContent = `${{Math.round((gameState.health / gameState.maxHealth) * 100)}}{percent}`; |
|
|
|
document.getElementById('fuel-value').textContent = gameState.fuel; |
|
document.getElementById('fuel-bar').style.width = `${{(gameState.fuel / gameState.maxFuel) * 100}}{percent}`; |
|
document.getElementById('fuel-bar').textContent = `${{Math.round((gameState.fuel / gameState.maxFuel) * 100)}}{percent}`; |
|
|
|
// Update resources |
|
const resourceList = document.getElementById('resource-list'); |
|
resourceList.innerHTML = ''; |
|
for (const [resource, amount] of Object.entries(gameState.resources)) {{ |
|
const item = document.createElement('div'); |
|
item.className = 'resource-item'; |
|
item.innerHTML = `<span>${{resource}}:</span><span>${{amount}}</span>`; |
|
resourceList.appendChild(item); |
|
}} |
|
}} |
|
|
|
// Mine resources |
|
function mineResources() {{ |
|
if (!gameState.currentPlanet) return; |
|
|
|
const planet = gameState.currentPlanet; |
|
planet.resources.forEach(resource => {{ |
|
gameState.resources[resource] = (gameState.resources[resource] || 0) + 1; |
|
}}); |
|
|
|
updateUI(); |
|
alert(`Mined resources from ${{planet.name}}!`); |
|
}} |
|
|
|
// Toggle menu |
|
function toggleMenu() {{ |
|
gameState.inMenu = !gameState.inMenu; |
|
document.getElementById('game-menu').style.display = gameState.inMenu ? 'block' : 'none'; |
|
controls.enabled = !gameState.inMenu; |
|
}} |
|
|
|
// Menu buttons |
|
document.getElementById('resume-btn').addEventListener('click', toggleMenu); |
|
document.getElementById('menu-btn').addEventListener('click', toggleMenu); |
|
document.getElementById('mine-btn').addEventListener('click', mineResources); |
|
|
|
// Save game |
|
document.getElementById('save-btn').addEventListener('click', () => {{ |
|
localStorage.setItem('webspace_save', JSON.stringify(gameState)); |
|
alert('Game saved successfully!'); |
|
}}); |
|
|
|
// Load game |
|
document.getElementById('load-btn').addEventListener('click', () => {{ |
|
const save = localStorage.getItem('webspace_save'); |
|
if (save) {{ |
|
Object.assign(gameState, JSON.parse(save)); |
|
updateUI(); |
|
alert('Game loaded successfully!'); |
|
toggleMenu(); |
|
}} else {{ |
|
alert('No saved game found!'); |
|
}} |
|
}}); |
|
|
|
// New game |
|
document.getElementById('new-btn').addEventListener('click', () => {{ |
|
if (confirm('Start a new game? All progress will be lost.')) {{ |
|
Object.assign(gameState, {{ |
|
health: 100, |
|
maxHealth: 100, |
|
fuel: 100, |
|
maxFuel: 100, |
|
resources: {{ |
|
'Iron': 10, |
|
'Water': 5, |
|
'Fuel': 20, |
|
'Gold': 2 |
|
}}, |
|
currentPlanet: null |
|
}}); |
|
updateUI(); |
|
toggleMenu(); |
|
}} |
|
}}); |
|
|
|
// Quit game |
|
document.getElementById('quit-btn').addEventListener('click', () => {{ |
|
if (confirm('Quit to desktop?')) {{ |
|
// In a real game, this would close the window |
|
alert('Thanks for playing!'); |
|
}} |
|
}}); |
|
|
|
// Initial UI update |
|
updateUI(); |
|
|
|
// Animation loop |
|
function animate() {{ |
|
requestAnimationFrame(animate); |
|
|
|
// Ship movement |
|
if (!gameState.inMenu) {{ |
|
if (keys['w'] || keys['arrowup']) {{ |
|
ship.position.z -= shipSpeed; |
|
}} |
|
if (keys['s'] || keys['arrowdown']) {{ |
|
ship.position.z += shipSpeed; |
|
}} |
|
if (keys['a'] || keys['arrowleft']) {{ |
|
ship.position.x -= shipSpeed; |
|
}} |
|
if (keys['d'] || keys['arrowright']) {{ |
|
ship.position.x += shipSpeed; |
|
}} |
|
if (keys['q']) {{ |
|
ship.rotation.z += 0.05; |
|
}} |
|
if (keys['e']) {{ |
|
ship.rotation.z -= 0.05; |
|
}} |
|
|
|
// Fuel consumption |
|
if (keys['w'] || keys['s'] || keys['a'] || keys['d']) {{ |
|
gameState.fuel = Math.max(0, gameState.fuel - 0.05); |
|
updateUI(); |
|
}} |
|
}} |
|
|
|
// Update camera to follow ship |
|
camera.position.x = ship.position.x; |
|
camera.position.y = ship.position.y + 5; |
|
camera.position.z = ship.position.z + 15; |
|
camera.lookAt(ship.position.x, ship.position.y, ship.position.z); |
|
|
|
controls.update(); |
|
renderer.render(scene, camera); |
|
}} |
|
|
|
animate(); |
|
|
|
// Handle window resize |
|
window.addEventListener('resize', () => {{ |
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
}}); |
|
</script> |
|
</body> |
|
</html> |
|
""".replace("{percent}", "%%") |
|
|
|
class PlanetGenerator: |
|
def __init__(self, seed=42): |
|
self.rng = np.random.RandomState(seed) |
|
self.planet_types = [ |
|
'Lava', 'Ocean', 'Desert', 'Ice', 'Jungle', 'Toxic', 'Radioactive' |
|
] |
|
self.resources = [ |
|
'Iron', 'Copper', 'Gold', 'Water', 'Oxygen', 'Hydrogen', |
|
'Silicon', 'Titanium', 'Uranium', 'Platinum' |
|
] |
|
|
|
def generate_planet(self, index): |
|
planet_type = self.rng.choice(self.planet_types) |
|
|
|
|
|
num_resources = self.rng.randint(2, 5) |
|
planet_resources = random.sample(self.resources, num_resources) |
|
|
|
return { |
|
'name': f"Planet-{chr(65 + index)}", |
|
'type': planet_type, |
|
'resources': planet_resources, |
|
'size': self.rng.uniform(0.8, 1.5), |
|
'habitability': self.rng.uniform(0.1, 0.9) |
|
} |
|
|
|
def generate_system(self, index): |
|
return { |
|
'name': f"System-{index}", |
|
'planets': [self.generate_planet(i) for i in range(self.rng.randint(3, 7))] |
|
} |
|
|
|
def generate_universe(self, num_systems=1): |
|
return { |
|
'systems': [self.generate_system(i) for i in range(num_systems)] |
|
} |
|
|
|
|
|
generator = PlanetGenerator() |
|
universe = generator.generate_universe() |
|
|
|
def get_threejs_app(): |
|
"""Generate the Three.js HTML with current universe data""" |
|
|
|
template = THREE_JS_TEMPLATE |
|
|
|
return template.replace("{universe_json}", json.dumps(universe)) |
|
|
|
with gr.Blocks(title="Webspace Network", css=".gradio-container {background: linear-gradient(to bottom, #000033, #000066);}") as demo: |
|
gr.Markdown("# π Webspace Network - Space Exploration Simulator") |
|
gr.Markdown("### Procedurally generated universe inspired by No Man's Sky") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
|
|
html = gr.HTML(get_threejs_app()) |
|
|
|
|
|
with gr.Accordion("Game Controls", open=False): |
|
gr.Markdown(""" |
|
**Movement:** |
|
- W/S: Move forward/backward |
|
- A/D: Move left/right |
|
- Q/E: Rotate ship |
|
- Mouse: Look around |
|
|
|
**Interaction:** |
|
- E: Interact with objects |
|
- M: Open game menu |
|
|
|
**Planets:** |
|
- Click on planets to see details |
|
- Press 'Mine Resources' to collect resources |
|
""") |
|
|
|
with gr.Column(scale=1): |
|
|
|
with gr.Group(): |
|
gr.Markdown("### Game State") |
|
health = gr.Slider(0, 100, value=100, label="Health", interactive=False) |
|
fuel = gr.Slider(0, 100, value=100, label="Fuel", interactive=False) |
|
|
|
|
|
with gr.Group(): |
|
gr.Markdown("### Resources") |
|
resources = gr.JSON(value={ |
|
'Iron': 10, |
|
'Water': 5, |
|
'Fuel': 20, |
|
'Gold': 2 |
|
}, label="Inventory") |
|
|
|
|
|
with gr.Group(): |
|
gr.Markdown("### Actions") |
|
with gr.Row(): |
|
save_btn = gr.Button("πΎ Save Game") |
|
load_btn = gr.Button("π Load Game") |
|
new_btn = gr.Button("π New Game") |
|
|
|
|
|
with gr.Group(): |
|
gr.Markdown("### Current Planet") |
|
planet_info = gr.JSON(label="Planet Data", value={}) |
|
|
|
|
|
with gr.Group(): |
|
gr.Markdown("### Debug Console") |
|
console = gr.Textbox(label="Game Events", interactive=False) |
|
|
|
|
|
def save_game(): |
|
return {"message": "Game saved successfully!"} |
|
|
|
def load_game(): |
|
return {"message": "Game loaded successfully!"} |
|
|
|
def new_game(): |
|
return { |
|
"health": 100, |
|
"fuel": 100, |
|
"resources": { |
|
'Iron': 10, |
|
'Water': 5, |
|
'Fuel': 20, |
|
'Gold': 2 |
|
}, |
|
"planet_info": {}, |
|
"console": "New game started" |
|
} |
|
|
|
save_btn.click( |
|
save_game, |
|
outputs=console |
|
) |
|
|
|
load_btn.click( |
|
load_game, |
|
outputs=console |
|
) |
|
|
|
new_btn.click( |
|
new_game, |
|
outputs=[health, fuel, resources, planet_info, console] |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |