awacke1 commited on
Commit
d5d2a63
·
verified ·
1 Parent(s): a03d3b9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +278 -377
app.py CHANGED
@@ -126,7 +126,7 @@ async def start_websocket_server():
126
  if not st.session_state.get('server_running', False):
127
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
128
  st.session_state['server_running'] = True
129
- st.session_state['server'] = server # Store server reference
130
  await asyncio.Future() # Run forever until cancelled
131
 
132
  # Chat Functions
@@ -148,7 +148,7 @@ async def load_chat():
148
  content = f.read().strip()
149
  return content.split('\n')
150
 
151
- # Game HTML
152
  html_code = f"""
153
  <!DOCTYPE html>
154
  <html lang="en">
@@ -158,7 +158,7 @@ html_code = f"""
158
  <title>Galaxian Snake 2D Multiplayer</title>
159
  <style>
160
  body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; }}
161
- #gameContainer {{ width: {container_width}px; height: {container_height}px; position: relative; }}
162
  canvas {{ width: 100%; height: 100%; display: block; }}
163
  .ui-container {{
164
  position: absolute; top: 10px; left: 10px; color: white;
@@ -174,6 +174,7 @@ html_code = f"""
174
  background: rgba(0, 0, 0, 0.7); color: white; padding: 10px;
175
  border-radius: 5px; overflow-y: auto;
176
  }}
 
177
  </style>
178
  </head>
179
  <body>
@@ -190,420 +191,320 @@ html_code = f"""
190
  <p>Controls: W/A/S/D or Arrow Keys to move</p>
191
  <p>Eat yellow cubes to grow and score!</p>
192
  </div>
 
193
  </div>
194
 
195
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
196
  <script>
197
- // Game variables
198
- let score = 0, players = {{}}, foodItems = [], lSysCreatures = [], quineAgents = [], buildings = [], lights = [];
199
- let snake = [], moveDir = new THREE.Vector3(1, 0, 0), moveCounter = 0, moveInterval = 0.1;
200
- const initialLength = 3, playerName = "{st.session_state.username}";
201
- const highScores = JSON.parse(localStorage.getItem('highScores')) || [];
202
- let ws = new WebSocket('ws://localhost:8765');
203
-
204
- // Scene setup
205
- const scene = new THREE.Scene();
206
- const camera = new THREE.PerspectiveCamera(75, {container_width} / {container_height}, 0.1, 1000);
207
- camera.position.set(0, 100, 0); // Top-down view
208
- camera.lookAt(0, 0, 0); // Looking straight down
209
- const renderer = new THREE.WebGLRenderer({{ antialias: true }});
210
- renderer.setSize({container_width}, {container_height});
211
- renderer.shadowMap.enabled = true;
212
- document.getElementById('gameContainer').appendChild(renderer.domElement);
213
-
214
- // Lighting
215
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
216
- scene.add(ambientLight);
217
-
218
- // Ground
219
- const textureLoader = new THREE.TextureLoader();
220
- const groundGeometry = new THREE.PlaneGeometry(200, 200);
221
- const groundMaterial = new THREE.MeshStandardMaterial({{
222
- color: 0x1a5e1a, roughness: 0.8, metalness: 0.2,
223
- bumpMap: textureLoader.load('https://threejs.org/examples/textures/terrain/grasslight-big-nm.jpg'),
224
- bumpScale: 0.1
225
- }});
226
- const ground = new THREE.Mesh(groundGeometry, groundMaterial);
227
- ground.rotation.x = -Math.PI / 2;
228
- ground.receiveShadow = true;
229
- scene.add(ground);
230
-
231
- // Building rules
232
- const buildingColors = [0x888888, 0x666666, 0x999999, 0xaaaaaa, 0x555555, 0x334455, 0x445566, 0x223344, 0x556677, 0x667788, 0x993333, 0x884422, 0x553333, 0x772222, 0x664433];
233
- const buildingRules = [
234
- {{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}},
235
- {{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}},
236
- {{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}},
237
- {{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}},
238
- {{name: "Simple", axiom: "F", rules: {{"F": "F[+F][-F]"}}, iterations: 1, baseHeight: 8, baseWidth: 6, baseDepth: 6, angle: Math.PI/4, probability: 0.25}}
239
- ];
240
-
241
- function interpretLSystem(rule, position, rotation) {{
242
- let currentString = rule.axiom;
243
- for (let i = 0; i < rule.iterations; i++) {{
244
- let newString = "";
245
- for (let j = 0; j < currentString.length; j++) {{
246
- newString += rule.rules[currentString[j]] || currentString[j];
 
 
 
 
 
 
247
  }}
248
- currentString = newString;
249
- }}
250
-
251
- let buildingGroup = new THREE.Group();
252
- buildingGroup.position.copy(position);
253
- const stack = [];
254
- let currentPosition = new THREE.Vector3(0, 0, 0);
255
- let currentRotation = rotation || new THREE.Euler();
256
- let scale = new THREE.Vector3(1, 1, 1);
257
- const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
258
- const material = new THREE.MeshStandardMaterial({{color: color, roughness: 0.7, metalness: 0.2}});
259
-
260
- for (let i = 0; i < currentString.length; i++) {{
261
- const char = currentString[i];
262
- switch (char) {{
263
- case 'F':
264
- const width = rule.baseWidth * (0.5 + Math.random() * 0.5) * scale.x;
265
- const height = rule.baseHeight * (0.5 + Math.random() * 0.5) * scale.y;
266
- const depth = rule.baseDepth * (0.5 + Math.random() * 0.5) * scale.z;
267
- const geometry = new THREE.BoxGeometry(width, height, depth);
268
- const buildingPart = new THREE.Mesh(geometry, material);
269
- buildingPart.position.copy(currentPosition);
270
- buildingPart.position.y += height / 2;
271
- buildingPart.rotation.copy(currentRotation);
272
- buildingPart.castShadow = true;
273
- buildingPart.receiveShadow = true;
274
- buildingGroup.add(buildingPart);
275
- const direction = new THREE.Vector3(0, height, 0);
276
- direction.applyEuler(currentRotation);
277
- currentPosition.add(direction);
278
- break;
279
- case '+': currentRotation.y += rule.angle; break;
280
- case '-': currentRotation.y -= rule.angle; break;
281
- case '/': currentRotation.x += rule.angle; break;
282
- case '\\\\': currentRotation.x -= rule.angle; break;
283
- case '^': currentRotation.z += rule.angle; break;
284
- case '&': currentRotation.z -= rule.angle; break;
285
- case '[': stack.push({{position: currentPosition.clone(), rotation: currentRotation.clone(), scale: scale.clone()}}); break;
286
- case ']': if (stack.length > 0) {{ const state = stack.pop(); currentPosition = state.position; currentRotation = state.rotation; scale = state.scale; }} break;
287
- case '>': scale.multiplyScalar(1.2); break;
288
- case '<': scale.multiplyScalar(0.8); break;
289
  }}
 
290
  }}
291
- return buildingGroup;
292
- }}
293
 
294
- function createCity() {{
295
- const citySize = 5, spacing = 15;
296
- for (let x = -citySize; x <= citySize; x++) {{
297
- for (let z = -citySize; z <= citySize; z++) {{
298
- if (Math.random() < 0.2) continue;
299
- const position = new THREE.Vector3(x * spacing + (Math.random() * 2 - 1), 0, z * spacing + (Math.random() * 2 - 1));
300
- let selectedRule, random = Math.random(), cumulativeProbability = 0;
301
- for (const rule of buildingRules) {{
302
- cumulativeProbability += rule.probability;
303
- if (random <= cumulativeProbability) {{ selectedRule = rule; break; }}
304
  }}
305
- if (!selectedRule) selectedRule = buildingRules[0];
306
- const building = interpretLSystem(selectedRule, position, new THREE.Euler());
307
- scene.add(building);
308
- buildings.push(building);
309
- if (Math.random() > 0.7) spawnLSysCreature(building.position);
310
  }}
311
- }}
312
- const roadWidth = 8, roadColor = 0x333333;
313
- for (let x = -citySize; x <= citySize; x++) {{
314
- const road = new THREE.Mesh(
315
- new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth),
316
- new THREE.MeshStandardMaterial({{color: roadColor, roughness: 0.9, metalness: 0.1}})
317
- );
318
- road.rotation.x = -Math.PI / 2;
319
- road.position.set(x * spacing, 0.01, 0);
320
- scene.add(road);
321
- }}
322
- for (let z = -citySize; z <= citySize; z++) {{
323
- const road = new THREE.Mesh(
324
- new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth),
325
- new THREE.MeshStandardMaterial({{color: roadColor, roughness: 0.9, metalness: 0.1}})
326
- );
327
- road.rotation.x = -Math.PI / 2;
328
- road.position.set(0, 0.01, z * spacing);
329
- scene.add(road);
330
- }}
331
- // Add city lights at intersections
332
- for (let x = -citySize; x <= citySize; x++) {{
333
  for (let z = -citySize; z <= citySize; z++) {{
334
- const light = new THREE.PointLight(0xffff99, 1, 20);
335
- light.position.set(x * spacing, 5, z * spacing);
336
- light.castShadow = true;
337
- scene.add(light);
338
- lights.push(light);
 
 
 
 
 
 
 
 
 
 
 
 
339
  }}
340
  }}
341
- }}
342
 
