awacke1 commited on
Commit
e485ea3
ยท
verified ยท
1 Parent(s): 4c39985

Update backup1.app.py

Browse files
Files changed (1) hide show
  1. backup1.app.py +182 -175
backup1.app.py CHANGED
@@ -25,12 +25,12 @@ nest_asyncio.apply()
25
  # Page Config
26
  st.set_page_config(
27
  layout="wide",
28
- page_title="Rocky Mountain Quest ๐Ÿ”๏ธ๐ŸŽฎ",
29
  page_icon="๐ŸฆŒ"
30
  )
31
 
32
  # Game Config
33
- GAME_NAME = "Rocky Mountain Quest ๐Ÿ”๏ธ๐ŸŽฎ"
34
  START_LOCATION = "Trailhead Camp โ›บ"
35
  CHARACTERS = {
36
  "Trailblazer Tim ๐ŸŒ„": {"voice": "en-US-GuyNeural", "desc": "Fearless hiker seeking epic trails!", "color": 0x00ff00},
@@ -101,7 +101,7 @@ async def async_edge_tts_generate(text, voice, username):
101
  if cache_key in st.session_state['audio_cache']:
102
  return st.session_state['audio_cache'][cache_key]
103
  text = clean_text_for_tts(text)
104
- filename = f"{format_timestamp(username)}-{hashlib.md5(text.encode()).hexdigest()[:8]}.mp3"
105
  communicate = edge_tts.Communicate(text, voice)
106
  await communicate.save(filename)
107
  if os.path.exists(filename) and os.path.getsize(filename) > 0:
@@ -129,7 +129,7 @@ async def broadcast_message(message, room_id):
129
 
130
  # Chat and Quest Log
131
  async def save_chat_entry(username, message, voice, is_markdown=False):
132
- if not message.strip() or message == st.session_state.last_transcript:
133
  return None, None
134
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
135
  entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
@@ -142,6 +142,7 @@ async def save_chat_entry(username, message, voice, is_markdown=False):
142
  st.session_state.last_transcript = message
143
  st.session_state.score += 10
144
  st.session_state.treasures += 1
 
145
  return md_file, audio_file
146
 
147
  async def load_chat():
@@ -159,9 +160,9 @@ def init_session_state():
159
  'chat_history': [], 'audio_cache': {}, 'last_transcript': "",
160
  'username': None, 'score': 0, 'treasures': 0, 'location': START_LOCATION,
161
  'speech_processed': False, 'players': {}, 'last_update': time.time(),
162
- 'update_interval': 20, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
163
  'move_right': False, 'move_up': False, 'move_down': False,
164
- 'prairie_players': {}
165
  }
166
  for k, v in defaults.items():
167
  if k not in st.session_state:
@@ -177,18 +178,7 @@ def init_session_state():
177
 
178
  init_session_state()
179
 
180
- # ArXiv Integration
181
- async def perform_arxiv_search(query, username):
182
- gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
183
- refs = gradio_client.predict(
184
- query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
185
- )[0]
186
- result = f"๐Ÿ“š Ancient Rocky Knowledge:\n{refs}"
187
- voice = CHARACTERS[username]["voice"]
188
- md_file, audio_file = await save_chat_entry(username, result, voice, True)
189
- return md_file, audio_file
190
-
191
- # WebSocket for Multiplayer with Map Updates
192
  async def websocket_handler(websocket, path):
193
  client_id = str(uuid.uuid4())
194
  room_id = "quest"
@@ -204,21 +194,23 @@ async def websocket_handler(websocket, path):
204
  "location": PRAIRIE_LOCATIONS["Deadwood, SD"],
205
  "color": CHARACTERS[username]["color"]
206
  }
 
207
  else:
208
  st.session_state.players[client_id] = {
209
  "username": username,
210
  "x": random.uniform(-20, 20),
211
  "z": random.uniform(-50, 50),
212
- "color": CHARACTERS[username]["color"]
 
 
213
  }
