awacke1 commited on
Commit
9e8778b
Β·
verified Β·
1 Parent(s): e485ea3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -45
app.py CHANGED
@@ -51,7 +51,7 @@ PRAIRIE_LOCATIONS = {
51
  "Wyoming Spring Creek": (41.6666, -106.6666)
52
  }
53
 
54
- # Directories
55
  for d in ["chat_logs", "audio_logs"]:
56
  os.makedirs(d, exist_ok=True)
57
 
@@ -59,6 +59,28 @@ CHAT_DIR = "chat_logs"
59
  AUDIO_DIR = "audio_logs"
60
  STATE_FILE = "user_state.txt"
61
  CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  # Helpers
64
  def format_timestamp(username=""):
@@ -162,7 +184,8 @@ def init_session_state():
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:
@@ -175,6 +198,17 @@ def init_session_state():
175
  st.session_state.username = random.choice(list(CHARACTERS.keys()))
176
  asyncio.run(save_chat_entry(st.session_state.username, "πŸ—ΊοΈ Begins the Rocky Mountain Quest!", CHARACTERS[st.session_state.username]["voice"]))
177
  save_username(st.session_state.username)
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  init_session_state()
180
 
@@ -187,6 +221,7 @@ async def websocket_handler(websocket, path):
187
  st.session_state.active_connections[room_id][client_id] = websocket
188
  username = st.session_state.username
189
 
 
190
  if "prairie" in path:
191
  st.session_state.prairie_players[client_id] = {
192
  "username": username,
@@ -196,14 +231,16 @@ async def websocket_handler(websocket, path):
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:
@@ -211,10 +248,30 @@ async def websocket_handler(websocket, path):
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)
217
- st.session_state.players[client_id]["z"] = float(z)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  elif content.startswith("PRAIRIE:"):
219
  action, value = content.split(":", 1)
220
  if action == "ANIMAL":
@@ -224,10 +281,6 @@ async def websocket_handler(websocket, path):
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)
@@ -244,9 +297,10 @@ async def websocket_handler(websocket, path):
244
  async def periodic_update():
245
  while True:
246
  if st.session_state.active_connections.get("quest"):
 
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!"
@@ -254,9 +308,9 @@ async def periodic_update():
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
 