343
- function evolveCity() {{
344
- if (Math.random() < 0.1) {{
345
- const citySize = 5, spacing = 15;
346
- const x = (Math.random() * 2 - 1) * citySize * spacing;
347
- const z = (Math.random() * 2 - 1) * citySize * spacing;
348
- const position = new THREE.Vector3(x, 0, z);
349
- let selectedRule = buildingRules[Math.floor(Math.random() * buildingRules.length)];
350
- const building = interpretLSystem(selectedRule, position, new THREE.Euler());
351
- scene.add(building);
352
- buildings.push(building);
353
  }}
354
- }}
355
 
356
- function spawnFood() {{
357
- const citySize = 5, spacing = 15;
358
- if (foodItems.length < 10) {{
359
- const food = new THREE.Mesh(
360
- new THREE.BoxGeometry(1, 1, 1),
361
- new THREE.MeshStandardMaterial({{color: 0xffff00, emissive: 0xffff00, emissiveIntensity: 0.5, transparent: true, opacity: 0.8}})
362
- );
363
- const x = (Math.random() * 2 - 1) * citySize * spacing;
364
- const z = (Math.random() * 2 - 1) * citySize * spacing;
365
- food.position.set(x, 0.5, z);
366
- foodItems.push(food);
367
- scene.add(food);
 
368
  }}
369
- }}
370
 
