awacke1 commited on
Commit
bcaa97a
·
verified ·
1 Parent(s): 5cddd96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -16
app.py CHANGED
@@ -11,6 +11,7 @@ import pandas as pd
11
  import uuid
12
  import math
13
  import time
 
14
 
15
  # Import our GameState class from gamestate.py
16
  from gamestate import GameState
@@ -26,6 +27,63 @@ CSV_COLUMNS = ['obj_id', 'type', 'pos_x', 'pos_y', 'pos_z', 'rot_x', 'rot_y', 'r
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():
31
  """Scans SAVE_DIR for plot files and returns metadata."""
@@ -42,9 +100,9 @@ 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})"
49
  parsed_plots.append({
50
  'id': filename[:-4],
@@ -110,14 +168,14 @@ def save_plot_data(filename, objects_data_list, plot_x_offset, plot_z_offset):
110
  continue
111
 
112
  relative_obj = {
113
- 'obj_id': obj_id,
114
  'type': obj_type,
115
  'pos_x': pos.get('x', 0.0) - plot_x_offset,
116
  'pos_y': pos.get('y', 0.0),
117
  'pos_z': pos.get('z', 0.0) - plot_z_offset,
118
- 'rot_x': rot.get('_x', 0.0),
119
  'rot_y': rot.get('_y', 0.0),
120
- 'rot_z': rot.get('_z', 0.0),
121
  'rot_order': rot.get('_order', 'XYZ')
122
  }
123
  relative_objects.append(relative_obj)
@@ -134,7 +192,6 @@ def save_plot_data(filename, objects_data_list, plot_x_offset, plot_z_offset):
134
  # --- Initialize GameState Singleton ---
135
  @st.cache_resource
136
  def get_game_state():
137
- # This instance is shared across all sessions and reruns.
138
  return GameState(save_dir=SAVE_DIR, csv_filename="world_state.csv")
139
 
140
  game_state = get_game_state()
@@ -147,17 +204,24 @@ if 'new_plot_name' not in st.session_state:
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 = []
157
  for plot in plots_metadata:
158
  all_initial_objects.extend(load_plot_objects(plot['filename'], plot['x_offset'], plot['z_offset']))
159
 
160
- # --- Update GameState with initial objects if empty ---
161
  if not game_state.get_state():
162
  game_state.update_state(all_initial_objects)
163
 
@@ -214,10 +278,12 @@ with st.sidebar:
214
  with open(save_file_path, "w", encoding="utf-8") as f:
215
  json.dump(global_save_data, f, indent=2)
216
  st.success(f"Global state saved to {default_save_name}")
217
- # Also store it in session state for download purposes.
218
  st.session_state.loaded_global_state = global_save_data
 
 
 
 
219
 
220
- # List global save files available in the folder.
221
  st.subheader("📂 Global Saves")
222
  save_files = sorted([f for f in os.listdir(GLOBAL_SAVES_DIR) if f.endswith(".json")])
223
  for file in save_files:
@@ -225,7 +291,6 @@ with st.sidebar:
225
  file_path = os.path.join(GLOBAL_SAVES_DIR, file)
226
  with open(file_path, "r", encoding="utf-8") as f:
227
  loaded_save = json.load(f)
228
- # Update the shared game state with loaded data.
229
  with game_state.lock:
230
  game_state.world_state = loaded_save.get("game_state", [])
231
  st.session_state.player_position = loaded_save.get("player_position", {"x": 0, "y": 0, "z": 0})
@@ -234,7 +299,6 @@ with st.sidebar:
234
 
235
  st.markdown("---")
236
  st.header("Download Global Save as Markdown")
237
- # Use the loaded global state if available; otherwise use a default dictionary.
238
  current_save = st.session_state.get("loaded_global_state")
239
  if current_save is None:
240
  current_save = {"timestamp": "N/A", "game_state": [], "player_position": {"x": 0, "y": 0, "z": 0}}
@@ -284,7 +348,6 @@ if save_data_from_js is not None:
284
  st.success(f"New plot created and saved: {target_filename}")
285
  else:
286
  st.success(f"Updated existing plot: {target_filename}")
287
- # Update shared game state with new objects from this session
288
  game_state.update_state(objects_to_save)
289
  save_processed_successfully = True
290
  else:
@@ -321,8 +384,6 @@ html_content_with_state = None
321
  try:
322
  with open(html_file_path, 'r', encoding='utf-8') as f:
323
  html_template = f.read()
324
-
325
- # Use doubled curly braces for literal braces in the injected JS code.
326
  js_injection_script = f"""
327
  <script>
328
  window.ALL_INITIAL_OBJECTS = {json.dumps(injected_state["ALL_INITIAL_OBJECTS"])};
 
11
  import uuid
12
  import math
13
  import time
14
+ from datetime import datetime
15
 
16
  # Import our GameState class from gamestate.py
17
  from gamestate import GameState
 
27
  os.makedirs(SAVE_DIR, exist_ok=True)
28
  os.makedirs(GLOBAL_SAVES_DIR, exist_ok=True)
29
 
30
+ # --- Helper Functions for Global Save Management ---
31
+
32
+ def load_latest_global_save():
33
+ """
34
+ Load the JSON file with the most recent timestamp from GLOBAL_SAVES_DIR.
35
+ Returns None if no global save exists.
36
+ """
37
+ json_files = [f for f in os.listdir(GLOBAL_SAVES_DIR) if f.endswith(".json")]
38
+ if not json_files:
39
+ return None
40
+ # Assuming filenames contain timestamp info, sort them in reverse order.
41
+ json_files.sort(reverse=True)
42
+ latest_file = json_files[0]
43
+ with open(os.path.join(GLOBAL_SAVES_DIR, latest_file), "r", encoding="utf-8") as f:
44
+ return json.load(f)
45
+
46
+ def consolidate_global_saves():
47
+ """
48
+ Merge all JSON files in GLOBAL_SAVES_DIR into one consolidated file and delete older ones.
49
+ The union of game objects (using unique 'obj_id's) is computed.
50
+ Returns the consolidated state.
51
+ """
52
+ json_files = [f for f in os.listdir(GLOBAL_SAVES_DIR) if f.endswith(".json")]
53
+ if not json_files:
54
+ return None
55
+ merged_state = {"timestamp": "", "game_state": [], "player_position": {"x": 0, "y": 0, "z": 0}}
56
+ # Use a dictionary keyed by obj_id to union objects.
57
+ obj_dict = {}
58
+ latest_timestamp = None
59
+ latest_player_position = {"x": 0, "y": 0, "z": 0}
60
+ for f in json_files:
61
+ with open(os.path.join(GLOBAL_SAVES_DIR, f), "r", encoding="utf-8") as file:
62
+ data = json.load(file)
63
+ for obj in data.get("game_state", []):
64
+ obj_id = obj.get("obj_id")
65
+ if obj_id:
66
+ obj_dict[obj_id] = obj
67
+ file_timestamp = data.get("timestamp")
68
+ if not latest_timestamp or file_timestamp > latest_timestamp:
69
+ latest_timestamp = file_timestamp
70
+ latest_player_position = data.get("player_position", {"x": 0, "y": 0, "z": 0})
71
+ merged_state["timestamp"] = latest_timestamp if latest_timestamp else time.strftime("%Y-%m-%d %H:%M:%S")
72
+ merged_state["game_state"] = list(obj_dict.values())
73
+ merged_state["player_position"] = latest_player_position
74
+ # Write the consolidated state with current timestamp.
75
+ consolidated_filename = f"consolidated_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
76
+ consolidated_path = os.path.join(GLOBAL_SAVES_DIR, consolidated_filename)
77
+ with open(consolidated_path, "w", encoding="utf-8") as f:
78
+ json.dump(merged_state, f, indent=2)
79
+ # Delete all old JSON files except the newly consolidated one.
80
+ for f in json_files:
81
+ try:
82
+ os.remove(os.path.join(GLOBAL_SAVES_DIR, f))
83
+ except Exception as e:
84
+ st.error(f"Error deleting old global save {f}: {e}")
85
+ return merged_state
86
+
87
  @st.cache_data(ttl=3600)
88
  def load_plot_metadata():
89
  """Scans SAVE_DIR for plot files and returns metadata."""
 
100
  parsed_plots = []
101
  for filename in plot_files:
102
  try:
103
+ parts = filename[:-4].split('_')
104
+ grid_x = int(parts[1][1:])
105
+ grid_z = int(parts[2][1:])
106
  plot_name = " ".join(parts[3:]) if len(parts) > 3 else f"Plot ({grid_x},{grid_z})"
107
  parsed_plots.append({
108
  'id': filename[:-4],
 
168
  continue
169
 
170
  relative_obj = {
171
+ 'obj_id': obj_id,
172
  'type': obj_type,
173
  'pos_x': pos.get('x', 0.0) - plot_x_offset,
174
  'pos_y': pos.get('y', 0.0),
175
  'pos_z': pos.get('z', 0.0) - plot_z_offset,
176
+ 'rot_x': rot.get('_x', 0.0),
177
  'rot_y': rot.get('_y', 0.0),
178
+ 'rot_z': rot.get('_z', 0.0),
179
  'rot_order': rot.get('_order', 'XYZ')
180
  }
181
  relative_objects.append(relative_obj)
 
192
  # --- Initialize GameState Singleton ---
193
  @st.cache_resource
194
  def get_game_state():
 
195
  return GameState(save_dir=SAVE_DIR, csv_filename="world_state.csv")
196
 
197
  game_state = get_game_state()
 
204
  if 'js_save_data_result' not in st.session_state:
205
  st.session_state.js_save_data_result = None
206
  if 'player_position' not in st.session_state:
 
207
  st.session_state.player_position = {"x": 0, "y": 0, "z": 0}
208
  if 'loaded_global_state' not in st.session_state:
209
  st.session_state.loaded_global_state = None
210
 
211
+ # --- On Client Start: Load Latest Global Save if Available ---
212
+ latest_global_save = load_latest_global_save()
213
+ if latest_global_save is not None:
214
+ with game_state.lock:
215
+ game_state.world_state = latest_global_save.get("game_state", [])
216
+ st.session_state.player_position = latest_global_save.get("player_position", {"x": 0, "y": 0, "z": 0})
217
+ st.session_state.loaded_global_state = latest_global_save
218
+
219
  plots_metadata = load_plot_metadata()
220
  all_initial_objects = []
221
  for plot in plots_metadata:
222
  all_initial_objects.extend(load_plot_objects(plot['filename'], plot['x_offset'], plot['z_offset']))
223
 
224
+ # If GameState is still empty, update it with initial objects.
225
  if not game_state.get_state():
226
  game_state.update_state(all_initial_objects)
227
 
 
278
  with open(save_file_path, "w", encoding="utf-8") as f:
279
  json.dump(global_save_data, f, indent=2)
280
  st.success(f"Global state saved to {default_save_name}")
 
281
  st.session_state.loaded_global_state = global_save_data
282
+ # Consolidate all global saves into one file and delete older ones.
283
+ consolidated_state = consolidate_global_saves()
284
+ if consolidated_state is not None:
285
+ st.session_state.loaded_global_state = consolidated_state
286
 
 
287
  st.subheader("📂 Global Saves")
288
  save_files = sorted([f for f in os.listdir(GLOBAL_SAVES_DIR) if f.endswith(".json")])
289
  for file in save_files:
 
291
  file_path = os.path.join(GLOBAL_SAVES_DIR, file)
292
  with open(file_path, "r", encoding="utf-8") as f:
293
  loaded_save = json.load(f)
 
294
  with game_state.lock:
295
  game_state.world_state = loaded_save.get("game_state", [])
296
  st.session_state.player_position = loaded_save.get("player_position", {"x": 0, "y": 0, "z": 0})
 
299
 
300
  st.markdown("---")
301
  st.header("Download Global Save as Markdown")
 
302
  current_save = st.session_state.get("loaded_global_state")
303
  if current_save is None:
304
  current_save = {"timestamp": "N/A", "game_state": [], "player_position": {"x": 0, "y": 0, "z": 0}}
 
348
  st.success(f"New plot created and saved: {target_filename}")
349
  else:
350
  st.success(f"Updated existing plot: {target_filename}")
 
351
  game_state.update_state(objects_to_save)
352
  save_processed_successfully = True
353
  else:
 
384
  try:
385
  with open(html_file_path, 'r', encoding='utf-8') as f:
386
  html_template = f.read()
 
 
387
  js_injection_script = f"""
388
  <script>
389
  window.ALL_INITIAL_OBJECTS = {json.dumps(injected_state["ALL_INITIAL_OBJECTS"])};