ewebspace commited on
Commit
b30d1e3
·
verified ·
1 Parent(s): bb25ccb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -122
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 for the space simulation
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%, -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%">100%</div>
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%">100%</div>
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
- planetData.systems[0].planets.forEach((planet, i) => {
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 / planetData.systems[0].planets.length) * Math.PI * 2;
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
- return THREE_JS_TEMPLATE % json.dumps(universe)
 
 
 
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")