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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +293 -183
app.py CHANGED
@@ -15,163 +15,233 @@ with st.sidebar:
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
-
77
- const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
78
- scene.add(ambientLight);
79
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
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
-
94
- window.addEventListener('keydown', onKeyDown);
95
- window.addEventListener('resize', onWindowResize);
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),
135
- material
136
- );
137
- segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
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') {{
@@ -181,9 +251,9 @@ game_html = f"""
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 === ']') {{
@@ -203,8 +273,10 @@ game_html = f"""
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);
@@ -220,31 +292,57 @@ game_html = f"""
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;
241
  moveCounter = 0;
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
  }}
@@ -256,11 +354,18 @@ game_html = f"""
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);
@@ -286,7 +391,11 @@ game_html = f"""
286
  return;
287
  }}
288
  }}
289
- updateUI();
 
 
 
 
290
  }}
291
 
292
  function checkLengthBonus() {{
@@ -332,95 +441,96 @@ game_html = f"""
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));
353
- cityscape.forEach(b => scene.remove(b));
354
  foodItems = [];
355
  lSysCreatures = [];
356
- cityscape = [];
357
- spawnFood();
358
- spawnCityscape();
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>
425
  </body>
426
  </html>
 
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 (yellow cubes) to grow and score")
19
+ st.write("- Avoid buildings 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 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
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
  <title>Galaxian Snake 3D</title>
34
  <style>
35
+ body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; background: #000; }}
36
+ canvas {{ display: block; }}
37
+ .ui-container {{
38
+ position: absolute; top: 10px; left: 10px; color: white;
39
+ background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
40
+ user-select: none;
41
+ }}
42
+ .controls {{
43
+ position: absolute; bottom: 10px; left: 10px; color: white;
44
+ background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
45
+ }}
46
  .message {{ position: absolute; color: cyan; font-size: 16px; z-index: 1; }}
47
  #gameOver {{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: red; font-size: 48px; z-index: 1; display: none; }}
48
  </style>
49
  </head>
50
  <body>
51
+ <div class="ui-container">
52
+ <h2>Galaxian Snake 3D</h2>
53
+ <div id="score">Score: 0</div>
54
+ <div id="time">Time: 0</div>
55
+ <div id="length">Length: 3</div>
56
+ <div id="lives">Lives: 3</div>
57
+ </div>
58
+ <div class="controls">
59
+ <p>Controls: W/A/S/D or Arrow Keys to move, R to reset</p>
60
+ <p>Eat yellow cubes to grow and score points!</p>
61
  </div>
62
  <div id="gameOver">Game Over</div>
 
 
63
 
64
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
65
+ <script>
66
+ // Game variables
67
+ let score = 0, gameTime = 0, lives = 3, gameActive = true;
68
+ let snake, foodItems = [], lSysCreatures = [], messages = [], buildings = [];
 
69
  const playerName = "{player_name}";
70
  const maxGameTime = 300; // 5 minutes in seconds
71
+ let initialLength = 3, lastLength = 3, moveCounter = 0, moveInterval = 0.1;
72
+ let moveDir = new THREE.Vector3(1, 0, 0);
73
+
74
+ // Scene setup
75
+ const scene = new THREE.Scene();
76
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
77
+ camera.position.set(0, 20, 30);
78
+ const renderer = new THREE.WebGLRenderer({{ antialias: true }});
79
+ renderer.setSize(window.innerWidth, window.innerHeight);
80
+ renderer.shadowMap.enabled = true;
81
+ document.body.appendChild(renderer.domElement);
82
+
83
+ // Lighting
84
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
85
+ scene.add(ambientLight);
86
+ const sunLight = new THREE.DirectionalLight(0xffddaa, 0.8);
87
+ sunLight.castShadow = true;
88
+ sunLight.shadow.mapSize.width = 2048;
89
+ sunLight.shadow.mapSize.height = 2048;
90
+ sunLight.shadow.camera.near = 1;
91
+ sunLight.shadow.camera.far = 500;
92
+ sunLight.shadow.camera.left = -100;
93
+ sunLight.shadow.camera.right = 100;
94
+ sunLight.shadow.camera.top = 100;
95
+ sunLight.shadow.camera.bottom = -100;
96
+ scene.add(sunLight);
97
+
98
+ // Ground
99
+ const textureLoader = new THREE.TextureLoader();
100
+ const groundGeometry = new THREE.PlaneGeometry(200, 200);
101
+ const groundMaterial = new THREE.MeshStandardMaterial({{
102
+ color: 0x1a5e1a,
103
+ roughness: 0.8,
104
+ metalness: 0.2,
105
+ bumpMap: textureLoader.load('https://threejs.org/examples/textures/terrain/grasslight-big-nm.jpg'),
106
+ bumpScale: 0.1
107
+ }});
108
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
109
+ ground.rotation.x = -Math.PI / 2;
110
+ ground.receiveShadow = true;
111
+ scene.add(ground);
112
+
113
+ // Building rules
114
+ const buildingRules = [
115
+ {{name: "Colonial", axiom: "A", rules: {{"A": "B[+F][-F]", "B": "F[-C][+C]F", "C": "D[-E][+E]", "D": "F[+F][-F]F", "E": "F[-F][+F]"}}, iterations: 2, baseHeight: 10, baseWidth: 6, baseDepth: 6, angle: Math.PI/6, probability: 0.2}},
116
+ {{name: "Victorian", axiom: "A", rules: {{"A": "B[+C][-C][/D][\\D]", "B": "F[+F][-F][/F][\\F]", "C": "F[++F][--F]", "D": "F[+\\F][-\\F]"}}, iterations: 3, baseHeight: 15, baseWidth: 5, baseDepth: 5, angle: Math.PI/5, probability: 0.15}},
117
+ {{name: "Modern", axiom: "A", rules: {{"A": "B[+B][-B]", "B": "F[/C][\\C]", "C": "F"}}, iterations: 2, baseHeight: 20, baseWidth: 8, baseDepth: 8, angle: Math.PI/2, probability: 0.25}},
118
+ {{name: "Skyscraper", axiom: "A", rules: {{"A": "FB[+C][-C]", "B": "FB", "C": "F[+F][-F]"}}, iterations: 4, baseHeight: 30, baseWidth: 10, baseDepth: 10, angle: Math.PI/8, probability: 0.15}},
119
+ {{name: "Simple", axiom: "F", rules: {{"F": "F[+F][-F]"}}, iterations: 1, baseHeight: 8, baseWidth: 6, baseDepth: 6, angle: Math.PI/4, probability: 0.25}}
120
+ ];
121
+ const buildingColors = [0x888888, 0x666666, 0x999999, 0xaaaaaa, 0x555555, 0x334455, 0x445566, 0x223344, 0x556677, 0x667788, 0x993333, 0x884422, 0x553333, 0x772222, 0x664433];
122
+
123
+ function interpretLSystem(rule, position, rotation) {{
124
+ let currentString = rule.axiom;
125
+ for (let i = 0; i < rule.iterations; i++) {{
126
+ let newString = "";
127
+ for (let j = 0; j < currentString.length; j++) {{
128
+ newString += rule.rules[currentString[j]] || currentString[j];
129
+ }}
130
+ currentString = newString;
131
  }}
132
+
133
+ let group = new THREE.Group();
134
+ group.position.copy(position);
135
+ const stack = [];
136
+ let currentPosition = new THREE.Vector3(0, 0, 0);
137
+ let currentRotation = rotation || new THREE.Euler();
138
+ let scale = new THREE.Vector3(1, 1, 1);
139
+ const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
140
+ const material = new THREE.MeshStandardMaterial({{color: color, roughness: 0.7, metalness: 0.2}});
141
+
142
+ for (let i = 0; i < currentString.length; i++) {{
143
+ const char = currentString[i];
144
+ switch (char) {{
145
+ case 'F':
146
+ const width = rule.baseWidth * (0.5 + Math.random() * 0.5) * scale.x;
147
+ const height = rule.baseHeight * (0.5 + Math.random() * 0.5) * scale.y;
148
+ const depth = rule.baseDepth * (0.5 + Math.random() * 0.5) * scale.z;
149
+ const geometry = new THREE.BoxGeometry(width, height, depth);
150
+ const part = new THREE.Mesh(geometry, material);
151
+ part.position.copy(currentPosition);
152
+ part.position.y += height / 2;
153
+ part.rotation.copy(currentRotation);
154
+ part.castShadow = true;
155
+ part.receiveShadow = true;
156
+ group.add(part);
157
+ const direction = new THREE.Vector3(0, height, 0);
158
+ direction.applyEuler(currentRotation);
159
+ currentPosition.add(direction);
160
+ break;
161
+ case '+': currentRotation.y += rule.angle; break;
162
+ case '-': currentRotation.y -= rule.angle; break;
163
+ case '/': currentRotation.x += rule.angle; break;
164
+ case '\\\\': currentRotation.x -= rule.angle; break;
165
+ case '^': currentRotation.z += rule.angle; break;
166
+ case '&': currentRotation.z -= rule.angle; break;
167
+ case '[': stack.push({{position: currentPosition.clone(), rotation: currentRotation.clone(), scale: scale.clone()}}); break;
168
+ case ']': if (stack.length > 0) {{ const state = stack.pop(); currentPosition = state.position; currentRotation = state.rotation; scale = state.scale; }} break;
169
+ case '>': scale.multiplyScalar(1.2); break;
170
+ case '<': scale.multiplyScalar(0.8); break;
171
+ }}
172
+ }}
173
+ return group;
174
  }}
175
 
176
+ function createCity() {{
177
+ const citySize = 5, spacing = 15;
178
+ for (let x = -citySize; x <= citySize; x++) {{
179
+ for (let z = -citySize; z <= citySize; z++) {{
180
+ if (Math.random() < 0.2) continue;
181
+ const position = new THREE.Vector3(x * spacing + (Math.random() * 2 - 1), 0, z * spacing + (Math.random() * 2 - 1));
182
+ let selectedRule, random = Math.random(), cumulativeProbability = 0;
183
+ for (const rule of buildingRules) {{
184
+ cumulativeProbability += rule.probability;
185
+ if (random <= cumulativeProbability) {{ selectedRule = rule; break; }}
186
+ }}
187
+ if (!selectedRule) selectedRule = buildingRules[0];
188
+ const building = interpretLSystem(selectedRule, position, new THREE.Euler());
189
+ scene.add(building);
190
+ buildings.push(building);
191
+ if (Math.random() > 0.7) spawnLSysCreature(building.position);
192
+ }}
193
+ }}
194
+ const roadWidth = 8, roadColor = 0x333333;
195
+ for (let x = -citySize; x <= citySize; x++) {{
196
+ const road = new THREE.Mesh(
197
+ new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth),
198
+ new THREE.MeshStandardMaterial({{color: roadColor, roughness: 0.9, metalness: 0.1}})
199
+ );
200
+ road.rotation.x = -Math.PI / 2;
201
+ road.position.set(x * spacing, 0.01, 0);
202
+ scene.add(road);
203
+ }}
204
+ for (let z = -citySize; z <= citySize; z++) {{
205
+ const road = new THREE.Mesh(
206
+ new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth),
207
+ new THREE.MeshStandardMaterial({{color: roadColor, roughness: 0.9, metalness: 0.1}})
208
+ );
209
+ road.rotation.x = -Math.PI / 2;
210
+ road.position.set(0, 0.01, z * spacing);
211
+ scene.add(road);
212
  }}
213
  }}
