Spaces:
Sleeping
Sleeping
# app.py | |
import streamlit as st | |
# st.set_page_config MUST be the first Streamlit command | |
st.set_page_config(page_title="Infinite World Builder", layout="wide") | |
import streamlit.components.v1 as components | |
import os, json, time, uuid, math | |
from datetime import datetime | |
# --- Global State File --- | |
GLOBAL_STATE_FILE = "global_state.json" | |
# If the global state file does not exist, create it with an empty state. | |
if not os.path.exists(GLOBAL_STATE_FILE): | |
empty_state = { | |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), | |
"game_state": [], # List of object records | |
"player_position": {"x": 0, "y": 0, "z": 0} | |
} | |
with open(GLOBAL_STATE_FILE, "w", encoding="utf-8") as f: | |
json.dump(empty_state, f, indent=2) | |
def load_global_state(): | |
try: | |
with open(GLOBAL_STATE_FILE, "r", encoding="utf-8") as f: | |
return json.load(f) | |
except Exception as e: | |
st.error(f"Error loading global state: {e}") | |
return {"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), "game_state": [], "player_position": {"x":0, "y":0, "z":0}} | |
def save_global_state(state): | |
try: | |
with open(GLOBAL_STATE_FILE, "w", encoding="utf-8") as f: | |
json.dump(state, f, indent=2) | |
except Exception as e: | |
st.error(f"Error saving global state: {e}") | |
# --- Initialize global state in session_state --- | |
if "global_state" not in st.session_state: | |
st.session_state["global_state"] = load_global_state() | |
# --- Utility: Update Global State with New Objects --- | |
def add_objects_to_global(new_objects, player_position): | |
""" | |
Merge new object records into the global state. | |
Each object is expected to have a unique 'obj_id'. If not, one is generated. | |
""" | |
state = st.session_state["global_state"] | |
# Create dictionary of existing objects by obj_id | |
existing = {obj["obj_id"]: obj for obj in state.get("game_state", []) if "obj_id" in obj} | |
for obj in new_objects: | |
obj_id = obj.get("obj_id", str(uuid.uuid4())) | |
obj["obj_id"] = obj_id | |
existing[obj_id] = obj | |
state["game_state"] = list(existing.values()) | |
# Update player position and timestamp (for simplicity, overwrite with latest) | |
state["player_position"] = player_position | |
state["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S") | |
st.session_state["global_state"] = state | |
save_global_state(state) | |
# --- Sidebar Controls --- | |
with st.sidebar: | |
st.title("๐๏ธ World Controls") | |
# Button to force reload global state from file. | |
if st.button("๐ Reload Global State"): | |
st.session_state["global_state"] = load_global_state() | |
st.success("Global state reloaded.") | |
# Button to clear the global state. | |
if st.button("๐๏ธ Clear Global State"): | |
empty = { | |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), | |
"game_state": [], | |
"player_position": {"x": 0, "y": 0, "z": 0} | |
} | |
st.session_state["global_state"] = empty | |
save_global_state(empty) | |
st.success("Global state cleared.") | |
st.markdown("---") | |
st.header("Download Global Save as Markdown") | |
state = st.session_state["global_state"] | |
default_md_name = state.get("timestamp", "save").replace(":", "").replace(" ", "_") + ".md" | |
download_name = st.text_input("Override File Name", value=default_md_name) | |
md_outline = f"""# Global Save: {download_name} | |
- โฐ **Timestamp:** {state.get("timestamp", "N/A")} | |
- ๐ฎ **Number of Game Objects:** {len(state.get("game_state", []))} | |
- ๐งญ **Player Position:** {state.get("player_position", {"x":0, "y":0, "z":0})} | |
## Game Objects: | |
""" | |
for i, obj in enumerate(state.get("game_state", []), start=1): | |
obj_type = obj.get("type", "Unknown") | |
pos = (obj.get("x", 0), obj.get("y", 0), obj.get("z", 0)) | |
md_outline += f"- {i}. โจ **{obj_type}** at {pos}\n" | |
st.download_button("Download Markdown Save", data=md_outline, file_name=download_name, mime="text/markdown") | |
# --- Process Save Data from JavaScript --- | |
# The JS component will set st.session_state["js_save_data"] to a JSON string when an object is placed. | |
save_data_from_js = st.session_state.get("js_save_data", None) | |
if save_data_from_js: | |
try: | |
payload = json.loads(save_data_from_js) if isinstance(save_data_from_js, str) else save_data_from_js | |
if isinstance(payload, dict) and "playerPosition" in payload and "objectsToSave" in payload: | |
player_pos = payload["playerPosition"] | |
objects = payload["objectsToSave"] | |
add_objects_to_global(objects, player_pos) | |
st.success("Global state updated with new objects.") | |
st.session_state["js_save_data"] = None | |
st.experimental_rerun() | |
except Exception as e: | |
st.error(f"Error processing JS save data: {e}") | |
st.session_state["js_save_data"] = None | |
# --- Inject Global State into JavaScript --- | |
injected_state = { | |
"GLOBAL_STATE": st.session_state["global_state"] | |
} | |
html_file_path = "index.html" | |
try: | |
with open(html_file_path, "r", encoding="utf-8") as f: | |
html_template = f.read() | |
js_injection_script = f""" | |
<script> | |
// Inject the global state from Streamlit into the web app. | |
window.GLOBAL_STATE = {json.dumps(injected_state["GLOBAL_STATE"])}; | |
console.log("Injected Global State:", window.GLOBAL_STATE); | |
</script> | |
""" | |
html_content = html_template.replace("</head>", js_injection_script + "\n</head>", 1) | |
components.html(html_content, height=750, scrolling=True) | |
except Exception as e: | |
st.error(f"Error loading index.html: {e}") | |