awacke1 commited on
Commit
948c597
·
verified ·
1 Parent(s): db57c27

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +256 -175
app.py CHANGED
@@ -1,261 +1,328 @@
1
  import streamlit as st
 
2
 
3
- # Set wide layout
4
- st.set_page_config(layout="wide", page_title="Galaxian 3D Evolution", initial_sidebar_state="expanded")
5
-
6
- # Sidebar instructions
7
- st.sidebar.markdown("## Galaxian 3D Evolution Controls")
8
- st.sidebar.markdown("""
9
- - **Controls:**
10
- - **WASD:** Move camera (forward, left, back, right)
11
- - **QE:** Rotate camera up/down
12
- - **Mouse:** Orbit camera (click and drag)
13
- - **Arrow Keys:** Direct snake (Up, Down, Left, Right)
14
- - **R:** Reset game
15
-
16
- - **Features:**
17
- - 3D snake navigates a space grid.
18
- - Eat alien food (👾) to grow and trigger L-system growth.
19
- - Creatures exchange quine messages (hover to see).
20
- - Avoid walls and self-collision.
21
-
22
- Enjoy the 3D Galaxian experience!
23
- """)
24
-
25
- # Three.js HTML/JavaScript code
26
- html_code = r"""
27
  <!DOCTYPE html>
28
- <html>
29
  <head>
30
  <meta charset="UTF-8">
31
- <title>Galaxian 3D Evolution</title>
32
  <style>
33
- body { margin: 0; padding: 0; overflow: hidden; background: black; }
34
- canvas { display: block; }
 
 
 
 
35
  </style>
36
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
37
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
38
  </head>
39
  <body>
40
- <script>
41
- let scene, camera, renderer, controls, snake, food, creatures = [], messages = [];
42
- const gridSize = 20;
43
- const worldSize = 400;
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  function init() {
46
- // Scene setup
47
  scene = new THREE.Scene();
48
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
49
- renderer = new THREE.WebGLRenderer();
50
  renderer.setSize(window.innerWidth, window.innerHeight);
51
  document.body.appendChild(renderer.domElement);
52
 
53
- // Orbit controls
54
- controls = new THREE.OrbitControls(camera, renderer.domElement);
55
- controls.enableDamping = true;
56
- controls.dampingFactor = 0.05;
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- // Lighting
59
- const ambientLight = new THREE.AmbientLight(0x404040);
60
  scene.add(ambientLight);
61
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
62
- directionalLight.position.set(1, 1, 1);
63
  scene.add(directionalLight);
64
 
65
  // Starfield
66
  const starsGeometry = new THREE.BufferGeometry();
67
- const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 2 });
68
  const starPositions = new Float32Array(1000 * 3);
69
  for (let i = 0; i < 1000; i++) {
70
- starPositions[i * 3] = (Math.random() - 0.5) * worldSize;
71
- starPositions[i * 3 + 1] = (Math.random() - 0.5) * worldSize;
72
- starPositions[i * 3 + 2] = (Math.random() - 0.5) * worldSize;
73
  }
74
  starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
75
- const stars = new THREE.Points(starsGeometry, starsMaterial);
76
- scene.add(stars);
77
 
78
- // Initialize snake
79
- snake = [];
80
- const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
81
- for (let i = 0; i < 3; i++) {
82
- const segment = new THREE.Mesh(new THREE.SphereGeometry(gridSize / 2, 32, 32), snakeMaterial);
83
- segment.position.set(-i * gridSize, 0, 0);
84
- snake.push(segment);
85
- scene.add(segment);
86
- }
87
- snake.direction = new THREE.Vector3(gridSize, 0, 0);
88
-
89
- // Food (alien)
90
- placeFood();
91
-
92
- // L-system creature
93
- createLSysCreature();
94
-
95
- // Camera position
96
- camera.position.set(0, 100, 200);
97
- camera.lookAt(0, 0, 0);
98
 
 
99
  animate();
100
  }
101
 
102
- function placeFood() {
103
- if (food) scene.remove(food);
104
- const foodGeometry = new THREE.DodecahedronGeometry(gridSize / 2);
105
  const foodMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff });
106
- food = new THREE.Mesh(foodGeometry, foodMaterial);
107
- food.position.set(
108
- (Math.floor(Math.random() * (worldSize / gridSize)) - worldSize / (2 * gridSize)) * gridSize,
109
- 0,
110
- (Math.floor(Math.random() * (worldSize / gridSize)) - worldSize / (2 * gridSize)) * gridSize
111
- );
112
- scene.add(food);
113
  }
114
 
115
- function createLSysCreature() {
116
  const lSys = {
117
  axiom: "F",
118
  rules: { "F": "F[+F]F[-F]F" },
119
  angle: 25,
120
- length: gridSize,
121
  iterations: 3
122
  };
123
- let turtleString = lSys.axiom;
124
- for (let i = 0; i < lSys.iterations; i++) {
125
- turtleString = applyLSysRules(turtleString, lSys.rules);
126
- }
127
- const creatureMaterial = new THREE.MeshPhongMaterial({ color: 0x0000ff });
128
- const creature = new THREE.Group();
129
- let stack = [];
130
- let pos = new THREE.Vector3(worldSize / 4, 0, worldSize / 4);
131
- let dir = new THREE.Vector3(0, lSys.length, 0);
132
-
133
- for (let char of turtleString) {
134
- if (char === "F") {
135
- const segment = new THREE.Mesh(new THREE.CylinderGeometry(2, 2, lSys.length, 16), creatureMaterial);
136
- segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
137
- segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
138
- creature.add(segment);
139
- pos.add(dir);
140
- } else if (char === "+") {
141
- dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
142
- } else if (char === "-") {
143
- dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
144
- } else if (char === "[") {
145
- stack.push({ pos: pos.clone(), dir: dir.clone() });
146
- } else if (char === "]") {
147
- const state = stack.pop();
148
- pos = state.pos;
149
- dir = state.dir;
150
  }
151
- }
152
- scene.add(creature);
153
- creatures.push(creature);
154
- sendQuineMessage(creature);
155
- }
156
 
157
- function applyLSysRules(str, rules) {
158
- return str.split("").map(char => rules[char] || char).join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
 
161
  function sendQuineMessage(sender) {
162
- const quine = "function q(){console.log('Message from creature: '+q.toString())}q()";
163
- const messageGeometry = new THREE.TextGeometry("Quine Msg", {
164
- font: new THREE.FontLoader().parse({}), // Placeholder: requires font file
165
- size: 10,
166
- height: 2
167
- });
168
- const messageMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
169
- const message = new THREE.Mesh(messageGeometry, messageMaterial);
170
- message.position.copy(sender.position).add(new THREE.Vector3(0, 50, 0));
171
- scene.add(message);
172
- messages.push({ mesh: message, ttl: 100 });
173
- setTimeout(() => creatures.forEach(c => c !== sender && listenToMessage(c, quine)), 1000);
174
  }
175
 
176
- function listenToMessage(creature, msg) {
177
  const response = new THREE.Mesh(
178
- new THREE.SphereGeometry(5, 32, 32),
179
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
180
  );
181
- response.position.copy(creature.position).add(new THREE.Vector3(0, 30, 0));
182
  scene.add(response);
183
  setTimeout(() => scene.remove(response), 2000);
184
  }
185
 
186
- function updateSnake() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  const head = snake[0];
188
- const newHead = head.clone();
189
- newHead.position.add(snake.direction);
190
 
191
- if (Math.abs(newHead.position.x) > worldSize / 2 || Math.abs(newHead.position.z) > worldSize / 2) {
192
- resetGame();
 
193
  return;
194
  }
 
 
195
  for (let i = 1; i < snake.length; i++) {
196
- if (newHead.position.distanceTo(snake[i].position) < gridSize) {
197
- resetGame();
198
  return;
199
  }
200
  }
201
 
202
  snake.unshift(newHead);
203
  scene.add(newHead);
204
- if (newHead.position.distanceTo(food.position) < gridSize) {
205
- placeFood();
206
- createLSysCreature();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  } else {
208
- scene.remove(snake.pop());
209
  }
210
  }
211
 
212
- function resetGame() {
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  snake.forEach(seg => scene.remove(seg));
214
  snake = [];
215
- const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
216
  for (let i = 0; i < 3; i++) {
217
- const segment = new THREE.Mesh(new THREE.SphereGeometry(gridSize / 2, 32, 32), snakeMaterial);
218
- segment.position.set(-i * gridSize, 0, 0);
219
  snake.push(segment);
220
  scene.add(segment);
221
  }
222
- snake.direction = new THREE.Vector3(gridSize, 0, 0);
223
- placeFood();
224
  }
225
 
226
- let moveCounter = 0;
227
- const moveDelay = 10;
228
- function animate() {
229
- requestAnimationFrame(animate);
230
- controls.update();
231
- moveCounter++;
232
- if (moveCounter >= moveDelay) {
233
- updateSnake();
234
- moveCounter = 0;
235
- }
236
- messages = messages.filter(m => {
237
- m.ttl--;
238
- if (m.ttl <= 0) scene.remove(m.mesh);
239
- return m.ttl > 0;
240
- });
241
- renderer.render(scene, camera);
 
 
 
 
 
 
 
242
  }
243
 
244
- document.addEventListener("keydown", (e) => {
245
- switch (e.key) {
246
- case "ArrowUp": snake.direction.set(0, 0, -gridSize); break;
247
- case "ArrowDown": snake.direction.set(0, 0, gridSize); break;
248
- case "ArrowLeft": snake.direction.set(-gridSize, 0, 0); break;
249
- case "ArrowRight": snake.direction.set(gridSize, 0, 0); break;
250
- case "r": resetGame(); break;
 
251
  }
252
- });
 
 
 
 
 
253
 
254
- window.addEventListener("resize", () => {
255
  camera.aspect = window.innerWidth / window.innerHeight;
256
  camera.updateProjectionMatrix();
257
  renderer.setSize(window.innerWidth, window.innerHeight);
258
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  init();
261
  </script>
@@ -263,4 +330,18 @@ html_code = r"""
263
  </html>