214
 
215
+ function spawnFood() {{
216
+ const citySize = 5, spacing = 15;
 
 
 
 
 
 
 
217
  for (let i = 0; i < 10; i++) {{
218
+ const food = new THREE.Mesh(
219
+ new THREE.BoxGeometry(1, 1, 1),
220
+ new THREE.MeshStandardMaterial({{color: 0xffff00, emissive: 0xffff00, emissiveIntensity: 0.5, transparent: true, opacity: 0.8}})
221
+ );
222
+ const x = (Math.random() * 2 - 1) * citySize * spacing;
223
+ const z = (Math.random() * 2 - 1) * citySize * spacing;
224
+ food.position.set(x, 0.5, z);
225
+ foodItems.push(food);
226
+ scene.add(food);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }}
228
  }}
229
 
230
  function spawnLSysCreature(position) {{
231
  const lSys = {{
232
  axiom: "F",
233
+ rules: {{"F": "F[+F]F[-F]F"}},
234
+ angle: 25 * Math.PI / 180,
235
  length: 2,
236
  iterations: 2
237
  }};
 
238
  let turtleString = lSys.axiom;
239
  for (let j = 0; j < lSys.iterations; j++) {{
240
  turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
241
  }}
242
  const creature = new THREE.Group();
243
  let stack = [], pos = position.clone(), dir = new THREE.Vector3(0, lSys.length, 0);
244
+ const material = new THREE.MeshStandardMaterial({{color: 0x0000ff}});
245
 
246
  for (let char of turtleString) {{
247
  if (char === 'F') {{
 
251
  creature.add(segment);
252
  pos.add(dir);
253
  }} else if (char === '+') {{
254
+ dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle);
255
  }} else if (char === '-') {{
256
+ dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle);
257
  }} else if (char === '[') {{
258
  stack.push({{ pos: pos.clone(), dir: dir.clone() }});
259
  }} else if (char === ']') {{
 
273
  const messageDiv = document.createElement('div');
274
  messageDiv.className = 'message';
275
  messageDiv.innerText = 'Quine Msg';
276
+ const screenX = (sender.position.x + 75) * window.innerWidth / 150;
277
+ const screenY = (75 - sender.position.z) * window.innerHeight / 150;
278
+ messageDiv.style.left = `${{screenX}}px`;
279
+ messageDiv.style.top = `${{screenY}}px`;
280
  document.body.appendChild(messageDiv);
281
  messages.push({{ div: messageDiv, ttl: 3 }});
282
  setTimeout(() => lSysCreatures.forEach(c => c !== sender && listenToMessage(c)), 1000);
 
292
  setTimeout(() => scene.remove(response), 2000);
293
  }}
294
 
295
+ function resetSnake() {{
296
+ snake.forEach(seg => scene.remove(seg));
297
+ snake = [];
298
+ const snakeMaterial = new THREE.MeshStandardMaterial({{color: 0x00ff00}});
299
+ for (let i = 0; i < initialLength; i++) {{
300
+ const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
301
+ segment.position.set(-i * 1.2, 0.5, 0);
302
+ segment.castShadow = true;
303
+ snake.push(segment);
304
+ scene.add(segment);
 
305
  }}
306
+ moveDir.set(1, 0, 0);
307
+ lastLength = initialLength;
308
  }}
309
 
310
+ // Controls
311
+ const keys = {{w: false, a: false, s: false, d: false, r: false}};
312
+ document.addEventListener('keydown', (event) => {{
313
+ const key = event.key.toLowerCase();
314
+ if (key === 'w' || key === 'arrowup') keys.w = true;
315
+ if (key === 'a' || key === 'arrowleft') keys.a = true;
316
+ if (key === 's' || key === 'arrowdown') keys.s = true;
317
+ if (key === 'd' || key === 'arrowright') keys.d = true;
318
+ if (key === 'r') keys.r = true;
319
+ }});
320
+ document.addEventListener('keyup', (event) => {{
321
+ const key = event.key.toLowerCase();
322
+ if (key === 'w' || key === 'arrowup') keys.w = false;
323
+ if (key === 'a' || key === 'arrowleft') keys.a = false;
324
+ if (key === 's' || key === 'arrowdown') keys.s = false;
325
+ if (key === 'd' || key === 'arrowright') keys.d = false;
326
+ if (key === 'r' && gameActive === false) resetGame();
327
+ }});
328
+
329
  function updateSnake(delta) {{
330
+ if (!gameActive) return;
331
  moveCounter += delta;
332
  if (moveCounter < moveInterval) return;
333
  moveCounter = 0;
334
 
335
+ if (keys.w && moveDir.z !== 1) moveDir.set(0, 0, -1);
336
+ else if (keys.s && moveDir.z !== -1) moveDir.set(0, 0, 1);
337
+ else if (keys.a && moveDir.x !== 1) moveDir.set(-1, 0, 0);
338
+ else if (keys.d && moveDir.x !== -1) moveDir.set(1, 0, 0);
339
+
340
  const head = snake[0];
341
  const newHead = new THREE.Mesh(head.geometry, head.material);
342
  newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(1));
343
+ newHead.castShadow = true;
344
 
345
+ if (Math.abs(newHead.position.x) > 75 || Math.abs(newHead.position.z) > 75) {{
346
  loseLife();
347
  return;
348
  }}
 
354
  }}
355
  }}
