awacke1 commited on
Commit
6aaf265
·
verified ·
1 Parent(s): 6cd5904

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +170 -82
app.py CHANGED
@@ -10,22 +10,27 @@ import base64
10
  from io import BytesIO
11
  import numpy as np
12
 
 
13
  GRID_WIDTH = 16
14
  GRID_HEIGHT = 9
15
- REFRESH_RATE = 10
16
  INTERACTION_RADIUS = 2
17
  POINTS_PER_INTERACTION = 1
18
 
19
- @st.cache_resource
 
20
  def get_game_images():
 
21
  return {
22
  'tile': Image.new('RGB', (50, 50), color='green'),
23
  'player': Image.new('RGB', (50, 50), color='blue'),
24
  'other_player': Image.new('RGB', (50, 50), color='red')
25
  }
26
 
 
27
  @st.cache_data
28
  def get_name_components():
 
29
  return {
30
  'prefixes': ['Aer', 'Bal', 'Cal', 'Dor', 'El', 'Fae', 'Gor', 'Hel', 'Il', 'Jor',
31
  'Kal', 'Lyr', 'Mel', 'Nym', 'Oro', 'Pyr', 'Qar', 'Ryn', 'Syl', 'Tyr'],
@@ -33,8 +38,9 @@ def get_name_components():
33
  'is', 'ax', 'on', 'ir', 'ex', 'az', 'er', 'eth', 'ys', 'ix']
34
  }
35
 
36
- @st.cache_data(ttl=2)
37
  def load_all_players(timestamp):
 
38
  players = {}
39
  if os.path.exists('players'):
40
  for filename in os.listdir('players'):
@@ -48,90 +54,166 @@ def load_all_players(timestamp):
48
  continue
49
  return {'players': players, 'last_update': time.time()}
50
 
51
- def create_sync_mechanism():
52
- sync_js = """
53
- <script>
54
- function updateGameState() {
55
- const timestamp = Date.now();
56
- window.parent.document.querySelector('.stApp').classList.add('refreshing');
57
- setTimeout(() => window.location.reload(), 100);
58
- }
59
-
60
- function displayCharacterSheet(playerData) {
61
- const sidebar = window.parent.document.querySelector('[data-testid="stSidebar"]');
62
- if (sidebar && playerData) {
63
- const statsDiv = document.createElement('div');
64
- statsDiv.innerHTML = `
65
- <div style="padding: 10px; margin: 5px; border: 1px solid #ccc; border-radius: 5px;">
66
- <h3>${playerData.name}</h3>
67
- <p>Score: ${playerData.stats.score}</p>
68
- <p>HP: ${playerData.stats.HP}/${playerData.stats.MAX_HP}</p>
69
- <p>STR: ${playerData.stats.STR}</p>
70
- <p>DEX: ${playerData.stats.DEX}</p>
71
- <p>CON: ${playerData.stats.CON}</p>
72
- <p>INT: ${playerData.stats.INT}</p>
73
- <p>WIS: ${playerData.stats.WIS}</p>
74
- <p>CHA: ${playerData.stats.CHA}</p>
75
- </div>
76
- `;
77
- }
78
- }
79
-
80
- function startSync() {
81
- // Update game state every 10 seconds
82
- setInterval(updateGameState, 10000);
83
-
84
- // Handle visibility changes
85
- document.addEventListener('visibilitychange', function() {
86
- if (!document.hidden) {
87
- updateGameState();
88
- }
89
- });
90
-
91
- // Handle focus changes
92
- window.addEventListener('focus', updateGameState);
93
- }
94
-
95
- if (document.readyState === 'complete') {
96
- startSync();
97
- } else {
98
- window.addEventListener('load', startSync);
99
- }
100
- </script>
101
- """
102
- st.components.v1.html(sync_js, height=0)
103
-
104
- def show_character_sheet(player_data):
105
- st.sidebar.markdown("### Character Sheet")
106
- st.sidebar.markdown(f"""
107
- **Name:** {player_data['name']}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- **Stats:**
110
- - HP: {player_data['stats']['HP']}/{player_data['stats']['MAX_HP']}
111
- - STR: {player_data['stats']['STR']}
112
- - DEX: {player_data['stats']['DEX']}
113
- - CON: {player_data['stats']['CON']}
114
- - INT: {player_data['stats']['INT']}
115
- - WIS: {player_data['stats']['WIS']}
116
- - CHA: {player_data['stats']['CHA']}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- **Score:** {player_data['stats']['score']}
119
- """)
120
 
