Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -17,7 +17,7 @@ import streamlit.components.v1 as components
|
|
17 |
from gradio_client import Client
|
18 |
from streamlit_marquee import streamlit_marquee
|
19 |
import folium
|
20 |
-
from streamlit_folium import folium_static
|
21 |
import glob
|
22 |
import pytz
|
23 |
from collections import defaultdict
|
@@ -486,7 +486,10 @@ def log_performance_metrics():
|
|
486 |
# -----------------------------------------------------------
|
487 |
# Enhanced 3D Game HTML (With dynamic insertion of username and fluid movement)
|
488 |
# -----------------------------------------------------------
|
489 |
-
#
|
|
|
|
|
|
|
490 |
rocky_map_html = f"""
|
491 |
<!DOCTYPE html>
|
492 |
<html lang="en">
|
@@ -543,7 +546,7 @@ rocky_map_html = f"""
|
|
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 |
|
@@ -555,7 +558,7 @@ rocky_map_html = f"""
|
|
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;
|
@@ -563,12 +566,12 @@ rocky_map_html = f"""
|
|
563 |
|
564 |
// Fluid Movement Variables
|
565 |
let velocity = new THREE.Vector2(0, 0);
|
566 |
-
const acceleration = 50;
|
567 |
-
const friction = 5;
|
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,173 +581,163 @@ rocky_map_html = f"""
|
|
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: {
|
583 |
-
const playerMesh = new THREE.Mesh(shapeGeometries["{
|
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 |
-
|
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 |
-
|
632 |
-
|
633 |
-
|
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 |
-
|
655 |
-
|
656 |
-
|
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;
|
@@ -753,7 +746,7 @@ rocky_map_html = f"""
|
|
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>
|
@@ -850,7 +843,8 @@ def main():
|
|
850 |
fill_opacity=0.7,
|
851 |
popup=f"{player['username']} ({player['animal']})"
|
852 |
).add_to(prairie_map)
|
853 |
-
|
|
|
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 🚶"):
|
|
|
17 |
from gradio_client import Client
|
18 |
from streamlit_marquee import streamlit_marquee
|
19 |
import folium
|
20 |
+
from streamlit_folium import st_folium # Updated: use st_folium instead of folium_static
|
21 |
import glob
|
22 |
import pytz
|
23 |
from collections import defaultdict
|
|
|
486 |
# -----------------------------------------------------------
|
487 |
# Enhanced 3D Game HTML (With dynamic insertion of username and fluid movement)
|
488 |
# -----------------------------------------------------------
|
489 |
+
# Precompute the player's color and shape to simplify f-string expressions.
|
490 |
+
player_color = next(c["color"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)
|
491 |
+
player_shape = next(c["shape"] for c in EDGE_TTS_VOICES if c["name"] == st.session_state.username)
|
492 |
+
|
493 |
rocky_map_html = f"""
|
494 |
<!DOCTYPE html>
|
495 |
<html lang="en">
|
|
|
546 |
const camera = new THREE.PerspectiveCamera(75, 800 / 600, 0.1, 1000);
|
547 |
camera.position.set(0, 50, 50);
|
548 |
camera.lookAt(0, 0, 0);
|
549 |
+
const renderer = new THREE.WebGLRenderer({{ antialias: true }});
|
550 |
renderer.setSize(800, 600);
|
551 |
document.getElementById('gameContainer').appendChild(renderer.domElement);
|
552 |
|
|
|
558 |
sunLight.castShadow = true;
|
559 |
scene.add(sunLight);
|
560 |
const groundGeometry = new THREE.PlaneGeometry(100, 100);
|
561 |
+
const groundMaterial = new THREE.MeshStandardMaterial({{ color: 0x228B22 }});
|
562 |
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
563 |
ground.rotation.x = -Math.PI / 2;
|
564 |
ground.receiveShadow = true;
|
|
|
566 |
|
567 |
// Fluid Movement Variables
|
568 |
let velocity = new THREE.Vector2(0, 0);
|
569 |
+
const acceleration = 50;
|
570 |
+
const friction = 5;
|
571 |
|
572 |
// Player Setup
|
573 |
let xPos = 0, zPos = 0;
|
574 |
+
const shapeGeometries = {{
|
575 |
"sphere": new THREE.SphereGeometry(1, 16, 16),
|
576 |
"cube": new THREE.BoxGeometry(2, 2, 2),
|
577 |
"cylinder": new THREE.CylinderGeometry(1, 1, 2, 16),
|
|
|
581 |
"octahedron": new THREE.OctahedronGeometry(1),
|
582 |
"tetrahedron": new THREE.TetrahedronGeometry(1),
|
583 |
"icosahedron": new THREE.IcosahedronGeometry(1)
|
584 |
+
}};
|
585 |
+
const playerMaterial = new THREE.MeshPhongMaterial({{ color: {player_color} }});
|
586 |
+
const playerMesh = new THREE.Mesh(shapeGeometries["{player_shape}"], playerMaterial);
|
587 |
playerMesh.position.set(xPos, 1, zPos);
|
588 |
playerMesh.castShadow = true;
|
589 |
scene.add(playerMesh);
|
590 |
let score = 0, treasureCount = 0;
|
591 |
let lastActive = performance.now() / 1000;
|
|
|
|
|
592 |
let moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
593 |
|
594 |
+
document.addEventListener('keydown', (event) => {{
|
595 |
+
switch (event.code) {{
|
596 |
case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
|
597 |
case 'ArrowRight': case 'KeyD': moveRight = true; break;
|
598 |
case 'ArrowUp': case 'KeyW': moveUp = true; break;
|
599 |
case 'ArrowDown': case 'KeyS': moveDown = true; break;
|
600 |
case 'Space': collect = true; break;
|
601 |
+
}}
|
602 |
lastActive = performance.now() / 1000;
|
603 |
+
}});
|
604 |
+
document.addEventListener('keyup', (event) => {{
|
605 |
+
switch (event.code) {{
|
606 |
case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
|
607 |
case 'ArrowRight': case 'KeyD': moveRight = false; break;
|
608 |
case 'ArrowUp': case 'KeyW': moveUp = false; break;
|
609 |
case 'ArrowDown': case 'KeyS': moveDown = false; break;
|
610 |
case 'Space': collect = false; break;
|
611 |
+
}}
|
612 |
+
}});
|
613 |
|
614 |
+
function updatePlayer(delta) {{
|
|
|
|
|
615 |
if (moveLeft) velocity.x -= acceleration * delta;
|
616 |
if (moveRight) velocity.x += acceleration * delta;
|
617 |
if (moveUp) velocity.y -= acceleration * delta;
|
618 |
if (moveDown) velocity.y += acceleration * delta;
|
|
|
619 |
velocity.x -= velocity.x * friction * delta;
|
620 |
velocity.y -= velocity.y * friction * delta;
|
|
|
621 |
xPos += velocity.x * delta;
|
622 |
zPos += velocity.y * delta;
|
|
|
623 |
xPos = Math.max(-40, Math.min(40, xPos));
|
624 |
zPos = Math.max(-40, Math.min(40, zPos));
|
625 |
playerMesh.position.set(xPos, 1, zPos);
|
626 |
+
ws.send(`${{playerName}}|MOVE:${{xPos}}:${{zPos}}`);
|
627 |
+
if (collect) {{
|
628 |
+
for (let i = treasures.length - 1; i >= 0; i--) {{
|
629 |
+
if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
|
|
|
630 |
const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
|
631 |
scene.remove(treasures[i]);
|
632 |
treasures.splice(i, 1);
|
633 |
delete treasureMeshes[id];
|
634 |
score += 50;
|
635 |
treasureCount += 1;
|
636 |
+
ws.send(`${{playerName}}|SCORE:${{score}}`);
|
637 |
+
ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
|
638 |
+
}}
|
639 |
+
}}
|
640 |
+
}}
|
|
|
641 |
camera.position.lerp(new THREE.Vector3(xPos, 50, zPos + 50), 0.1);
|
642 |
camera.lookAt(new THREE.Vector3(xPos, 0, zPos));
|
643 |
+
document.getElementById('timeout').textContent = `Timeout: ${{Math.max(0, (60 - (performance.now()/1000 - lastActive)).toFixed(0))}}s`;
|
644 |
+
document.getElementById('score').textContent = `Score: $${{score}}`;
|
645 |
+
document.getElementById('treasures').textContent = `Treasures: $${{treasureCount}}`;
|
646 |
+
}}
|
647 |
|
648 |
+
function updatePlayers(playerData) {{
|
649 |
+
playerData.forEach(player => {{
|
650 |
+
if (!playerMeshes[player.username]) {{
|
|
|
651 |
const geometry = shapeGeometries[player.shape] || new THREE.BoxGeometry(2, 2, 2);
|
652 |
+
const material = new THREE.MeshPhongMaterial({{ color: player.color }});
|
653 |
const mesh = new THREE.Mesh(geometry, material);
|
654 |
mesh.castShadow = true;
|
655 |
scene.add(mesh);
|
656 |
playerMeshes[player.username] = mesh;
|
657 |
+
}}
|
658 |
const mesh = playerMeshes[player.username];
|
659 |
mesh.position.set(player.x, 1, player.z);
|
660 |
+
players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
|
661 |
+
if (player.username === playerName) {{
|
662 |
xPos = player.x;
|
663 |
zPos = player.z;
|
664 |
score = player.score;
|
665 |
treasureCount = player.treasures;
|
666 |
playerMesh.position.set(xPos, 1, zPos);
|
667 |
+
}}
|
668 |
+
}});
|
669 |
+
document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
|
670 |
const leaderboard = playerData.sort((a, b) => b.score - a.score)
|
671 |
+
.map(p => `${{p.username}}: $${{p.score}}`)
|
672 |
.join('<br>');
|
673 |
+
document.getElementById('leaderboard').innerHTML = `Leaderboard<br>${{leaderboard}}`;
|
674 |
+
}}
|
675 |
|
676 |
+
function updateTreasures(treasureData) {{
|
677 |
+
treasureData.forEach(t => {{
|
678 |
+
if (!treasureMeshes[t.id]) {{
|
679 |
const treasure = new THREE.Mesh(
|
680 |
new THREE.SphereGeometry(1, 8, 8),
|
681 |
+
new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
|
682 |
);
|
683 |
treasure.position.set(t.x, 1, t.z);
|
684 |
treasure.castShadow = true;
|
685 |
treasureMeshes[t.id] = treasure;
|
686 |
treasures.push(treasure);
|
687 |
scene.add(treasure);
|
688 |
+
}} else {{
|
689 |
treasureMeshes[t.id].position.set(t.x, 1, t.z);
|
690 |
+
}}
|
691 |
+
}});
|
692 |
+
Object.keys(treasureMeshes).forEach(id => {{
|
693 |
+
if (!treasureData.some(t => t.id === id)) {{
|
694 |
scene.remove(treasureMeshes[id]);
|
695 |
treasures = treasures.filter(t => t !== treasureMeshes[id]);
|
696 |
delete treasureMeshes[id];
|
697 |
+
}}
|
698 |
+
}});
|
699 |
+
}}
|
700 |
|
701 |
+
function updateWorldObjects(objectData) {{
|
702 |
+
objectData.forEach(obj => {{
|
703 |
+
if (!worldObjectMeshes[obj.type + obj.x + obj.z]) {{
|
704 |
const geometry = shapeGeometries[obj.shape] || new THREE.BoxGeometry(2, 2, 2);
|
705 |
+
const material = new THREE.MeshPhongMaterial({{ color: obj.color }});
|
706 |
const objMesh = new THREE.Mesh(geometry, material);
|
707 |
objMesh.position.set(obj.x, 1, obj.z);
|
708 |
objMesh.castShadow = true;
|
709 |
worldObjectMeshes[obj.type + obj.x + obj.z] = objMesh;
|
710 |
worldObjects.push(objMesh);
|
711 |
scene.add(objMesh);
|
712 |
+
}}
|
713 |
+
}});
|
714 |
+
}}
|
715 |
|
716 |
+
ws.onmessage = function(event) {{
|
717 |
const data = event.data;
|
718 |
+
if (data.startsWith('MAP_UPDATE:')) {{
|
719 |
const playerData = JSON.parse(data.split('MAP_UPDATE:')[1]);
|
720 |
updatePlayers(playerData);
|
721 |
+
}} else if (data.startsWith('CHAT_UPDATE:')) {{
|
722 |
const chatData = JSON.parse(data.split('CHAT_UPDATE:')[1]);
|
723 |
const chatBox = document.getElementById('chatBox');
|
724 |
+
chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
|
725 |
chatBox.scrollTop = chatBox.scrollHeight;
|
726 |
+
}} else if (data.startsWith('GAME_STATE:')) {{
|
727 |
const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
|
728 |
updatePlayers(gameState.players);
|
729 |
updateTreasures(gameState.treasures);
|
730 |
updateWorldObjects(gameState.world_objects);
|
731 |
+
}} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
|
732 |
const [sender, message] = data.split('|');
|
733 |
const chatBox = document.getElementById('chatBox');
|
734 |
+
chatBox.innerHTML += `<p>${{sender}}: ${{message}}</p>`;
|
735 |
chatBox.scrollTop = chatBox.scrollHeight;
|
736 |
+
}}
|
737 |
+
}};
|
738 |
|
739 |
let lastTime = performance.now();
|
740 |
+
function animate() {{
|
741 |
requestAnimationFrame(animate);
|
742 |
const currentTime = performance.now();
|
743 |
const delta = (currentTime - lastTime) / 1000;
|
|
|
746 |
treasures.forEach(t => t.rotation.y += delta);
|
747 |
worldObjects.forEach(o => o.rotation.y += delta * 0.5);
|
748 |
renderer.render(scene, camera);
|
749 |
+
}}
|
750 |
animate();
|
751 |
</script>
|
752 |
</body>
|
|
|
843 |
fill_opacity=0.7,
|
844 |
popup=f"{player['username']} ({player['animal']})"
|
845 |
).add_to(prairie_map)
|
846 |
+
# Updated to use st_folium instead of folium_static
|
847 |
+
st_folium(prairie_map, width=600, height=400)
|
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 🚶"):
|