356
 
357
+ for (const building of buildings) {{
358
+ building.traverse((child) => {{
359
+ if (child.isMesh) {{
360
+ const buildingBox = new THREE.Box3().setFromObject(child);
361
+ const headPos = newHead.position.clone();
362
+ if (headPos.x + 0.5 > buildingBox.min.x && headPos.x - 0.5 < buildingBox.max.x &&
363
+ headPos.z + 0.5 > buildingBox.min.z && headPos.z - 0.5 < buildingBox.max.z) {{
364
+ loseLife();
365
+ return;
366
+ }}
367
+ }}
368
+ }});
369
  }}
370
 
371
  snake.unshift(newHead);
 
391
  return;
392
  }}
393
  }}
394
+
395
+ // Update camera to follow snake
396
+ const headPos = newHead.position;
397
+ camera.position.set(headPos.x, headPos.y + 20, headPos.z + 30);
398
+ camera.lookAt(headPos);
399
  }}
400
 
401
  function checkLengthBonus() {{
 
441
  resetSnake();
442
  }}
443
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  function resetGame() {{
445
  resetSnake();
446
  foodItems.forEach(f => scene.remove(f));
447
  lSysCreatures.forEach(c => scene.remove(c));
448
+ buildings.forEach(b => scene.remove(b));
449
  foodItems = [];
450
  lSysCreatures = [];
451
+ buildings = [];
 
 
452
  score = 0;
453
  lives = 3;
454
+ gameActive = true;
455
  gameTime = 0;
456
  moveCounter = 0;
457
+ createCity();
458
+ spawnFood();
459
  document.getElementById('gameOver').style.display = 'none';
460
  updateUI();
461
  }}
