awacke1 commited on
Commit
f41e68b
·
verified ·
1 Parent(s): cb20913

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -173
app.py CHANGED
@@ -4,53 +4,73 @@ from streamlit.components.v1 import html
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 = [], cityscape = [];
35
  let clock = new THREE.Clock();
36
- let moveDir = new THREE.Vector3(1, 0, 0), moveSpeed = 2; // Adjusted speed for visibility
37
- let score = 0, gameTime = 0, lives = 3, moveCounter = 0, moveInterval = 0.1; // Ticker interval in seconds
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, 30, 40);
49
  camera.lookAt(0, 0, 0);
50
 
51
- // Initialize 3D snake
52
  resetSnake();
53
-
54
  spawnFood();
55
  spawnCityscape();
56
 
@@ -60,15 +80,14 @@ game_html = """
60
  directionalLight.position.set(5, 10, 5);
61
  scene.add(directionalLight);
62
 
63
- // Starfield
64
  const starsGeometry = new THREE.BufferGeometry();
65
- const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1 });
66
  const starPositions = new Float32Array(1000 * 3);
67
- for (let i = 0; i < 1000; i++) {
68
  starPositions[i * 3] = (Math.random() - 0.5) * 100;
69
  starPositions[i * 3 + 1] = (Math.random() - 0.5) * 100;
70
  starPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
71
- }
72
  starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
73
  scene.add(new THREE.Points(starsGeometry, starsMaterial));
74
 
@@ -77,39 +96,39 @@ game_html = """
77
 
78
  updateHighScoresUI();
79
  animate();
80
- }
81
 
