| 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() |