214
-
215
- await save_chat_entry(username, f"๐Ÿ—บ๏ธ Joins the quest at {START_LOCATION}!", CHARACTERS[username]["voice"])
216
 
217
  try:
218
  async for message in websocket:
219
  if '|' in message:
220
- username, content = message.split('|', 1)
221
- voice = CHARACTERS.get(username, {"voice": "en-US-AriaNeural"})["voice"]
222
  if content.startswith("MOVE:"):
223
  _, x, z = content.split(":")
224
  st.session_state.players[client_id]["x"] = float(x)
@@ -228,15 +220,19 @@ async def websocket_handler(websocket, path):
228
  if action == "ANIMAL":
229
  st.session_state.prairie_players[client_id]["animal"] = value
230
  elif action == "MOVE":
231
- target = PRAIRIE_LOCATIONS.get(value, PRAIRIE_LOCATIONS["Deadwood, SD"]).ADDRESS
232
  st.session_state.prairie_players[client_id]["location"] = target
233
- action_msg = f"{username} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
234
- await save_chat_entry(username, action_msg, voice)
 
 
 
 
235
  else:
236
- await save_chat_entry(username, content, voice)
237
- await perform_arxiv_search(content, username)
238
  except websockets.ConnectionClosed:
239
- await save_chat_entry(username, "๐Ÿƒ Leaves the quest!", CHARACTERS[username]["voice"])
240
  if client_id in st.session_state.players:
241
  del st.session_state.players[client_id]
242
  if client_id in st.session_state.prairie_players:
@@ -251,15 +247,16 @@ async def periodic_update():
251
  player_list = ", ".join([p["username"] for p in st.session_state.players.values()]) or "No adventurers yet!"
252
  message = f"๐Ÿ“ข Quest Update: Active Adventurers - {player_list}"
253
  player_data = json.dumps(list(st.session_state.players.values()))
254
- await broadcast_message(f"System|{message}", "quest")
255
  await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
256
 
257
  prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
258
  prairie_message = f"๐ŸŒพ Prairie Update: {prairie_list}"
259
  prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
260
- await broadcast_message(f"System|{prairie_message}", "quest")
261
  await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
262
 
 
 
 
263
  await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
264
  await asyncio.sleep(st.session_state.update_interval)
265
 
@@ -275,33 +272,53 @@ def start_websocket_server():
275
  asyncio.set_event_loop(loop)
276
  loop.run_until_complete(run_websocket_server())
277
 
278
- # Rocky Mountain Quest Map HTML
 
 
 
 
 
 
 
 
 
 
 
279
  rocky_map_html = f"""
280
  <!DOCTYPE html>
281
  <html lang="en">
282
  <head>
283
  <meta charset="UTF-8">
284
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
285
- <title>Rocky Mountain Quest Map</title>
286
  <style>
287
  body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; background: #000; }}
288
  #gameContainer {{ width: 800px; height: 600px; position: relative; }}
289
  canvas {{ width: 100%; height: 100%; display: block; }}
 
 
 
 
290
  #chatBox {{
291
- position: absolute; bottom: 10px; left: 10px; width: 300px; height: 150px;
292
  background: rgba(0, 0, 0, 0.7); color: white; padding: 10px;
293
  border-radius: 5px; overflow-y: auto;
294
  }}
295
- #status {{
296
- position: absolute; top: 10px; left: 10px; color: white;
297
  background: rgba(0, 0, 0, 0.5); padding: 5px; border-radius: 5px;
298
  }}
299
  </style>
300
  </head>
301
  <body>
302
  <div id="gameContainer">
303
- <div id="status">Players: 1</div>
 
 
 
 
304
  <div id="chatBox"></div>
 
305
  </div>
306
 
307
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
@@ -321,61 +338,130 @@ rocky_map_html = f"""
321
  scene.add(ambientLight);
322
  const sunLight = new THREE.DirectionalLight(0xffddaa, 1);
323
  sunLight.position.set(50, 50, 50);
 
324
  scene.add(sunLight);
325
 