121
- [Previous functions remain the same: generate_fantasy_name, load_game_state, save_game_state, calculate_distance, update_position, etc.]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  def main():
124
- create_sync_mechanism()
125
  update_nearby_players()
126
 
 
127
  st.sidebar.title("Player Info")
128
 
129
- current_time = time.time()
130
- all_players = load_all_players(current_time)['players']
131
-
132
  if st.session_state.player_name is None:
133
  default_name = generate_fantasy_name()
134
- player_name = st.sidebar.text_input("Enter your name or use generated name:", value=default_name)
 
135
  if st.sidebar.button("Start Playing"):
136
  st.session_state.player_name = player_name
137
  if st.session_state.character_stats is None:
@@ -150,13 +232,14 @@ def main():
150
  save_player_state()
151
  st.rerun()
152
  else:
153
- show_character_sheet({'name': st.session_state.player_name, 'stats': st.session_state.character_stats})
154
-
155
- st.sidebar.markdown("### Other Players")
156
- for player_name, player_data in all_players.items():
157
- if player_name != st.session_state.player_name:
158
- show_character_sheet(player_data)
159
 
 
160
  st.sidebar.markdown("### Movement Controls")
161
  move_cols = st.sidebar.columns(3)
162
  if move_cols[1].button("⬆️", key="up"):
@@ -173,14 +256,19 @@ def main():
173
  update_position("right")
174
  st.rerun()
175
 
 
176
  if st.sidebar.button("Clear Game State"):
177
  st.query_params.clear()
178
  clear_caches()
179
  st.rerun()
180
 
 
181
  st.title("Multiplayer Tile Game")
 
 
182
  create_game_board()
183
 
 
184
  if (time.time() - st.session_state.last_move) > REFRESH_RATE:
185
  st.session_state.last_move = time.time()
186
  save_player_state()
 
10
  from io import BytesIO
11
  import numpy as np
12
 
13
+ # Constants
14
  GRID_WIDTH = 16
15
  GRID_HEIGHT = 9
16
+ REFRESH_RATE = 5
17
  INTERACTION_RADIUS = 2
18
  POINTS_PER_INTERACTION = 1
19
 
20
+ # Resource cache for game resources - using cache_resource for mutable objects
21
+ @st.cache_resource(show_spinner="Loading game resources...")
22
  def get_game_images():
23
+ """Cache game images to improve performance"""
24
  return {
25
  'tile': Image.new('RGB', (50, 50), color='green'),
26
  'player': Image.new('RGB', (50, 50), color='blue'),
27
  'other_player': Image.new('RGB', (50, 50), color='red')
28
  }
29
 
30
+ # Data cache for immutable data
31
  @st.cache_data
32
  def get_name_components():
33
+ """Cache name generation components"""
34
  return {
35
  'prefixes': ['Aer', 'Bal', 'Cal', 'Dor', 'El', 'Fae', 'Gor', 'Hel', 'Il', 'Jor',
36
  'Kal', 'Lyr', 'Mel', 'Nym', 'Oro', 'Pyr', 'Qar', 'Ryn', 'Syl', 'Tyr'],
 
38
  'is', 'ax', 'on', 'ir', 'ex', 'az', 'er', 'eth', 'ys', 'ix']
39
  }
40
 
41
+ @st.cache_data(ttl=5) # Cache for 5 seconds to match refresh rate
42
  def load_all_players(timestamp):
43
+ """Cache and load all player states with TTL"""
44
  players = {}
45
  if os.path.exists('players'):
46
  for filename in os.listdir('players'):
 
54
  continue
55
  return {'players': players, 'last_update': time.time()}
56
 
