awacke1 commited on
Commit
6c4d06c
ยท
verified ยท
1 Parent(s): 03717e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -797
app.py CHANGED
@@ -23,10 +23,10 @@ import pytz
23
  from collections import defaultdict
24
  import pandas as pd
25
 
26
- # Patch asyncio for nesting
27
  nest_asyncio.apply()
28
 
29
- # Page Config
30
  st.set_page_config(
31
  layout="wide",
32
  page_title="Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ",
@@ -39,813 +39,81 @@ st.set_page_config(
39
  }
40
  )
41
 
42
- # Game Config
43
- GAME_NAME = "Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ"
44
- START_LOCATION = "Trailhead Camp โ›บ"
45
- EDGE_TTS_VOICES = [
46
- {"name": "Aria ๐ŸŒธ", "voice": "en-US-AriaNeural", "desc": "Elegant, creative storytelling", "color": 0xFF69B4, "shape": "sphere"},
47
- {"name": "Guy ๐ŸŒŸ", "voice": "en-US-GuyNeural", "desc": "Authoritative, versatile", "color": 0x00FF00, "shape": "cube"},
48
- {"name": "Jenny ๐ŸŽถ", "voice": "en-US-JennyNeural", "desc": "Friendly, conversational", "color": 0xFFFF00, "shape": "cylinder"},
49
- {"name": "Sonia ๐ŸŒบ", "voice": "en-GB-SoniaNeural", "desc": "Bold, confident", "color": 0x0000FF, "shape": "cone"},
50
- {"name": "Ryan ๐Ÿ› ๏ธ", "voice": "en-GB-RyanNeural", "desc": "Approachable, casual", "color": 0x00FFFF, "shape": "torus"},
51
- {"name": "Natasha ๐ŸŒŒ", "voice": "en-AU-NatashaNeural", "desc": "Sophisticated, mysterious", "color": 0x800080, "shape": "dodecahedron"},
52
- {"name": "William ๐ŸŽป", "voice": "en-AU-WilliamNeural", "desc": "Classic, scholarly", "color": 0xFFA500, "shape": "octahedron"},
53
- {"name": "Clara ๐ŸŒท", "voice": "en-CA-ClaraNeural", "desc": "Cheerful, empathetic", "color": 0xFF4500, "shape": "tetrahedron"},
54
- {"name": "Liam ๐ŸŒŸ", "voice": "en-CA-LiamNeural", "desc": "Energetic, engaging", "color": 0xFFD700, "shape": "icosahedron"}
55
- ]
56
- FILE_EMOJIS = {"md": "๐Ÿ“œ", "mp3": "๐ŸŽต"}
57
- WORLD_OBJECTS = [
58
- {"type": "๐ŸŒฟ Plants", "emoji": "๐ŸŒฑ๐ŸŒฟ๐ŸŒพ", "color": 0x228B22, "shape": "cylinder", "count": 10},
59
- {"type": "๐Ÿฆ Animal Flocks", "emoji": "๐Ÿ•Š๏ธ๐Ÿฆ๐Ÿค", "color": 0x87CEEB, "shape": "sphere", "count": 5},
60
- {"type": "๐Ÿ”๏ธ Mountains", "emoji": "๐Ÿ”๏ธโ›ฐ๏ธ", "color": 0x808080, "shape": "cone", "count": 3},
61
- {"type": "๐ŸŒณ Trees", "emoji": "๐ŸŒฒ๐ŸŒณ๐ŸŒด", "color": 0x006400, "shape": "cylinder", "count": 8},
62
- {"type": "๐Ÿพ Animals", "emoji": "๐Ÿป๐Ÿบ๐ŸฆŒ", "color": 0x8B4513, "shape": "cube", "count": 6}
63
- ]
64
- PRAIRIE_LOCATIONS = {
65
- "Deadwood, SD": (44.3769, -103.7298),
66
- "Wind Cave National Park": (43.6047, -103.4798),
67
- "Wyoming Spring Creek": (41.6666, -106.6666)
68
- }
69
 
