Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -484,8 +484,9 @@ def log_performance_metrics():
|
|
484 |
st.sidebar.write(f"**{operation}:** {duration:.2f}s ({percentage:.1f}%)")
|
485 |
|
486 |
# -----------------------------------------------------------
|
487 |
-
# Enhanced 3D Game HTML (With dynamic insertion of username and
|
488 |
# -----------------------------------------------------------
|
|
|
489 |
rocky_map_html = f"""
|
490 |
<!DOCTYPE html>
|
491 |
<html lang="en">
|
@@ -529,7 +530,7 @@ rocky_map_html = f"""
|
|
529 |
<div class="leaderboard" id="leaderboard">Leaderboard</div>
|
530 |
<div id="chatBox"></div>
|
531 |
<div class="controls">
|
532 |
-
<p>Controls: WASD
|
533 |
<p>Chat to add world features!</p>
|
534 |
</div>
|
535 |
</div>
|
@@ -542,35 +543,32 @@ rocky_map_html = f"""
|
|
542 |
const camera = new THREE.PerspectiveCamera(75, 800 / 600, 0.1, 1000);
|
543 |
camera.position.set(0, 50, 50);
|
544 |
camera.lookAt(0, 0, 0);
|
545 |
-
|
546 |
-
const renderer = new THREE.WebGLRenderer({{ antialias: true }});
|
547 |
renderer.setSize(800, 600);
|
548 |
document.getElementById('gameContainer').appendChild(renderer.domElement);
|
549 |
|
|
|
550 |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
551 |
scene.add(ambientLight);
|
552 |
const sunLight = new THREE.DirectionalLight(0xffddaa, 1);
|
553 |
sunLight.position.set(50, 50, 50);
|
554 |
sunLight.castShadow = true;
|
555 |
scene.add(sunLight);
|
556 |
-
|
557 |
const groundGeometry = new THREE.PlaneGeometry(100, 100);
|
558 |
-
const groundMaterial = new THREE.MeshStandardMaterial({
|
559 |
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
560 |
ground.rotation.x = -Math.PI / 2;
|
561 |
ground.receiveShadow = true;
|
562 |
scene.add(ground);
|
563 |
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
const
|
568 |
-
let worldObjects = [];
|
569 |
-
const worldObjectMeshes = {{}};
|
570 |
-
let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
571 |
-
let score = 0, treasureCount = 0, lastActive = performance.now() / 1000;
|
572 |
|
573 |
-
|
|
|
|
|
574 |
"sphere": new THREE.SphereGeometry(1, 16, 16),
|
575 |
"cube": new THREE.BoxGeometry(2, 2, 2),
|
576 |
"cylinder": new THREE.CylinderGeometry(1, 1, 2, 16),
|
@@ -580,171 +578,182 @@ rocky_map_html = f"""
|
|
580 |
"octahedron": new THREE.OctahedronGeometry(1),
|
581 |
"tetrahedron": new THREE.TetrahedronGeometry(1),
|
582 |
"icosahedron": new THREE.IcosahedronGeometry(1)
|
583 |
-
}
|
584 |
-
|
585 |
-
const playerMaterial = new THREE.MeshPhongMaterial({{ color: {next(c["color"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)} }});
|
586 |
const playerMesh = new THREE.Mesh(shapeGeometries["{next(c['shape'] for c in EDGE_TTS_VOICES if c['name'] == st.session_state.username)}"], playerMaterial);
|
587 |
playerMesh.position.set(xPos, 1, zPos);
|
588 |
playerMesh.castShadow = true;
|
589 |
scene.add(playerMesh);
|
590 |
-
|
|
|
591 |
|
592 |
-
|
593 |
-
|
594 |
-
if (!treasureMeshes[t.id]) {{
|
595 |
-
const treasure = new THREE.Mesh(
|
596 |
-
new THREE.SphereGeometry(1, 8, 8),
|
597 |
-
new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
|
598 |
-
);
|
599 |
-
treasure.position.set(t.x, 1, t.z);
|
600 |
-
treasure.castShadow = true;
|
601 |
-
treasureMeshes[t.id] = treasure;
|
602 |
-
treasures.push(treasure);
|
603 |
-
scene.add(treasure);
|
604 |
-
}} else {{
|
605 |
-
treasureMeshes[t.id].position.set(t.x, 1, t.z);
|
606 |
-
}}
|
607 |
-
}});
|
608 |
-
Object.keys(treasureMeshes).forEach(id => {{
|
609 |
-
if (!treasureData.some(t => t.id === id)) {{
|
610 |
-
scene.remove(treasureMeshes[id]);
|
611 |
-
treasures = treasures.filter(t => t !== treasureMeshes[id]);
|
612 |
-
delete treasureMeshes[id];
|
613 |
-
}}
|
614 |
-
}});
|
615 |
-
}}
|
616 |
|
617 |
-
|
618 |
-
|
619 |
-
if (!worldObjectMeshes[obj.type + obj.x + obj.z]) {{
|
620 |
-
const geometry = shapeGeometries[obj.shape] || new THREE.BoxGeometry(2, 2, 2);
|
621 |
-
const material = new THREE.MeshPhongMaterial({{ color: obj.color }});
|
622 |
-
const objMesh = new THREE.Mesh(geometry, material);
|
623 |
-
objMesh.position.set(obj.x, 1, obj.z);
|
624 |
-
objMesh.castShadow = true;
|
625 |
-
worldObjectMeshes[obj.type + obj.x + obj.z] = objMesh;
|
626 |
-
worldObjects.push(objMesh);
|
627 |
-
scene.add(objMesh);
|
628 |
-
}}
|
629 |
-
}});
|
630 |
-
}}
|
631 |
-
|
632 |
-
document.addEventListener('keydown', (event) => {{
|
633 |
-
switch (event.code) {{
|
634 |
case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
|
635 |
case 'ArrowRight': case 'KeyD': moveRight = true; break;
|
636 |
case 'ArrowUp': case 'KeyW': moveUp = true; break;
|
637 |
case 'ArrowDown': case 'KeyS': moveDown = true; break;
|
638 |
case 'Space': collect = true; break;
|
639 |
-
}
|
640 |
lastActive = performance.now() / 1000;
|
641 |
-
}
|
642 |
-
document.addEventListener('keyup', (event) => {
|
643 |
-
switch (event.code) {
|
644 |
case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
|
645 |
case 'ArrowRight': case 'KeyD': moveRight = false; break;
|
646 |
case 'ArrowUp': case 'KeyW': moveUp = false; break;
|
647 |
case 'ArrowDown': case 'KeyS': moveDown = false; break;
|
648 |
case 'Space': collect = false; break;
|
649 |
-
}
|
650 |
-
}
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
if (
|
656 |
-
if (
|
657 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
658 |
playerMesh.position.set(xPos, 1, zPos);
|
659 |
-
ws.send(`${
|
660 |
-
|
661 |
-
if (collect) {
|
662 |
-
for (let i = treasures.length - 1; i >= 0; i--) {
|
663 |
-
if (playerMesh.position.distanceTo(treasures[i].position) < 2) {
|
664 |
const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
|
665 |
scene.remove(treasures[i]);
|
666 |
treasures.splice(i, 1);
|
667 |
delete treasureMeshes[id];
|
668 |
score += 50;
|
669 |
treasureCount += 1;
|
670 |
-
ws.send(`${
|
671 |
-
ws.send(`${
|
672 |
-
}
|
673 |
-
}
|
674 |
-
}
|
675 |
-
|
676 |
-
camera.position.
|
677 |
-
camera.lookAt(xPos, 0, zPos);
|
678 |
-
|
679 |
-
document.getElementById('
|
680 |
-
document.getElementById('
|
681 |
-
|
682 |
-
}}
|
683 |
|
684 |
-
|
685 |
-
|
686 |
-
|
|
|
687 |
const geometry = shapeGeometries[player.shape] || new THREE.BoxGeometry(2, 2, 2);
|
688 |
-
const material = new THREE.MeshPhongMaterial({
|
689 |
const mesh = new THREE.Mesh(geometry, material);
|
690 |
mesh.castShadow = true;
|
691 |
scene.add(mesh);
|
692 |
playerMeshes[player.username] = mesh;
|
693 |
-
}
|
694 |
const mesh = playerMeshes[player.username];
|
695 |
mesh.position.set(player.x, 1, player.z);
|
696 |
-
players[player.username] = {
|
697 |
-
if (player.username === playerName) {
|
698 |
xPos = player.x;
|
699 |
zPos = player.z;
|
700 |
score = player.score;
|
701 |
treasureCount = player.treasures;
|
702 |
playerMesh.position.set(xPos, 1, zPos);
|
703 |
-
}
|
704 |
-
}
|
705 |
-
document.getElementById('players').textContent = `Players: ${
|
706 |
const leaderboard = playerData.sort((a, b) => b.score - a.score)
|
707 |
-
.map(p => `${
|
708 |
.join('<br>');
|
709 |
-
document.getElementById('leaderboard').innerHTML = `Leaderboard<br>${
|
710 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
711 |
|
712 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
713 |
const data = event.data;
|
714 |
-
if (data.startsWith('MAP_UPDATE:')) {
|
715 |
const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
|
716 |
updatePlayers(playerData);
|
717 |
-
}
|
718 |
const chatData = JSON.parse(data.split('CHAT_UPDATE:')[1]);
|
719 |
const chatBox = document.getElementById('chatBox');
|
720 |
-
chatBox.innerHTML = chatData.map(line => `<p>${
|
721 |
chatBox.scrollTop = chatBox.scrollHeight;
|
722 |
-
}
|
723 |
const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
|
724 |
updatePlayers(gameState.players);
|
725 |
updateTreasures(gameState.treasures);
|
726 |
updateWorldObjects(gameState.world_objects);
|
727 |
-
}
|
728 |
const [sender, message] = data.split('|');
|
729 |
const chatBox = document.getElementById('chatBox');
|
730 |
-
chatBox.innerHTML += `<p>${
|
731 |
chatBox.scrollTop = chatBox.scrollHeight;
|
732 |
-
}
|
733 |
-
}
|
734 |
|
735 |
let lastTime = performance.now();
|
736 |
-
function animate() {
|
737 |
requestAnimationFrame(animate);
|
738 |
const currentTime = performance.now();
|
739 |
const delta = (currentTime - lastTime) / 1000;
|
740 |
lastTime = currentTime;
|
741 |
-
|
742 |
updatePlayer(delta);
|
743 |
treasures.forEach(t => t.rotation.y += delta);
|
744 |
worldObjects.forEach(o => o.rotation.y += delta * 0.5);
|
745 |
renderer.render(scene, camera);
|
746 |
-
}
|
747 |
-
|
748 |
animate();
|
749 |
</script>
|
750 |
</body>
|
@@ -790,7 +799,6 @@ def main():
|
|
790 |
asyncio.run(save_chat_entry(st.session_state.username, "mountains", st.session_state.tts_voice))
|
791 |
|
792 |
left_col, right_col = st.columns([2, 1])
|
793 |
-
|
794 |
with left_col:
|
795 |
components.html(rocky_map_html, width=800, height=600)
|
796 |
chat_content = asyncio.run(load_chat())
|
@@ -818,7 +826,6 @@ def main():
|
|
818 |
play_and_download_audio(audio_file)
|
819 |
st.session_state.last_activity = time.time()
|
820 |
st.rerun()
|
821 |
-
|
822 |
with right_col:
|
823 |
st.subheader("🌾 Prairie Map")
|
824 |
prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
|
@@ -844,7 +851,6 @@ def main():
|
|
844 |
popup=f"{player['username']} ({player['animal']})"
|
845 |
).add_to(prairie_map)
|
846 |
folium_static(prairie_map, width=600, height=400)
|
847 |
-
|
848 |
animal = st.selectbox("Choose Animal 🐾", ["prairie_dog", "deer", "sheep", "groundhog"])
|
849 |
location = st.selectbox("Move to 📍", list(PRAIRIE_LOCATIONS.keys()))
|
850 |
if st.button("Move 🚶"):
|
@@ -856,7 +862,6 @@ def main():
|
|
856 |
elapsed = time.time() - st.session_state.last_update
|
857 |
remaining = max(0, st.session_state.update_interval - elapsed)
|
858 |
st.sidebar.markdown(f"⏳ Next Update in: {int(remaining)}s")
|
859 |
-
|
860 |
if time.time() - st.session_state.last_activity > st.session_state.timeout:
|
861 |
st.sidebar.warning("Timed out! Refreshing in 5 seconds... ⏰")
|
862 |
time.sleep(5)
|
@@ -869,11 +874,9 @@ def main():
|
|
869 |
st.session_state.last_update = time.time()
|
870 |
st.session_state.game_state_timestamp = time.time()
|
871 |
st.rerun()
|
872 |
-
|
873 |
if not st.session_state.get('server_running', False):
|
874 |
st.session_state.server_task = threading.Thread(target=start_websocket_server, daemon=True)
|
875 |
st.session_state.server_task.start()
|
876 |
-
|
877 |
display_file_history_in_sidebar()
|
878 |
log_performance_metrics()
|
879 |
|
|
|
484 |
st.sidebar.write(f"**{operation}:** {duration:.2f}s ({percentage:.1f}%)")
|
485 |
|
486 |
# -----------------------------------------------------------
|
487 |
+
# Enhanced 3D Game HTML (With dynamic insertion of username and fluid movement)
|
488 |
# -----------------------------------------------------------
|
489 |
+
# Note: Added velocity-based movement for smoother control.
|
490 |
rocky_map_html = f"""
|
491 |
<!DOCTYPE html>
|
492 |
<html lang="en">
|
|
|
530 |
<div class="leaderboard" id="leaderboard">Leaderboard</div>
|
531 |
<div id="chatBox"></div>
|
532 |
<div class="controls">
|
533 |
+
<p>Controls: Use WASD or Arrow keys for movement. Space to collect treasure.</p>
|
534 |
<p>Chat to add world features!</p>
|
535 |
</div>
|
536 |
</div>
|
|
|
543 |
const camera = new THREE.PerspectiveCamera(75, 800 / 600, 0.1, 1000);
|
544 |
camera.position.set(0, 50, 50);
|
545 |
camera.lookAt(0, 0, 0);
|
546 |
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
|
547 |
renderer.setSize(800, 600);
|
548 |
document.getElementById('gameContainer').appendChild(renderer.domElement);
|
549 |
|
550 |
+
// Lighting & Ground
|
551 |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
552 |
scene.add(ambientLight);
|
553 |
const sunLight = new THREE.DirectionalLight(0xffddaa, 1);
|
554 |
sunLight.position.set(50, 50, 50);
|
555 |
sunLight.castShadow = true;
|
556 |
scene.add(sunLight);
|
|
|
557 |
const groundGeometry = new THREE.PlaneGeometry(100, 100);
|
558 |
+
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
|
559 |
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
560 |
ground.rotation.x = -Math.PI / 2;
|
561 |
ground.receiveShadow = true;
|
562 |
scene.add(ground);
|
563 |
|
564 |
+
// Fluid Movement Variables
|
565 |
+
let velocity = new THREE.Vector2(0, 0);
|
566 |
+
const acceleration = 50; // Increase for faster acceleration
|
567 |
+
const friction = 5; // Friction factor to smooth movement
|
|
|
|
|
|
|
|
|
568 |
|
569 |
+
// Player Setup
|
570 |
+
let xPos = 0, zPos = 0;
|
571 |
+
const shapeGeometries = {
|
572 |
"sphere": new THREE.SphereGeometry(1, 16, 16),
|
573 |
"cube": new THREE.BoxGeometry(2, 2, 2),
|
574 |
"cylinder": new THREE.CylinderGeometry(1, 1, 2, 16),
|
|
|
578 |
"octahedron": new THREE.OctahedronGeometry(1),
|
579 |
"tetrahedron": new THREE.TetrahedronGeometry(1),
|
580 |
"icosahedron": new THREE.IcosahedronGeometry(1)
|
581 |
+
};
|
582 |
+
const playerMaterial = new THREE.MeshPhongMaterial({ color: {next(c["color"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)} });
|
|
|
583 |
const playerMesh = new THREE.Mesh(shapeGeometries["{next(c['shape'] for c in EDGE_TTS_VOICES if c['name'] == st.session_state.username)}"], playerMaterial);
|
584 |
playerMesh.position.set(xPos, 1, zPos);
|
585 |
playerMesh.castShadow = true;
|
586 |
scene.add(playerMesh);
|
587 |
+
let score = 0, treasureCount = 0;
|
588 |
+
let lastActive = performance.now() / 1000;
|
589 |
|
590 |
+
// Key Flags
|
591 |
+
let moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
592 |
|
593 |
+
document.addEventListener('keydown', (event) => {
|
594 |
+
switch (event.code) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
595 |
case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
|
596 |
case 'ArrowRight': case 'KeyD': moveRight = true; break;
|
597 |
case 'ArrowUp': case 'KeyW': moveUp = true; break;
|
598 |
case 'ArrowDown': case 'KeyS': moveDown = true; break;
|
599 |
case 'Space': collect = true; break;
|
600 |
+
}
|
601 |
lastActive = performance.now() / 1000;
|
602 |
+
});
|
603 |
+
document.addEventListener('keyup', (event) => {
|
604 |
+
switch (event.code) {
|
605 |
case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
|
606 |
case 'ArrowRight': case 'KeyD': moveRight = false; break;
|
607 |
case 'ArrowUp': case 'KeyW': moveUp = false; break;
|
608 |
case 'ArrowDown': case 'KeyS': moveDown = false; break;
|
609 |
case 'Space': collect = false; break;
|
610 |
+
}
|
611 |
+
});
|
612 |
+
|
613 |
+
// Fluid Movement Update using velocity-based control
|
614 |
+
function updatePlayer(delta) {
|
615 |
+
// Accelerate based on key presses
|
616 |
+
if (moveLeft) velocity.x -= acceleration * delta;
|
617 |
+
if (moveRight) velocity.x += acceleration * delta;
|
618 |
+
if (moveUp) velocity.y -= acceleration * delta;
|
619 |
+
if (moveDown) velocity.y += acceleration * delta;
|
620 |
+
// Apply friction
|
621 |
+
velocity.x -= velocity.x * friction * delta;
|
622 |
+
velocity.y -= velocity.y * friction * delta;
|
623 |
+
// Update positions
|
624 |
+
xPos += velocity.x * delta;
|
625 |
+
zPos += velocity.y * delta;
|
626 |
+
// Clamp position boundaries
|
627 |
+
xPos = Math.max(-40, Math.min(40, xPos));
|
628 |
+
zPos = Math.max(-40, Math.min(40, zPos));
|
629 |
playerMesh.position.set(xPos, 1, zPos);
|
630 |
+
ws.send(`${playerName}|MOVE:${xPos}:${zPos}`);
|
631 |
+
// Treasure collection
|
632 |
+
if (collect) {
|
633 |
+
for (let i = treasures.length - 1; i >= 0; i--) {
|
634 |
+
if (playerMesh.position.distanceTo(treasures[i].position) < 2) {
|
635 |
const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
|
636 |
scene.remove(treasures[i]);
|
637 |
treasures.splice(i, 1);
|
638 |
delete treasureMeshes[id];
|
639 |
score += 50;
|
640 |
treasureCount += 1;
|
641 |
+
ws.send(`${playerName}|SCORE:${score}`);
|
642 |
+
ws.send(`${playerName}|TREASURE:${treasureCount}`);
|
643 |
+
}
|
644 |
+
}
|
645 |
+
}
|
646 |
+
// Update camera position for a dynamic view
|
647 |
+
camera.position.lerp(new THREE.Vector3(xPos, 50, zPos + 50), 0.1);
|
648 |
+
camera.lookAt(new THREE.Vector3(xPos, 0, zPos));
|
649 |
+
document.getElementById('timeout').textContent = `Timeout: ${Math.max(0, (60 - (performance.now()/1000 - lastActive)).toFixed(0))}s`;
|
650 |
+
document.getElementById('score').textContent = `Score: $${score}`;
|
651 |
+
document.getElementById('treasures').textContent = `Treasures: $${treasureCount}`;
|
652 |
+
}
|
|
|
653 |
|
654 |
+
// Update functions for other players, treasures, and world objects remain similar
|
655 |
+
function updatePlayers(playerData) {
|
656 |
+
playerData.forEach(player => {
|
657 |
+
if (!playerMeshes[player.username]) {
|
658 |
const geometry = shapeGeometries[player.shape] || new THREE.BoxGeometry(2, 2, 2);
|
659 |
+
const material = new THREE.MeshPhongMaterial({ color: player.color });
|
660 |
const mesh = new THREE.Mesh(geometry, material);
|
661 |
mesh.castShadow = true;
|
662 |
scene.add(mesh);
|
663 |
playerMeshes[player.username] = mesh;
|
664 |
+
}
|
665 |
const mesh = playerMeshes[player.username];
|
666 |
mesh.position.set(player.x, 1, player.z);
|
667 |
+
players[player.username] = { mesh: mesh, score: player.score, treasures: player.treasures };
|
668 |
+
if (player.username === playerName) {
|
669 |
xPos = player.x;
|
670 |
zPos = player.z;
|
671 |
score = player.score;
|
672 |
treasureCount = player.treasures;
|
673 |
playerMesh.position.set(xPos, 1, zPos);
|
674 |
+
}
|
675 |
+
});
|
676 |
+
document.getElementById('players').textContent = `Players: ${Object.keys(playerMeshes).length}`;
|
677 |
const leaderboard = playerData.sort((a, b) => b.score - a.score)
|
678 |
+
.map(p => `${p.username}: $${p.score}`)
|
679 |
.join('<br>');
|
680 |
+
document.getElementById('leaderboard').innerHTML = `Leaderboard<br>${leaderboard}`;
|
681 |
+
}
|
682 |
+
|
683 |
+
function updateTreasures(treasureData) {
|
684 |
+
treasureData.forEach(t => {
|
685 |
+
if (!treasureMeshes[t.id]) {
|
686 |
+
const treasure = new THREE.Mesh(
|
687 |
+
new THREE.SphereGeometry(1, 8, 8),
|
688 |
+
new THREE.MeshPhongMaterial({ color: 0xffff00 })
|
689 |
+
);
|
690 |
+
treasure.position.set(t.x, 1, t.z);
|
691 |
+
treasure.castShadow = true;
|
692 |
+
treasureMeshes[t.id] = treasure;
|
693 |
+
treasures.push(treasure);
|
694 |
+
scene.add(treasure);
|
695 |
+
} else {
|
696 |
+
treasureMeshes[t.id].position.set(t.x, 1, t.z);
|
697 |
+
}
|
698 |
+
});
|
699 |
+
Object.keys(treasureMeshes).forEach(id => {
|
700 |
+
if (!treasureData.some(t => t.id === id)) {
|
701 |
+
scene.remove(treasureMeshes[id]);
|
702 |
+
treasures = treasures.filter(t => t !== treasureMeshes[id]);
|
703 |
+
delete treasureMeshes[id];
|
704 |
+
}
|
705 |
+
});
|
706 |
+
}
|
707 |
|
708 |
+
function updateWorldObjects(objectData) {
|
709 |
+
objectData.forEach(obj => {
|
710 |
+
if (!worldObjectMeshes[obj.type + obj.x + obj.z]) {
|
711 |
+
const geometry = shapeGeometries[obj.shape] || new THREE.BoxGeometry(2, 2, 2);
|
712 |
+
const material = new THREE.MeshPhongMaterial({ color: obj.color });
|
713 |
+
const objMesh = new THREE.Mesh(geometry, material);
|
714 |
+
objMesh.position.set(obj.x, 1, obj.z);
|
715 |
+
objMesh.castShadow = true;
|
716 |
+
worldObjectMeshes[obj.type + obj.x + obj.z] = objMesh;
|
717 |
+
worldObjects.push(objMesh);
|
718 |
+
scene.add(objMesh);
|
719 |
+
}
|
720 |
+
});
|
721 |
+
}
|
722 |
+
|
723 |
+
ws.onmessage = function(event) {
|
724 |
const data = event.data;
|
725 |
+
if (data.startsWith('MAP_UPDATE:')) {
|
726 |
const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
|
727 |
updatePlayers(playerData);
|
728 |
+
} else if (data.startsWith('CHAT_UPDATE:')) {
|
729 |
const chatData = JSON.parse(data.split('CHAT_UPDATE:')[1]);
|
730 |
const chatBox = document.getElementById('chatBox');
|
731 |
+
chatBox.innerHTML = chatData.map(line => `<p>${line}</p>`).join('');
|
732 |
chatBox.scrollTop = chatBox.scrollHeight;
|
733 |
+
} else if (data.startsWith('GAME_STATE:')) {
|
734 |
const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
|
735 |
updatePlayers(gameState.players);
|
736 |
updateTreasures(gameState.treasures);
|
737 |
updateWorldObjects(gameState.world_objects);
|
738 |
+
} else if (!data.startsWith('PRAIRIE_UPDATE:')) {
|
739 |
const [sender, message] = data.split('|');
|
740 |
const chatBox = document.getElementById('chatBox');
|
741 |
+
chatBox.innerHTML += `<p>${sender}: ${message}</p>`;
|
742 |
chatBox.scrollTop = chatBox.scrollHeight;
|
743 |
+
}
|
744 |
+
};
|
745 |
|
746 |
let lastTime = performance.now();
|
747 |
+
function animate() {
|
748 |
requestAnimationFrame(animate);
|
749 |
const currentTime = performance.now();
|
750 |
const delta = (currentTime - lastTime) / 1000;
|
751 |
lastTime = currentTime;
|
|
|
752 |
updatePlayer(delta);
|
753 |
treasures.forEach(t => t.rotation.y += delta);
|
754 |
worldObjects.forEach(o => o.rotation.y += delta * 0.5);
|
755 |
renderer.render(scene, camera);
|
756 |
+
}
|
|
|
757 |
animate();
|
758 |
</script>
|
759 |
</body>
|
|
|
799 |
asyncio.run(save_chat_entry(st.session_state.username, "mountains", st.session_state.tts_voice))
|
800 |
|
801 |
left_col, right_col = st.columns([2, 1])
|
|
|
802 |
with left_col:
|
803 |
components.html(rocky_map_html, width=800, height=600)
|
804 |
chat_content = asyncio.run(load_chat())
|
|
|
826 |
play_and_download_audio(audio_file)
|
827 |
st.session_state.last_activity = time.time()
|
828 |
st.rerun()
|
|
|
829 |
with right_col:
|
830 |
st.subheader("🌾 Prairie Map")
|
831 |
prairie_map = folium.Map(location=[44.0, -103.0], zoom_start=8, tiles="CartoDB Positron")
|
|
|
851 |
popup=f"{player['username']} ({player['animal']})"
|
852 |
).add_to(prairie_map)
|
853 |
folium_static(prairie_map, width=600, height=400)
|
|
|
854 |
animal = st.selectbox("Choose Animal 🐾", ["prairie_dog", "deer", "sheep", "groundhog"])
|
855 |
location = st.selectbox("Move to 📍", list(PRAIRIE_LOCATIONS.keys()))
|
856 |
if st.button("Move 🚶"):
|
|
|
862 |
elapsed = time.time() - st.session_state.last_update
|
863 |
remaining = max(0, st.session_state.update_interval - elapsed)
|
864 |
st.sidebar.markdown(f"⏳ Next Update in: {int(remaining)}s")
|
|
|
865 |
if time.time() - st.session_state.last_activity > st.session_state.timeout:
|
866 |
st.sidebar.warning("Timed out! Refreshing in 5 seconds... ⏰")
|
867 |
time.sleep(5)
|
|
|
874 |
st.session_state.last_update = time.time()
|
875 |
st.session_state.game_state_timestamp = time.time()
|
876 |
st.rerun()
|
|
|
877 |
if not st.session_state.get('server_running', False):
|
878 |
st.session_state.server_task = threading.Thread(target=start_websocket_server, daemon=True)
|
879 |
st.session_state.server_task.start()
|
|
|
880 |
display_file_history_in_sidebar()
|
881 |
log_performance_metrics()
|
882 |
|