264
  """
265
 
266
- st.components.v1.html(html_code, height=700, scrolling=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from streamlit.components.v1 import html
3
 
4
+ # Set Streamlit to wide mode
5
+ st.set_page_config(layout="wide", page_title="Galaxian Snake 3D")
6
+
7
+ # Define the enhanced HTML content with Three.js
8
+ game_html = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  <!DOCTYPE html>
10
+ <html lang="en">
11
  <head>
12
  <meta charset="UTF-8">
13
+ <title>Galaxian Snake 3D</title>
14
  <style>
15
+ html, body { margin: 0; padding: 0; overflow: hidden; background: #000; font-family: Arial; height: 100%; width: 100%; }
16
+ canvas { display: block; width: 100vw !important; height: 100vh !important; }
17
+ #ui { position: absolute; top: 10px; left: 10px; color: white; z-index: 1; }
18
+ #sidebar { position: absolute; top: 10px; right: 10px; color: white; width: 200px; background: rgba(0,0,0,0.7); padding: 10px; z-index: 1; }
19
+ #lives { position: absolute; top: 40px; left: 10px; color: white; z-index: 1; }
20
+ .message { position: absolute; color: cyan; font-size: 16px; z-index: 1; }
21
  </style>
 
 
22
  </head>
23
  <body>
24
+ <div id="ui">Score: <span id="score">0</span> | Time: <span id="timer">0</span>s</div>
25
+ <div id="lives">Lives: <span id="livesCount">3</span></div>
26
+ <div id="sidebar">
27
+ <h3>High Scores</h3>
28
+ <div id="highScores"></div>
29
+ <button onclick="saveScore()">Save Score</button>
30
+ </div>
31
+ <script type="module">
32
+ import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
33
+
34
+ let scene, camera, renderer, snake, foodItems = [], lSysCreatures = [], messages = [];
35
+ let clock = new THREE.Clock();
36
+ let moveDir = new THREE.Vector3(1, 0, 0), moveSpeed = 0.2;
37
+ let score = 0, gameTime = 0, lives = 3;
38
+ let highScores = JSON.parse(localStorage.getItem('highScores')) || [];
39
+ let gameOver = false;
40
 
41
  function init() {
 
42
  scene = new THREE.Scene();
43
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
44
+ renderer = new THREE.WebGLRenderer({ antialias: true });
45
  renderer.setSize(window.innerWidth, window.innerHeight);
46
  document.body.appendChild(renderer.domElement);
47
 
48
+ camera.position.set(0, 20, 30);
49
+ camera.lookAt(0, 0, 0);
50
+
51
+ // Initialize 3D snake
52
+ snake = [];
53
+ const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
54
+ for (let i = 0; i < 3; i++) {
55
+ const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
56
+ segment.position.set(-i * 1.2, 0, 0);
57
+ snake.push(segment);
58
+ scene.add(segment);
59
+ }
60
+
61
+ spawnFood();
62
+ spawnLSysCreatures();
63
 
64
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
 
65
  scene.add(ambientLight);
66
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
67
+ directionalLight.position.set(5, 10, 5);
68
  scene.add(directionalLight);
69
 
70
  // Starfield
71
  const starsGeometry = new THREE.BufferGeometry();
72
+ const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1 });
73
  const starPositions = new Float32Array(1000 * 3);
74
  for (let i = 0; i < 1000; i++) {
75
+ starPositions[i * 3] = (Math.random() - 0.5) * 100;
76
+ starPositions[i * 3 + 1] = (Math.random() - 0.5) * 100;
77
+ starPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
78
  }
79
  starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
80
+ scene.add(new THREE.Points(starsGeometry, starsMaterial));
 
81
 
82
+ window.addEventListener('keydown', onKeyDown);
83
+ window.addEventListener('resize', onWindowResize);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ updateHighScoresUI();
86
  animate();
87
  }
88
 
89
+ function spawnFood() {
90
+ const foodGeometry = new THREE.DodecahedronGeometry(0.5);
 
91
  const foodMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff });
92
+ for (let i = 0; i < 5; i++) {
93
+ const food = new THREE.Mesh(foodGeometry, foodMaterial);
94
+ food.position.set((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
95
+ foodItems.push(food);
96
+ scene.add(food);
97
+ }
 
98
  }
99
 
100
+ function spawnLSysCreatures() {
101
  const lSys = {
102
  axiom: "F",
103
  rules: { "F": "F[+F]F[-F]F" },
104
  angle: 25,
105
+ length: 2,
106
  iterations: 3
107
  };
108
+ const material = new THREE.MeshPhongMaterial({ color: 0x0000ff });
109
+ for (let i = 0; i < 3; i++) {
110
+ let turtleString = lSys.axiom;
111
+ for (let j = 0; j < lSys.iterations; j++) {
112
+ turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  }
114
+ const creature = new THREE.Group();
115
+ let stack = [], pos = new THREE.Vector3((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
116
+ let dir = new THREE.Vector3(0, lSys.length, 0);
 
 
117
 
118
+ for (let char of turtleString) {
119
+ if (char === 'F') {
120
+ const segment = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, lSys.length, 8), material);
121
+ segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
122
+ segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
123
+ creature.add(segment);
124
+ pos.add(dir);
125
+ } else if (char === '+') {
126
+ dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
127
+ } else if (char === '-') {
128
+ dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
129
+ } else if (char === '[') {
130
+ stack.push({ pos: pos.clone(), dir: dir.clone() });
131
+ } else if (char === ']') {
132
+ const state = stack.pop();
133
+ pos = state.pos;
134
+ dir = state.dir;
135
+ }
136
+ }
137
+ creature.position.set(pos.x, 0, pos.z);
138
+ lSysCreatures.push(creature);
139
+ scene.add(creature);
140
+ sendQuineMessage(creature);
141
+ }
142
  }
143
 
144
  function sendQuineMessage(sender) {
145
+ const quine = "function q(){alert('I am alive! '+q.toString())}q()";
146
+ const messageDiv = document.createElement('div');
147
+ messageDiv.className = 'message';
148
+ messageDiv.innerText = 'Quine Msg';
149
+ messageDiv.style.left = `${(sender.position.x + 20) * window.innerWidth / 40}px`;
150
+ messageDiv.style.top = `${(20 - sender.position.z) * window.innerHeight / 40}px`;
151
+ document.body.appendChild(messageDiv);
152
+ messages.push({ div: messageDiv, ttl: 3 });
153
+ setTimeout(() => lSysCreatures.forEach(c => c !== sender && listenToMessage(c)), 1000);
 
 
 
154
  }
155
 
156
+ function listenToMessage(creature) {
157
  const response = new THREE.Mesh(
158
+ new THREE.SphereGeometry(0.3, 16, 16),
159
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
160
  );
161
+ response.position.copy(creature.position).add(new THREE.Vector3(0, 5, 0));
162
  scene.add(response);
163
  setTimeout(() => scene.remove(response), 2000);
164
  }
165
 
166
+ function onKeyDown(event) {
167
+ if (gameOver && event.code === 'KeyR') {
168
+ resetGame();
169
+ return;
170
+ }
171
+ if (gameOver) return;
172
+ switch (event.code) {
173
+ case 'ArrowLeft': case 'KeyA': moveDir.set(-1, 0, 0); break;
174
+ case 'ArrowRight': case 'KeyD': moveDir.set(1, 0, 0); break;
175
+ case 'ArrowUp': case 'KeyW': moveDir.set(0, 0, -1); break;
176
+ case 'ArrowDown': case 'KeyS': moveDir.set(0, 0, 1); break;
177
+ }
178
+ }
179
+
180
+ function updateSnake(delta) {
181
+ if (gameOver) return;
182
  const head = snake[0];
183
+ const newHead = new THREE.Mesh(head.geometry, head.material);
184
+ newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(moveSpeed));
185
 
186
+ // Boundary check
187
+ if (Math.abs(newHead.position.x) > 20 || Math.abs(newHead.position.z) > 20) {
188
+ loseLife();
189
  return;
190
  }
191
+
192
+ // Self-collision check
193
  for (let i = 1; i < snake.length; i++) {
194
+ if (newHead.position.distanceTo(snake[i].position) < 0.9) {
195
+ loseLife();
196
  return;
197
  }
198
  }
199
 
200
  snake.unshift(newHead);
201
  scene.add(newHead);
202
+
203
+ // Food collision
204
+ for (let i = foodItems.length - 1; i >= 0; i--) {
205
+ if (newHead.position.distanceTo(foodItems[i].position) < 1) {
206
+ scene.remove(foodItems[i]);
207
+ foodItems.splice(i, 1);
208
+ score += 10;
209
+ spawnFood(); // Add new food
210
+ spawnLSysCreatures(); // Add new creature
211
+ break;
212
+ } else {
213
+ snake.pop(); // Remove tail if no food eaten
214
+ scene.remove(snake[snake.length - 1]);
215
+ }
216
+ }
217
+
218
+ // Creature collision
219
+ for (let creature of lSysCreatures) {
220
+ if (newHead.position.distanceTo(creature.position) < 2) {
221
+ loseLife();
222
+ return;
223
+ }
224
+ }
225
+ }
226
+
227
+ function loseLife() {
228
+ lives--;
229
+ updateUI();
230
+ if (lives <= 0) {
231
+ gameOver = true;
232
+ alert("Game Over! Final Score: " + score);
233
+ saveScore();
234
  } else {
235
+ explodeSnake();
236
  }
237
  }
238
 
239
+ function explodeSnake() {
240
+ const particleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
241
+ const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
242
+ for (let i = 0; i < 20; i++) {
243
+ const particle = new THREE.Mesh(particleGeometry, particleMaterial);
244
+ particle.position.copy(snake[0].position);
245
+ particle.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(3);
246
+ scene.add(particle);
247
+ setTimeout(() => scene.remove(particle), 1000);
248
+ }
249
+ resetSnake();
250
+ }
251
+
252
+ function resetSnake() {
253
  snake.forEach(seg => scene.remove(seg));
254
  snake = [];
255
+ const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
256
  for (let i = 0; i < 3; i++) {
257
+ const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
258
+ segment.position.set(-i * 1.2, 0, 0);
259
  snake.push(segment);
260
  scene.add(segment);
261
  }
262
+ moveDir.set(1, 0, 0);
 
263
  }
264
 
265
+ function resetGame() {
266
+ resetSnake();
267
+ foodItems.forEach(f => scene.remove(f));
268
+ lSysCreatures.forEach(c => scene.remove(c));
269
+ foodItems = [];
270
+ lSysCreatures = [];
271
+ spawnFood();
272
+ spawnLSysCreatures();
273
+ score = 0;
274
+ lives = 3;
275
+ gameOver = false;
276
+ updateUI();
277
+ }
278
+
279
+ function updateUI() {
280
+ document.getElementById('score').innerText = score;
281
+ document.getElementById('timer').innerText = Math.floor(gameTime);
282
+ document.getElementById('livesCount').innerText = lives;
283
+ }
284
+
285
+ function updateHighScoresUI() {
286
+ const scoresDiv = document.getElementById('highScores');
287
+ scoresDiv.innerHTML = highScores.map(s => `${s.name}: ${s.score} (${s.time}s)`).join('<br>');
288
  }
289
 
290
+ window.saveScore = function() {
291
+ const name = prompt("Enter 3-letter name:", generateRandomName());
292
+ if (name && name.length === 3) {
293
+ highScores.push({ name, score, time: Math.floor(gameTime) });
294
+ highScores.sort((a, b) => b.score - a.score);
295
+ highScores = highScores.slice(0, 5);
296
+ localStorage.setItem('highScores', JSON.stringify(highScores));
297
+ updateHighScoresUI();
298
  }
299
+ }
300
+
301
+ function generateRandomName() {
302
+ const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
303
+ return Array(3).fill().map(() => letters[Math.floor(Math.random() * letters.length)]).join('');
304
+ }
305
 
306
+ function onWindowResize() {
307
  camera.aspect = window.innerWidth / window.innerHeight;
308
  camera.updateProjectionMatrix();
309
  renderer.setSize(window.innerWidth, window.innerHeight);
310
+ }
311
+
312
+ function animate() {
313
+ requestAnimationFrame(animate);
314
+ const delta = clock.getDelta();
315
+ gameTime += delta;
316
+
317
+ updateSnake(delta);
318
+ messages = messages.filter(m => {
319
+ m.ttl -= delta;
320
+ if (m.ttl <= 0) document.body.removeChild(m.div);
321
+ return m.ttl > 0;
322
+ });
323
+
324
+ renderer.render(scene, camera);
325
+ }
326
 
327
  init();
328
  </script>
 
330
  </html>
331
  """
332
 
333
+ # Streamlit app with sidebar
334
+ with st.sidebar:
335
+ st.title("Galaxian Snake 3D")
336
+ st.write("**Controls:**")
337
+ st.write("- WASD or Arrow Keys to move")
338
+ st.write("- R to reset after game over")
339
+ st.write("**Objective:**")
340
+ st.write("- Eat alien food (pink dodecahedrons) to grow")
341
+ st.write("- Avoid L-system creatures (blue structures)")
342
+ st.write("- Watch creatures exchange quine messages")
343
+
344
+ # Render the HTML game
345
+ html(game_html, height=800, width=2000, scrolling=False)
346
+
347
+ st.write("Note: Requires internet for Three.js to load.")