70
- # Directories and Files
71
- for d in ["chat_logs", "audio_logs"]:
72
- os.makedirs(d, exist_ok=True)
73
-
74
- CHAT_DIR = "chat_logs"
75
- AUDIO_DIR = "audio_logs"
76
- STATE_FILE = "user_state.txt"
77
- CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
78
- GAME_STATE_FILE = "game_state.json"
79
-
80
- # Cached Game State as "Resource"
81
- @st.cache_resource
82
- def load_game_state(_timestamp):
83
- """Load or initialize the game state, treated as a cached resource."""
84
- if os.path.exists(GAME_STATE_FILE):
85
- with open(GAME_STATE_FILE, 'r') as f:
86
- state = json.load(f)
87
- else:
88
- state = {
89
- "players": {},
90
- "treasures": [
91
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
92
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
93
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
94
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
95
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)}
96
- ],
97
- "world_objects": [],
98
- "history": []
99
- }
100
- with open(GAME_STATE_FILE, 'w') as f:
101
- json.dump(state, f)
102
- return state
103
-
104
- def update_game_state(state):
105
- """Update the game state and persist to file."""
106
- state["timestamp"] = os.path.getmtime(GAME_STATE_FILE) if os.path.exists(GAME_STATE_FILE) else time.time()
107
- with open(GAME_STATE_FILE, 'w') as f:
108
- json.dump(state, f)
109
- load_game_state.clear()
110
- return load_game_state(time.time())
111
-
112
- def reset_game_state():
113
- """Reset the game state to initial conditions."""
114
- state = {
115
- "players": {},
116
- "treasures": [
117
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
118
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
119
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
120
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
121
- {"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)}
122
- ],
123
- "world_objects": [],
124
- "history": []
125
- }
126
- return update_game_state(state)
127
-
128
- # Agent Class for Players
129
- class PlayerAgent:
130
- def __init__(self, username, char_data):
131
- self.username = username
132
- self.x = random.uniform(-20, 20)
133
- self.z = random.uniform(-40, 40)
134
- self.color = char_data["color"]
135
- self.shape = char_data["shape"]
136
- self.score = 0
137
- self.treasures = 0
138
- self.last_active = time.time()
139
- self.voice = char_data["voice"]
140
-
141
- def to_dict(self):
142
- return {
143
- "username": self.username,
144
- "x": self.x,
145
- "z": self.z,
146
- "color": self.color,
147
- "shape": self.shape,
148
- "score": self.score,
149
- "treasures": self.treasures,
150
- "last_active": self.last_active
151
- }
152
-
153
- def update_from_message(self, message):
154
- if '|' in message:
155
- _, content = message.split('|', 1)
156
- if content.startswith("MOVE:"):
157
- _, x, z = content.split(":")
158
- self.x, self.z = float(x), float(z)
159
- self.last_active = time.time()
160
- elif content.startswith("SCORE:"):
161
- self.score = int(content.split(":")[1])
162
- self.last_active = time.time()
163
- elif content.startswith("TREASURE:"):
164
- self.treasures = int(content.split(":")[1])
165
- self.last_active = time.time()
166
-
167
- # Helpers
168
- def format_timestamp(username=""):
169
- now = datetime.now(pytz.timezone('US/Central')).strftime("%Y%m%d_%H%M%S")
170
- return f"{now}-by-{username}"
171
-
172
- def clean_text_for_tts(text):
173
- return re.sub(r'[#*!\[\]]+', '', ' '.join(text.split()))[:200] or "No text"
174
-
175
- def generate_filename(prompt, username, file_type="md"):
176
- timestamp = format_timestamp(username)
177
- cleaned_prompt = clean_text_for_tts(prompt)[:50].replace(" ", "_")
178
- return f"{cleaned_prompt}_{timestamp}.{file_type}"
179
-
180
- def create_file(prompt, username, file_type="md"):
181
- filename = generate_filename(prompt, username, file_type)
182
- with open(filename, 'w', encoding='utf-8') as f:
183
- f.write(prompt)
184
- return filename
185
-
186
- def get_download_link(file, file_type="mp3"):
187
- with open(file, "rb") as f:
188
- b64 = base64.b64encode(f.read()).decode()
189
- mime_types = {"mp3": "audio/mpeg", "md": "text/markdown"}
190
- return f'<a href="data:{mime_types.get(file_type, "application/octet-stream")};base64,{b64}" download="{os.path.basename(file)}">{FILE_EMOJIS.get(file_type, "๐Ÿ“ฅ")} {os.path.basename(file)}</a>'
191
-
192
- def save_username(username):
193
- with open(STATE_FILE, 'w') as f:
194
- f.write(username)
195
-
196
- def load_username():
197
- if os.path.exists(STATE_FILE):
198
- with open(STATE_FILE, 'r') as f:
199
- return f.read().strip()
200
- return None
201
-
202
- # Audio Processing
203
- async def async_edge_tts_generate(text, voice, username):
204
- cache_key = f"{text[:100]}_{voice}"
205
- if cache_key in st.session_state['audio_cache']:
206
- return st.session_state['audio_cache'][cache_key]
207
- text = clean_text_for_tts(text)
208
- filename = generate_filename(text, username, "mp3")
209
- communicate = edge_tts.Communicate(text, voice)
210
- await communicate.save(filename)
211
- if os.path.exists(filename) and os.path.getsize(filename) > 0:
212
- st.session_state['audio_cache'][cache_key] = filename
213
- return filename
214
- return None
215
-
216
- def play_and_download_audio(file_path):
217
- if file_path and os.path.exists(file_path):
218
- st.audio(file_path, start_time=0)
219
- st.markdown(get_download_link(file_path), unsafe_allow_html=True)
220
-
221
- # WebSocket Broadcast
222
- async def broadcast_message(message, room_id):
223
- if room_id in st.session_state.active_connections:
224
- disconnected = []
225
- for client_id, ws in st.session_state.active_connections[room_id].items():
226
- try:
227
- await ws.send(message)
228
- except websockets.ConnectionClosed:
229
- disconnected.append(client_id)
230
- for client_id in disconnected:
231
- if client_id in st.session_state.active_connections[room_id]:
232
- del st.session_state.active_connections[room_id][client_id]
233
-
234
- # Chat and Quest Log
235
- async def save_chat_entry(username, message, voice, is_markdown=False):
236
- if not message.strip() or message == st.session_state.get('last_transcript', ''):
237
- return None, None
238
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
239
- entry = f"[{timestamp}] {username}: {message}" if not is_markdown else f"[{timestamp}] {username}:\n```markdown\n{message}\n```"
240
- md_file = create_file(entry, username, "md")
241
- with open(CHAT_FILE, 'a') as f:
242
- f.write(f"{entry}\n")
243
- audio_file = await async_edge_tts_generate(message, voice, username)
244
- await broadcast_message(f"{username}|{message}", "quest")
245
- st.session_state.chat_history.append({"username": username, "message": message, "audio": audio_file})
246
- st.session_state.last_transcript = message
247
- game_state = load_game_state(st.session_state.game_state_timestamp)
248
- if username in game_state["players"]:
249
- game_state["players"][username]["score"] += 10
250
- game_state["players"][username]["treasures"] += 1
251
- game_state["players"][username]["last_active"] = time.time()
252
- game_state["history"].append(entry)
253
- if message.lower() in ["plants", "animal flocks", "mountains", "trees", "animals"]:
254
- obj_type = next(o for o in WORLD_OBJECTS if message.lower() in o["type"].lower())
255
- for _ in range(obj_type["count"]):
256
- game_state["world_objects"].append({
257
- "type": obj_type["type"],
258
- "emoji": random.choice(obj_type["emoji"].split()),
259
- "x": random.uniform(-40, 40),
260
- "z": random.uniform(-40, 40),
261
- "color": obj_type["color"],
262
- "shape": obj_type["shape"]
263
- })
264
- update_game_state(game_state)
265
- return md_file, audio_file
266
-
267
- async def load_chat():
268
- if not os.path.exists(CHAT_FILE):
269
- with open(CHAT_FILE, 'a') as f:
270
- f.write(f"# {GAME_NAME} Log\n\nThe adventure begins at {START_LOCATION}! ๐Ÿ”๏ธ\n")
271
- with open(CHAT_FILE, 'r') as f:
272
- content = f.read().strip()
273
- return content.split('\n')
274
-
275
- # Session State Init
276
- def init_session_state():
277
- defaults = {
278
- 'server_running': False, 'server_task': None, 'active_connections': {},
279
- 'chat_history': [], 'audio_cache': {}, 'last_transcript': "",
280
- 'username': None, 'score': 0, 'treasures': 0, 'location': START_LOCATION,
281
- 'speech_processed': False, 'players': {}, 'last_update': time.time(),
282
- 'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
283
- 'move_right': False, 'move_up': False, 'move_down': False,
284
- 'prairie_players': {}, 'last_chat_update': 0, 'game_state_timestamp': time.time(),
285
- 'timeout': 60, 'auto_refresh': 30, 'last_activity': time.time(),
286
- 'marquee_settings': {"background": "#1E1E1E", "color": "#FFFFFF", "font-size": "14px", "animationDuration": "20s", "width": "100%", "lineHeight": "35px"},
287
- 'operation_timings': {}, 'performance_metrics': defaultdict(list), 'download_link_cache': {}
288
- }
289
- for k, v in defaults.items():
290
- if k not in st.session_state:
291
- st.session_state[k] = v
292
- if st.session_state.username is None:
293
- char = random.choice(EDGE_TTS_VOICES)
294
- st.session_state.username = char["name"]
295
- st.session_state.tts_voice = char["voice"]
296
- asyncio.run(save_chat_entry(st.session_state.username, f"๐Ÿ—บ๏ธ Welcome to the Rocky Mountain Quest, {st.session_state.username}!", char["voice"]))
297
- game_state = load_game_state(st.session_state.game_state_timestamp)
298
- if st.session_state.username not in game_state["players"]:
299
- char = next(c for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)
300
- agent = PlayerAgent(st.session_state.username, char)
301
- game_state["players"][st.session_state.username] = agent.to_dict()
302
- update_game_state(game_state)
303
-
304
- init_session_state()
305
-
306
- # WebSocket for Multiplayer
307
- async def websocket_handler(websocket, path):
308
- client_id = str(uuid.uuid4())
309
- room_id = "quest"
310
- if room_id not in st.session_state.active_connections:
311
- st.session_state.active_connections[room_id] = {}
312
- st.session_state.active_connections[room_id][client_id] = websocket
313
- username = st.session_state.username
314
-
315
- game_state = load_game_state(st.session_state.game_state_timestamp)
316
- if "prairie" in path:
317
- char = next(c for c in EDGE_TTS_VOICES if c["name"] == username)
318
- st.session_state.prairie_players[client_id] = {
319
- "username": username,
320
- "animal": "prairie_dog",
321
- "location": PRAIRIE_LOCATIONS["Deadwood, SD"],
322
- "color": char["color"]
323
- }
324
- await broadcast_message(f"System|{username} joins the prairie!", room_id)
325
- else:
326
- if username not in game_state["players"]:
327
- char = next(c for c in EDGE_TTS_VOICES if c["name"] == username)
328
- agent = PlayerAgent(username, char)
329
- game_state["players"][username] = agent.to_dict()
330
- update_game_state(game_state)
331
- st.session_state.players[client_id] = game_state["players"][username]
332
- await broadcast_message(f"System|{username} joins the quest!", room_id)
333
-
334
- try:
335
- async for message in websocket:
336
- if '|' in message:
337
- sender, content = message.split('|', 1)
338
- voice = next(c["voice"] for c in EDGE_TTS_VOICES if c["name"] == sender)
339
- game_state = load_game_state(st.session_state.game_state_timestamp)
340
- agent = PlayerAgent(sender, next(c for c in EDGE_TTS_VOICES if c["name"] == sender))
341
- agent.update_from_message(f"{sender}|{content}")
342
- if sender in game_state["players"]:
343
- game_state["players"][sender] = agent.to_dict()
344
- if content.startswith("PRAIRIE:"):
345
- action, value = content.split(":", 1)
346
- if action == "ANIMAL":
347
- st.session_state.prairie_players[client_id]["animal"] = value
348
- elif action == "MOVE":
349
- target = PRAIRIE_LOCATIONS.get(value, PRAIRIE_LOCATIONS["Deadwood, SD"])
350
- st.session_state.prairie_players[client_id]["location"] = target
351
- action_msg = f"{sender} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
352
- await save_chat_entry(sender, action_msg, voice)
353
- else:
354
- await save_chat_entry(sender, content, voice)
355
- await perform_arxiv_search(content, sender)
356
- update_game_state(game_state)
357
- except websockets.ConnectionClosed:
358
- await broadcast_message(f"System|{username} leaves the quest!", room_id)
359
- if client_id in st.session_state.players:
360
- del st.session_state.players[client_id]
361
- if client_id in st.session_state.prairie_players:
362
- del st.session_state.prairie_players[client_id]
363
- finally:
364
- if room_id in st.session_state.active_connections and client_id in st.session_state.active_connections[room_id]:
365
- del st.session_state.active_connections[room_id][client_id]
366
-
367
- async def periodic_update():
368
  while True:
369
- if st.session_state.active_connections.get("quest"):
370
- game_state = load_game_state(st.session_state.game_state_timestamp)
371
- current_time = time.time()
372
- inactive_players = [p for p, data in game_state["players"].items() if current_time - data["last_active"] > st.session_state.timeout]
373
- for player in inactive_players:
374
- del game_state["players"][player]
375
- await broadcast_message(f"System|{player} timed out after 60 seconds!", "quest")
376
- for username in game_state["players"]:
377
- game_state["players"][username]["score"] += int((current_time - game_state["players"][username]["last_active"]) * 60) # $1 per second
378
- update_game_state(game_state)
379
-
380
- player_list = ", ".join([p for p in game_state["players"].keys()]) or "No adventurers yet!"
381
- message = f"๐Ÿ“ข Quest Update: Active Adventurers - {player_list}"
382
- player_data = json.dumps(list(game_state["players"].values()))
383
- await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
384
-
385
- prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
386
- prairie_message = f"๐ŸŒพ Prairie Update: {prairie_list}"
387
- prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
388
- await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
389
-
390
- chat_content = await load_chat()
391
- await broadcast_message(f"CHAT_UPDATE:{json.dumps(chat_content[-10:])}", "quest")
392
- await broadcast_message(f"GAME_STATE:{json.dumps(game_state)}", "quest")
393
- await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
394
- await asyncio.sleep(st.session_state.update_interval)
395
-
396
- async def run_websocket_server():
397
- if not st.session_state.get('server_running', False):
398
- server = await websockets.serve(websocket_handler, '0.0.0.0', 8765)
399
- st.session_state['server_running'] = True
400
- asyncio.create_task(periodic_update())
401
- await server.wait_closed()
402
-
403
- def start_websocket_server():
404
- loop = asyncio.new_event_loop()
405
- asyncio.set_event_loop(loop)
406
- loop.run_until_complete(run_websocket_server())
407
-
408
- # ArXiv Integration
409
- async def perform_arxiv_search(query, username):
410
- gradio_client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
411
- refs = gradio_client.predict(
412
- query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
413
- )[0]
414
- result = f"๐Ÿ“š Ancient Rocky Knowledge:\n{refs}"
415
- voice = next(c["voice"] for c in EDGE_TTS_VOICES if c["name"] == username)
416
- md_file, audio_file = await save_chat_entry(username, result, voice, True)
417
- return md_file, audio_file
418
-
419
- # Sidebar Functions
420
- def update_marquee_settings_ui():
421
- st.sidebar.markdown("### ๐ŸŽฏ Marquee Settings")
422
- cols = st.sidebar.columns(2)
423
- with cols[0]:
424
- bg_color = st.color_picker("๐ŸŽจ Background", st.session_state['marquee_settings']["background"], key="bg_color_picker")
425
- text_color = st.color_picker("โœ๏ธ Text", st.session_state['marquee_settings']["color"], key="text_color_picker")
426
- with cols[1]:
427
- font_size = st.slider("๐Ÿ“ Size", 10, 24, 14, key="font_size_slider")
428
- duration = st.slider("โฑ๏ธ Speed (secs)", 1, 20, 20, key="duration_slider")
429
- st.session_state['marquee_settings'].update({
430
- "background": bg_color,
431
- "color": text_color,
432
- "font-size": f"{font_size}px",
433
- "animationDuration": f"{duration}s"
434
- })
435
-
436
- def display_file_history_in_sidebar():
437
- st.sidebar.markdown("---")
438
- st.sidebar.markdown("### ๐Ÿ“‚ Chat Gallery")
439
- chat_files = sorted(glob.glob("*.mp3"), key=os.path.getmtime, reverse=True)
440
- if not chat_files:
441
- st.sidebar.write("No chat audio files found.")
442
- return
443
- for audio_file in chat_files[:10]:
444
- with st.sidebar.expander(os.path.basename(audio_file)):
445
- st.write(f"**Said:** {os.path.splitext(os.path.basename(audio_file))[0].split('_')[0]}")
446
- play_and_download_audio(audio_file)
447
-
448
- def log_performance_metrics():
449
- st.sidebar.markdown("### โฑ๏ธ Performance Metrics")
450
- metrics = st.session_state['operation_timings']
451
- if metrics:
452
- total_time = sum(metrics.values())
453
- st.sidebar.write(f"**Total Processing Time:** {total_time:.2f}s")
454
- for operation, duration in metrics.items():
455
- percentage = (duration / total_time) * 100
456
- st.sidebar.write(f"**{operation}:** {duration:.2f}s ({percentage:.1f}%)")
457
-
458
- # Enhanced 3D Game HTML
459
- rocky_map_html = f"""
460
- <!DOCTYPE html>
461
- <html lang="en">
462
- <head>
463
- <meta charset="UTF-8">
464
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
465
- <title>Rocky Mountain Quest 3D</title>
466
- <style>
467
- body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; background: #000; }}
468
- #gameContainer {{ width: 800px; height: 600px; position: relative; }}
469
- canvas {{ width: 100%; height: 100%; display: block; }}
470
- .ui-container {{
471
- position: absolute; top: 10px; left: 10px; color: white;
472
- background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
473
- user-select: none;
474
- }}
475
- .leaderboard {{
476
- position: absolute; top: 10px; left: 50%; transform: translateX(-50%); color: white;
477
- background-color: rgba(0, 0, 0, 0.7); padding: 10px; border-radius: 5px;
478
- text-align: center;
479
- }}
480
- .controls {{
481
- position: absolute; bottom: 10px; left: 10px; color: white;
482
- background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
483
- }}
484
- #chatBox {{
485
- position: absolute; bottom: 60px; left: 10px; width: 300px; height: 150px;
486
- background: rgba(0, 0, 0, 0.7); color: white; padding: 10px;
487
- border-radius: 5px; overflow-y: auto;
488
- }}
489
- </style>
490
- </head>
491
- <body>
492
- <div id="gameContainer">
493
- <div class="ui-container">
494
- <h2>{GAME_NAME}</h2>
495
- <div id="score">Score: 0</div>
496
- <div id="treasures">Treasures: 0</div>
497
- <div id="timeout">Timeout: 60s</div>
498
- </div>
499
- <div class="leaderboard" id="leaderboard">Leaderboard</div>
500
- <div id="chatBox"></div>
501
- <div class="controls">
502
- <p>Controls: WASD/Arrows to move, Space to collect treasure</p>
503
- <p>Chat to add world features!</p>
504
- </div>
505
- </div>
506
-
507
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
508
- <script>
509
- const playerName = "{st.session_state.username}";
510
- let ws = new WebSocket('ws://localhost:8765');
511
- const scene = new THREE.Scene();
512
- const camera = new THREE.PerspectiveCamera(75, 800 / 600, 0.1, 1000);
513
- camera.position.set(0, 50, 50);
514
- camera.lookAt(0, 0, 0);
515
-
516
- const renderer = new THREE.WebGLRenderer({{ antialias: true }});
517
- renderer.setSize(800, 600);
518
- document.getElementById('gameContainer').appendChild(renderer.domElement);
519
-
520
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
521
- scene.add(ambientLight);
522
- const sunLight = new THREE.DirectionalLight(0xffddaa, 1);
523
- sunLight.position.set(50, 50, 50);
524
- sunLight.castShadow = true;
525
- scene.add(sunLight);
526
-
527
- const groundGeometry = new THREE.PlaneGeometry(100, 100);
528
- const groundMaterial = new THREE.MeshStandardMaterial({{ color: 0x228B22 }});
529
- const ground = new THREE.Mesh(groundGeometry, groundMaterial);
530
- ground.rotation.x = -Math.PI / 2;
531
- ground.receiveShadow = true;
532
- scene.add(ground);
533
-
534
- let players = {{}};
535
- const playerMeshes = {{}};
536
- let treasures = [];
537
- const treasureMeshes = {{}};
538
- let worldObjects = [];
539
- const worldObjectMeshes = {{}};
540
- let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
541
- let score = 0, treasureCount = 0, lastActive = performance.now() / 1000;
542
-
543
- const shapeGeometries = {{
544
- "sphere": new THREE.SphereGeometry(1, 16, 16),
545
- "cube": new THREE.BoxGeometry(2, 2, 2),
546
- "cylinder": new THREE.CylinderGeometry(1, 1, 2, 16),
547
- "cone": new THREE.ConeGeometry(1, 2, 16),
548
- "torus": new THREE.TorusGeometry(1, 0.4, 16, 100),
549
- "dodecahedron": new THREE.DodecahedronGeometry(1),
550
- "octahedron": new THREE.OctahedronGeometry(1),
551
- "tetrahedron": new THREE.TetrahedronGeometry(1),
552
- "icosahedron": new THREE.IcosahedronGeometry(1)
553
- }};
554
-
555
- const playerMaterial = new THREE.MeshPhongMaterial({{ color: {next(c["color"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)} }});
556
- const playerMesh = new THREE.Mesh(shapeGeometries["{next(c['shape'] for c in EDGE_TTS_VOICES if c['name'] == st.session_state.username)}"], playerMaterial);
557
- playerMesh.position.set(xPos, 1, zPos);
558
- playerMesh.castShadow = true;
559
- scene.add(playerMesh);
560
- players[playerName] = {{ mesh: playerMesh, score: 0, treasures: 0 }};
561
-
562
- function updateTreasures(treasureData) {{
563
- treasureData.forEach(t => {{
564
- if (!treasureMeshes[t.id]) {{
565
- const treasure = new THREE.Mesh(
566
- new THREE.SphereGeometry(1, 8, 8),
567
- new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
568
- );
569
- treasure.position.set(t.x, 1, t.z);
570
- treasure.castShadow = true;
571
- treasureMeshes[t.id] = treasure;
572
- treasures.push(treasure);
573
- scene.add(treasure);
574
- }} else {{
575
- treasureMeshes[t.id].position.set(t.x, 1, t.z);
576
- }}
577
- }});
578
- Object.keys(treasureMeshes).forEach(id => {{
579
- if (!treasureData.some(t => t.id === id)) {{
580
- scene.remove(treasureMeshes[id]);
581
- treasures = treasures.filter(t => t !== treasureMeshes[id]);
582
- delete treasureMeshes[id];
583
- }}
584
- }});
585
- }}
586
-
587
- function updateWorldObjects(objectData) {{
588
- objectData.forEach(obj => {{
589
- if (!worldObjectMeshes[obj.type + obj.x + obj.z]) {{
590
- const geometry = shapeGeometries[obj.shape] || new THREE.BoxGeometry(2, 2, 2);
591
- const material = new THREE.MeshPhongMaterial({{ color: obj.color }});
592
- const objMesh = new THREE.Mesh(geometry, material);
593
- objMesh.position.set(obj.x, 1, obj.z);
594
- objMesh.castShadow = true;
595
- worldObjectMeshes[obj.type + obj.x + obj.z] = objMesh;
596
- worldObjects.push(objMesh);
597
- scene.add(objMesh);
598
- }}
599
- }});
600
- }}
601
-
602
- document.addEventListener('keydown', (event) => {{
603
- switch (event.code) {{
604
- case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
605
- case 'ArrowRight': case 'KeyD': moveRight = true; break;
606
- case 'ArrowUp': case 'KeyW': moveUp = true; break;
607
- case 'ArrowDown': case 'KeyS': moveDown = true; break;
608
- case 'Space': collect = true; break;
609
- }}
610
- lastActive = performance.now() / 1000;
611
- }});
612
- document.addEventListener('keyup', (event) => {{
613
- switch (event.code) {{
614
- case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
615
- case 'ArrowRight': case 'KeyD': moveRight = false; break;
616
- case 'ArrowUp': case 'KeyW': moveUp = false; break;
617
- case 'ArrowDown': case 'KeyS': moveDown = false; break;
618
- case 'Space': collect = false; break;
619
- }}
620
- }});
621
-
622
- function updatePlayer(delta) {{
623
- const speed = 20;
624
- if (moveLeft && xPos > -40) xPos -= speed * delta;
625
- if (moveRight && xPos < 40) xPos += speed * delta;
626
- if (moveUp && zPos > -40) zPos -= speed * delta;
627
- if (moveDown && zPos < 40) zPos += speed * delta;
628
- playerMesh.position.set(xPos, 1, zPos);
629
- ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
630
-
631
- if (collect) {{
632
- for (let i = treasures.length - 1; i >= 0; i--) {{
633
- if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
634
- const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
635
- scene.remove(treasures[i]);
636
- treasures.splice(i, 1);
637
- delete treasureMeshes[id];
638
- score += 50;
639
- treasureCount += 1;
640
- ws.send(`${{playerName}}|SCORE:${{score}}`);
641
- ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
642
- }}
643
- }}
644
- }}
645
-
646
- camera.position.set(xPos, 50, zPos + 50);
647
- camera.lookAt(xPos, 0, zPos);
648
- const timeout = 60 - (performance.now() / 1000 - lastActive);
649
- document.getElementById('timeout').textContent = `Timeout: ${{Math.max(0, timeout.toFixed(0))}}s`;
650
- document.getElementById('score').textContent = `Score: $${{score}}`;
651
- document.getElementById('treasures').textContent = `Treasures: ${{treasureCount}}`;
652
- }}
653
-
654
- function updatePlayers(playerData) {{
655
- playerData.forEach(player => {{
656
- if (!playerMeshes[player.username]) {{
657
- const geometry = shapeGeometries[player.shape] || new THREE.BoxGeometry(2, 2, 2);
658
- const material = new THREE.MeshPhongMaterial({{ color: player.color }});
659
- const mesh = new THREE.Mesh(geometry, material);
660
- mesh.castShadow = true;
661
- scene.add(mesh);
662
- playerMeshes[player.username] = mesh;
663
- }}
664
- const mesh = playerMeshes[player.username];
665
- mesh.position.set(player.x, 1, player.z);
666
- players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
667
- if (player.username === playerName) {{
668
- xPos = player.x;
669
- zPos = player.z;
670
- score = player.score;
671
- treasureCount = player.treasures;
672
- playerMesh.position.set(xPos, 1, zPos);
673
- }}
674
- }});
675
- document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
676
- const leaderboard = playerData.sort((a, b) => b.score - a.score)
677
- .map(p => `${{p.username}}: $${{p.score}}`)
678
- .join('<br>');
679
- document.getElementById('leaderboard').innerHTML = `Leaderboard<br>${{leaderboard}}`;
680
- }}
681
-
682
- ws.onmessage = function(event) {{
683
- const data = event.data;
684
- if (data.startsWith('MAP_UPDATE:')) {{
685
- const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
686
- updatePlayers(playerData);
687
- }} else if (data.startsWith('CHAT_UPDATE:')) {{
688
- const chatData = JSON.parse(data.split('CHAT_UPDATE:')[1]);
689
- const chatBox = document.getElementById('chatBox');
690
- chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
691
- chatBox.scrollTop = chatBox.scrollHeight;
692
- }} else if (data.startsWith('GAME_STATE:')) {{
693
- const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
694
- updatePlayers(gameState.players);
695
- updateTreasures(gameState.treasures);
696
- updateWorldObjects(gameState.world_objects);
697
- }} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
698
- const [sender, message] = data.split('|');
699
- const chatBox = document.getElementById('chatBox');
700
- chatBox.innerHTML += `<p>${{sender}}: ${{message}}</p>`;
701
- chatBox.scrollTop = chatBox.scrollHeight;
702
- }}
703
- }};
704
-
705
- let lastTime = performance.now();
706
- function animate() {{
707
- requestAnimationFrame(animate);
708
- const currentTime = performance.now();
709
- const delta = (currentTime - lastTime) / 1000;
710
- lastTime = currentTime;
711
 
712
- updatePlayer(delta);
713
- treasures.forEach(t => t.rotation.y += delta);
714
- worldObjects.forEach(o => o.rotation.y += delta * 0.5);
715
- renderer.render(scene, camera);
716
- }}
717
 
718
- animate();
719
- </script>
720
- </body>
721
- </html>
722
- """
723
 
724
- # Main Game Loop
725
  def main():
726
- # Top Center Chat and Character Display
727
- st.markdown(f"<h2 style='text-align: center;'>Welcome, {st.session_state.username}!</h2>", unsafe_allow_html=True)
728
- message = st.text_input(f"๐Ÿ—จ๏ธ Chat as {st.session_state.username}:", placeholder="Type to chat or add world features! ๐ŸŒฒ", key="chat_input")
729
- if st.button("๐ŸŒŸ Send"):
730
- if message:
731
- voice = next(c["voice"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)
732
- md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, message, voice))
733
- if audio_file:
734
- play_and_download_audio(audio_file)
735
- st.session_state.last_activity = time.time()
736
- st.success(f"๐ŸŒ„ +10 points! New Score: ${st.session_state.score}")
737
-
738
- update_marquee_settings_ui()
739
- st.sidebar.title(f"๐ŸŽฎ {GAME_NAME}")
740
- st.sidebar.subheader(f"๐ŸŒ„ {st.session_state.username}โ€™s Adventure - Score: ${st.session_state.score} ๐Ÿ†")
741
- st.sidebar.write(f"๐Ÿ“œ {next(c['desc'] for c in EDGE_TTS_VOICES if c['name'] == st.session_state.username)}")
742
- st.sidebar.write(f"๐Ÿ“ Location: {st.session_state.location}")
743
- st.sidebar.write(f"๐Ÿ… Score: ${st.session_state.score}")
744
- st.sidebar.write(f"๐ŸŽต Treasures: {st.session_state.treasures}")
745
- st.sidebar.write(f"๐Ÿ‘ฅ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
746
-
747
- st.session_state.update_interval = st.sidebar.slider("Refresh Interval (seconds)", 1, 10, 1, step=1)
748
- if st.sidebar.button("Reset World ๐ŸŒ"):
749
- reset_game_state()
750
- st.session_state.game_state_timestamp = time.time()
751
- st.rerun()
752
 
753
- # Demo Buttons
754
- st.sidebar.markdown("### ๐ŸŒŸ World Additions")
755
- if st.sidebar.button("๐ŸŒฟ Add Plants"):
756
- asyncio.run(save_chat_entry(st.session_state.username, "plants", st.session_state.tts_voice))
757
- if st.sidebar.button("๐Ÿฆ Add Animal Flocks"):
758
- asyncio.run(save_chat_entry(st.session_state.username, "animal flocks", st.session_state.tts_voice))
759
- if st.sidebar.button("๐Ÿ”๏ธ Add Mountains"):
760
- asyncio.run(save_chat_entry(st.session_state.username, "mountains", st.session_state.tts_voice))
761
 
762
- left_col, right_col = st.columns([2, 1])
763
-
764
- with left_col:
765
  components.html(rocky_map_html, width=800, height=600)
766
- chat_content = asyncio.run(load_chat())
767
- st.text_area("๐Ÿ“œ Quest Log", "\n".join(chat_content[-10:]), height=200, disabled=True)
768
- uploaded_file = st.file_uploader("Upload a File ๐Ÿ“ค", type=["txt", "md", "mp3"])
769
- if uploaded_file:
770
- with open(uploaded_file.name, "wb") as f:
771
- f.write(uploaded_file.getbuffer())
772
- game_state = load_game_state(st.session_state.game_state_timestamp)
773
- if st.session_state.username in game_state["players"]:
774
- game_state["players"][st.session_state.username]["score"] += 20
775
- game_state["history"].append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {st.session_state.username}: Uploaded {uploaded_file.name}")
776
- update_game_state(game_state)
777
- st.session_state.last_activity = time.time()
778
- st.success(f"File uploaded! +20 points! New Score: ${st.session_state.score}")
779
- mycomponent = components.declare_component("speech_component", path="./speech_component")
780
- val = mycomponent(my_input_value="", key=f"speech_{st.session_state.get('speech_processed', False)}")
781
- if val and val != st.session_state.last_transcript:
782
- val_stripped = val.strip().replace('\n', ' ')
783
- if val_stripped:
784
- voice = next(c["voice"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)
785
- st.session_state['speech_processed'] = True
786
- md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, val_stripped, voice))
787
- if audio_file:
788
- play_and_download_audio(audio_file)
789
- st.session_state.last_activity = time.time()
790
- st.rerun()
791
 