371
- function spawnLSysCreature(position) {{
372
- const lSys = {{
373
- axiom: "F",
374
- rules: {{"F": "F[+F]F[-F]F"}},
375
- angle: 25 * Math.PI / 180,
376
- length: 2,
377
- iterations: 2
378
- }};
379
- let turtleString = lSys.axiom;
380
- for (let j = 0; j < lSys.iterations; j++) {{
381
- turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
382
- }}
383
- const creature = new THREE.Group();
384
- let stack = [], pos = position.clone(), dir = new THREE.Vector3(0, lSys.length, 0);
385
- const material = new THREE.MeshStandardMaterial({{color: 0x0000ff}});
386
-
387
- for (let char of turtleString) {{
388
- if (char === 'F') {{
389
- const segment = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, lSys.length, 8), material);
390
- segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
391
- segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
392
- creature.add(segment);
393
- pos.add(dir);
394
- }} else if (char === '+') {{
395
- dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle);
396
- }} else if (char === '-') {{
397
- dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle);
398
- }} else if (char === '[') {{
399
- stack.push({{ pos: pos.clone(), dir: dir.clone() }});
400
- }} else if (char === ']') {{
401
- const state = stack.pop();
402
- pos = state.pos;
403
- dir = state.dir;
404
  }}
 
 
 
405
  }}
406
- creature.position.copy(position);
407
- lSysCreatures.push(creature);
408
- scene.add(creature);
409
- sendQuineAgent(creature);
410
- }}
411
 
412
- function sendQuineAgent(sender) {{
413
- const agent = new THREE.Mesh(
414
- new THREE.SphereGeometry(0.3, 16, 16),
415
- new THREE.MeshStandardMaterial({{color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 0.5}})
416
- );
417
- agent.position.copy(sender.position).add(new THREE.Vector3(0, 5, 0));
418
- agent.userData = {{
419
- origin: sender.position.clone(),
420
- angle: Math.random() * Math.PI * 2,
421
- speed: 0.5 + Math.random() * 0.5,
422
- radius: 3 + Math.random() * 2,
423
- ttl: 5
424
- }};
425
- quineAgents.push(agent);
426
- scene.add(agent);
427
- }}
 
 
428
 
429
- function resetSnake() {{
430
- snake.forEach(seg => scene.remove(seg));
431
- snake = [];
432
- const snakeMaterial = new THREE.MeshStandardMaterial({{color: 0x00ff00}});
433
- for (let i = 0; i < initialLength; i++) {{
434
- const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
435
- segment.position.set(-i * 1.2, 0.5, 0);
436
- segment.castShadow = true;
437
- snake.push(segment);
438
- scene.add(segment);
439
- }}
440
- moveDir.set(1, 0, 0);
441
- players[playerName] = {{ snake: snake, score: 0, length: initialLength }};
442
- }}
443
 