57
+ def clear_caches():
58
+ """Clear all caches properly"""
59
+ st.cache_resource.clear()
60
+ st.cache_data.clear()
61
+
62
+ def generate_fantasy_name():
63
+ """Generate random fantasy name using cached components"""
64
+ name_parts = get_name_components()
65
+ return random.choice(name_parts['prefixes']) + random.choice(name_parts['suffixes'])
66
+
67
+ def load_game_state():
68
+ """Load game state from query parameters"""
69
+ if 'player_name' in st.query_params:
70
+ st.session_state.player_name = st.query_params.player_name
71
+ st.session_state.position = {
72
+ 'x': int(st.query_params.get('x', random.randint(0, GRID_WIDTH - 1))),
73
+ 'y': int(st.query_params.get('y', random.randint(0, GRID_HEIGHT - 1)))
74
+ }
75
+ st.session_state.character_stats = {
76
+ 'STR': int(st.query_params.get('STR', 10)),
77
+ 'DEX': int(st.query_params.get('DEX', 10)),
78
+ 'CON': int(st.query_params.get('CON', 10)),
79
+ 'INT': int(st.query_params.get('INT', 10)),
80
+ 'WIS': int(st.query_params.get('WIS', 10)),
81
+ 'CHA': int(st.query_params.get('CHA', 10)),
82
+ 'HP': int(st.query_params.get('HP', 20)),
83
+ 'MAX_HP': int(st.query_params.get('MAX_HP', 40)),
84
+ 'score': int(st.query_params.get('score', 0)),
85
+ 'created_at': float(st.query_params.get('created_at', time.time()))
86
+ }
87
+
88
+ def save_game_state():
89
+ """Save game state to query parameters"""
90
+ if st.session_state.player_name and st.session_state.character_stats:
91
+ params = {
92
+ 'player_name': st.session_state.player_name,
93
+ 'x': str(st.session_state.position['x']),
94
+ 'y': str(st.session_state.position['y']),
95
+ **{k: str(v) for k, v in st.session_state.character_stats.items()}
96
+ }
97
+ st.query_params.from_dict(params)
98
+
99
+ # Initialize session state
100
+ if 'initialized' not in st.session_state:
101
+ st.session_state.initialized = False
102
+ st.session_state.game_state = {
103
+ 'players': {},
104
+ 'chat_messages': [],
105
+ 'last_sync': time.time()
106
+ }
107
+ st.session_state.player_name = None
108
+ st.session_state.character_stats = None
109
+ st.session_state.position = {
110
+ 'x': random.randint(0, GRID_WIDTH - 1),
111
+ 'y': random.randint(0, GRID_HEIGHT - 1)
112
+ }
113
+ st.session_state.last_move = time.time()
114
+ st.session_state.nearby_players = []
115
+ load_game_state()
116
+
117
+ def save_player_state():
118
+ """Save player state to JSON file"""
119
+ if st.session_state.player_name:
120
+ player_data = {
121
+ 'name': st.session_state.player_name,
122
+ 'position': st.session_state.position,
123
+ 'stats': st.session_state.character_stats,
124
+ 'last_update': time.time()
125
+ }
126
+
127
+ os.makedirs('players', exist_ok=True)
128
+ with open(f"players/{st.session_state.player_name}.json", 'w') as f:
129
+ json.dump(player_data, f)
130
+ save_game_state()
131
+
132
+ def calculate_distance(pos1, pos2):
133
+ """Calculate Manhattan distance between two positions with wrapping"""
134
+ dx = min(abs(pos1['x'] - pos2['x']), GRID_WIDTH - abs(pos1['x'] - pos2['x']))
135
+ dy = min(abs(pos1['y'] - pos2['y']), GRID_HEIGHT - abs(pos1['y'] - pos2['y']))
136
+ return dx + dy
137
+
138
+ def update_position(direction):
139
+ """Update player position with wrapping"""
140
+ if direction == "up":
141
+ st.session_state.position['y'] = (st.session_state.position['y'] - 1) % GRID_HEIGHT
142
+ elif direction == "down":
143
+ st.session_state.position['y'] = (st.session_state.position['y'] + 1) % GRID_HEIGHT
144
+ elif direction == "left":
145
+ st.session_state.position['x'] = (st.session_state.position['x'] - 1) % GRID_WIDTH
146
+ elif direction == "right":
147
+ st.session_state.position['x'] = (st.session_state.position['x'] + 1) % GRID_WIDTH
148
 
