Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -17,12 +17,14 @@ from gamestate import GameState
|
|
17 |
|
18 |
# --- Constants ---
|
19 |
SAVE_DIR = "saved_worlds"
|
|
|
20 |
PLOT_WIDTH = 50.0 # Width of each plot in 3D space
|
21 |
PLOT_DEPTH = 50.0 # Depth of each plot
|
22 |
CSV_COLUMNS = ['obj_id', 'type', 'pos_x', 'pos_y', 'pos_z', 'rot_x', 'rot_y', 'rot_z', 'rot_order']
|
23 |
|
24 |
-
# --- Ensure
|
25 |
os.makedirs(SAVE_DIR, exist_ok=True)
|
|
|
26 |
|
27 |
@st.cache_data(ttl=3600)
|
28 |
def load_plot_metadata():
|
@@ -40,7 +42,7 @@ def load_plot_metadata():
|
|
40 |
parsed_plots = []
|
41 |
for filename in plot_files:
|
42 |
try:
|
43 |
-
parts = filename[:-4].split('_') # Remove .csv
|
44 |
grid_x = int(parts[1][1:]) # After 'X'
|
45 |
grid_z = int(parts[2][1:]) # After 'Z'
|
46 |
plot_name = " ".join(parts[3:]) if len(parts) > 3 else f"Plot ({grid_x},{grid_z})"
|
@@ -144,6 +146,11 @@ if 'new_plot_name' not in st.session_state:
|
|
144 |
st.session_state.new_plot_name = ""
|
145 |
if 'js_save_data_result' not in st.session_state:
|
146 |
st.session_state.js_save_data_result = None
|
|
|
|
|
|
|
|
|
|
|
147 |
|
148 |
plots_metadata = load_plot_metadata()
|
149 |
all_initial_objects = []
|
@@ -181,7 +188,7 @@ with st.sidebar:
|
|
181 |
st.session_state.selected_object = selected_object_type_widget
|
182 |
|
183 |
st.markdown("---")
|
184 |
-
st.header("Save Work")
|
185 |
st.caption("Saves newly placed objects to the current plot. A new plot file is created for new areas.")
|
186 |
if st.button("💾 Save Current Work", key="save_button"):
|
187 |
from streamlit_js_eval import streamlit_js_eval
|
@@ -189,7 +196,61 @@ with st.sidebar:
|
|
189 |
streamlit_js_eval(js_code=js_get_data_code, key="js_save_processor")
|
190 |
st.rerun()
|
191 |
|
192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
save_data_from_js = st.session_state.get("js_save_processor", None)
|
194 |
if save_data_from_js is not None:
|
195 |
st.info("Received save data from client...")
|
@@ -238,9 +299,9 @@ if save_data_from_js is not None:
|
|
238 |
|
239 |
# --- Main Area ---
|
240 |
st.header("Infinite Shared 3D World")
|
241 |
-
st.caption("Move to empty areas to expand the world. Use the sidebar 'Save' to store your work.")
|
242 |
|
243 |
-
#
|
244 |
injected_state = {
|
245 |
"ALL_INITIAL_OBJECTS": all_initial_objects,
|
246 |
"PLOTS_METADATA": plots_metadata,
|
|
|
17 |
|
18 |
# --- Constants ---
|
19 |
SAVE_DIR = "saved_worlds"
|
20 |
+
GLOBAL_SAVES_DIR = "global_saves" # Folder for global game saves
|
21 |
PLOT_WIDTH = 50.0 # Width of each plot in 3D space
|
22 |
PLOT_DEPTH = 50.0 # Depth of each plot
|
23 |
CSV_COLUMNS = ['obj_id', 'type', 'pos_x', 'pos_y', 'pos_z', 'rot_x', 'rot_y', 'rot_z', 'rot_order']
|
24 |
|
25 |
+
# --- Ensure Required Directories Exist ---
|
26 |
os.makedirs(SAVE_DIR, exist_ok=True)
|
27 |
+
os.makedirs(GLOBAL_SAVES_DIR, exist_ok=True)
|
28 |
|
29 |
@st.cache_data(ttl=3600)
|
30 |
def load_plot_metadata():
|
|
|
42 |
parsed_plots = []
|
43 |
for filename in plot_files:
|
44 |
try:
|
45 |
+
parts = filename[:-4].split('_') # Remove .csv extension
|
46 |
grid_x = int(parts[1][1:]) # After 'X'
|
47 |
grid_z = int(parts[2][1:]) # After 'Z'
|
48 |
plot_name = " ".join(parts[3:]) if len(parts) > 3 else f"Plot ({grid_x},{grid_z})"
|
|
|
146 |
st.session_state.new_plot_name = ""
|
147 |
if 'js_save_data_result' not in st.session_state:
|
148 |
st.session_state.js_save_data_result = None
|
149 |
+
if 'player_position' not in st.session_state:
|
150 |
+
# This can be updated from JS if needed
|
151 |
+
st.session_state.player_position = {"x": 0, "y": 0, "z": 0}
|
152 |
+
if 'loaded_global_state' not in st.session_state:
|
153 |
+
st.session_state.loaded_global_state = None
|
154 |
|
155 |
plots_metadata = load_plot_metadata()
|
156 |
all_initial_objects = []
|
|
|
188 |
st.session_state.selected_object = selected_object_type_widget
|
189 |
|
190 |
st.markdown("---")
|
191 |
+
st.header("Save Work (Per Plot)")
|
192 |
st.caption("Saves newly placed objects to the current plot. A new plot file is created for new areas.")
|
193 |
if st.button("💾 Save Current Work", key="save_button"):
|
194 |
from streamlit_js_eval import streamlit_js_eval
|
|
|
196 |
streamlit_js_eval(js_code=js_get_data_code, key="js_save_processor")
|
197 |
st.rerun()
|
198 |
|
199 |
+
st.markdown("---")
|
200 |
+
st.header("Global Save & Load")
|
201 |
+
# Global Save: Persists the entire game state plus current player position.
|
202 |
+
if st.button("💾 Global Save"):
|
203 |
+
global_save_data = {
|
204 |
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
205 |
+
"game_state": game_state.get_state(),
|
206 |
+
"player_position": st.session_state.get("player_position", {"x": 0, "y": 0, "z": 0})
|
207 |
+
}
|
208 |
+
default_save_name = f"save_{time.strftime('%Y%m%d_%H%M%S')}.json"
|
209 |
+
save_file_path = os.path.join(GLOBAL_SAVES_DIR, default_save_name)
|
210 |
+
with open(save_file_path, "w", encoding="utf-8") as f:
|
211 |
+
json.dump(global_save_data, f, indent=2)
|
212 |
+
st.success(f"Global state saved to {default_save_name}")
|
213 |
+
# Also store it in session state for download purposes.
|
214 |
+
st.session_state.loaded_global_state = global_save_data
|
215 |
+
|
216 |
+
# List global save files available in the folder.
|
217 |
+
st.subheader("📂 Global Saves")
|
218 |
+
save_files = sorted([f for f in os.listdir(GLOBAL_SAVES_DIR) if f.endswith(".json")])
|
219 |
+
for file in save_files:
|
220 |
+
if st.button(f"Load {file}", key=file):
|
221 |
+
file_path = os.path.join(GLOBAL_SAVES_DIR, file)
|
222 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
223 |
+
loaded_save = json.load(f)
|
224 |
+
# Update the shared game state with loaded data.
|
225 |
+
with game_state.lock:
|
226 |
+
game_state.world_state = loaded_save.get("game_state", [])
|
227 |
+
st.session_state.player_position = loaded_save.get("player_position", {"x": 0, "y": 0, "z": 0})
|
228 |
+
st.session_state.loaded_global_state = loaded_save
|
229 |
+
st.success(f"Global state loaded from {file}")
|
230 |
+
|
231 |
+
st.markdown("---")
|
232 |
+
st.header("Download Global Save as Markdown")
|
233 |
+
# Use the loaded global state if available; otherwise, use the last global save data.
|
234 |
+
current_save = st.session_state.get("loaded_global_state", {"timestamp": "N/A", "game_state": [], "player_position": {"x": 0, "y": 0, "z": 0}})
|
235 |
+
# Auto-generate a default markdown file name
|
236 |
+
default_md_name = current_save.get("timestamp", "save").replace(":", "").replace(" ", "_") + ".md"
|
237 |
+
download_name = st.text_input("Override File Name", value=default_md_name)
|
238 |
+
if st.button("Generate Markdown & Download"):
|
239 |
+
md_outline = f"""# Global Save: {download_name}
|
240 |
+
- ⏰ **Timestamp:** {current_save.get("timestamp", "N/A")}
|
241 |
+
- 🎮 **Number of Game Objects:** {len(current_save.get("game_state", []))}
|
242 |
+
- 🧭 **Player Position:** {current_save.get("player_position", {"x":0, "y":0, "z":0})}
|
243 |
+
|
244 |
+
## Game Objects:
|
245 |
+
"""
|
246 |
+
# Optionally, list brief info for each object.
|
247 |
+
for i, obj in enumerate(current_save.get("game_state", []), start=1):
|
248 |
+
obj_type = obj.get("type", "Unknown")
|
249 |
+
pos = (obj.get("pos_x", 0), obj.get("pos_y", 0), obj.get("pos_z", 0))
|
250 |
+
md_outline += f"- {i}. ✨ **{obj_type}** at {pos}\n"
|
251 |
+
st.download_button("Download Markdown Save", data=md_outline, file_name=download_name, mime="text/markdown")
|
252 |
+
|
253 |
+
# --- Process Save Data from JS (Per Plot Save) ---
|
254 |
save_data_from_js = st.session_state.get("js_save_processor", None)
|
255 |
if save_data_from_js is not None:
|
256 |
st.info("Received save data from client...")
|
|
|
299 |
|
300 |
# --- Main Area ---
|
301 |
st.header("Infinite Shared 3D World")
|
302 |
+
st.caption("Move to empty areas to expand the world. Use the sidebar 'Save' controls to store your work.")
|
303 |
|
304 |
+
# Inject state into JS—including the shared GAME_STATE from our GameState singleton.
|
305 |
injected_state = {
|
306 |
"ALL_INITIAL_OBJECTS": all_initial_objects,
|
307 |
"PLOTS_METADATA": plots_metadata,
|