444
- // Controls
445
- const keys = {{w: false, a: false, s: false, d: false}};
446
- document.addEventListener('keydown', (event) => {{
447
- switch (event.key.toLowerCase()) {{
448
- case 'w': case 'arrowup': keys.w = true; break;
449
- case 'a': case 'arrowleft': keys.a = true; break;
450
- case 's': case 'arrowdown': keys.s = true; break;
451
- case 'd': case 'arrowright': keys.d = true; break;
452
- }}
453
- }});
454
- document.addEventListener('keyup', (event) => {{
455
- switch (event.key.toLowerCase()) {{
456
- case 'w': case 'arrowup': keys.w = false; break;
457
- case 'a': case 'arrowleft': keys.a = false; break;
458
- case 's': case 'arrowdown': keys.s = false; break;
459
- case 'd': case 'arrowright': keys.d = false; break;
460
- }}
461
- }});
462
-
463
- function updateSnake(delta) {{
464
- moveCounter += delta;
465
- if (moveCounter < moveInterval) return;
466
- moveCounter = 0;
467
-
468
- if (keys.w && moveDir.z !== 1) moveDir.set(0, 0, -1);
469
- else if (keys.s && moveDir.z !== -1) moveDir.set(0, 0, 1);
470
- else if (keys.a && moveDir.x !== 1) moveDir.set(-1, 0, 0);
471
- else if (keys.d && moveDir.x !== -1) moveDir.set(1, 0, 0);
472
-
473
- const head = snake[0];
474
- const newHead = new THREE.Mesh(head.geometry, head.material);
475
- newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(1));
476
- newHead.castShadow = true;
477
-
478
- if (Math.abs(newHead.position.x) > 75 || Math.abs(newHead.position.z) > 75) {{
479
- resetSnake();
480
- return;
481
- }}
482
 
483
- for (let i = 1; i < snake.length; i++) {{
484
- if (newHead.position.distanceTo(snake[i].position) < 0.9) {{
485
  resetSnake();
486
  return;
487
  }}
488
- }}
489
 
490
- for (const building of buildings) {{
491
- building.traverse((child) => {{
492
- if (child.isMesh) {{
493
- const buildingBox = new THREE.Box3().setFromObject(child);
494
- const headPos = newHead.position.clone();
495
- if (headPos.x + 0.5 > buildingBox.min.x && headPos.x - 0.5 < buildingBox.max.x &&
496
- headPos.z + 0.5 > buildingBox.min.z && headPos.z - 0.5 < buildingBox.max.z) {{
497
- resetSnake();
498
- return;
499
- }}
500
  }}
501
- }});
502
- }}
503
 
504
- snake.unshift(newHead);
505
- scene.add(newHead);
506
-
507
- for (let i = foodItems.length - 1; i >= 0; i--) {{
508
- if (newHead.position.distanceTo(foodItems[i].position) < 1) {{
509
- scene.remove(foodItems[i]);
510
- foodItems.splice(i, 1);
511
- spawnFood();
512
- if (Math.random() > 0.8) spawnLSysCreature(newHead.position.clone());
513
- score += 2;
514
- players[playerName].score = score;
515
- players[playerName].length = snake.length;
516
- updateUI();
517
- break;
518
- }} else {{
519
- const tail = snake.pop();
520
- scene.remove(tail);
521
  }}
522
- }}
523
 
524
- for (let creature of lSysCreatures) {{
525
- if (newHead.position.distanceTo(creature.position) < 2) {{
526
- resetSnake();
527
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
528
  }}
529
  }}
530
- }}
531
 
532
- function updateQuineAgents(delta) {{
533
- for (let i = quineAgents.length - 1; i >= 0; i--) {{
534
- const agent = quineAgents[i];
535
- agent.userData.ttl -= delta;
536
- if (agent.userData.ttl <= 0) {{
537
- scene.remove(agent);
538
- quineAgents.splice(i, 1);
539
- continue;
540
- }}
541
- agent.userData.angle += agent.userData.speed * delta;
542
- const offsetX = Math.cos(agent.userData.angle) * agent.userData.radius;
543
- const offsetZ = Math.sin(agent.userData.angle) * agent.userData.radius;
544
- agent.position.set(
545
- agent.userData.origin.x + offsetX,
546
- agent.userData.origin.y + 5 + Math.sin(Date.now() * 0.001 * agent.userData.speed) * 0.5,
547
- agent.userData.origin.z + offsetZ
548
- );
549
  }}
550
- }}
551
 
552
- // Lighting cycle
553
- let cycleTime = 0;
554
- function updateLighting(delta) {{
555
- cycleTime += delta;
556
- const cycleDuration = 120;
557
- const angle = (cycleTime / cycleDuration) * Math.PI * 2;
558
- const intensity = Math.max(0, Math.sin(angle)) * 0.5 + 0.5;
559
- lights.forEach(light => light.intensity = intensity);
560
- const dayColor = new THREE.Color(0x87CEEB);
561
- const nightColor = new THREE.Color(0x001133);
562
- scene.background = dayColor.clone().lerp(nightColor, Math.max(0, -Math.sin(angle)));
563
- }}
564
 