462
 
463
  function endGame() {{
464
+ gameActive = false;
465
  document.getElementById('gameOver').style.display = 'block';
466
  saveScore();
467
  }}
468
 
469
  function updateUI() {{
470
+ document.getElementById('score').textContent = `Score: ${{Math.floor(score)}}`;
471
+ document.getElementById('time').textContent = `Time: ${{Math.floor(gameTime)}}`;
472
+ document.getElementById('length').textContent = `Length: ${{snake.length}}`;
473
+ document.getElementById('lives').textContent = `Lives: ${{lives}}`;
 
 
 
 
 
474
  }}
475
 
476
+ let highScores = JSON.parse(localStorage.getItem('highScores')) || [];
477
  function saveScore() {{
478
  highScores.push({{ name: playerName, score: Math.floor(score), time: Math.floor(gameTime) }});
479
  highScores.sort((a, b) => b.score - a.score);
480
  highScores = highScores.slice(0, 5);
481
  localStorage.setItem('highScores', JSON.stringify(highScores));
482
+ console.log("High Scores:", highScores); // For debugging
483
  }}
484
 
485
+ // Lighting cycle
486
+ let cycleTime = 0;
487
+ function updateLighting(delta) {{
488
+ cycleTime += delta;
489
+ const cycleDuration = 120;
490
+ const angle = (cycleTime / cycleDuration) * Math.PI * 2;
491
+ sunLight.position.set(Math.cos(angle) * 100, Math.sin(angle) * 100, Math.sin(angle) * 50);
492
+ sunLight.intensity = Math.max(0, Math.sin(angle)) * 0.8;
493
+ const dayColor = new THREE.Color(0x87CEEB);
494
+ const nightColor = new THREE.Color(0x001133);
495
+ scene.background = dayColor.clone().lerp(nightColor, Math.max(0, -Math.sin(angle)));
496
  }}