149
+ st.session_state.last_move = time.time()
150
+ save_player_state()
151
+
152
+ def update_nearby_players():
153
+ """Update list of nearby players and calculate score gains"""
154
+ current_time = time.time()
155
+ all_players = load_all_players(current_time)['players']
156
+ nearby = []
157
+ score_gain = 0
158
+
159
+ for player_name, player_data in all_players.items():
160
+ if player_name != st.session_state.player_name:
161
+ distance = calculate_distance(st.session_state.position, player_data['position'])
162
+ if distance <= INTERACTION_RADIUS:
163
+ nearby.append({
164
+ 'name': player_name,
165
+ 'distance': distance,
166
+ 'score': player_data['stats']['score']
167
+ })
168
+ score_gain += POINTS_PER_INTERACTION
169
+
170
+ if score_gain > 0:
171
+ st.session_state.character_stats['score'] += score_gain
172
+ save_player_state()
173
 
174
+ st.session_state.nearby_players = nearby
 
175
 
176
+ def create_game_board():
177
+ """Create and display the game board"""
178
+ current_time = time.time()
179
+ all_players = load_all_players(current_time)['players']
180
+ images = get_game_images()
181
+
182
+ # Create columns for each row
183
+ for y in range(GRID_HEIGHT):
184
+ cols = st.columns(GRID_WIDTH)
185
+ for x in range(GRID_WIDTH):
186
+ current_pos = {'x': x, 'y': y}
187
+
188
+ # Check if any player is on this tile
189
+ player_here = None
190
+ for player_name, player_data in all_players.items():
191
+ if (player_data['position']['x'] == x and
192
+ player_data['position']['y'] == y):
193
+ player_here = player_name
194
+
195
+ # Display appropriate image and tooltip
196
+ if x == st.session_state.position['x'] and y == st.session_state.position['y']:
197
+ cols[x].image(images['player'], use_column_width=True)
198
+ cols[x].markdown(f"**You** ({st.session_state.character_stats['score']} pts)")
199
+ elif player_here:
200
+ cols[x].image(images['other_player'], use_column_width=True)
201
+ cols[x].markdown(f"**{player_here}** ({all_players[player_here]['stats']['score']} pts)")
202
+ else:
203
+ cols[x].image(images['tile'], use_column_width=True)
204
 
205
  def main():
206
+ # Update nearby players on refresh
207
  update_nearby_players()
208
 
209
+ # Sidebar for player info and controls
210
  st.sidebar.title("Player Info")
211
 
212
+ # Player name handling
 
 
213
  if st.session_state.player_name is None:
214
  default_name = generate_fantasy_name()
215
+ player_name = st.sidebar.text_input("Enter your name or use generated name:",
216
+ value=default_name)
217
  if st.sidebar.button("Start Playing"):
218
  st.session_state.player_name = player_name
219
  if st.session_state.character_stats is None:
 
232
  save_player_state()
233
  st.rerun()
234
  else:
235
+ # Display nearby players
236
+ st.sidebar.markdown("### Nearby Players")
237
+ for player in st.session_state.nearby_players:
238
+ st.sidebar.markdown(
239
+ f"**{player['name']}** - {player['distance']} tiles away - {player['score']} pts"
240
+ )
241
 
242
+ # Movement controls
243
  st.sidebar.markdown("### Movement Controls")
244
  move_cols = st.sidebar.columns(3)
245
  if move_cols[1].button("⬆️", key="up"):
 
256
  update_position("right")
257
  st.rerun()
258
 
259
+ # Clear game state button
260
  if st.sidebar.button("Clear Game State"):
261
  st.query_params.clear()
262
  clear_caches()
263
  st.rerun()
264
 
265
+ # Main game area
266
  st.title("Multiplayer Tile Game")
267
+
268
+ # Display game board
269
  create_game_board()
270
 
271
+ # Auto-refresh logic
272
  if (time.time() - st.session_state.last_move) > REFRESH_RATE:
273
  st.session_state.last_move = time.time()
274
  save_player_state()