565
- function updateUI() {{
566
- document.getElementById('players').textContent = `Players: ${{Object.keys(players).length}}`;
567
- document.getElementById('score').textContent = `Score: ${{score}}`;
568
- document.getElementById('length').textContent = `Length: ${{snake.length}}`;
569
- document.getElementById('leaderboard').innerHTML = highScores.map(s => `<p>${{s.name}}: ${{s.score}}</p>`).join('');
570
- }}
 
571
 
572
- // WebSocket chat
573
- ws.onmessage = function(event) {{
574
- const [sender, message] = event.data.split('|');
575
- const chatBox = document.getElementById('chatBox');
576
- chatBox.innerHTML += `<p>${{sender}}: ${{message}}</p>`;
577
- chatBox.scrollTop = chatBox.scrollHeight;
578
- }};
579
-
580
- ws.onopen = function() {{ console.log('Connected to WebSocket server'); }};
581
- ws.onerror = function(error) {{ console.error('WebSocket error:', error); }};
582
- ws.onclose = function() {{ console.log('Disconnected from WebSocket server'); }};
583
-
584
- // Game loop
585
- let lastTime = performance.now();
586
- function animate() {{
587
- requestAnimationFrame(animate);
588
- const currentTime = performance.now();
589
- const delta = (currentTime - lastTime) / 1000;
590
- lastTime = currentTime;
591
-
592
- updateSnake(delta);
593
- updateQuineAgents(delta);
594
- updateLighting(delta);
595
- evolveCity();
596
- spawnFood();
597
- updateUI();
598
 
599
- renderer.render(scene, camera);
600
- }}
 
 
 
 
 
 
 
 
 
 
 
 
 
601
 
602
- // Initialize
603
- resetSnake();
604
- createCity();
605
- spawnFood();
606
- animate();
 
 
 
 
 
607
  </script>
608
  </body>
609
  </html>
@@ -644,6 +545,6 @@ st.sidebar.write("""
644
  ### How to Play
645
  - **W/A/S/D or Arrow Keys**: Move your snake
646
  - Eat yellow cubes to grow and score
647
- - City evolves with new buildings and lights at intersections
648
  - Chat with other players in real-time
649
  """)
 
126
  if not st.session_state.get('server_running', False):
127
  server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
128
  st.session_state['server_running'] = True
129
+ st.session_state['server'] = server
130
  await asyncio.Future() # Run forever until cancelled
131
 
132
  # Chat Functions
 
148
  content = f.read().strip()
149
  return content.split('\n')
150
 
