Update app.py
Browse files
app.py
CHANGED
|
@@ -2,22 +2,21 @@ import gradio as gr
|
|
| 2 |
import numpy as np
|
| 3 |
import json
|
| 4 |
import random
|
| 5 |
-
import math
|
| 6 |
|
| 7 |
-
# Three.js template
|
| 8 |
THREE_JS_TEMPLATE = """
|
| 9 |
<!DOCTYPE html>
|
| 10 |
<html>
|
| 11 |
<head>
|
| 12 |
<title>Webspace Network</title>
|
| 13 |
<style>
|
| 14 |
-
body {
|
| 15 |
margin: 0;
|
| 16 |
overflow: hidden;
|
| 17 |
font-family: 'Arial', sans-serif;
|
| 18 |
-
}
|
| 19 |
-
canvas { display: block; }
|
| 20 |
-
#ui {
|
| 21 |
position: absolute;
|
| 22 |
top: 10px;
|
| 23 |
left: 10px;
|
|
@@ -26,43 +25,43 @@ THREE_JS_TEMPLATE = """
|
|
| 26 |
padding: 15px;
|
| 27 |
border-radius: 10px;
|
| 28 |
width: 300px;
|
| 29 |
-
}
|
| 30 |
-
.bar-container {
|
| 31 |
-
width: 100
|
| 32 |
background: #333;
|
| 33 |
border-radius: 5px;
|
| 34 |
margin: 5px 0;
|
| 35 |
-
}
|
| 36 |
-
.bar {
|
| 37 |
height: 20px;
|
| 38 |
border-radius: 5px;
|
| 39 |
text-align: center;
|
| 40 |
line-height: 20px;
|
| 41 |
color: white;
|
| 42 |
font-size: 12px;
|
| 43 |
-
}
|
| 44 |
-
.health-bar { background: linear-gradient(to right, #ff0000, #00ff00); }
|
| 45 |
-
.fuel-bar { background: linear-gradient(to right, #0000ff, #00ffff); }
|
| 46 |
-
#resources {
|
| 47 |
margin-top: 10px;
|
| 48 |
-
}
|
| 49 |
-
.resource-item {
|
| 50 |
display: flex;
|
| 51 |
justify-content: space-between;
|
| 52 |
margin: 5px 0;
|
| 53 |
-
}
|
| 54 |
-
#interaction-prompt {
|
| 55 |
position: absolute;
|
| 56 |
bottom: 20px;
|
| 57 |
-
left: 50
|
| 58 |
-
transform: translateX(-50
|
| 59 |
background: rgba(0, 0, 0, 0.7);
|
| 60 |
padding: 10px 20px;
|
| 61 |
border-radius: 5px;
|
| 62 |
color: white;
|
| 63 |
display: none;
|
| 64 |
-
}
|
| 65 |
-
#planet-info {
|
| 66 |
position: absolute;
|
| 67 |
top: 10px;
|
| 68 |
right: 10px;
|
|
@@ -72,20 +71,20 @@ THREE_JS_TEMPLATE = """
|
|
| 72 |
width: 300px;
|
| 73 |
color: white;
|
| 74 |
display: none;
|
| 75 |
-
}
|
| 76 |
-
#game-menu {
|
| 77 |
position: absolute;
|
| 78 |
-
top: 50
|
| 79 |
-
left: 50
|
| 80 |
-
transform: translate(-50
|
| 81 |
background: rgba(0, 0, 0, 0.9);
|
| 82 |
padding: 20px;
|
| 83 |
border-radius: 10px;
|
| 84 |
color: white;
|
| 85 |
text-align: center;
|
| 86 |
display: none;
|
| 87 |
-
}
|
| 88 |
-
.menu-btn {
|
| 89 |
margin: 10px;
|
| 90 |
padding: 10px 20px;
|
| 91 |
background: #333;
|
|
@@ -93,10 +92,10 @@ THREE_JS_TEMPLATE = """
|
|
| 93 |
border-radius: 5px;
|
| 94 |
color: white;
|
| 95 |
cursor: pointer;
|
| 96 |
-
}
|
| 97 |
-
.menu-btn:hover {
|
| 98 |
background: #555;
|
| 99 |
-
}
|
| 100 |
</style>
|
| 101 |
</head>
|
| 102 |
<body>
|
|
@@ -105,13 +104,13 @@ THREE_JS_TEMPLATE = """
|
|
| 105 |
<div>
|
| 106 |
<div>Health: <span id="health-value">100</span>/100</div>
|
| 107 |
<div class="bar-container">
|
| 108 |
-
<div id="health-bar" class="bar health-bar" style="width: 100
|
| 109 |
</div>
|
| 110 |
</div>
|
| 111 |
<div>
|
| 112 |
<div>Fuel: <span id="fuel-value">100</span>/100</div>
|
| 113 |
<div class="bar-container">
|
| 114 |
-
<div id="fuel-bar" class="bar fuel-bar" style="width: 100
|
| 115 |
</div>
|
| 116 |
</div>
|
| 117 |
<div id="resources">
|
|
@@ -145,20 +144,23 @@ THREE_JS_TEMPLATE = """
|
|
| 145 |
|
| 146 |
<script>
|
| 147 |
// Game state
|
| 148 |
-
const gameState = {
|
| 149 |
health: 100,
|
| 150 |
maxHealth: 100,
|
| 151 |
fuel: 100,
|
| 152 |
maxFuel: 100,
|
| 153 |
-
resources: {
|
| 154 |
'Iron': 10,
|
| 155 |
'Water': 5,
|
| 156 |
'Fuel': 20,
|
| 157 |
'Gold': 2
|
| 158 |
-
},
|
| 159 |
currentPlanet: null,
|
| 160 |
inMenu: false
|
| 161 |
-
};
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
// Scene setup
|
| 164 |
const scene = new THREE.Scene();
|
|
@@ -169,7 +171,7 @@ THREE_JS_TEMPLATE = """
|
|
| 169 |
camera.position.set(0, 5, 15);
|
| 170 |
|
| 171 |
// Renderer
|
| 172 |
-
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
| 173 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 174 |
document.body.appendChild(renderer.domElement);
|
| 175 |
|
|
@@ -184,35 +186,34 @@ THREE_JS_TEMPLATE = """
|
|
| 184 |
// Stars background
|
| 185 |
const starGeometry = new THREE.BufferGeometry();
|
| 186 |
const starVertices = [];
|
| 187 |
-
for (let i = 0; i < 10000; i++) {
|
| 188 |
const x = (Math.random() - 0.5) * 2000;
|
| 189 |
const y = (Math.random() - 0.5) * 2000;
|
| 190 |
const z = (Math.random() - 0.5) * 2000;
|
| 191 |
starVertices.push(x, y, z);
|
| 192 |
-
}
|
| 193 |
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
|
| 194 |
-
const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 1 });
|
| 195 |
const stars = new THREE.Points(starGeometry, starMaterial);
|
| 196 |
scene.add(stars);
|
| 197 |
|
| 198 |
// Player ship
|
| 199 |
const shipGeometry = new THREE.ConeGeometry(1, 3, 8);
|
| 200 |
-
const shipMaterial = new THREE.MeshPhongMaterial({ color: 0x00aaff });
|
| 201 |
const ship = new THREE.Mesh(shipGeometry, shipMaterial);
|
| 202 |
ship.rotation.x = Math.PI / 2;
|
| 203 |
scene.add(ship);
|
| 204 |
|
| 205 |
// Planets
|
| 206 |
const planets = [];
|
| 207 |
-
const planetData = %s;
|
| 208 |
|
| 209 |
// Create planets
|
| 210 |
-
function createPlanets() {
|
| 211 |
-
|
| 212 |
const planetGeometry = new THREE.SphereGeometry(planet.size, 32, 32);
|
| 213 |
|
| 214 |
// Planet colors
|
| 215 |
-
const colors = {
|
| 216 |
'Lava': [0.8, 0.3, 0.1],
|
| 217 |
'Ocean': [0.1, 0.3, 0.8],
|
| 218 |
'Desert': [0.9, 0.8, 0.5],
|
|
@@ -220,18 +221,18 @@ THREE_JS_TEMPLATE = """
|
|
| 220 |
'Jungle': [0.1, 0.7, 0.2],
|
| 221 |
'Toxic': [0.5, 0.1, 0.7],
|
| 222 |
'Radioactive': [0.3, 0.8, 0.1]
|
| 223 |
-
};
|
| 224 |
|
| 225 |
-
const planetMaterial = new THREE.MeshStandardMaterial({
|
| 226 |
color: new THREE.Color(...colors[planet.type] || [0.5, 0.5, 0.5]),
|
| 227 |
roughness: 0.8,
|
| 228 |
metalness: 0.2
|
| 229 |
-
});
|
| 230 |
|
| 231 |
const planetMesh = new THREE.Mesh(planetGeometry, planetMaterial);
|
| 232 |
|
| 233 |
// Position in orbit
|
| 234 |
-
const angle = (i /
|
| 235 |
const distance = 15 + i * 5;
|
| 236 |
planetMesh.position.set(
|
| 237 |
Math.cos(angle) * distance,
|
|
@@ -244,7 +245,7 @@ THREE_JS_TEMPLATE = """
|
|
| 244 |
planets.push(planetMesh);
|
| 245 |
|
| 246 |
// Add click handler
|
| 247 |
-
planetMesh.addEventListener('click', (event) => {
|
| 248 |
gameState.currentPlanet = planet;
|
| 249 |
document.getElementById('planet-name').textContent = planet.name;
|
| 250 |
document.getElementById('planet-type').textContent = planet.type;
|
|
@@ -252,9 +253,9 @@ THREE_JS_TEMPLATE = """
|
|
| 252 |
document.getElementById('planet-habitability').textContent = planet.habitability.toFixed(2);
|
| 253 |
document.getElementById('planet-info').style.display = 'block';
|
| 254 |
event.stopPropagation();
|
| 255 |
-
});
|
| 256 |
-
});
|
| 257 |
-
}
|
| 258 |
|
| 259 |
createPlanets();
|
| 260 |
|
|
@@ -269,64 +270,64 @@ THREE_JS_TEMPLATE = """
|
|
| 269 |
|
| 270 |
// Ship movement
|
| 271 |
const shipSpeed = 0.1;
|
| 272 |
-
const keys = {};
|
| 273 |
|
| 274 |
-
window.addEventListener('keydown', (e) => {
|
| 275 |
keys[e.key.toLowerCase()] = true;
|
| 276 |
|
| 277 |
-
if (e.key === 'e' && gameState.currentPlanet) {
|
| 278 |
mineResources();
|
| 279 |
-
}
|
| 280 |
|
| 281 |
-
if (e.key === 'm') {
|
| 282 |
toggleMenu();
|
| 283 |
-
}
|
| 284 |
-
});
|
| 285 |
|
| 286 |
-
window.addEventListener('keyup', (e) => {
|
| 287 |
keys[e.key.toLowerCase()] = false;
|
| 288 |
-
});
|
| 289 |
|
| 290 |
// Update UI
|
| 291 |
-
function updateUI() {
|
| 292 |
document.getElementById('health-value').textContent = gameState.health;
|
| 293 |
-
document.getElementById('health-bar').style.width = `${(gameState.health / gameState.maxHealth) * 100}
|
| 294 |
-
document.getElementById('health-bar').textContent = `${Math.round((gameState.health / gameState.maxHealth) * 100)}
|
| 295 |
|
| 296 |
document.getElementById('fuel-value').textContent = gameState.fuel;
|
| 297 |
-
document.getElementById('fuel-bar').style.width = `${(gameState.fuel / gameState.maxFuel) * 100}
|
| 298 |
-
document.getElementById('fuel-bar').textContent = `${Math.round((gameState.fuel / gameState.maxFuel) * 100)}
|
| 299 |
|
| 300 |
// Update resources
|
| 301 |
const resourceList = document.getElementById('resource-list');
|
| 302 |
resourceList.innerHTML = '';
|
| 303 |
-
for (const [resource, amount] of Object.entries(gameState.resources)) {
|
| 304 |
const item = document.createElement('div');
|
| 305 |
item.className = 'resource-item';
|
| 306 |
-
item.innerHTML = `<span>${resource}:</span><span>${amount}</span>`;
|
| 307 |
resourceList.appendChild(item);
|
| 308 |
-
}
|
| 309 |
-
}
|
| 310 |
|
| 311 |
// Mine resources
|
| 312 |
-
function mineResources() {
|
| 313 |
if (!gameState.currentPlanet) return;
|
| 314 |
|
| 315 |
const planet = gameState.currentPlanet;
|
| 316 |
-
planet.resources.forEach(resource => {
|
| 317 |
gameState.resources[resource] = (gameState.resources[resource] || 0) + 1;
|
| 318 |
-
});
|
| 319 |
|
| 320 |
updateUI();
|
| 321 |
-
alert(`Mined resources from ${planet.name}!`);
|
| 322 |
-
}
|
| 323 |
|
| 324 |
// Toggle menu
|
| 325 |
-
function toggleMenu() {
|
| 326 |
gameState.inMenu = !gameState.inMenu;
|
| 327 |
document.getElementById('game-menu').style.display = gameState.inMenu ? 'block' : 'none';
|
| 328 |
controls.enabled = !gameState.inMenu;
|
| 329 |
-
}
|
| 330 |
|
| 331 |
// Menu buttons
|
| 332 |
document.getElementById('resume-btn').addEventListener('click', toggleMenu);
|
|
@@ -334,87 +335,87 @@ THREE_JS_TEMPLATE = """
|
|
| 334 |
document.getElementById('mine-btn').addEventListener('click', mineResources);
|
| 335 |
|
| 336 |
// Save game
|
| 337 |
-
document.getElementById('save-btn').addEventListener('click', () => {
|
| 338 |
localStorage.setItem('webspace_save', JSON.stringify(gameState));
|
| 339 |
alert('Game saved successfully!');
|
| 340 |
-
});
|
| 341 |
|
| 342 |
// Load game
|
| 343 |
-
document.getElementById('load-btn').addEventListener('click', () => {
|
| 344 |
const save = localStorage.getItem('webspace_save');
|
| 345 |
-
if (save) {
|
| 346 |
Object.assign(gameState, JSON.parse(save));
|
| 347 |
updateUI();
|
| 348 |
alert('Game loaded successfully!');
|
| 349 |
toggleMenu();
|
| 350 |
-
} else {
|
| 351 |
alert('No saved game found!');
|
| 352 |
-
}
|
| 353 |
-
});
|
| 354 |
|
| 355 |
// New game
|
| 356 |
-
document.getElementById('new-btn').addEventListener('click', () => {
|
| 357 |
-
if (confirm('Start a new game? All progress will be lost.')) {
|
| 358 |
-
Object.assign(gameState, {
|
| 359 |
health: 100,
|
| 360 |
maxHealth: 100,
|
| 361 |
fuel: 100,
|
| 362 |
maxFuel: 100,
|
| 363 |
-
resources: {
|
| 364 |
'Iron': 10,
|
| 365 |
'Water': 5,
|
| 366 |
'Fuel': 20,
|
| 367 |
'Gold': 2
|
| 368 |
-
},
|
| 369 |
currentPlanet: null
|
| 370 |
-
});
|
| 371 |
updateUI();
|
| 372 |
toggleMenu();
|
| 373 |
-
}
|
| 374 |
-
});
|
| 375 |
|
| 376 |
// Quit game
|
| 377 |
-
document.getElementById('quit-btn').addEventListener('click', () => {
|
| 378 |
-
if (confirm('Quit to desktop?')) {
|
| 379 |
// In a real game, this would close the window
|
| 380 |
alert('Thanks for playing!');
|
| 381 |
-
}
|
| 382 |
-
});
|
| 383 |
|
| 384 |
// Initial UI update
|
| 385 |
updateUI();
|
| 386 |
|
| 387 |
// Animation loop
|
| 388 |
-
function animate() {
|
| 389 |
requestAnimationFrame(animate);
|
| 390 |
|
| 391 |
// Ship movement
|
| 392 |
-
if (!gameState.inMenu) {
|
| 393 |
-
if (keys['w'] || keys['arrowup']) {
|
| 394 |
ship.position.z -= shipSpeed;
|
| 395 |
-
}
|
| 396 |
-
if (keys['s'] || keys['arrowdown']) {
|
| 397 |
ship.position.z += shipSpeed;
|
| 398 |
-
}
|
| 399 |
-
if (keys['a'] || keys['arrowleft']) {
|
| 400 |
ship.position.x -= shipSpeed;
|
| 401 |
-
}
|
| 402 |
-
if (keys['d'] || keys['arrowright']) {
|
| 403 |
ship.position.x += shipSpeed;
|
| 404 |
-
}
|
| 405 |
-
if (keys['q']) {
|
| 406 |
ship.rotation.z += 0.05;
|
| 407 |
-
}
|
| 408 |
-
if (keys['e']) {
|
| 409 |
ship.rotation.z -= 0.05;
|
| 410 |
-
}
|
| 411 |
|
| 412 |
// Fuel consumption
|
| 413 |
-
if (keys['w'] || keys['s'] || keys['a'] || keys['d']) {
|
| 414 |
gameState.fuel = Math.max(0, gameState.fuel - 0.05);
|
| 415 |
updateUI();
|
| 416 |
-
}
|
| 417 |
-
}
|
| 418 |
|
| 419 |
// Update camera to follow ship
|
| 420 |
camera.position.x = ship.position.x;
|
|
@@ -424,20 +425,20 @@ THREE_JS_TEMPLATE = """
|
|
| 424 |
|
| 425 |
controls.update();
|
| 426 |
renderer.render(scene, camera);
|
| 427 |
-
}
|
| 428 |
|
| 429 |
animate();
|
| 430 |
|
| 431 |
// Handle window resize
|
| 432 |
-
window.addEventListener('resize', () => {
|
| 433 |
camera.aspect = window.innerWidth / window.innerHeight;
|
| 434 |
camera.updateProjectionMatrix();
|
| 435 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 436 |
-
});
|
| 437 |
</script>
|
| 438 |
</body>
|
| 439 |
</html>
|
| 440 |
-
"""
|
| 441 |
|
| 442 |
class PlanetGenerator:
|
| 443 |
def __init__(self, seed=42):
|
|
@@ -482,7 +483,10 @@ universe = generator.generate_universe()
|
|
| 482 |
|
| 483 |
def get_threejs_app():
|
| 484 |
"""Generate the Three.js HTML with current universe data"""
|
| 485 |
-
|
|
|
|
|
|
|
|
|
|
| 486 |
|
| 487 |
with gr.Blocks(title="Webspace Network", css=".gradio-container {background: linear-gradient(to bottom, #000033, #000066);}") as demo:
|
| 488 |
gr.Markdown("# 🚀 Webspace Network - Space Exploration Simulator")
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
import json
|
| 4 |
import random
|
|
|
|
| 5 |
|
| 6 |
+
# Three.js template with escaped CSS
|
| 7 |
THREE_JS_TEMPLATE = """
|
| 8 |
<!DOCTYPE html>
|
| 9 |
<html>
|
| 10 |
<head>
|
| 11 |
<title>Webspace Network</title>
|
| 12 |
<style>
|
| 13 |
+
body {{
|
| 14 |
margin: 0;
|
| 15 |
overflow: hidden;
|
| 16 |
font-family: 'Arial', sans-serif;
|
| 17 |
+
}}
|
| 18 |
+
canvas {{ display: block; }}
|
| 19 |
+
#ui {{
|
| 20 |
position: absolute;
|
| 21 |
top: 10px;
|
| 22 |
left: 10px;
|
|
|
|
| 25 |
padding: 15px;
|
| 26 |
border-radius: 10px;
|
| 27 |
width: 300px;
|
| 28 |
+
}}
|
| 29 |
+
.bar-container {{
|
| 30 |
+
width: 100{percent};
|
| 31 |
background: #333;
|
| 32 |
border-radius: 5px;
|
| 33 |
margin: 5px 0;
|
| 34 |
+
}}
|
| 35 |
+
.bar {{
|
| 36 |
height: 20px;
|
| 37 |
border-radius: 5px;
|
| 38 |
text-align: center;
|
| 39 |
line-height: 20px;
|
| 40 |
color: white;
|
| 41 |
font-size: 12px;
|
| 42 |
+
}}
|
| 43 |
+
.health-bar {{ background: linear-gradient(to right, #ff0000, #00ff00); }}
|
| 44 |
+
.fuel-bar {{ background: linear-gradient(to right, #0000ff, #00ffff); }}
|
| 45 |
+
#resources {{
|
| 46 |
margin-top: 10px;
|
| 47 |
+
}}
|
| 48 |
+
.resource-item {{
|
| 49 |
display: flex;
|
| 50 |
justify-content: space-between;
|
| 51 |
margin: 5px 0;
|
| 52 |
+
}}
|
| 53 |
+
#interaction-prompt {{
|
| 54 |
position: absolute;
|
| 55 |
bottom: 20px;
|
| 56 |
+
left: 50{percent};
|
| 57 |
+
transform: translateX(-50{percent});
|
| 58 |
background: rgba(0, 0, 0, 0.7);
|
| 59 |
padding: 10px 20px;
|
| 60 |
border-radius: 5px;
|
| 61 |
color: white;
|
| 62 |
display: none;
|
| 63 |
+
}}
|
| 64 |
+
#planet-info {{
|
| 65 |
position: absolute;
|
| 66 |
top: 10px;
|
| 67 |
right: 10px;
|
|
|
|
| 71 |
width: 300px;
|
| 72 |
color: white;
|
| 73 |
display: none;
|
| 74 |
+
}}
|
| 75 |
+
#game-menu {{
|
| 76 |
position: absolute;
|
| 77 |
+
top: 50{percent};
|
| 78 |
+
left: 50{percent};
|
| 79 |
+
transform: translate(-50{percent}, -50{percent});
|
| 80 |
background: rgba(0, 0, 0, 0.9);
|
| 81 |
padding: 20px;
|
| 82 |
border-radius: 10px;
|
| 83 |
color: white;
|
| 84 |
text-align: center;
|
| 85 |
display: none;
|
| 86 |
+
}}
|
| 87 |
+
.menu-btn {{
|
| 88 |
margin: 10px;
|
| 89 |
padding: 10px 20px;
|
| 90 |
background: #333;
|
|
|
|
| 92 |
border-radius: 5px;
|
| 93 |
color: white;
|
| 94 |
cursor: pointer;
|
| 95 |
+
}}
|
| 96 |
+
.menu-btn:hover {{
|
| 97 |
background: #555;
|
| 98 |
+
}}
|
| 99 |
</style>
|
| 100 |
</head>
|
| 101 |
<body>
|
|
|
|
| 104 |
<div>
|
| 105 |
<div>Health: <span id="health-value">100</span>/100</div>
|
| 106 |
<div class="bar-container">
|
| 107 |
+
<div id="health-bar" class="bar health-bar" style="width: 100{percent}">100{percent}</div>
|
| 108 |
</div>
|
| 109 |
</div>
|
| 110 |
<div>
|
| 111 |
<div>Fuel: <span id="fuel-value">100</span>/100</div>
|
| 112 |
<div class="bar-container">
|
| 113 |
+
<div id="fuel-bar" class="bar fuel-bar" style="width: 100{percent}">100{percent}</div>
|
| 114 |
</div>
|
| 115 |
</div>
|
| 116 |
<div id="resources">
|
|
|
|
| 144 |
|
| 145 |
<script>
|
| 146 |
// Game state
|
| 147 |
+
const gameState = {{
|
| 148 |
health: 100,
|
| 149 |
maxHealth: 100,
|
| 150 |
fuel: 100,
|
| 151 |
maxFuel: 100,
|
| 152 |
+
resources: {{
|
| 153 |
'Iron': 10,
|
| 154 |
'Water': 5,
|
| 155 |
'Fuel': 20,
|
| 156 |
'Gold': 2
|
| 157 |
+
}},
|
| 158 |
currentPlanet: null,
|
| 159 |
inMenu: false
|
| 160 |
+
}};
|
| 161 |
+
|
| 162 |
+
// Universe data
|
| 163 |
+
const universeData = {universe_json};
|
| 164 |
|
| 165 |
// Scene setup
|
| 166 |
const scene = new THREE.Scene();
|
|
|
|
| 171 |
camera.position.set(0, 5, 15);
|
| 172 |
|
| 173 |
// Renderer
|
| 174 |
+
const renderer = new THREE.WebGLRenderer({{ antialias: true }});
|
| 175 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 176 |
document.body.appendChild(renderer.domElement);
|
| 177 |
|
|
|
|
| 186 |
// Stars background
|
| 187 |
const starGeometry = new THREE.BufferGeometry();
|
| 188 |
const starVertices = [];
|
| 189 |
+
for (let i = 0; i < 10000; i++) {{
|
| 190 |
const x = (Math.random() - 0.5) * 2000;
|
| 191 |
const y = (Math.random() - 0.5) * 2000;
|
| 192 |
const z = (Math.random() - 0.5) * 2000;
|
| 193 |
starVertices.push(x, y, z);
|
| 194 |
+
}}
|
| 195 |
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
|
| 196 |
+
const starMaterial = new THREE.PointsMaterial({{ color: 0xffffff, size: 1 }});
|
| 197 |
const stars = new THREE.Points(starGeometry, starMaterial);
|
| 198 |
scene.add(stars);
|
| 199 |
|
| 200 |
// Player ship
|
| 201 |
const shipGeometry = new THREE.ConeGeometry(1, 3, 8);
|
| 202 |
+
const shipMaterial = new THREE.MeshPhongMaterial({{ color: 0x00aaff }});
|
| 203 |
const ship = new THREE.Mesh(shipGeometry, shipMaterial);
|
| 204 |
ship.rotation.x = Math.PI / 2;
|
| 205 |
scene.add(ship);
|
| 206 |
|
| 207 |
// Planets
|
| 208 |
const planets = [];
|
|
|
|
| 209 |
|
| 210 |
// Create planets
|
| 211 |
+
function createPlanets() {{
|
| 212 |
+
universeData.systems[0].planets.forEach((planet, i) => {{
|
| 213 |
const planetGeometry = new THREE.SphereGeometry(planet.size, 32, 32);
|
| 214 |
|
| 215 |
// Planet colors
|
| 216 |
+
const colors = {{
|
| 217 |
'Lava': [0.8, 0.3, 0.1],
|
| 218 |
'Ocean': [0.1, 0.3, 0.8],
|
| 219 |
'Desert': [0.9, 0.8, 0.5],
|
|
|
|
| 221 |
'Jungle': [0.1, 0.7, 0.2],
|
| 222 |
'Toxic': [0.5, 0.1, 0.7],
|
| 223 |
'Radioactive': [0.3, 0.8, 0.1]
|
| 224 |
+
}};
|
| 225 |
|
| 226 |
+
const planetMaterial = new THREE.MeshStandardMaterial({{
|
| 227 |
color: new THREE.Color(...colors[planet.type] || [0.5, 0.5, 0.5]),
|
| 228 |
roughness: 0.8,
|
| 229 |
metalness: 0.2
|
| 230 |
+
}});
|
| 231 |
|
| 232 |
const planetMesh = new THREE.Mesh(planetGeometry, planetMaterial);
|
| 233 |
|
| 234 |
// Position in orbit
|
| 235 |
+
const angle = (i / universeData.systems[0].planets.length) * Math.PI * 2;
|
| 236 |
const distance = 15 + i * 5;
|
| 237 |
planetMesh.position.set(
|
| 238 |
Math.cos(angle) * distance,
|
|
|
|
| 245 |
planets.push(planetMesh);
|
| 246 |
|
| 247 |
// Add click handler
|
| 248 |
+
planetMesh.addEventListener('click', (event) => {{
|
| 249 |
gameState.currentPlanet = planet;
|
| 250 |
document.getElementById('planet-name').textContent = planet.name;
|
| 251 |
document.getElementById('planet-type').textContent = planet.type;
|
|
|
|
| 253 |
document.getElementById('planet-habitability').textContent = planet.habitability.toFixed(2);
|
| 254 |
document.getElementById('planet-info').style.display = 'block';
|
| 255 |
event.stopPropagation();
|
| 256 |
+
}});
|
| 257 |
+
}});
|
| 258 |
+
}}
|
| 259 |
|
| 260 |
createPlanets();
|
| 261 |
|
|
|
|
| 270 |
|
| 271 |
// Ship movement
|
| 272 |
const shipSpeed = 0.1;
|
| 273 |
+
const keys = {{}};
|
| 274 |
|
| 275 |
+
window.addEventListener('keydown', (e) => {{
|
| 276 |
keys[e.key.toLowerCase()] = true;
|
| 277 |
|
| 278 |
+
if (e.key === 'e' && gameState.currentPlanet) {{
|
| 279 |
mineResources();
|
| 280 |
+
}}
|
| 281 |
|
| 282 |
+
if (e.key === 'm') {{
|
| 283 |
toggleMenu();
|
| 284 |
+
}}
|
| 285 |
+
}});
|
| 286 |
|
| 287 |
+
window.addEventListener('keyup', (e) => {{
|
| 288 |
keys[e.key.toLowerCase()] = false;
|
| 289 |
+
}});
|
| 290 |
|
| 291 |
// Update UI
|
| 292 |
+
function updateUI() {{
|
| 293 |
document.getElementById('health-value').textContent = gameState.health;
|
| 294 |
+
document.getElementById('health-bar').style.width = `${{(gameState.health / gameState.maxHealth) * 100}}{percent}`;
|
| 295 |
+
document.getElementById('health-bar').textContent = `${{Math.round((gameState.health / gameState.maxHealth) * 100)}}{percent}`;
|
| 296 |
|
| 297 |
document.getElementById('fuel-value').textContent = gameState.fuel;
|
| 298 |
+
document.getElementById('fuel-bar').style.width = `${{(gameState.fuel / gameState.maxFuel) * 100}}{percent}`;
|
| 299 |
+
document.getElementById('fuel-bar').textContent = `${{Math.round((gameState.fuel / gameState.maxFuel) * 100)}}{percent}`;
|
| 300 |
|
| 301 |
// Update resources
|
| 302 |
const resourceList = document.getElementById('resource-list');
|
| 303 |
resourceList.innerHTML = '';
|
| 304 |
+
for (const [resource, amount] of Object.entries(gameState.resources)) {{
|
| 305 |
const item = document.createElement('div');
|
| 306 |
item.className = 'resource-item';
|
| 307 |
+
item.innerHTML = `<span>${{resource}}:</span><span>${{amount}}</span>`;
|
| 308 |
resourceList.appendChild(item);
|
| 309 |
+
}}
|
| 310 |
+
}}
|
| 311 |
|
| 312 |
// Mine resources
|
| 313 |
+
function mineResources() {{
|
| 314 |
if (!gameState.currentPlanet) return;
|
| 315 |
|
| 316 |
const planet = gameState.currentPlanet;
|
| 317 |
+
planet.resources.forEach(resource => {{
|
| 318 |
gameState.resources[resource] = (gameState.resources[resource] || 0) + 1;
|
| 319 |
+
}});
|
| 320 |
|
| 321 |
updateUI();
|
| 322 |
+
alert(`Mined resources from ${{planet.name}}!`);
|
| 323 |
+
}}
|
| 324 |
|
| 325 |
// Toggle menu
|
| 326 |
+
function toggleMenu() {{
|
| 327 |
gameState.inMenu = !gameState.inMenu;
|
| 328 |
document.getElementById('game-menu').style.display = gameState.inMenu ? 'block' : 'none';
|
| 329 |
controls.enabled = !gameState.inMenu;
|
| 330 |
+
}}
|
| 331 |
|
| 332 |
// Menu buttons
|
| 333 |
document.getElementById('resume-btn').addEventListener('click', toggleMenu);
|
|
|
|
| 335 |
document.getElementById('mine-btn').addEventListener('click', mineResources);
|
| 336 |
|
| 337 |
// Save game
|
| 338 |
+
document.getElementById('save-btn').addEventListener('click', () => {{
|
| 339 |
localStorage.setItem('webspace_save', JSON.stringify(gameState));
|
| 340 |
alert('Game saved successfully!');
|
| 341 |
+
}});
|
| 342 |
|
| 343 |
// Load game
|
| 344 |
+
document.getElementById('load-btn').addEventListener('click', () => {{
|
| 345 |
const save = localStorage.getItem('webspace_save');
|
| 346 |
+
if (save) {{
|
| 347 |
Object.assign(gameState, JSON.parse(save));
|
| 348 |
updateUI();
|
| 349 |
alert('Game loaded successfully!');
|
| 350 |
toggleMenu();
|
| 351 |
+
}} else {{
|
| 352 |
alert('No saved game found!');
|
| 353 |
+
}}
|
| 354 |
+
}});
|
| 355 |
|
| 356 |
// New game
|
| 357 |
+
document.getElementById('new-btn').addEventListener('click', () => {{
|
| 358 |
+
if (confirm('Start a new game? All progress will be lost.')) {{
|
| 359 |
+
Object.assign(gameState, {{
|
| 360 |
health: 100,
|
| 361 |
maxHealth: 100,
|
| 362 |
fuel: 100,
|
| 363 |
maxFuel: 100,
|
| 364 |
+
resources: {{
|
| 365 |
'Iron': 10,
|
| 366 |
'Water': 5,
|
| 367 |
'Fuel': 20,
|
| 368 |
'Gold': 2
|
| 369 |
+
}},
|
| 370 |
currentPlanet: null
|
| 371 |
+
}});
|
| 372 |
updateUI();
|
| 373 |
toggleMenu();
|
| 374 |
+
}}
|
| 375 |
+
}});
|
| 376 |
|
| 377 |
// Quit game
|
| 378 |
+
document.getElementById('quit-btn').addEventListener('click', () => {{
|
| 379 |
+
if (confirm('Quit to desktop?')) {{
|
| 380 |
// In a real game, this would close the window
|
| 381 |
alert('Thanks for playing!');
|
| 382 |
+
}}
|
| 383 |
+
}});
|
| 384 |
|
| 385 |
// Initial UI update
|
| 386 |
updateUI();
|
| 387 |
|
| 388 |
// Animation loop
|
| 389 |
+
function animate() {{
|
| 390 |
requestAnimationFrame(animate);
|
| 391 |
|
| 392 |
// Ship movement
|
| 393 |
+
if (!gameState.inMenu) {{
|
| 394 |
+
if (keys['w'] || keys['arrowup']) {{
|
| 395 |
ship.position.z -= shipSpeed;
|
| 396 |
+
}}
|
| 397 |
+
if (keys['s'] || keys['arrowdown']) {{
|
| 398 |
ship.position.z += shipSpeed;
|
| 399 |
+
}}
|
| 400 |
+
if (keys['a'] || keys['arrowleft']) {{
|
| 401 |
ship.position.x -= shipSpeed;
|
| 402 |
+
}}
|
| 403 |
+
if (keys['d'] || keys['arrowright']) {{
|
| 404 |
ship.position.x += shipSpeed;
|
| 405 |
+
}}
|
| 406 |
+
if (keys['q']) {{
|
| 407 |
ship.rotation.z += 0.05;
|
| 408 |
+
}}
|
| 409 |
+
if (keys['e']) {{
|
| 410 |
ship.rotation.z -= 0.05;
|
| 411 |
+
}}
|
| 412 |
|
| 413 |
// Fuel consumption
|
| 414 |
+
if (keys['w'] || keys['s'] || keys['a'] || keys['d']) {{
|
| 415 |
gameState.fuel = Math.max(0, gameState.fuel - 0.05);
|
| 416 |
updateUI();
|
| 417 |
+
}}
|
| 418 |
+
}}
|
| 419 |
|
| 420 |
// Update camera to follow ship
|
| 421 |
camera.position.x = ship.position.x;
|
|
|
|
| 425 |
|
| 426 |
controls.update();
|
| 427 |
renderer.render(scene, camera);
|
| 428 |
+
}}
|
| 429 |
|
| 430 |
animate();
|
| 431 |
|
| 432 |
// Handle window resize
|
| 433 |
+
window.addEventListener('resize', () => {{
|
| 434 |
camera.aspect = window.innerWidth / window.innerHeight;
|
| 435 |
camera.updateProjectionMatrix();
|
| 436 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 437 |
+
}});
|
| 438 |
</script>
|
| 439 |
</body>
|
| 440 |
</html>
|
| 441 |
+
""".replace("{percent}", "%%") # Replace temporary placeholder with actual % sign
|
| 442 |
|
| 443 |
class PlanetGenerator:
|
| 444 |
def __init__(self, seed=42):
|
|
|
|
| 483 |
|
| 484 |
def get_threejs_app():
|
| 485 |
"""Generate the Three.js HTML with current universe data"""
|
| 486 |
+
# First replace all {percent} placeholders with %%
|
| 487 |
+
template = THREE_JS_TEMPLATE
|
| 488 |
+
# Then format the universe_json part
|
| 489 |
+
return template.replace("{universe_json}", json.dumps(universe))
|
| 490 |
|
| 491 |
with gr.Blocks(title="Webspace Network", css=".gradio-container {background: linear-gradient(to bottom, #000033, #000066);}") as demo:
|
| 492 |
gr.Markdown("# 🚀 Webspace Network - Space Exploration Simulator")
|