497
 
498
+ // Game loop
499
+ let lastTime = performance.now();
500
  function animate() {{
501
  requestAnimationFrame(animate);
502
+ const currentTime = performance.now();
503
+ const delta = (currentTime - lastTime) / 1000;
504
+ lastTime = currentTime;
 
 
 
505
 
506
+ if (gameActive) {{
507
+ gameTime += delta;
508
+ if (gameTime >= maxGameTime) endGame();
509
  updateSnake(delta);
510
  updateScore(delta);
511
+ updateLighting(delta);
512
+ messages = messages.filter(m => {{
513
+ m.ttl -= delta;
514
+ if (m.ttl <= 0) document.body.removeChild(m.div);
515
+ return m.ttl > 0;
516
+ }});
517
  }}
518
 
 
 
 
 
 
 
519
  renderer.render(scene, camera);
520
  }}
521
 
522
+ window.addEventListener('resize', () => {{
523
+ camera.aspect = window.innerWidth / window.innerHeight;
524
+ camera.updateProjectionMatrix();
525
+ renderer.setSize(window.innerWidth, window.innerHeight);
526
+ }});
527
+
528
+ // Initialize
529
+ snake = [];
530
+ resetSnake();
531
+ createCity();
532
+ spawnFood();
533
+ animate();
534
  </script>
535
  </body>
536
  </html>