Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -32,16 +32,16 @@ st.set_page_config(
|
|
32 |
# Game Config
|
33 |
GAME_NAME = "Rocky Mountain Quest 3D ๐๏ธ๐ฎ"
|
34 |
START_LOCATION = "Trailhead Camp โบ"
|
35 |
-
CHARACTERS =
|
36 |
-
"Trailblazer Tim ๐"
|
37 |
-
"Meme Queen Mia ๐"
|
38 |
-
"Elk Whisperer Eve ๐ฆ"
|
39 |
-
"Tech Titan Tara ๐พ"
|
40 |
-
"Ski Guru Sam โท๏ธ"
|
41 |
-
"Cosmic Camper Cal ๐ "
|
42 |
-
"Rasta Ranger Rick ๐"
|
43 |
-
"Boulder Bro Ben ๐ชจ"
|
44 |
-
|
45 |
FILE_EMOJIS = {"md": "๐", "mp3": "๐ต"}
|
46 |
|
47 |
# Prairie Simulator Locations
|
@@ -61,15 +61,25 @@ 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.
|
66 |
def load_game_state(_timestamp):
|
67 |
-
"""Load or initialize the game state, treated as a cached
|
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 = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
with open(GAME_STATE_FILE, 'w') as f:
|
74 |
json.dump(state, f)
|
75 |
return state
|
@@ -78,10 +88,24 @@ 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
|
82 |
-
load_game_state.clear()
|
83 |
return load_game_state(time.time())
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
# Helpers
|
86 |
def format_timestamp(username=""):
|
87 |
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
@@ -162,9 +186,13 @@ async def save_chat_entry(username, message, voice, is_markdown=False):
|
|
162 |
await broadcast_message(f"{username}|{message}", "quest")
|
163 |
st.session_state.chat_history.append(entry)
|
164 |
st.session_state.last_transcript = message
|
165 |
-
st.session_state.
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
168 |
return md_file, audio_file
|
169 |
|
170 |
async def load_chat():
|
@@ -185,28 +213,26 @@ def init_session_state():
|
|
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:
|
192 |
st.session_state[k] = v
|
193 |
if st.session_state.username is None:
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
else:
|
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.
|
208 |
"score": 0,
|
209 |
-
"treasures": 0
|
|
|
210 |
}
|
211 |
update_game_state(game_state)
|
212 |
|
@@ -227,7 +253,7 @@ async def websocket_handler(websocket, path):
|
|
227 |
"username": username,
|
228 |
"animal": "prairie_dog",
|
229 |
"location": PRAIRIE_LOCATIONS["Deadwood, SD"],
|
230 |
-
"color": CHARACTERS[
|
231 |
}
|
232 |
await broadcast_message(f"System|{username} joins the prairie!", room_id)
|
233 |
else:
|
@@ -235,9 +261,10 @@ async def websocket_handler(websocket, path):
|
|
235 |
game_state["players"][username] = {
|
236 |
"x": random.uniform(-20, 20),
|
237 |
"z": random.uniform(-40, 40),
|
238 |
-
"color": CHARACTERS[
|
239 |
"score": 0,
|
240 |
-
"treasures": 0
|
|
|
241 |
}
|
242 |
update_game_state(game_state)
|
243 |
st.session_state.players[client_id] = game_state["players"][username]
|
@@ -247,7 +274,7 @@ async def websocket_handler(websocket, path):
|
|
247 |
async for message in websocket:
|
248 |
if '|' in message:
|
249 |
sender, content = message.split('|', 1)
|
250 |
-
voice =
|
251 |
game_state = load_game_state(st.session_state.game_state_timestamp)
|
252 |
if content.startswith("MOVE:"):
|
253 |
_, x, z = content.split(":")
|
@@ -256,19 +283,22 @@ async def websocket_handler(websocket, path):
|
|
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"] -
|
271 |
-
st.session_state.players[client_id]["z"] -
|
272 |
game_state["treasures"].pop(i)
|
273 |
break
|
274 |
update_game_state(game_state)
|
@@ -298,7 +328,15 @@ 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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")
|
@@ -333,7 +371,7 @@ async def perform_arxiv_search(query, username):
|
|
333 |
query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
|
334 |
)[0]
|
335 |
result = f"๐ Ancient Rocky Knowledge:\n{refs}"
|
336 |
-
voice =
|
337 |
md_file, audio_file = await save_chat_entry(username, result, voice, True)
|
338 |
return md_file, audio_file
|
339 |
|
@@ -370,6 +408,7 @@ rocky_map_html = f"""
|
|
370 |
<div id="players">Players: 1</div>
|
371 |
<div id="score">Score: 0</div>
|
372 |
<div id="treasures">Treasures: 0</div>
|
|
|
373 |
</div>
|
374 |
<div id="chatBox"></div>
|
375 |
<div id="controls">WASD/Arrows to move, Space to collect treasure</div>
|
@@ -407,11 +446,11 @@ rocky_map_html = f"""
|
|
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 |
|
412 |
// Player initialization
|
413 |
const playerGeometry = new THREE.BoxGeometry(2, 2, 2);
|
414 |
-
const playerMaterial = new THREE.MeshPhongMaterial({{ color: {CHARACTERS[st.session_state.username
|
415 |
const playerMesh = new THREE.Mesh(playerGeometry, playerMaterial);
|
416 |
playerMesh.position.set(xPos, 1, zPos);
|
417 |
playerMesh.castShadow = true;
|
@@ -435,7 +474,6 @@ rocky_map_html = f"""
|
|
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]);
|
@@ -454,6 +492,7 @@ rocky_map_html = f"""
|
|
454 |
case 'ArrowDown': case 'KeyS': moveDown = true; break;
|
455 |
case 'Space': collect = true; break;
|
456 |
}}
|
|
|
457 |
}});
|
458 |
document.addEventListener('keyup', (event) => {{
|
459 |
switch (event.code) {{
|
@@ -491,6 +530,8 @@ rocky_map_html = f"""
|
|
491 |
|
492 |
camera.position.set(xPos, 50, zPos + 50);
|
493 |
camera.lookAt(xPos, 0, zPos);
|
|
|
|
|
494 |
}}
|
495 |
|
496 |
function updatePlayers(playerData) {{
|
@@ -561,20 +602,16 @@ rocky_map_html = f"""
|
|
561 |
def main():
|
562 |
st.sidebar.title(f"๐ฎ {GAME_NAME}")
|
563 |
st.sidebar.subheader(f"๐ {st.session_state.username}โs Adventure - Score: {st.session_state.score} ๐")
|
564 |
-
st.sidebar.write(f"๐ {CHARACTERS[st.session_state.username
|
565 |
st.sidebar.write(f"๐ Location: {st.session_state.location}")
|
566 |
st.sidebar.write(f"๐
Score: {st.session_state.score}")
|
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 |
-
|
574 |
-
|
575 |
-
asyncio.run(save_chat_entry(st.session_state.username, f"๐ Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
|
576 |
-
st.session_state.username = new_username
|
577 |
-
save_username(st.session_state.username)
|
578 |
st.rerun()
|
579 |
|
580 |
left_col, right_col = st.columns([2, 1])
|
@@ -586,21 +623,23 @@ def main():
|
|
586 |
message = st.text_input(f"๐จ๏ธ {st.session_state.username} says:", placeholder="Speak or type to chat! ๐ฒ")
|
587 |
if st.button("๐ Send & Chat ๐ค"):
|
588 |
if message:
|
589 |
-
voice = CHARACTERS[st.session_state.username
|
590 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, message, voice))
|
591 |
if audio_file:
|
592 |
play_and_download_audio(audio_file)
|
|
|
593 |
st.success(f"๐ +10 points! New Score: {st.session_state.score}")
|
594 |
mycomponent = components.declare_component("speech_component", path="./speech_component")
|
595 |
val = mycomponent(my_input_value="", key=f"speech_{st.session_state.get('speech_processed', False)}")
|
596 |
if val and val != st.session_state.last_transcript:
|
597 |
val_stripped = val.strip().replace('\n', ' ')
|
598 |
if val_stripped:
|
599 |
-
voice =
|
600 |
st.session_state['speech_processed'] = True
|
601 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, val_stripped, voice))
|
602 |
if audio_file:
|
603 |
play_and_download_audio(audio_file)
|
|
|
604 |
st.rerun()
|
605 |
|
606 |
with right_col:
|
@@ -608,6 +647,16 @@ def main():
|
|
608 |
prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
|
609 |
for loc, (lat, lon) in PRAIRIE_LOCATIONS.items():
|
610 |
folium.Marker([lat, lon], popup=loc).add_to(prairie_map)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
for client_id, player in st.session_state.prairie_players.items():
|
612 |
folium.CircleMarker(
|
613 |
location=player['location'],
|
@@ -624,14 +673,25 @@ def main():
|
|
624 |
if st.button("Move"):
|
625 |
asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:ANIMAL:{animal}", "quest"))
|
626 |
asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:MOVE:{location}", "quest"))
|
|
|
627 |
st.rerun()
|
628 |
|
629 |
elapsed = time.time() - st.session_state.last_update
|
630 |
remaining = max(0, st.session_state.update_interval - elapsed)
|
631 |
st.sidebar.markdown(f"โณ Next Update in: {int(remaining)}s")
|
632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
633 |
st.session_state.last_update = time.time()
|
634 |
-
st.session_state.game_state_timestamp = time.time()
|
635 |
st.rerun()
|
636 |
|
637 |
if not st.session_state.get('server_running', False):
|
|
|
32 |
# Game Config
|
33 |
GAME_NAME = "Rocky Mountain Quest 3D ๐๏ธ๐ฎ"
|
34 |
START_LOCATION = "Trailhead Camp โบ"
|
35 |
+
CHARACTERS = [
|
36 |
+
{"name": "Trailblazer Tim ๐", "voice": "en-US-GuyNeural", "desc": "Fearless hiker seeking epic trails!", "color": 0x00ff00},
|
37 |
+
{"name": "Meme Queen Mia ๐", "voice": "en-US-JennyNeural", "desc": "Spreads laughs with wild memes!", "color": 0xff00ff},
|
38 |
+
{"name": "Elk Whisperer Eve ๐ฆ", "voice": "en-GB-SoniaNeural", "desc": "Talks to wildlife, loves nature!", "color": 0x0000ff},
|
39 |
+
{"name": "Tech Titan Tara ๐พ", "voice": "en-AU-NatashaNeural", "desc": "Codes her way through the Rockies!", "color": 0xffff00},
|
40 |
+
{"name": "Ski Guru Sam โท๏ธ", "voice": "en-CA-ClaraNeural", "desc": "Shreds slopes, lives for snow!", "color": 0xffa500},
|
41 |
+
{"name": "Cosmic Camper Cal ๐ ", "voice": "en-US-AriaNeural", "desc": "Stargazes and tells epic tales!", "color": 0x800080},
|
42 |
+
{"name": "Rasta Ranger Rick ๐", "voice": "en-GB-RyanNeural", "desc": "Chills with natureโs vibes!", "color": 0x00ffff},
|
43 |
+
{"name": "Boulder Bro Ben ๐ชจ", "voice": "en-AU-WilliamNeural", "desc": "Climbs rocks, bro-style!", "color": 0xff4500}
|
44 |
+
]
|
45 |
FILE_EMOJIS = {"md": "๐", "mp3": "๐ต"}
|
46 |
|
47 |
# Prairie Simulator Locations
|
|
|
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" using st.cache_resource
|
65 |
+
@st.cache_resource
|
66 |
def load_game_state(_timestamp):
|
67 |
+
"""Load or initialize the game state, treated as a cached resource."""
|
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 = {
|
73 |
+
"players": {},
|
74 |
+
"treasures": [
|
75 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
76 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
77 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
78 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
79 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)}
|
80 |
+
],
|
81 |
+
"history": []
|
82 |
+
}
|
83 |
with open(GAME_STATE_FILE, 'w') as f:
|
84 |
json.dump(state, f)
|
85 |
return state
|
|
|
88 |
"""Update the game state and persist to file."""
|
89 |
with open(GAME_STATE_FILE, 'w') as f:
|
90 |
json.dump(state, f)
|
91 |
+
load_game_state.clear() # Clear cache to force reload
|
|
|
92 |
return load_game_state(time.time())
|
93 |
|
94 |
+
def reset_game_state():
|
95 |
+
"""Reset the game state to initial conditions."""
|
96 |
+
state = {
|
97 |
+
"players": {},
|
98 |
+
"treasures": [
|
99 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
100 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
101 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
102 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)},
|
103 |
+
{"id": str(uuid.uuid4()), "x": random.uniform(-40, 40), "z": random.uniform(-40, 40)}
|
104 |
+
],
|
105 |
+
"history": []
|
106 |
+
}
|
107 |
+
return update_game_state(state)
|
108 |
+
|
109 |
# Helpers
|
110 |
def format_timestamp(username=""):
|
111 |
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
186 |
await broadcast_message(f"{username}|{message}", "quest")
|
187 |
st.session_state.chat_history.append(entry)
|
188 |
st.session_state.last_transcript = message
|
189 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
190 |
+
game_state["history"].append(entry)
|
191 |
+
if username in game_state["players"]:
|
192 |
+
game_state["players"][username]["score"] += 10
|
193 |
+
game_state["players"][username]["treasures"] += 1
|
194 |
+
game_state["players"][username]["last_active"] = time.time()
|
195 |
+
update_game_state(game_state)
|
196 |
return md_file, audio_file
|
197 |
|
198 |
async def load_chat():
|
|
|
213 |
'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
|
214 |
'move_right': False, 'move_up': False, 'move_down': False,
|
215 |
'prairie_players': {}, 'last_chat_update': 0,
|
216 |
+
'game_state_timestamp': time.time(), 'name_index': 0,
|
217 |
+
'timeout': 60, 'auto_refresh': 30, 'last_activity': time.time()
|
218 |
}
|
219 |
for k, v in defaults.items():
|
220 |
if k not in st.session_state:
|
221 |
st.session_state[k] = v
|
222 |
if st.session_state.username is None:
|
223 |
+
st.session_state.name_index = (st.session_state.name_index + 1) % len(CHARACTERS)
|
224 |
+
st.session_state.username = CHARACTERS[st.session_state.name_index]["name"]
|
225 |
+
asyncio.run(save_chat_entry(st.session_state.username, "๐บ๏ธ Joins the Rocky Mountain Quest!", CHARACTERS[st.session_state.name_index]["voice"]))
|
|
|
|
|
|
|
226 |
save_username(st.session_state.username)
|
|
|
227 |
game_state = load_game_state(st.session_state.game_state_timestamp)
|
228 |
if st.session_state.username not in game_state["players"]:
|
229 |
game_state["players"][st.session_state.username] = {
|
230 |
"x": random.uniform(-20, 20),
|
231 |
"z": random.uniform(-40, 40),
|
232 |
+
"color": CHARACTERS[st.session_state.name_index]["color"],
|
233 |
"score": 0,
|
234 |
+
"treasures": 0,
|
235 |
+
"last_active": time.time()
|
236 |
}
|
237 |
update_game_state(game_state)
|
238 |
|
|
|
253 |
"username": username,
|
254 |
"animal": "prairie_dog",
|
255 |
"location": PRAIRIE_LOCATIONS["Deadwood, SD"],
|
256 |
+
"color": CHARACTERS[st.session_state.name_index]["color"]
|
257 |
}
|
258 |
await broadcast_message(f"System|{username} joins the prairie!", room_id)
|
259 |
else:
|
|
|
261 |
game_state["players"][username] = {
|
262 |
"x": random.uniform(-20, 20),
|
263 |
"z": random.uniform(-40, 40),
|
264 |
+
"color": CHARACTERS[st.session_state.name_index]["color"],
|
265 |
"score": 0,
|
266 |
+
"treasures": 0,
|
267 |
+
"last_active": time.time()
|
268 |
}
|
269 |
update_game_state(game_state)
|
270 |
st.session_state.players[client_id] = game_state["players"][username]
|
|
|
274 |
async for message in websocket:
|
275 |
if '|' in message:
|
276 |
sender, content = message.split('|', 1)
|
277 |
+
voice = next(c["voice"] for c in CHARACTERS if c["name"] == sender)
|
278 |
game_state = load_game_state(st.session_state.game_state_timestamp)
|
279 |
if content.startswith("MOVE:"):
|
280 |
_, x, z = content.split(":")
|
|
|
283 |
st.session_state.players[client_id]["z"] = z
|
284 |
game_state["players"][sender]["x"] = x
|
285 |
game_state["players"][sender]["z"] = z
|
286 |
+
game_state["players"][sender]["last_active"] = time.time()
|
287 |
update_game_state(game_state)
|
288 |
elif content.startswith("SCORE:"):
|
289 |
score = int(content.split(":")[1])
|
290 |
st.session_state.players[client_id]["score"] = score
|
291 |
game_state["players"][sender]["score"] = score
|
292 |
+
game_state["players"][sender]["last_active"] = time.time()
|
293 |
update_game_state(game_state)
|
294 |
elif content.startswith("TREASURE:"):
|
295 |
treasures = int(content.split(":")[1])
|
296 |
st.session_state.players[client_id]["treasures"] = treasures
|
297 |
game_state["players"][sender]["treasures"] = treasures
|
298 |
+
game_state["players"][sender]["last_active"] = time.time()
|
299 |
for i, t in enumerate(game_state["treasures"]):
|
300 |
+
if abs(st.session_state.players[client_id]["x"] - t["x"]) < 2 and \
|
301 |
+
abs(st.session_state.players[client_id]["z"] - t["z"]) < 2:
|
302 |
game_state["treasures"].pop(i)
|
303 |
break
|
304 |
update_game_state(game_state)
|
|
|
328 |
while True:
|
329 |
if st.session_state.active_connections.get("quest"):
|
330 |
game_state = load_game_state(st.session_state.game_state_timestamp)
|
331 |
+
# Remove inactive players (timeout after 60 seconds)
|
332 |
+
current_time = time.time()
|
333 |
+
inactive_players = [p for p, data in game_state["players"].items() if current_time - data["last_active"] > st.session_state.timeout]
|
334 |
+
for player in inactive_players:
|
335 |
+
del game_state["players"][player]
|
336 |
+
await broadcast_message(f"System|{player} timed out after 60 seconds!", "quest")
|
337 |
+
update_game_state(game_state)
|
338 |
+
|
339 |
+
player_list = ", ".join([p for p in game_state["players"].keys()]) or "No adventurers yet!"
|
340 |
message = f"๐ข Quest Update: Active Adventurers - {player_list}"
|
341 |
player_data = json.dumps(list(game_state["players"].values()))
|
342 |
await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
|
|
|
371 |
query, 5, "Semantic Search", "mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md"
|
372 |
)[0]
|
373 |
result = f"๐ Ancient Rocky Knowledge:\n{refs}"
|
374 |
+
voice = next(c["voice"] for c in CHARACTERS if c["name"] == username)
|
375 |
md_file, audio_file = await save_chat_entry(username, result, voice, True)
|
376 |
return md_file, audio_file
|
377 |
|
|
|
408 |
<div id="players">Players: 1</div>
|
409 |
<div id="score">Score: 0</div>
|
410 |
<div id="treasures">Treasures: 0</div>
|
411 |
+
<div id="timeout">Timeout: 60s</div>
|
412 |
</div>
|
413 |
<div id="chatBox"></div>
|
414 |
<div id="controls">WASD/Arrows to move, Space to collect treasure</div>
|
|
|
446 |
let treasures = [];
|
447 |
const treasureMeshes = {{}};
|
448 |
let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
449 |
+
let score = 0, treasureCount = 0, lastActive = performance.now() / 1000;
|
450 |
|
451 |
// Player initialization
|
452 |
const playerGeometry = new THREE.BoxGeometry(2, 2, 2);
|
453 |
+
const playerMaterial = new THREE.MeshPhongMaterial({{ color: {next(c["color"] for c in CHARACTERS if c["name"] == st.session_state.username)} }});
|
454 |
const playerMesh = new THREE.Mesh(playerGeometry, playerMaterial);
|
455 |
playerMesh.position.set(xPos, 1, zPos);
|
456 |
playerMesh.castShadow = true;
|
|
|
474 |
treasureMeshes[t.id].position.set(t.x, 1, t.z);
|
475 |
}}
|
476 |
}});
|
|
|
477 |
Object.keys(treasureMeshes).forEach(id => {{
|
478 |
if (!treasureData.some(t => t.id === id)) {{
|
479 |
scene.remove(treasureMeshes[id]);
|
|
|
492 |
case 'ArrowDown': case 'KeyS': moveDown = true; break;
|
493 |
case 'Space': collect = true; break;
|
494 |
}}
|
495 |
+
lastActive = performance.now() / 1000;
|
496 |
}});
|
497 |
document.addEventListener('keyup', (event) => {{
|
498 |
switch (event.code) {{
|
|
|
530 |
|
531 |
camera.position.set(xPos, 50, zPos + 50);
|
532 |
camera.lookAt(xPos, 0, zPos);
|
533 |
+
const timeout = 60 - (performance.now() / 1000 - lastActive);
|
534 |
+
document.getElementById('timeout').textContent = `Timeout: ${{Math.max(0, timeout.toFixed(0))}}s`;
|
535 |
}}
|
536 |
|
537 |
function updatePlayers(playerData) {{
|
|
|
602 |
def main():
|
603 |
st.sidebar.title(f"๐ฎ {GAME_NAME}")
|
604 |
st.sidebar.subheader(f"๐ {st.session_state.username}โs Adventure - Score: {st.session_state.score} ๐")
|
605 |
+
st.sidebar.write(f"๐ {next(c['desc'] for c in CHARACTERS if c['name'] == st.session_state.username)}")
|
606 |
st.sidebar.write(f"๐ Location: {st.session_state.location}")
|
607 |
st.sidebar.write(f"๐
Score: {st.session_state.score}")
|
608 |
st.sidebar.write(f"๐ต Treasures: {st.session_state.treasures}")
|
609 |
st.sidebar.write(f"๐ฅ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
|
610 |
|
|
|
611 |
st.session_state.update_interval = st.sidebar.slider("Refresh Interval (seconds)", 1, 10, 1, step=1)
|
612 |
+
if st.sidebar.button("Reset World"):
|
613 |
+
reset_game_state()
|
614 |
+
st.session_state.game_state_timestamp = time.time()
|
|
|
|
|
|
|
615 |
st.rerun()
|
616 |
|
617 |
left_col, right_col = st.columns([2, 1])
|
|
|
623 |
message = st.text_input(f"๐จ๏ธ {st.session_state.username} says:", placeholder="Speak or type to chat! ๐ฒ")
|
624 |
if st.button("๐ Send & Chat ๐ค"):
|
625 |
if message:
|
626 |
+
voice = next(c["voice"] for c in CHARACTERS if c["name"] == st.session_state.username)
|
627 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, message, voice))
|
628 |
if audio_file:
|
629 |
play_and_download_audio(audio_file)
|
630 |
+
st.session_state.last_activity = time.time()
|
631 |
st.success(f"๐ +10 points! New Score: {st.session_state.score}")
|
632 |
mycomponent = components.declare_component("speech_component", path="./speech_component")
|
633 |
val = mycomponent(my_input_value="", key=f"speech_{st.session_state.get('speech_processed', False)}")
|
634 |
if val and val != st.session_state.last_transcript:
|
635 |
val_stripped = val.strip().replace('\n', ' ')
|
636 |
if val_stripped:
|
637 |
+
voice = next(c["voice"] for c in CHARACTERS if c["name"] == st.session_state.username)
|
638 |
st.session_state['speech_processed'] = True
|
639 |
md_file, audio_file = asyncio.run(save_chat_entry(st.session_state.username, val_stripped, voice))
|
640 |
if audio_file:
|
641 |
play_and_download_audio(audio_file)
|
642 |
+
st.session_state.last_activity = time.time()
|
643 |
st.rerun()
|
644 |
|
645 |
with right_col:
|
|
|
647 |
prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
|
648 |
for loc, (lat, lon) in PRAIRIE_LOCATIONS.items():
|
649 |
folium.Marker([lat, lon], popup=loc).add_to(prairie_map)
|
650 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
651 |
+
for player in game_state["players"].values():
|
652 |
+
folium.CircleMarker(
|
653 |
+
location=[44.0 + player["x"] * 0.01, -103.0 + player["z"] * 0.01], # Approximate mapping to lat/lon
|
654 |
+
radius=5,
|
655 |
+
color=f"#{player['color']:06x}",
|
656 |
+
fill=True,
|
657 |
+
fill_opacity=0.7,
|
658 |
+
popup=f"{player['username']} (Score: {player['score']}, Treasures: {player['treasures']})"
|
659 |
+
).add_to(prairie_map)
|
660 |
for client_id, player in st.session_state.prairie_players.items():
|
661 |
folium.CircleMarker(
|
662 |
location=player['location'],
|
|
|
673 |
if st.button("Move"):
|
674 |
asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:ANIMAL:{animal}", "quest"))
|
675 |
asyncio.run(broadcast_message(f"{st.session_state.username}|PRAIRIE:MOVE:{location}", "quest"))
|
676 |
+
st.session_state.last_activity = time.time()
|
677 |
st.rerun()
|
678 |
|
679 |
elapsed = time.time() - st.session_state.last_update
|
680 |
remaining = max(0, st.session_state.update_interval - elapsed)
|
681 |
st.sidebar.markdown(f"โณ Next Update in: {int(remaining)}s")
|
682 |
+
|
683 |
+
# Timeout and Auto-Refresh Logic
|
684 |
+
if time.time() - st.session_state.last_activity > st.session_state.timeout:
|
685 |
+
st.sidebar.warning("Timed out! Refreshing in 5 seconds...")
|
686 |
+
time.sleep(5)
|
687 |
+
st.session_state.game_state_timestamp = time.time()
|
688 |
+
st.rerun()
|
689 |
+
elif time.time() - st.session_state.last_update > st.session_state.auto_refresh:
|
690 |
+
st.session_state.game_state_timestamp = time.time()
|
691 |
+
st.rerun()
|
692 |
+
elif remaining <= 0:
|
693 |
st.session_state.last_update = time.time()
|
694 |
+
st.session_state.game_state_timestamp = time.time()
|
695 |
st.rerun()
|
696 |
|
697 |
if not st.session_state.get('server_running', False):
|