# 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""" """ html_content = html_template.replace("", js_injection_script + "\n", 1) components.html(html_content, height=750, scrolling=True) except Exception as e: st.error(f"Error loading index.html: {e}")