792
- with right_col:
 
 
 
 
 
 
793
  st.subheader("๐ŸŒพ Prairie Map")
794
- prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
795
- for loc, (lat, lon) in PRAIRIE_LOCATIONS.items():
796
- folium.Marker([lat, lon], popup=loc).add_to(prairie_map)
797
- game_state = load_game_state(st.session_state.game_state_timestamp)
798
- for username, player in game_state["players"].items():
799
- folium.CircleMarker(
800
- location=[44.0 + player["x"] * 0.01, -103.0 + player["z"] * 0.01],
801
- radius=5,
802
- color=f"#{player['color']:06x}",
803
- fill=True,
804
- fill_opacity=0.7,
805
- popup=f"{username} (Score: ${player['score']}, Treasures: {player['treasures']})"
806
- ).add_to(prairie_map)
807
- for client_id, player in st.session_state.prairie_players.items():
808
- folium.CircleMarker(
809
- location=player['location'],
810
- radius=5,
811
- color=f"#{player['color']:06x}",
812
- fill=True,
813
- fill_opacity=0.7,
814
- popup=f"{player['username']} ({player['animal']})"
815
- ).add_to(prairie_map)
816
- folium_static(prairie_map, width=600, height=400)
817
-
818
- animal = st.selectbox("Choose Animal ๐Ÿพ", ["prairie_dog", "deer", "sheep", "groundhog"])
819
- location = st.selectbox("Move to ๐Ÿ“", list(PRAIRIE_LOCATIONS.keys()))
820
- if st.button("Move ๐Ÿšถ"):
821
- asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:ANIMAL:{animal}", "quest"))
822
- asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:MOVE:{location}", "quest"))
823
- st.session_state.last_activity = time.time()
824
- st.rerun()
825
 