82
- function spawnFood() {
83
  const foodGeometry = new THREE.DodecahedronGeometry(0.5);
84
- const foodMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff });
85
- for (let i = 0; i < 5; i++) {
86
  const food = new THREE.Mesh(foodGeometry, foodMaterial);
87
  food.position.set((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
88
  foodItems.push(food);
89
  scene.add(food);
90
- }
91
- }
92
 
93
- function spawnCityscape() {
94
- const lSys = {
95
  axiom: "F",
96
- rules: { "F": "F[+F]F[-F][F]" },
97
  angle: 30,
98
  length: 3,
99
  iterations: 2
100
- };
101
- const material = new THREE.MeshPhongMaterial({ color: 0x808080 });
102
- for (let i = 0; i < 10; i++) {
103
  let turtleString = lSys.axiom;
104
- for (let j = 0; j < lSys.iterations; j++) {
105
  turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
106
- }
107
  const building = new THREE.Group();
108
  let stack = [], pos = new THREE.Vector3((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
109
  let dir = new THREE.Vector3(0, lSys.length, 0);
110
 
111
- for (let char of turtleString) {
112
- if (char === 'F') {
113
  const height = Math.random() * 2 + 1;
114
  const segment = new THREE.Mesh(
115
  Math.random() > 0.5 ? new THREE.BoxGeometry(1, height, 1) : new THREE.CylinderGeometry(0.5, 0.5, height, 8),
@@ -119,103 +138,103 @@ game_html = """
119
  segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
120
  building.add(segment);
121
  pos.add(dir);
122
- } else if (char === '+') {
123
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
124
- } else if (char === '-') {
125
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
126
- } else if (char === '[') {
127
- stack.push({ pos: pos.clone(), dir: dir.clone() });
128
- } else if (char === ']') {
129
  const state = stack.pop();
130
  pos = state.pos;
131
  dir = state.dir;
132
- }
133
- }
134
  building.position.set(pos.x, 0, pos.z);
135
  cityscape.push(building);
136
  scene.add(building);
137
  if (Math.random() > 0.7) spawnLSysCreature(building.position);
138
- }
139
- }
140
 
141
- function spawnLSysCreature(position) {
142
- const lSys = {
143
  axiom: "F",
144
- rules: { "F": "F[+F]F[-F]F" },
145
  angle: 25,
146
  length: 2,
147
  iterations: 2
148
- };
149
- const material = new THREE.MeshPhongMaterial({ color: 0x0000ff });
150
  let turtleString = lSys.axiom;
151
- for (let j = 0; j < lSys.iterations; j++) {
152
  turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
153
- }
154
  const creature = new THREE.Group();
155
  let stack = [], pos = position.clone(), dir = new THREE.Vector3(0, lSys.length, 0);
156
 
157
- for (let char of turtleString) {
158
- if (char === 'F') {
159
  const segment = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, lSys.length, 8), material);
160
  segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
161
  segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
162
  creature.add(segment);
163
  pos.add(dir);
164
- } else if (char === '+') {
165
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
166
- } else if (char === '-') {
167
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
168
- } else if (char === '[') {
169
- stack.push({ pos: pos.clone(), dir: dir.clone() });
170
- } else if (char === ']') {
171
- const state = stack.pop();
172
- pos = state.pos;
173
- dir = state.dir;
174
- }
175
- }
176
  creature.position.copy(position);
177
  lSysCreatures.push(creature);
178
  scene.add(creature);
179
  sendQuineMessage(creature);
180
- }
181
 
182
- function sendQuineMessage(sender) {
183
- const quine = "function q(){console.log('Alive! '+q.toString())}q()";
184
  const messageDiv = document.createElement('div');
185
  messageDiv.className = 'message';
186
  messageDiv.innerText = 'Quine Msg';
187
- messageDiv.style.left = `${(sender.position.x + 20) * window.innerWidth / 40}px`;
188
- messageDiv.style.top = `${(20 - sender.position.z) * window.innerHeight / 40}px`;
189
  document.body.appendChild(messageDiv);
190
- messages.push({ div: messageDiv, ttl: 3 });
191
  setTimeout(() => lSysCreatures.forEach(c => c !== sender && listenToMessage(c)), 1000);
192
- }
193
 
194
- function listenToMessage(creature) {
195
  const response = new THREE.Mesh(
196
  new THREE.SphereGeometry(0.3, 16, 16),
197
- new THREE.MeshBasicMaterial({ color: 0xff0000 })
198
  );
199
  response.position.copy(creature.position).add(new THREE.Vector3(0, 5, 0));
200
  scene.add(response);
201
  setTimeout(() => scene.remove(response), 2000);
202
- }
203
 
204
- function onKeyDown(event) {
205
- if (gameOver && event.code === 'KeyR') {
206
  resetGame();
207
  return;
208
- }
209
  if (gameOver) return;
210
- switch (event.code) {
211
  case 'ArrowLeft': case 'KeyA': if (moveDir.x !== 1) moveDir.set(-1, 0, 0); break;
212
  case 'ArrowRight': case 'KeyD': if (moveDir.x !== -1) moveDir.set(1, 0, 0); break;
213
  case 'ArrowUp': case 'KeyW': if (moveDir.z !== 1) moveDir.set(0, 0, -1); break;
214
  case 'ArrowDown': case 'KeyS': if (moveDir.z !== -1) moveDir.set(0, 0, 1); break;
215
- }
216
- }
217
 
218
- function updateSnake(delta) {
219
  if (gameOver) return;
220
  moveCounter += delta;
221
  if (moveCounter < moveInterval) return;
@@ -223,96 +242,111 @@ game_html = """
223
 
224
  const head = snake[0];
225
  const newHead = new THREE.Mesh(head.geometry, head.material);
226
- newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(1)); // Move by 1 unit
227
 
228
- // Boundary check
229
- if (Math.abs(newHead.position.x) > 20 || Math.abs(newHead.position.z) > 20) {
230
  loseLife();
231
  return;
232
- }
233
 
234
- // Self-collision check
235
- for (let i = 1; i < snake.length; i++) {
236
- if (newHead.position.distanceTo(snake[i].position) < 0.9) {
237
  loseLife();
238
  return;
239
- }
240
- }
241
 
242
- // Cityscape collision
243
- for (let building of cityscape) {
244
- if (newHead.position.distanceTo(building.position) < 2) {
245
  loseLife();
246
  return;
247
- }
248
- }
249
 
250
  snake.unshift(newHead);
251
  scene.add(newHead);
252
 
253
- // Food collision
254
- for (let i = foodItems.length - 1; i >= 0; i--) {
255
- if (newHead.position.distanceTo(foodItems[i].position) < 1) {
256
  scene.remove(foodItems[i]);
257
  foodItems.splice(i, 1);
258
- score += 10;
259
- spawnFood(); // Add new food
260
  if (Math.random() > 0.8) spawnLSysCreature(newHead.position.clone());
 
261
  break;
262
- } else {
263
  const tail = snake.pop();
264
  scene.remove(tail);
265
- }
266
- }
267
 
268
- // Creature collision
269
- for (let creature of lSysCreatures) {
270
- if (newHead.position.distanceTo(creature.position) < 2) {
271
  loseLife();
272
  return;
273
- }
274
- }
275
- }
276
-
277
- function loseLife() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  lives--;
279
  updateUI();
280
- if (lives <= 0) {
281
- gameOver = true;
282
- alert("Game Over! Final Score: " + score);
283
- saveScore();
284
- } else {
285
  explodeSnake();
286
- }
287
- }
288
 
289
- function explodeSnake() {
290
  const particleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
291
- const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
292
- for (let i = 0; i < 20; i++) {
293
  const particle = new THREE.Mesh(particleGeometry, particleMaterial);
294
  particle.position.copy(snake[0].position);
295
  particle.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(3);
296
  scene.add(particle);
297
  setTimeout(() => scene.remove(particle), 1000);
298
- }
299
  resetSnake();
300
- }
301
 
302
- function resetSnake() {
303
  snake.forEach(seg => scene.remove(seg));
304
  snake = [];
305
- const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
306
- for (let i = 0; i < 3; i++) {
307
  const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
308
  segment.position.set(-i * 1.2, 0, 0);
309
  snake.push(segment);
310
  scene.add(segment);
311
- }
312
  moveDir.set(1, 0, 0);
313
- }
 
314
 
315
- function resetGame() {
316
  resetSnake();
317
  foodItems.forEach(f => scene.remove(f));
318
  lSysCreatures.forEach(c => scene.remove(c));
@@ -325,57 +359,66 @@ game_html = """
325
  score = 0;
326
  lives = 3;
327
  gameOver = false;
 
328
  moveCounter = 0;
 
329
  updateUI();
330
- }
331
 
332
- function updateUI() {
333
- document.getElementById('score').innerText = score;
 
 
 
 
 
 
334
  document.getElementById('timer').innerText = Math.floor(gameTime);
335
  document.getElementById('livesCount').innerText = lives;
336
- }
 
337
 
338
- function updateHighScoresUI() {
339
  const scoresDiv = document.getElementById('highScores');
340
- scoresDiv.innerHTML = highScores.map(s => `${s.name}: ${s.score} (${s.time}s)`).join('<br>');
341
- }
342
-
343
- window.saveScore = function() {
344
- const name = prompt("Enter 3-letter name:", generateRandomName());
345
- if (name && name.length === 3) {
346
- highScores.push({ name, score, time: Math.floor(gameTime) });
347
- highScores.sort((a, b) => b.score - a.score);
348
- highScores = highScores.slice(0, 5);
349
- localStorage.setItem('highScores', JSON.stringify(highScores));
350
- updateHighScoresUI();
351
- }
352
- }
353
-
354
- function generateRandomName() {
355
- const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
356
- return Array(3).fill().map(() => letters[Math.floor(Math.random() * letters.length)]).join('');
357
- }
358
-
359
- function onWindowResize() {
360
  camera.aspect = window.innerWidth / window.innerHeight;
361
  camera.updateProjectionMatrix();
362
  renderer.setSize(window.innerWidth, window.innerHeight);
363
- }
364
 
365
- function animate() {
366
  requestAnimationFrame(animate);
367
  const delta = clock.getDelta();
368
  gameTime += delta;
369
 
370
- updateSnake(delta);
371
- messages = messages.filter(m => {
 
 
 
 
 
 
 
 
372
  m.ttl -= delta;
373
  if (m.ttl <= 0) document.body.removeChild(m.div);
374
  return m.ttl > 0;
375
- });
376
 
377
  renderer.render(scene, camera);
378
- }
379
 
380
  init();
381
  </script>
@@ -383,18 +426,7 @@ game_html = """
383
  </html>
384
  """
385
 
386
- # Streamlit app with sidebar
387
- with st.sidebar:
388
- st.title("Galaxian Snake 3D")
389
- st.write("**Controls:**")
390
- st.write("- WASD or Arrow Keys to move")
391
- st.write("- R to reset after game over")
392
- st.write("**Objective:**")
393
- st.write("- Eat alien food (pink dodecahedrons) to grow")
394
- st.write("- Avoid cityscape buildings and L-system creatures")
395
- st.write("- Watch creatures exchange quine messages")
396
-
397
- # Render the HTML game
398
  html(game_html, height=800, width=2000, scrolling=False)
399
 
400
  st.write("Note: Requires internet for Three.js to load.")
 
4
  # Set Streamlit to wide mode
5
  st.set_page_config(layout="wide", page_title="Galaxian Snake 3D")
6
 
7
+ # Player name input in Streamlit
8
+ with st.sidebar:
9
+ st.title("Galaxian Snake 3D")
10
+ player_name = st.text_input("Enter 3-letter name (e.g., ABC):", max_chars=3, value="XYZ").upper()
11
+ if len(player_name) != 3 or not player_name.isalpha():
12
+ st.warning("Please enter a valid 3-letter name using A-Z.")
13
+ player_name = "XYZ" # Default if invalid
14
+ st.write("**Controls:**")
15
+ st.write("- WASD or Arrow Keys to move")
16
+ st.write("- R to reset after game over")
17
+ st.write("**Objective:**")
18
+ st.write("- Eat food to grow and score")
19
+ st.write("- Avoid obstacles and creatures")
20
+ st.write("**Scoring:**")
21
+ st.write("- 2 pts for doubling length")
22
+ st.write("- +2 pts/sec, +4 pts/sec after 10 units")
23
+ st.write("- 10 pt bonus at 10+ units")
24
+ st.write("- Game ends at 5 minutes")
25
+
26
+ # Define the enhanced HTML content with Three.js, injecting player_name
27
+ game_html = f"""
28
  <!DOCTYPE html>
29
  <html lang="en">
30
  <head>
31
  <meta charset="UTF-8">
32
  <title>Galaxian Snake 3D</title>
33
  <style>
34
+ html, body {{ margin: 0; padding: 0; overflow: hidden; background: #000; font-family: Arial; height: 100%; width: 100%; }}
35
+ canvas {{ display: block; width: 100vw !important; height: 100vh !important; }}
36
+ #ui {{ position: absolute; top: 10px; left: 10px; color: white; z-index: 1; }}
37
+ #sidebar {{ position: absolute; top: 10px; right: 10px; color: white; width: 200px; background: rgba(0,0,0,0.7); padding: 10px; z-index: 1; }}
38
+ #lives {{ position: absolute; top: 40px; left: 10px; color: white; z-index: 1; }}
39
+ .message {{ position: absolute; color: cyan; font-size: 16px; z-index: 1; }}
40
+ #gameOver {{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: red; font-size: 48px; z-index: 1; display: none; }}
41
  </style>
42
  </head>
43
  <body>
44
+ <div id="ui">Score: <span id="score">0</span> | Time: <span id="timer">0</span>s | Length: <span id="length">3</span></div>
45
  <div id="lives">Lives: <span id="livesCount">3</span></div>
46
  <div id="sidebar">
47
  <h3>High Scores</h3>
48
  <div id="highScores"></div>
 
49
  </div>
50
+ <div id="gameOver">Game Over</div>
51
  <script type="module">
52
  import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
53
 
54
  let scene, camera, renderer, snake, foodItems = [], lSysCreatures = [], messages = [], cityscape = [];
55
  let clock = new THREE.Clock();
56
+ let moveDir = new THREE.Vector3(1, 0, 0), moveSpeed = 2;
57
+ let score = 0, gameTime = 0, lives = 3, moveCounter = 0, moveInterval = 0.1;
58
  let highScores = JSON.parse(localStorage.getItem('highScores')) || [];
59
+ let gameOver = false, initialLength = 3, lastLength = 3;
60
+ const playerName = "{player_name}";
61
+ const maxGameTime = 300; // 5 minutes in seconds
62
 
63
+ function init() {{
64
  scene = new THREE.Scene();
65
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
66
+ renderer = new THREE.WebGLRenderer({{ antialias: true }});
67
  renderer.setSize(window.innerWidth, window.innerHeight);
68
  document.body.appendChild(renderer.domElement);
69
 
70
  camera.position.set(0, 30, 40);
71
  camera.lookAt(0, 0, 0);
72
 
 
73
  resetSnake();
 
74
  spawnFood();
75
  spawnCityscape();
76
 
 
80
  directionalLight.position.set(5, 10, 5);
81
  scene.add(directionalLight);
82
 
 
83
  const starsGeometry = new THREE.BufferGeometry();
84
+ const starsMaterial = new THREE.PointsMaterial({{ color: 0xffffff, size: 0.1 }});
85
  const starPositions = new Float32Array(1000 * 3);
86
+ for (let i = 0; i < 1000; i++) {{
87
  starPositions[i * 3] = (Math.random() - 0.5) * 100;
88
  starPositions[i * 3 + 1] = (Math.random() - 0.5) * 100;
89
  starPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
90
+ }}
91
  starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
92
  scene.add(new THREE.Points(starsGeometry, starsMaterial));
93
 
 
96
 
97
  updateHighScoresUI();
98
  animate();
99
+ }}
100
 
101
+ function spawnFood() {{
102
  const foodGeometry = new THREE.DodecahedronGeometry(0.5);
103
+ const foodMaterial = new THREE.MeshPhongMaterial({{ color: 0xff00ff }});
104
+ for (let i = 0; i < 5; i++) {{
105
  const food = new THREE.Mesh(foodGeometry, foodMaterial);
106
  food.position.set((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
107
  foodItems.push(food);
108
  scene.add(food);
109
+ }}
110
+ }}
111
 
112
+ function spawnCityscape() {{
113
+ const lSys = {{
114
  axiom: "F",
115
+ rules: {{ "F": "F[+F]F[-F][F]" }},
116
  angle: 30,
117
  length: 3,
118
  iterations: 2
119
+ }};
120
+ const material = new THREE.MeshPhongMaterial({{ color: 0x808080 }});
121
+ for (let i = 0; i < 10; i++) {{
122
  let turtleString = lSys.axiom;
123
+ for (let j = 0; j < lSys.iterations; j++) {{
124
  turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
125
+ }}
126
  const building = new THREE.Group();
127
  let stack = [], pos = new THREE.Vector3((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
128
  let dir = new THREE.Vector3(0, lSys.length, 0);
129
 
130
+ for (let char of turtleString) {{
131
+ if (char === 'F') {{
132
  const height = Math.random() * 2 + 1;
133
  const segment = new THREE.Mesh(
134
  Math.random() > 0.5 ? new THREE.BoxGeometry(1, height, 1) : new THREE.CylinderGeometry(0.5, 0.5, height, 8),
 
138
  segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
139
  building.add(segment);
140
  pos.add(dir);
141
+ }} else if (char === '+') {{
142
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
143
+ }} else if (char === '-') {{
144
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
145
+ }} else if (char === '[') {{
146
+ stack.push({{ pos: pos.clone(), dir: dir.clone() }});
147
+ }} else if (char === ']') {{
148
  const state = stack.pop();
149
  pos = state.pos;
150
  dir = state.dir;
151
+ }}
152
+ }}
153
  building.position.set(pos.x, 0, pos.z);
154
  cityscape.push(building);
155
  scene.add(building);
156
  if (Math.random() > 0.7) spawnLSysCreature(building.position);
157
+ }}
158
+ }}
159
 
160
+ function spawnLSysCreature(position) {{
161
+ const lSys = {{
162
  axiom: "F",
163
+ rules: {{ "F": "F[+F]F[-F]F" }},
164
  angle: 25,
165
  length: 2,
166
  iterations: 2
167
+ }};
168
+ const material = new THREE.MeshPhongMaterial({{ color: 0x0000ff }});
169
  let turtleString = lSys.axiom;
170
+ for (let j = 0; j < lSys.iterations; j++) {{
171
  turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
172
+ }}
173
  const creature = new THREE.Group();
174
  let stack = [], pos = position.clone(), dir = new THREE.Vector3(0, lSys.length, 0);
175
 
176
+ for (let char of turtleString) {{
177
+ if (char === 'F') {{
178
  const segment = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, lSys.length, 8), material);
179
  segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
180
  segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
181
  creature.add(segment);
182
  pos.add(dir);
183
+ }} else if (char === '+') {{
184
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
185
+ }} else if (char === '-') {{
186
  dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
187
+ }} else if (char === '[') {{
188
+ stack.push({{ pos: pos.clone(), dir: dir.clone() }});
189
+ }} else if (char === ']') {{
190
+ const state = stack.pop();
191
+ pos = state.pos;
192
+ dir = state.dir;
193
+ }}
194
+ }}
195
  creature.position.copy(position);
196
  lSysCreatures.push(creature);
197
  scene.add(creature);
198
  sendQuineMessage(creature);
199
+ }}
200
 
201
+ function sendQuineMessage(sender) {{
202
+ const quine = "function q(){{console.log('Alive! '+q.toString())}}q()";
203
  const messageDiv = document.createElement('div');
204
  messageDiv.className = 'message';
205
  messageDiv.innerText = 'Quine Msg';
206
+ messageDiv.style.left = `${{(sender.position.x + 20) * window.innerWidth / 40}}px`;
207
+ messageDiv.style.top = `${{(20 - sender.position.z) * window.innerHeight / 40}}px`;
208
  document.body.appendChild(messageDiv);
209
+ messages.push({{ div: messageDiv, ttl: 3 }});
210
  setTimeout(() => lSysCreatures.forEach(c => c !== sender && listenToMessage(c)), 1000);
211
+ }}
212
 
213
+ function listenToMessage(creature) {{
214
  const response = new THREE.Mesh(
215
  new THREE.SphereGeometry(0.3, 16, 16),
216
+ new THREE.MeshBasicMaterial({{ color: 0xff0000 }})
217
  );
218
  response.position.copy(creature.position).add(new THREE.Vector3(0, 5, 0));
219
  scene.add(response);
220
  setTimeout(() => scene.remove(response), 2000);
221
+ }}
222
 
223
+ function onKeyDown(event) {{
224
+ if (gameOver && event.code === 'KeyR') {{
225
  resetGame();
226
  return;
227
+ }}
228
  if (gameOver) return;
229
+ switch (event.code) {{
230
  case 'ArrowLeft': case 'KeyA': if (moveDir.x !== 1) moveDir.set(-1, 0, 0); break;
231
  case 'ArrowRight': case 'KeyD': if (moveDir.x !== -1) moveDir.set(1, 0, 0); break;
232
  case 'ArrowUp': case 'KeyW': if (moveDir.z !== 1) moveDir.set(0, 0, -1); break;
233
  case 'ArrowDown': case 'KeyS': if (moveDir.z !== -1) moveDir.set(0, 0, 1); break;
234
+ }}
235
+ }}
236
 
237
+ function updateSnake(delta) {{
238
  if (gameOver) return;
239
  moveCounter += delta;
240
  if (moveCounter < moveInterval) return;
 
242
 
243
  const head = snake[0];
244
  const newHead = new THREE.Mesh(head.geometry, head.material);
245
+ newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(1));
246
 
247
+ if (Math.abs(newHead.position.x) > 20 || Math.abs(newHead.position.z) > 20) {{
 
248
  loseLife();
249
  return;
250
+ }}
251
 
252
+ for (let i = 1; i < snake.length; i++) {{
253
+ if (newHead.position.distanceTo(snake[i].position) < 0.9) {{
 
254
  loseLife();
255
  return;
256
+ }}
257
+ }}
258
 
259
+ for (let building of cityscape) {{
260
+ if (newHead.position.distanceTo(building.position) < 2) {{
 
261
  loseLife();
262
  return;
263
+ }}
264
+ }}
265
 
266
  snake.unshift(newHead);
267
  scene.add(newHead);
268
 
269
+ for (let i = foodItems.length - 1; i >= 0; i--) {{
270
+ if (newHead.position.distanceTo(foodItems[i].position) < 1) {{
 
271
  scene.remove(foodItems[i]);
272
  foodItems.splice(i, 1);
273
+ spawnFood();
 
274
  if (Math.random() > 0.8) spawnLSysCreature(newHead.position.clone());
275
+ checkLengthBonus();
276
  break;
277
+ }} else {{
278
  const tail = snake.pop();
279
  scene.remove(tail);
280
+ }}
281
+ }}
282
 
283
+ for (let creature of lSysCreatures) {{
284
+ if (newHead.position.distanceTo(creature.position) < 2) {{
 
285
  loseLife();
286
  return;
287
+ }}
288
+ }}
289
+ updateUI();
290
+ }}
291
+
292
+ function checkLengthBonus() {{
293
+ const currentLength = snake.length;
294
+ if (currentLength >= 2 * lastLength) {{
295
+ score += 2;
296
+ lastLength = currentLength;
297
+ }}
298
+ if (currentLength > 10 && lastLength <= 10) {{
299
+ score += 10;
300
+ }}
301
+ }}
302
+
303
+ function updateScore(delta) {{
304
+ const currentLength = snake.length;
305
+ if (currentLength > 10) {{
306
+ score += 4 * delta;
307
+ }} else {{
308
+ score += 2 * delta;
309
+ }}
310
+ }}
311
+
312
+ function loseLife() {{
313
  lives--;
314
  updateUI();
315
+ if (lives <= 0) {{
316
+ endGame();
317
+ }} else {{
 
 
318
  explodeSnake();
319
+ }}
320
+ }}
321
 
322
+ function explodeSnake() {{
323
  const particleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
324
+ const particleMaterial = new THREE.MeshBasicMaterial({{ color: 0xff0000 }});
325
+ for (let i = 0; i < 20; i++) {{
326
  const particle = new THREE.Mesh(particleGeometry, particleMaterial);
327
  particle.position.copy(snake[0].position);
328
  particle.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(3);
329
  scene.add(particle);
330
  setTimeout(() => scene.remove(particle), 1000);
331
+ }}
332
  resetSnake();
333
+ }}
334
 
335
+ function resetSnake() {{
336
  snake.forEach(seg => scene.remove(seg));
337
  snake = [];
338
+ const snakeMaterial = new THREE.MeshPhongMaterial({{ color: 0x00ff00, shininess: 100 }});
339
+ for (let i = 0; i < initialLength; i++) {{
340
  const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
341
  segment.position.set(-i * 1.2, 0, 0);
342
  snake.push(segment);
343
  scene.add(segment);
344
+ }}
345
  moveDir.set(1, 0, 0);
346
+ lastLength = initialLength;
347
+ }}
348
 
349
+ function resetGame() {{
350
  resetSnake();
351
  foodItems.forEach(f => scene.remove(f));
352
  lSysCreatures.forEach(c => scene.remove(c));
 
359
  score = 0;
360
  lives = 3;
361
  gameOver = false;
362
+ gameTime = 0;
363
  moveCounter = 0;
364
+ document.getElementById('gameOver').style.display = 'none';
365
  updateUI();
366
+ }}
367
 
368
+ function endGame() {{
369
+ gameOver = true;
370
+ document.getElementById('gameOver').style.display = 'block';
371
+ saveScore();
372
+ }}
373
+
374
+ function updateUI() {{
375
+ document.getElementById('score').innerText = Math.floor(score);
376
  document.getElementById('timer').innerText = Math.floor(gameTime);
377
  document.getElementById('livesCount').innerText = lives;
378
+ document.getElementById('length').innerText = snake.length;
379
+ }}
380
 
381
+ function updateHighScoresUI() {{
382
  const scoresDiv = document.getElementById('highScores');
383
+ scoresDiv.innerHTML = highScores.map(s => `${{s.name}}: ${{s.score}} (${{s.time}}s)`).join('<br>');
384
+ }}
385
+
386
+ function saveScore() {{
387
+ highScores.push({{ name: playerName, score: Math.floor(score), time: Math.floor(gameTime) }});
388
+ highScores.sort((a, b) => b.score - a.score);
389
+ highScores = highScores.slice(0, 5);
390
+ localStorage.setItem('highScores', JSON.stringify(highScores));
391
+ updateHighScoresUI();
392
+ }}
393
+
394
+ function onWindowResize() {{
 
 
 
 
 
 
 
 
395
  camera.aspect = window.innerWidth / window.innerHeight;
396
  camera.updateProjectionMatrix();
397
  renderer.setSize(window.innerWidth, window.innerHeight);
398
+ }}
399
 
400
+ function animate() {{
401
  requestAnimationFrame(animate);
402
  const delta = clock.getDelta();
403
  gameTime += delta;
404
 
405
+ if (gameTime >= maxGameTime && !gameOver) {{
406
+ endGame();
407
+ }}
408
+
409
+ if (!gameOver) {{
410
+ updateSnake(delta);
411
+ updateScore(delta);
412
+ }}
413
+
414
+ messages = messages.filter(m => {{
415
  m.ttl -= delta;
416
  if (m.ttl <= 0) document.body.removeChild(m.div);
417
  return m.ttl > 0;
418
+ }});
419
 
420
  renderer.render(scene, camera);
421
+ }}
422
 
423
  init();
424
  </script>
 
426
  </html>
427
  """
428
 
429
+ # Render the HTML game with the injected player name
 
 
 
 
 
 
 
 
 
 
 
430
  html(game_html, height=800, width=2000, scrolling=False)
431
 
432
  st.write("Note: Requires internet for Three.js to load.")