151
+ # Game HTML with Debugging
152
  html_code = f"""
153
  <!DOCTYPE html>
154
  <html lang="en">
 
158
  <title>Galaxian Snake 2D Multiplayer</title>
159
  <style>
160
  body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; }}
161
+ #gameContainer {{ width: {container_width}px; height: {container_height}px; position: relative; background: #000; }}
162
  canvas {{ width: 100%; height: 100%; display: block; }}
163
  .ui-container {{
164
  position: absolute; top: 10px; left: 10px; color: white;
 
174
  background: rgba(0, 0, 0, 0.7); color: white; padding: 10px;
175
  border-radius: 5px; overflow-y: auto;
176
  }}
177
+ #debug {{ position: absolute; top: 10px; right: 10px; color: white; }}
178
  </style>
179
  </head>
180
  <body>
 
191
  <p>Controls: W/A/S/D or Arrow Keys to move</p>
192
  <p>Eat yellow cubes to grow and score!</p>
193
  </div>
194
+ <div id="debug"></div>
195
  </div>
196
 
197
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
198
  <script>
199
+ try {{
200
+ console.log('Starting game initialization...');
201
+ // Game variables
202
+ let score = 0, players = {{}}, foodItems = [], lSysCreatures = [], quineAgents = [], buildings = [], lights = [];
203
+ let snake = [], moveDir = new THREE.Vector3(1, 0, 0), moveCounter = 0, moveInterval = 0.1;
204
+ const initialLength = 3, playerName = "{st.session_state.username}";
205
+ const highScores = JSON.parse(localStorage.getItem('highScores')) || [];
206
+ let ws = new WebSocket('ws://localhost:8765');
207
+ const debug = document.getElementById('debug');
208
+
209
+ // Scene setup
210
+ const scene = new THREE.Scene();
211
+ const camera = new THREE.PerspectiveCamera(75, {container_width} / {container_height}, 0.1, 1000);
212
+ camera.position.set(0, 50, 0); // Lowered from 100 to ensure visibility
213
+ camera.lookAt(0, 0, 0);
214
+ console.log('Camera initialized at:', camera.position);
215
+
216
+ const renderer = new THREE.WebGLRenderer({{ antialias: true }});
217
+ renderer.setSize({container_width}, {container_height});
218
+ renderer.shadowMap.enabled = true;
219
+ document.getElementById('gameContainer').appendChild(renderer.domElement);
220
+ console.log('Renderer initialized with size:', {container_width}, {container_height});
221
+
222
+ // Lighting
223
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.3); // Increased for visibility
224
+ scene.add(ambientLight);
225
+ console.log('Ambient light added');
226
+
227
+ // Ground
228
+ const textureLoader = new THREE.TextureLoader();
229
+ const groundGeometry = new THREE.PlaneGeometry(100, 100); // Reduced size for tighter view
230
+ const groundMaterial = new THREE.MeshStandardMaterial({{
231
+ color: 0x1a5e1a, roughness: 0.8, metalness: 0.2,
232
+ bumpMap: textureLoader.load('https://threejs.org/examples/textures/terrain/grasslight-big-nm.jpg'),
233
+ bumpScale: 0.1
234
+ }});
235
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
236
+ ground.rotation.x = -Math.PI / 2;
237
+ ground.receiveShadow = true;
238
+ scene.add(ground);
239
+ console.log('Ground added at y=0');
240
+
241
+ // Building rules
242
+ const buildingColors = [0x888888, 0x666666, 0x999999];
243
+ const buildingRules = [
244
+ {{name: "Simple", axiom: "F", rules: {{"F": "F[+F][-F]"}}, iterations: 1, baseHeight: 5, baseWidth: 4, baseDepth: 4, angle: Math.PI/4, probability: 1}}
245
+ ];
246
+
247
+ function interpretLSystem(rule, position, rotation) {{
248
+ let currentString = rule.axiom;
249
+ for (let i = 0; i < rule.iterations; i++) {{
250
+ let newString = "";
251
+ for (let j = 0; j < currentString.length; j++) {{
252
+ newString += rule.rules[currentString[j]] || currentString[j];
253
+ }}
254
+ currentString = newString;
255
  }}
256
+
257
+ let buildingGroup = new THREE.Group();
258
+ buildingGroup.position.copy(position);
259
+ const stack = [];
260
+ let currentPosition = new THREE.Vector3(0, 0, 0);
261
+ let currentRotation = rotation || new THREE.Euler();
262
+ let scale = new THREE.Vector3(1, 1, 1);
263
+ const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
264
+ const material = new THREE.MeshStandardMaterial({{color: color, roughness: 0.7, metalness: 0.2}});
265
+
266
+ for (let i = 0; i < currentString.length; i++) {{
267
+ const char = currentString[i];
268
+ switch (char) {{
269
+ case 'F':
270
+ const width = rule.baseWidth * scale.x;
271
+ const height = rule.baseHeight * scale.y;
272
+ const depth = rule.baseDepth * scale.z;
273
+ const geometry = new THREE.BoxGeometry(width, height, depth);
274
+ const buildingPart = new THREE.Mesh(geometry, material);
275
+ buildingPart.position.copy(currentPosition);
276
+ buildingPart.position.y += height / 2;
277
+ buildingPart.rotation.copy(currentRotation);
278
+ buildingPart.castShadow = true;
279
+ buildingPart.receiveShadow = true;
280
+ buildingGroup.add(buildingPart);
281
+ const direction = new THREE.Vector3(0, height, 0);
282
+ direction.applyEuler(currentRotation);
283
+ currentPosition.add(direction);
284
+ break;
285
+ case '+': currentRotation.y += rule.angle; break;
286
+ case '-': currentRotation.y -= rule.angle; break;
287
+ case '/': currentRotation.x += rule.angle; break;
288
+ case '\\\\': currentRotation.x -= rule.angle; break;
289
+ case '^': currentRotation.z += rule.angle; break;
290
+ case '&': currentRotation.z -= rule.angle; break;
291
+ case '[': stack.push({{position: currentPosition.clone(), rotation: currentRotation.clone(), scale: scale.clone()}}); break;
292
+ case ']': if (stack.length > 0) {{ const state = stack.pop(); currentPosition = state.position; currentRotation = state.rotation; scale = state.scale; }} break;
293
+ case '>': scale.multiplyScalar(1.2); break;
294
+ case '<': scale.multiplyScalar(0.8); break;
295
+ }}
 
296
  }}
297
+ return buildingGroup;
298
  }}
 
 
299
 
300
+ function createCity() {{
301
+ const citySize = 2, spacing = 15; // Smaller city for debugging
302
+ for (let x = -citySize; x <= citySize; x++) {{
303
+ for (let z = -citySize; z <= citySize; z++) {{
304
+ if (Math.random() < 0.5) continue; // Fewer buildings
305
+ const position = new THREE.Vector3(x * spacing, 0, z * spacing);
306
+ const building = interpretLSystem(buildingRules[0], position, new THREE.Euler());
307
+ scene.add(building);
308
+ buildings.push(building);
309
+ console.log('Building added at:', position);
310
  }}
 
 
 
 
 
311
  }}
312
+ const roadWidth = 4;
313
+ for (let x = -citySize; x <= citySize; x++) {{
314
+ const road = new THREE.Mesh(
315
+ new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth),
316
+ new THREE.MeshStandardMaterial({{color: 0x333333}})
317
+ );
318
+ road.rotation.x = -Math.PI / 2;
319
+ road.position.set(x * spacing, 0.01, 0);
320
+ scene.add(road);
321
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
322
  for (let z = -citySize; z <= citySize; z++) {{
323
+ const road = new THREE.Mesh(
324
+ new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth),
325
+ new THREE.MeshStandardMaterial({{color: 0x333333}})
326
+ );
327
+ road.rotation.x = -Math.PI / 2;
328
+ road.position.set(0, 0.01, z * spacing);
329
+ scene.add(road);
330
+ }}
331
+ for (let x = -citySize; x <= citySize; x++) {{
332
+ for (let z = -citySize; z <= citySize; z++) {{
333
+ const light = new THREE.PointLight(0xffff99, 1, 20);
334
+ light.position.set(x * spacing, 5, z * spacing);
335
+ light.castShadow = true;
336
+ scene.add(light);
337
+ lights.push(light);
338
+ console.log('Light added at:', light.position);
339
+ }}
340
  }}
341
  }}
 
342
 
343
+ function evolveCity() {{
344
+ // Simplified for debugging
 
 
 
 
 
 
 
 
345
  }}
 
346
 
347
+ function spawnFood() {{
348
+ if (foodItems.length < 5) {{
349
+ const food = new THREE.Mesh(
350
+ new THREE.BoxGeometry(1, 1, 1),
351
+ new THREE.MeshStandardMaterial({{color: 0xffff00}})
352
+ );
353
+ const x = (Math.random() * 30) - 15;
354
+ const z = (Math.random() * 30) - 15;
355
+ food.position.set(x, 0.5, z);
356
+ foodItems.push(food);
357
+ scene.add(food);
358
+ console.log('Food spawned at:', food.position);
359
+ }}
360
  }}
 
361
 
362
+ function resetSnake() {{
363
+ snake.forEach(seg => scene.remove(seg));
364
+ snake = [];
365
+ const snakeMaterial = new THREE.MeshStandardMaterial({{color: 0x00ff00}});
366
+ for (let i = 0; i < initialLength; i++) {{
367
+ const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
368
+ segment.position.set(-i * 1.2, 0.5, 0);
369
+ segment.castShadow = true;
370
+ snake.push(segment);
371
+ scene.add(segment);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }}
373
+ moveDir.set(1, 0, 0);
374
+ players[playerName] = {{ snake: snake, score: 0, length: initialLength }};
375
+ console.log('Snake reset at:', snake[0].position);
376
  }}
 
 
 
 
 
377
 
378
+ // Controls
379
+ const keys = {{w: false, a: false, s: false, d: false}};
380
+ document.addEventListener('keydown', (event) => {{
381
+ switch (event.key.toLowerCase()) {{
382
+ case 'w': case 'arrowup': keys.w = true; break;
383
+ case 'a': case 'arrowleft': keys.a = true; break;
384
+ case 's': case 'arrowdown': keys.s = true; break;
385
+ case 'd': case 'arrowright': keys.d = true; break;
386
+ }}
387
+ }});
388
+ document.addEventListener('keyup', (event) => {{
389
+ switch (event.key.toLowerCase()) {{
390
+ case 'w': case 'arrowup': keys.w = false; break;
391
+ case 'a': case 'arrowleft': keys.a = false; break;
392
+ case 's': case 'arrowdown': keys.s = false; break;
393
+ case 'd': case 'arrowright': keys.d = false; break;
394
+ }}
395
+ }});
396
 
397
+ function updateSnake(delta) {{
398
+ moveCounter += delta;
399
+ if (moveCounter < moveInterval) return;
400
+ moveCounter = 0;
 
 
 
 
 
 
 
 
 
 
401
 
402
+ if (keys.w && moveDir.z !== 1) moveDir.set(0, 0, -1);
403
+ else if (keys.s && moveDir.z !== -1) moveDir.set(0, 0, 1);
404
+ else if (keys.a && moveDir.x !== 1) moveDir.set(-1, 0, 0);
405
+ else if (keys.d && moveDir.x !== -1) moveDir.set(1, 0, 0);
406
+
407
+ const head = snake[0];
408
+ const newHead = new THREE.Mesh(head.geometry, head.material);
409
+ newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(1));
410
+ newHead.castShadow = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
+ if (Math.abs(newHead.position.x) > 50 || Math.abs(newHead.position.z) > 50) {{
 
413
  resetSnake();
414
  return;
415
  }}
 
416
 
417
+ for (let i = 1; i < snake.length; i++) {{
418
+ if (newHead.position.distanceTo(snake[i].position) < 0.9) {{
419
+ resetSnake();
420
+ return;
 
 
 
 
 
 
421
  }}
422
+ }}
 
423
 
424
+ for (const building of buildings) {{
425
+ building.traverse((child) => {{
426
+ if (child.isMesh) {{
427
+ const buildingBox = new THREE.Box3().setFromObject(child);
428
+ const headPos = newHead.position.clone();
429
+ if (headPos.x + 0.5 > buildingBox.min.x && headPos.x - 0.5 < buildingBox.max.x &&
430
+ headPos.z + 0.5 > buildingBox.min.z && headPos.z - 0.5 < buildingBox.max.z) {{
431
+ resetSnake();
432
+ return;
433
+ }}
434
+ }}
435
+ }});
 
 
 
 
 
436
  }}
 
437
 
438
+ snake.unshift(newHead);
439
+ scene.add(newHead);
440
+
441
+ for (let i = foodItems.length - 1; i >= 0; i--) {{
442
+ if (newHead.position.distanceTo(foodItems[i].position) < 1) {{
443
+ scene.remove(foodItems[i]);
444
+ foodItems.splice(i, 1);
445
+ spawnFood();
446
+ score += 2;
447
+ players[playerName].score = score;
448
+ players[playerName].length = snake.length;
449
+ updateUI();
450
+ break;
451
+ }} else {{
452
+ const tail = snake.pop();
453
+ scene.remove(tail);
454
+ }}
455
  }}
456
  }}
 
457
 
458
+ function updateLighting(delta) {{
459
+ lights.forEach(light => light.intensity = 1); // Fixed for debugging
460
+ scene.background = new THREE.Color(0x87CEEB); // Fixed sky color
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  }}
 
462
 
463
+ function updateUI() {{
464
+ document.getElementById('players').textContent = `Players: ${{Object.keys(players).length}}`;
465
+ document.getElementById('score').textContent = `Score: ${{score}}`;
466
+ document.getElementById('length').textContent = `Length: ${{snake.length}}`;
467
+ debug.innerHTML = `Scene objects: ${{scene.children.length}}<br>Buildings: ${{buildings.length}}<br>Snake: ${{snake.length}}`;
468
+ }}
 
 
 
 
 
 
469
 
470
+ // WebSocket chat
471
+ ws.onmessage = function(event) {{
472
+ const [sender, message] = event.data.split('|');
473
+ const chatBox = document.getElementById('chatBox');
474
+ chatBox.innerHTML += `<p>${{sender}}: ${{message}}</p>`;
475
+ chatBox.scrollTop = chatBox.scrollHeight;
476
+ }};
477
 
478
+ ws.onopen = function() {{ console.log('Connected to WebSocket server'); }};
479
+ ws.onerror = function(error) {{ console.error('WebSocket error:', error); }};
480
+ ws.onclose = function() {{ console.log('Disconnected from WebSocket server'); }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
 
482
+ // Game loop
483
+ let lastTime = performance.now();
484
+ function animate() {{
485
+ requestAnimationFrame(animate);
486
+ const currentTime = performance.now();
487
+ const delta = (currentTime - lastTime) / 1000;
488
+ lastTime = currentTime;
489
+
490
+ updateSnake(delta);
491
+ updateLighting(delta);
492
+ spawnFood();
493
+ updateUI();
494
+
495
+ renderer.render(scene, camera);
496
+ }}
497
 
498
+ // Initialize
499
+ resetSnake();
500
+ createCity();
501
+ spawnFood();
502
+ animate();
503
+ console.log('Game initialized');
504
+ }} catch (e) {{
505
+ console.error('Error in game initialization:', e);
506
+ document.getElementById('debug').innerHTML = 'Error: ' + e.message;
507
+ }}
508
  </script>
509
  </body>
510
  </html>
 
545
  ### How to Play
546
  - **W/A/S/D or Arrow Keys**: Move your snake
547
  - Eat yellow cubes to grow and score
548
+ - City has buildings and lights at intersections
549
  - Chat with other players in real-time
550
  """)