826
- elapsed = time.time() - st.session_state.last_update
827
- remaining = max(0, st.session_state.update_interval - elapsed)
828
- st.sidebar.markdown(f"โณ Next Update in: {int(remaining)}s")
829
-
830
- if time.time() - st.session_state.last_activity > st.session_state.timeout:
831
- st.sidebar.warning("Timed out! Refreshing in 5 seconds... โฐ")
832
- time.sleep(5)
833
- st.session_state.game_state_timestamp = time.time()
834
- st.rerun()
835
- elif time.time() - st.session_state.last_update > st.session_state.auto_refresh:
836
- st.session_state.game_state_timestamp = time.time()
837
- st.rerun()
838
- elif remaining <= 0:
839
- st.session_state.last_update = time.time()
840
- st.session_state.game_state_timestamp = time.time()
841
  st.rerun()
842
 
843
- if not st.session_state.get('server_running', False):
844
- st.session_state.server_task = threading.Thread(target=start_websocket_server, daemon=True)
845
- st.session_state.server_task.start()
846
-
847
- display_file_history_in_sidebar()
848
- log_performance_metrics()
849
-
850
  if __name__ == "__main__":
851
- main()
 
23
  from collections import defaultdict
24
  import pandas as pd
25
 
26
+ # ๐Ÿ› ๏ธ (1) Allow nested asyncio loops for compatibility
27
  nest_asyncio.apply()