@@ -351,6 +405,7 @@ rocky_map_html = f"""
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
 
@@ -363,22 +418,31 @@ rocky_map_html = f"""
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
@@ -413,13 +477,14 @@ rocky_map_html = f"""
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
  }}
@@ -428,13 +493,6 @@ rocky_map_html = f"""
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]) {{
@@ -446,6 +504,13 @@ rocky_map_html = f"""
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}}`;
@@ -462,6 +527,10 @@ rocky_map_html = f"""
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');
@@ -478,12 +547,10 @@ rocky_map_html = f"""
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>
@@ -500,6 +567,9 @@ def main():
500
  st.sidebar.write(f"🎡 Treasures: {st.session_state.treasures}")
501
  st.sidebar.write(f"πŸ‘₯ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
502
 
 
 
 
503
  new_username = st.sidebar.selectbox("πŸ§™β€β™‚οΈ Choose Your Hero", list(CHARACTERS.keys()), index=list(CHARACTERS.keys()).index(st.session_state.username))
504
  if new_username != st.session_state.username:
505
  asyncio.run(save_chat_entry(st.session_state.username, f"πŸ”„ Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
@@ -561,6 +631,7 @@ def main():
561
  st.sidebar.markdown(f"⏳ Next Update in: {int(remaining)}s")
562
  if remaining <= 0:
563
  st.session_state.last_update = time.time()
 
564
  st.rerun()
565
 
566
  if not st.session_state.get('server_running', False):
 
51
  "Wyoming Spring Creek": (41.6666, -106.6666)
52
  }
53
 
54
+ # Directories and Files
55
  for d in ["chat_logs", "audio_logs"]:
56
  os.makedirs(d, exist_ok=True)
57
 
 
59
  AUDIO_DIR = "audio_logs"
60
  STATE_FILE = "user_state.txt"
61
  CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
62
+ GAME_STATE_FILE = "game_state.json"
63
+
64
+ # Cached Game State as "ML Model"
65
+ @st.cache_data
66
+ def load_game_state(_timestamp):
67
+ """Load or initialize the game state, treated as a cached 'model'."""
68
+ if os.path.exists(GAME_STATE_FILE):
69
+ with open(GAME_STATE_FILE, 'r') as f:
70
+ state = json.load(f)
71
+ else:
72
+ state = {"players": {}, "treasures": []}
73
+ with open(GAME_STATE_FILE, 'w') as f:
74
+ json.dump(state, f)
75
+ return state
76
+
77
+ def update_game_state(state):
78
+ """Update the game state and persist to file."""
79
+ with open(GAME_STATE_FILE, 'w') as f:
80
+ json.dump(state, f)
81
+ # Clear cache to force reload with new timestamp
82
+ load_game_state.clear()
83
+ return load_game_state(time.time())
84
 
85
  # Helpers
86
  def format_timestamp(username=""):
 
184
  'speech_processed': False, 'players': {}, 'last_update': time.time(),
185
  'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
186
  'move_right': False, 'move_up': False, 'move_down': False,
187
+ 'prairie_players': {}, 'last_chat_update': 0,
188
+ 'game_state_timestamp': time.time()
189
  }
190
  for k, v in defaults.items():
191
  if k not in st.session_state:
 
198
  st.session_state.username = random.choice(list(CHARACTERS.keys()))
199
  asyncio.run(save_chat_entry(st.session_state.username, "πŸ—ΊοΈ Begins the Rocky Mountain Quest!", CHARACTERS[st.session_state.username]["voice"]))
200
  save_username(st.session_state.username)
201
+ # Initialize player in cached game state if new
202
+ game_state = load_game_state(st.session_state.game_state_timestamp)
203
+ if st.session_state.username not in game_state["players"]:
204
+ game_state["players"][st.session_state.username] = {
205
+ "x": random.uniform(-20, 20),
206
+ "z": random.uniform(-40, 40),
207
+ "color": CHARACTERS[st.session_state.username]["color"],
208
+ "score": 0,
209
+ "treasures": 0
210
+ }
211
+ update_game_state(game_state)
212
 
213
  init_session_state()
214
 
 
221
  st.session_state.active_connections[room_id][client_id] = websocket
222
  username = st.session_state.username
223
 
224
+ game_state = load_game_state(st.session_state.game_state_timestamp)
225
  if "prairie" in path:
226
  st.session_state.prairie_players[client_id] = {
227
  "username": username,
 
231
  }
232
  await broadcast_message(f"System|{username} joins the prairie!", room_id)
233
  else:
234
+ if username not in game_state["players"]:
235
+ game_state["players"][username] = {
236
+ "x": random.uniform(-20, 20),
237
+ "z": random.uniform(-40, 40),
238
+ "color": CHARACTERS[username]["color"],
239
+ "score": 0,
240
+ "treasures": 0
241
+ }
242
+ update_game_state(game_state)
243
+ st.session_state.players[client_id] = game_state["players"][username]
244
  await broadcast_message(f"System|{username} joins the quest!", room_id)
245
 
246
  try:
 
248
  if '|' in message:
249
  sender, content = message.split('|', 1)
250
  voice = CHARACTERS.get(sender, {"voice": "en-US-AriaNeural"})["voice"]
251
+ game_state = load_game_state(st.session_state.game_state_timestamp)
252
  if content.startswith("MOVE:"):
253
  _, x, z = content.split(":")
254
+ x, z = float(x), float(z)
255
+ st.session_state.players[client_id]["x"] = x
256
+ st.session_state.players[client_id]["z"] = z
257
+ game_state["players"][sender]["x"] = x
258
+ game_state["players"][sender]["z"] = z
259
+ update_game_state(game_state)
260
+ elif content.startswith("SCORE:"):
261
+ score = int(content.split(":")[1])
262
+ st.session_state.players[client_id]["score"] = score
263
+ game_state["players"][sender]["score"] = score
264
+ update_game_state(game_state)
265
+ elif content.startswith("TREASURE:"):
266
+ treasures = int(content.split(":")[1])
267
+ st.session_state.players[client_id]["treasures"] = treasures
268
+ game_state["players"][sender]["treasures"] = treasures
269
+ for i, t in enumerate(game_state["treasures"]):
270
+ if st.session_state.players[client_id]["x"] - 2 < t["x"] < st.session_state.players[client_id]["x"] + 2 and \
271
+ st.session_state.players[client_id]["z"] - 2 < t["z"] < st.session_state.players[client_id]["z"] + 2:
272
+ game_state["treasures"].pop(i)
273
+ break
274
+ update_game_state(game_state)
275
  elif content.startswith("PRAIRIE:"):
276
  action, value = content.split(":", 1)
277
  if action == "ANIMAL":
 
281
  st.session_state.prairie_players[client_id]["location"] = target
282
  action_msg = f"{sender} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
283
  await save_chat_entry(sender, action_msg, voice)
 
 
 
 
284
  else:
285
  await save_chat_entry(sender, content, voice)
286
  await perform_arxiv_search(content, sender)
 
297
  async def periodic_update():
298
  while True:
299
  if st.session_state.active_connections.get("quest"):
300
+ game_state = load_game_state(st.session_state.game_state_timestamp)
301
  player_list = ", ".join([p["username"] for p in st.session_state.players.values()]) or "No adventurers yet!"
302
  message = f"πŸ“’ Quest Update: Active Adventurers - {player_list}"
303
+ player_data = json.dumps(list(game_state["players"].values()))
304
  await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
305
 
306
  prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
 
308
  prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
309
  await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
310
 
 
311
  chat_content = await load_chat()
312
  await broadcast_message(f"CHAT_UPDATE:{json.dumps(chat_content[-10:])}", "quest")
313
+ await broadcast_message(f"GAME_STATE:{json.dumps(game_state)}", "quest")
314
  await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
315
  await asyncio.sleep(st.session_state.update_interval)
316
 
 
405
  let players = {{}};
406
  const playerMeshes = {{}};
407
  let treasures = [];
408
+ const treasureMeshes = {{}};
409
  let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
410
  let score = 0, treasureCount = 0;
411
 
 
418
  scene.add(playerMesh);
419
  players[playerName] = {{ mesh: playerMesh, score: 0, treasures: 0 }};
420
 
421
+ // Treasure updating
422
+ function updateTreasures(treasureData) {{
423
+ treasureData.forEach(t => {{
424
+ if (!treasureMeshes[t.id]) {{
425
+ const treasure = new THREE.Mesh(
426
+ new THREE.SphereGeometry(1, 8, 8),
427
+ new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
428
+ );
429
+ treasure.position.set(t.x, 1, t.z);
430
+ treasure.castShadow = true;
431
+ treasureMeshes[t.id] = treasure;
432
+ treasures.push(treasure);
433
+ scene.add(treasure);
434
+ }} else {{
435
+ treasureMeshes[t.id].position.set(t.x, 1, t.z);
436
+ }}
437
+ }});
438
+ // Remove treasures not in the data
439
+ Object.keys(treasureMeshes).forEach(id => {{
440
+ if (!treasureData.some(t => t.id === id)) {{
441
+ scene.remove(treasureMeshes[id]);
442
+ treasures = treasures.filter(t => t !== treasureMeshes[id]);
443
+ delete treasureMeshes[id];
444
+ }}
445
+ }});
446
  }}
447
 
448
  // Controls
 
477
  if (collect) {{
478
  for (let i = treasures.length - 1; i >= 0; i--) {{
479
  if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
480
+ const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
481
  scene.remove(treasures[i]);
482
  treasures.splice(i, 1);
483
+ delete treasureMeshes[id];
484
  score += 50;
485
  treasureCount += 1;
486
  ws.send(`${{playerName}}|SCORE:${{score}}`);
487
  ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
 
488
  }}
489
  }}
490
  }}
 
493
  camera.lookAt(xPos, 0, zPos);
494
  }}
495
 
 
 
 
 
 
 
 
496
  function updatePlayers(playerData) {{
497
  playerData.forEach(player => {{
498
  if (!playerMeshes[player.username]) {{
 
504
  const mesh = playerMeshes[player.username];
505
  mesh.position.set(player.x, 1, player.z);
506
  players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
507
+ if (player.username === playerName) {{
508
+ xPos = player.x;
509
+ zPos = player.z;
510
+ score = player.score;
511
+ treasureCount = player.treasures;
512
+ playerMesh.position.set(xPos, 1, zPos);
513
+ }}
514
  }});
515
  document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
516
  document.getElementById('score').textContent = `Score: ${{score}}`;
 
527
  const chatBox = document.getElementById('chatBox');
528
  chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
529
  chatBox.scrollTop = chatBox.scrollHeight;
530
+ }} else if (data.startsWith('GAME_STATE:')) {{
531
+ const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
532
+ updatePlayers(gameState.players);
533
+ updateTreasures(gameState.treasures);
534
  }} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
535
  const [sender, message] = data.split('|');
536
  const chatBox = document.getElementById('chatBox');
 
547
  lastTime = currentTime;
548
 
549
  updatePlayer(delta);
550
+ treasures.forEach(t => t.rotation.y += delta);
551
  renderer.render(scene, camera);
552
  }}
553
 
 
 
554
  animate();
555
  </script>
556
  </body>
 
567
  st.sidebar.write(f"🎡 Treasures: {st.session_state.treasures}")
568
  st.sidebar.write(f"πŸ‘₯ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
569
 
570
+ # User-controlled refresh interval
571
+ st.session_state.update_interval = st.sidebar.slider("Refresh Interval (seconds)", 1, 10, 1, step=1)
572
+
573
  new_username = st.sidebar.selectbox("πŸ§™β€β™‚οΈ Choose Your Hero", list(CHARACTERS.keys()), index=list(CHARACTERS.keys()).index(st.session_state.username))
574
  if new_username != st.session_state.username:
575
  asyncio.run(save_chat_entry(st.session_state.username, f"πŸ”„ Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
 
631
  st.sidebar.markdown(f"⏳ Next Update in: {int(remaining)}s")
632
  if remaining <= 0:
633
  st.session_state.last_update = time.time()
634
+ st.session_state.game_state_timestamp = time.time() # Update timestamp to refresh cache
635
  st.rerun()
636
 
637
  if not st.session_state.get('server_running', False):