326
  const groundGeometry = new THREE.PlaneGeometry(100, 100);
327
  const groundMaterial = new THREE.MeshStandardMaterial({{ color: 0x228B22 }});
328
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
329
  ground.rotation.x = -Math.PI / 2;
 
330
  scene.add(ground);
331
 
332
  let players = {{}};
333
  const playerMeshes = {{}};
334
- let xPos = 0;
335
- let zPos = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
  function updatePlayers(playerData) {{
338
  playerData.forEach(player => {{
339
  if (!playerMeshes[player.username]) {{
340
- const geometry = new THREE.BoxGeometry(2, 2, 2);
341
- const material = new THREE.MeshPhongMaterial({{ color: player.color }});
342
- const mesh = new THREE.Mesh(geometry, material);
343
  scene.add(mesh);
344
  playerMeshes[player.username] = mesh;
345
  }}
346
  const mesh = playerMeshes[player.username];
347
  mesh.position.set(player.x, 1, player.z);
 
348
  }});
349
- document.getElementById('status').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
 
 
350
  }}
351
 
352
- document.addEventListener('keydown', (event) => {{
353
- const speed = 2;
354
- switch (event.code) {{
355
- case 'ArrowLeft': case 'KeyA':
356
- xPos -= speed;
357
- ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
358
- break;
359
- case 'ArrowRight': case 'KeyD':
360
- xPos += speed;
361
- ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
362
- break;
363
- case 'ArrowUp': case 'KeyW':
364
- zPos -= speed;
365
- ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
366
- break;
367
- case 'ArrowDown': case 'KeyS':
368
- zPos += speed;
369
- ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
370
- break;
371
- }}
372
- }});
373
-
374
  ws.onmessage = function(event) {{
375
  const data = event.data;
376
  if (data.startsWith('MAP_UPDATE:')) {{
377
  const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
378
  updatePlayers(playerData);
 
 
 
 
 
379
  }} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
380
  const [sender, message] = data.split('|');
381
  const chatBox = document.getElementById('chatBox');
@@ -384,122 +470,28 @@ rocky_map_html = f"""
384
  }}
385
  }};
386
 
 
387
  function animate() {{
388
  requestAnimationFrame(animate);
 
 
 
 
 
 
389
  renderer.render(scene, camera);
390
  }}
 
 
 
391
  animate();
392
  </script>
393
  </body>
394
  </html>
395
  """
396
 
397
- # Prairie Simulator HTML (Corrected)
398
- prairie_simulator_html = """
399
- <!DOCTYPE html>
400
- <html lang="en">
401
- <head>
402
- <meta charset="UTF-8">
403
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
404
- <title>Prairie Simulator</title>
405
- <style>
406
- body {{ margin: 0; font-family: Arial, sans-serif; background: #f0f0f0; }}
407
- #simContainer {{ width: 100%; height: 600px; position: relative; }}
408
- #map {{ width: 100%; height: 400px; }}
409
- #controls {{ padding: 10px; background: #fff; border-radius: 5px; }}
410
- #status {{ color: #333; padding: 5px; }}
411
- #chatBox {{ height: 100px; overflow-y: auto; background: #fff; border: 1px solid #ccc; padding: 5px; }}
412
- </style>
413
- </head>
414
- <body>
415
- <div id="simContainer">
416
- <div id="map"></div>
417
- <div id="controls">
418
- <label>Animal: </label>
419
- <select id="animal" onchange="updateAnimal()">
420
- <option value="prairie_dog">Prairie Dog</option>
421
- <option value="deer">Deer</option>
422
- <option value="sheep">Sheep</option>
423
- <option value="groundhog">Groundhog</option>
424
- </select>
425
- <label>Move to: </label>
426
- <select id="location">
427
- <option value="Deadwood, SD">Deadwood, SD</option>
428
- <option value="Wind Cave National Park">Wind Cave National Park</option>
429
- <option value="Wyoming Spring Creek">Wyoming Spring Creek</option>
430
- </select>
431
- <button onclick="move()">Move</button>
432
- <div id="status">Players: 0</div>
433
- <div id="chatBox"></div>
434
- </div>
435
- </div>
436
-
437
- <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
438
- <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
439
- <script>
440
- const playerName = "{username}";
441
- let ws = new WebSocket('ws://localhost:8765/prairie');
442
- const map = L.map('map').setView([44.0, -103.0], 7);
443
- L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
444
- attribution: 'ยฉ OpenStreetMap contributors'
445
- }}).addTo(map);
446
-
447
- const locations = {{
448
- "Deadwood, SD": [44.3769, -103.7298],
449
- "Wind Cave National Park": [43.6047, -103.4798],
450
- "Wyoming Spring Creek": [41.6666, -106.6666]
451
- }};
452
- for (let loc in locations) {{
453
- L.marker(locations[loc]).addTo(map).bindPopup(loc);
454
- }}
455
-
456
- let players = {{}};
457
- const markers = {{}};
458
-
459
- function updatePlayers(playerData) {{
460
- playerData.forEach(player => {{
461
- if (!markers[player.username]) {{
462
- markers[player.username] = L.circleMarker(player.location, {{
463
- color: `#${{player.color.toString(16).padStart(6, '0')}}`,
464
- radius: 5
465
- }}).addTo(map).bindPopup(`${{player.username}} (${{player.animal}})`);
466
- }} else {{
467
- markers[player.username].setLatLng(player.location);
468
- }}
469
- }});
470
- document.getElementById('status').textContent = `Players: ${{Object.keys(markers).length}}`;
471
- }}
472
-
473
- function updateAnimal() {{
474
- const animal = document.getElementById('animal').value;
475
- ws.send(`${{playerName}}|PRAIRIE:ANIMAL:${{animal}}`);
476
- }}
477
-
478
- function move() {{
479
- const location = document.getElementById('location').value;
480
- ws.send(`${{playerName}}|PRAIRIE:MOVE:${{location}}`);
481
- }}
482
-
483
- ws.onmessage = function(event) {{
484
- const data = event.data;
485
- if (data.startsWith('PRAIRIE_UPDATE:')) {{
486
- const playerData = JSON.parse(data.split('PRAIRIE_UPDATE:')[1]);
487
- updatePlayers(playerData);
488
- }} else if (!data.startsWith('MAP_UPDATE:')) {{
489
- const [sender, message] = data.split('|');
490
- const chatBox = document.getElementById('chatBox');
491
- chatBox.innerHTML += `<p>${{sender}}: ${{message}}</p>`;
492
- chatBox.scrollTop = chatBox.scrollHeight;
493
- }}
494
- }};
495
- </script>
496
- </body>
497
- </html>
498
- """.format(username=st.session_state.username)
499
-
500
  # Main Game Loop
501
  def main():
502
- # Sidebar Titles and HUD
503
  st.sidebar.title(f"๐ŸŽฎ {GAME_NAME}")
504
  st.sidebar.subheader(f"๐ŸŒ„ {st.session_state.username}โ€™s Adventure - Score: {st.session_state.score} ๐Ÿ†")
505
  st.sidebar.write(f"๐Ÿ“œ {CHARACTERS[st.session_state.username]['desc']}")
@@ -515,10 +507,8 @@ def main():
515
  save_username(st.session_state.username)
516
  st.rerun()
517
 
518
- # Two-column layout
519
  left_col, right_col = st.columns([2, 1])
520
 
521
- # Left Column: Rocky Mountain Quest
522
  with left_col:
523
  components.html(rocky_map_html, width=800, height=600)
524
  chat_content = asyncio.run(load_chat())
@@ -543,12 +533,29 @@ def main():
543
  play_and_download_audio(audio_file)
544
  st.rerun()
545
 
546
- # Right Column: Prairie Simulator
547
  with right_col:
548
- st.subheader("๐ŸŒพ Prairie Simulator")
549
- components.html(prairie_simulator_html, width=600, height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
- # Countdown Timer
552
  elapsed = time.time() - st.session_state.last_update
553
  remaining = max(0, st.session_state.update_interval - elapsed)
554
  st.sidebar.markdown(f"โณ Next Update in: {int(remaining)}s")
 
25
  # Page Config
26
  st.set_page_config(
27
  layout="wide",
28
+ page_title="Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ",
29
  page_icon="๐ŸฆŒ"
30
  )
31
 
32
  # Game Config
33
+ GAME_NAME = "Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ"
34
  START_LOCATION = "Trailhead Camp โ›บ"
35
  CHARACTERS = {
36
  "Trailblazer Tim ๐ŸŒ„": {"voice": "en-US-GuyNeural", "desc": "Fearless hiker seeking epic trails!", "color": 0x00ff00},
 
101
  if cache_key in st.session_state['audio_cache']:
102
  return st.session_state['audio_cache'][cache_key]
103
  text = clean_text_for_tts(text)
104
+ filename = f"{AUDIO_DIR}/{format_timestamp(username)}-{hashlib.md5(text.encode()).hexdigest()[:8]}.mp3"
105
  communicate = edge_tts.Communicate(text, voice)
106
  await communicate.save(filename)
107
  if os.path.exists(filename) and os.path.getsize(filename) > 0:
 
129
 
130
  # Chat and Quest Log
131
  async def save_chat_entry(username, message, voice, is_markdown=False):
132
+ if not message.strip() or message == st.session_state.get('last_transcript', ''):
133
  return None, None
134
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
135
  entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
 
142
  st.session_state.last_transcript = message
143
  st.session_state.score += 10
144
  st.session_state.treasures += 1
145
+ st.session_state.last_chat_update = time.time()
146
  return md_file, audio_file
147
 
148
  async def load_chat():
 
160
  'chat_history': [], 'audio_cache': {}, 'last_transcript': "",
161
  'username': None, 'score': 0, 'treasures': 0, 'location': START_LOCATION,
162
  'speech_processed': False, 'players': {}, 'last_update': time.time(),
163
+ 'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
164
  'move_right': False, 'move_up': False, 'move_down': False,
165
+ 'prairie_players': {}, 'last_chat_update': 0
166
  }
167
  for k, v in defaults.items():
168
  if k not in st.session_state:
 
178
 
179
  init_session_state()
180
 
181
+ # WebSocket for Multiplayer
 
 
 
 
 
 
 
 
 
 
 
182
  async def websocket_handler(websocket, path):
183
  client_id = str(uuid.uuid4())
184
  room_id = "quest"
 
194
  "location": PRAIRIE_LOCATIONS["Deadwood, SD"],
195
  "color": CHARACTERS[username]["color"]
196
  }
197
+ await broadcast_message(f"System|{username} joins the prairie!", room_id)
198
  else:
199
  st.session_state.players[client_id] = {
200
  "username": username,
201
  "x": random.uniform(-20, 20),
202
  "z": random.uniform(-50, 50),
203
+ "color": CHARACTERS[username]["color"],
204
+ "score": 0,
205
+ "treasures": 0
206
  }
207
+ await broadcast_message(f"System|{username} joins the quest!", room_id)
 
208
 
209
  try:
210
  async for message in websocket:
211
  if '|' in message:
212
+ sender, content = message.split('|', 1)
213
+ voice = CHARACTERS.get(sender, {"voice": "en-US-AriaNeural"})["voice"]
214
  if content.startswith("MOVE:"):
215
  _, x, z = content.split(":")
216
  st.session_state.players[client_id]["x"] = float(x)
 
220
  if action == "ANIMAL":
221
  st.session_state.prairie_players[client_id]["animal"] = value
222
  elif action == "MOVE":
223
+ target = PRAIRIE_LOCATIONS.get(value, PRAIRIE_LOCATIONS["Deadwood, SD"])
224
  st.session_state.prairie_players[client_id]["location"] = target
225
+ action_msg = f"{sender} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
226
+ await save_chat_entry(sender, action_msg, voice)
227
+ elif content.startswith("SCORE:"):
228
+ st.session_state.players[client_id]["score"] = int(content.split(":")[1])
229
+ elif content.startswith("TREASURE:"):
230
+ st.session_state.players[client_id]["treasures"] = int(content.split(":")[1])
231
  else:
232
+ await save_chat_entry(sender, content, voice)
233
+ await perform_arxiv_search(content, sender)
234
  except websockets.ConnectionClosed:
235
+ await broadcast_message(f"System|{username} leaves the quest!", room_id)
236
  if client_id in st.session_state.players:
237
  del st.session_state.players[client_id]
238
  if client_id in st.session_state.prairie_players:
 
247
  player_list = ", ".join([p["username"] for p in st.session_state.players.values()]) or "No adventurers yet!"
248
  message = f"๐Ÿ“ข Quest Update: Active Adventurers - {player_list}"
249
  player_data = json.dumps(list(st.session_state.players.values()))
 
250
  await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
251
 
252
  prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
253
  prairie_message = f"๐ŸŒพ Prairie Update: {prairie_list}"
254
  prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
 
255
  await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
256
 
257
+ # Multicast chat update every second
258
+ chat_content = await load_chat()
259
+ await broadcast_message(f"CHAT_UPDATE:{json.dumps(chat_content[-10:])}", "quest")
260
  await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
261
  await asyncio.sleep(st.session_state.update_interval)
262
 
 
272
  asyncio.set_event_loop(loop)
273
  loop.run_until_complete(run_websocket_server())
274
 
275
+ # ArXiv Integration
276
+ async def perform_arxiv_search(query, username):
277
+ gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
278
+ refs = gradio_client.predict(
279
+ query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
280
+ )[0]
281
+ result = f"๐Ÿ“š Ancient Rocky Knowledge:\n{refs}"
282
+ voice = CHARACTERS[username]["voice"]
283
+ md_file, audio_file = await save_chat_entry(username, result, voice, True)
284
+ return md_file, audio_file
285
+
286
+ # Enhanced 3D Game HTML
287
  rocky_map_html = f"""
288
  <!DOCTYPE html>
289
  <html lang="en">
290
  <head>
291
  <meta charset="UTF-8">
292
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
293
+ <title>Rocky Mountain Quest 3D</title>
294
  <style>
295
  body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; background: #000; }}
296
  #gameContainer {{ width: 800px; height: 600px; position: relative; }}
297
  canvas {{ width: 100%; height: 100%; display: block; }}
298
+ #ui {{
299
+ position: absolute; top: 10px; left: 10px; color: white;
300
+ background: rgba(0, 0, 0, 0.5); padding: 5px; border-radius: 5px;
301
+ }}
302
  #chatBox {{
303
+ position: absolute; bottom: 60px; left: 10px; width: 300px; height: 150px;
304
  background: rgba(0, 0, 0, 0.7); color: white; padding: 10px;
305
  border-radius: 5px; overflow-y: auto;
306
  }}
307
+ #controls {{
308
+ position: absolute; bottom: 10px; left: 10px; color: white;
309
  background: rgba(0, 0, 0, 0.5); padding: 5px; border-radius: 5px;
310
  }}
311
  </style>
312
  </head>
313
  <body>
314
  <div id="gameContainer">
315
+ <div id="ui">
316
+ <div id="players">Players: 1</div>
317
+ <div id="score">Score: 0</div>
318
+ <div id="treasures">Treasures: 0</div>
319
+ </div>
320
  <div id="chatBox"></div>
321
+ <div id="controls">WASD/Arrows to move, Space to collect treasure</div>
322
  </div>
323
 
324
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
 
338
  scene.add(ambientLight);
339
  const sunLight = new THREE.DirectionalLight(0xffddaa, 1);
340
  sunLight.position.set(50, 50, 50);
341
+ sunLight.castShadow = true;
342
  scene.add(sunLight);
343
 
344
  const groundGeometry = new THREE.PlaneGeometry(100, 100);
345
  const groundMaterial = new THREE.MeshStandardMaterial({{ color: 0x228B22 }});
346
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
347
  ground.rotation.x = -Math.PI / 2;
348
+ ground.receiveShadow = true;
349
  scene.add(ground);
350
 
351
  let players = {{}};
352
  const playerMeshes = {{}};
353
+ let treasures = [];
354
+ let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
355
+ let score = 0, treasureCount = 0;
356
+
357
+ // Player initialization
358
+ const playerGeometry = new THREE.BoxGeometry(2, 2, 2);
359
+ const playerMaterial = new THREE.MeshPhongMaterial({{ color: {CHARACTERS[st.session_state.username]["color"]} }});
360
+ const playerMesh = new THREE.Mesh(playerGeometry, playerMaterial);
361
+ playerMesh.position.set(xPos, 1, zPos);
362
+ playerMesh.castShadow = true;
363
+ scene.add(playerMesh);
364
+ players[playerName] = {{ mesh: playerMesh, score: 0, treasures: 0 }};
365
+
366
+ // Treasure spawning
367
+ function spawnTreasure() {{
368
+ if (treasures.length < 5) {{
369
+ const treasure = new THREE.Mesh(
370
+ new THREE.SphereGeometry(1, 8, 8),
371
+ new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
372
+ );
373
+ treasure.position.set(
374
+ Math.random() * 80 - 40,
375
+ 1,
376
+ Math.random() * 80 - 40
377
+ );
378
+ treasure.castShadow = true;
379
+ treasures.push(treasure);
380
+ scene.add(treasure);
381
+ }}
382
+ }}
383
+
384
+ // Controls
385
+ document.addEventListener('keydown', (event) => {{
386
+ switch (event.code) {{
387
+ case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
388
+ case 'ArrowRight': case 'KeyD': moveRight = true; break;
389
+ case 'ArrowUp': case 'KeyW': moveUp = true; break;
390
+ case 'ArrowDown': case 'KeyS': moveDown = true; break;
391
+ case 'Space': collect = true; break;
392
+ }}
393
+ }});
394
+ document.addEventListener('keyup', (event) => {{
395
+ switch (event.code) {{
396
+ case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
397
+ case 'ArrowRight': case 'KeyD': moveRight = false; break;
398
+ case 'ArrowUp': case 'KeyW': moveUp = false; break;
399
+ case 'ArrowDown': case 'KeyS': moveDown = false; break;
400
+ case 'Space': collect = false; break;
401
+ }}
402
+ }});
403
+
404
+ function updatePlayer(delta) {{
405
+ const speed = 20;
406
+ if (moveLeft && xPos > -40) xPos -= speed * delta;
407
+ if (moveRight && xPos < 40) xPos += speed * delta;
408
+ if (moveUp && zPos > -40) zPos -= speed * delta;
409
+ if (moveDown && zPos < 40) zPos += speed * delta;
410
+ playerMesh.position.set(xPos, 1, zPos);
411
+ ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
412
+
413
+ if (collect) {{
414
+ for (let i = treasures.length - 1; i >= 0; i--) {{
415
+ if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
416
+ scene.remove(treasures[i]);
417
+ treasures.splice(i, 1);
418
+ score += 50;
419
+ treasureCount += 1;
420
+ ws.send(`${{playerName}}|SCORE:${{score}}`);
421
+ ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
422
+ spawnTreasure();
423
+ }}
424
+ }}
425
+ }}
426
+
427
+ camera.position.set(xPos, 50, zPos + 50);
428
+ camera.lookAt(xPos, 0, zPos);
429
+ }}
430
+
431
+ function updateTreasures(delta) {{
432
+ treasures.forEach(treasure => {{
433
+ treasure.rotation.y += delta;
434
+ }});
435
+ if (Math.random() < 0.01) spawnTreasure();
436
+ }}
437
 
438
  function updatePlayers(playerData) {{
439
  playerData.forEach(player => {{
440
  if (!playerMeshes[player.username]) {{
441
+ const mesh = new THREE.Mesh(playerGeometry, new THREE.MeshPhongMaterial({{ color: player.color }}));
442
+ mesh.castShadow = true;
 
443
  scene.add(mesh);
444
  playerMeshes[player.username] = mesh;
445
  }}
446
  const mesh = playerMeshes[player.username];
447
  mesh.position.set(player.x, 1, player.z);
448
+ players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
449
  }});
450
+ document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
451
+ document.getElementById('score').textContent = `Score: ${{score}}`;
452
+ document.getElementById('treasures').textContent = `Treasures: ${{treasureCount}}`;
453
  }}
454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  ws.onmessage = function(event) {{
456
  const data = event.data;
457
  if (data.startsWith('MAP_UPDATE:')) {{
458
  const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
459
  updatePlayers(playerData);
460
+ }} else if (data.startsWith('CHAT_UPDATE:')) {{
461
+ const chatData = JSON.parse(data.split('CHAT_UPDATE:')[1]);
462
+ const chatBox = document.getElementById('chatBox');
463
+ chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
464
+ chatBox.scrollTop = chatBox.scrollHeight;
465
  }} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
466
  const [sender, message] = data.split('|');
467
  const chatBox = document.getElementById('chatBox');
 
470
  }}
471
  }};
472
 
473
+ let lastTime = performance.now();
474
  function animate() {{
475
  requestAnimationFrame(animate);
476
+ const currentTime = performance.now();
477
+ const delta = (currentTime - lastTime) / 1000;
478
+ lastTime = currentTime;
479
+
480
+ updatePlayer(delta);
481
+ updateTreasures(delta);
482
  renderer.render(scene, camera);
483
  }}
484
+
485
+ // Initial spawn
486
+ for (let i = 0; i < 5; i++) spawnTreasure();
487
  animate();
488
  </script>
489
  </body>
490
  </html>
491
  """
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  # Main Game Loop
494
  def main():
 
495
  st.sidebar.title(f"๐ŸŽฎ {GAME_NAME}")
496
  st.sidebar.subheader(f"๐ŸŒ„ {st.session_state.username}โ€™s Adventure - Score: {st.session_state.score} ๐Ÿ†")
497
  st.sidebar.write(f"๐Ÿ“œ {CHARACTERS[st.session_state.username]['desc']}")
 
507
  save_username(st.session_state.username)
508
  st.rerun()
509
 
 
510
  left_col, right_col = st.columns([2, 1])
511
 
 
512
  with left_col:
513
  components.html(rocky_map_html, width=800, height=600)
514
  chat_content = asyncio.run(load_chat())
 
533
  play_and_download_audio(audio_file)
534
  st.rerun()
535
 
 
536
  with right_col:
537
+ st.subheader("๐ŸŒพ Prairie Map")
538
+ prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
539
+ for loc, (lat, lon) in PRAIRIE_LOCATIONS.items():
540
+ folium.Marker([lat, lon], popup=loc).add_to(prairie_map)
541
+ for client_id, player in st.session_state.prairie_players.items():
542
+ folium.CircleMarker(
543
+ location=player['location'],
544
+ radius=5,
545
+ color=f"#{player['color']:06x}",
546
+ fill=True,
547
+ fill_opacity=0.7,
548
+ popup=f"{player['username']} ({player['animal']})"
549
+ ).add_to(prairie_map)
550
+ folium_static(prairie_map, width=600, height=400)
551
+
552
+ animal = st.selectbox("Choose Animal", ["prairie_dog", "deer", "sheep", "groundhog"])
553
+ location = st.selectbox("Move to", list(PRAIRIE_LOCATIONS.keys()))
554
+ if st.button("Move"):
555
+ asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:ANIMAL:{animal}", "quest"))
556
+ asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:MOVE:{location}", "quest"))
557
+ st.rerun()
558
 
 
559
  elapsed = time.time() - st.session_state.last_update
560
  remaining = max(0, st.session_state.update_interval - elapsed)
561
  st.sidebar.markdown(f"โณ Next Update in: {int(remaining)}s")