28
 
29
+ # ๐ŸŽจ (2) Page Configuration for Streamlit UI
30
  st.set_page_config(
31
  layout="wide",
32
  page_title="Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ",
 
39
  }
40
  )
41
 
42
+ # ๐ŸŒŸ (3) Prominent Player Join Notifications
43
+ async def broadcast_player_join(username):
44
+ message = json.dumps({"type": "player_joined", "username": username})
45
+ await broadcast_message(message, "quest")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ # ๐Ÿ”„ (4) Real-Time Game State Synchronization
48
+ async def broadcast_game_state():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  while True:
50
+ game_state = load_game_state(time.time())
51
+ await broadcast_message(json.dumps({"type": "sync", "state": game_state}), "quest")
52
+ await asyncio.sleep(1)
53
+
54
+ # ๐ŸŒŒ (5) Fractal and Emergent Data Generation for 3D Visualization
55
+ FRAC_SEEDS = [random.uniform(-40, 40) for _ in range(100)]
56
+
57
+ # ๐ŸŽฒ (6) Three.js Shape Primitives and Fractal Data Structures
58
+ shape_primitives = ['sphere', 'cube', 'cylinder', 'cone', 'torus', 'dodecahedron', 'octahedron', 'tetrahedron', 'icosahedron']
59
+ fractals = [{
60
+ "type": random.choice(shape_primitives),
61
+ "position": [random.uniform(-40,40), random.uniform(0,20), random.uniform(-40,40)],
62
+ "color": random.randint(0, 0xFFFFFF)
63
+ } for _ in range(100)]
64
+
65
+ # ๐Ÿ–ฅ๏ธ (7) Initialize WebSocket Server for Multiplayer Connectivity
66
+ if not st.session_state.get('server_running', False):
67
+ threading.Thread(target=start_websocket_server, daemon=True).start()
68
+ st.session_state['server_running'] = True
69
+
70
+ # ๐Ÿ’ฐ (8) Increment Scores and Automatically Save at Milestones
71
+ async def increment_and_save_scores():
72
+ while True:
73
+ game_state = load_game_state(time.time())
74
+ for player in game_state["players"].values():
75
+ player["score"] += 1
76
+ if player["score"] % 100 == 0:
77
+ filename = f"cache_{player['username']}_score_{player['score']}.json"
78
+ with open(filename, 'w') as f:
79
+ json.dump(player, f)
80
+ update_game_state(game_state)
81
+ await asyncio.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ # ๐Ÿš€ (9) Start Background Async Tasks for Game Operations
84
+ asyncio.run(increment_and_save_scores())
85
+ asyncio.run(broadcast_game_state())
 
 
86
 
87
+ # ๐ŸŒ (10) Main Streamlit UI Function
 
 
 
 
88
 
 
89
  def main():
90
+ # ๐Ÿ”๏ธ Game Title Display
91
+ st.title("Rocky Mountain Quest 3D ๐Ÿ”๏ธ๐ŸŽฎ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ # ๐Ÿ“ UI Layout with Columns
94
+ col1, col2 = st.columns([3, 1])
 
 
 
 
 
 
95
 
96
+ # ๐ŸŽฎ Left Column - Three.js Game Canvas
97
+ with col1:
 
98
  components.html(rocky_map_html, width=800, height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ # ๐Ÿ“‹ Right Column - Player Information and Maps
101
+ with col2:
102
+ st.subheader("Active Players ๐Ÿง™โ€โ™‚๏ธ")
103
+ current_state = load_game_state(time.time())
104
+ for player in current_state["players"].values():
105
+ st.write(f"๐Ÿ‘ค {player['username']} - ๐ŸŒŸ {player['score']} points")
106
+
107
  st.subheader("๐ŸŒพ Prairie Map")
108
+ prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8)
109
+ folium_static(prairie_map)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ # ๐ŸŽฏ Sidebar Game Controls
112
+ st.sidebar.markdown("### ๐ŸŽฏ Game Controls")
113
+ if st.sidebar.button("Reset World ๐ŸŒ"):
114
+ reset_game_state()
 
 
 
 
 
 
 
 
 
 
 
115
  st.rerun()
116
 
117
+ # โœ… Run the application
 
 
 
 
 
 
118
  if __name__ == "__main__":
119
+ main()