awacke1 commited on
Commit
d812df4
·
verified ·
1 Parent(s): c0ad2f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -219
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import streamlit as st
2
- import streamlit.components.v1 as components
3
  import os
4
  import random
5
  import time
@@ -10,241 +9,212 @@ from pathlib import Path
10
  import base64
11
  from io import BytesIO
12
 
13
- # Initialize session state variables
14
- if 'game_state' not in st.session_state:
 
 
 
 
 
 
 
 
 
15
  st.session_state.game_state = {
16
  'players': {},
17
  'chat_messages': [],
18
  'tile_map': [],
19
- 'current_player': None
20
  }
21
-
22
- if 'player_name' not in st.session_state:
23
  st.session_state.player_name = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- if 'last_refresh' not in st.session_state:
26
- st.session_state.last_refresh = time.time()
27
-
28
- if 'start_time' not in st.session_state:
29
- st.session_state.start_time = None
30
-
31
- if 'play_duration' not in st.session_state:
32
- st.session_state.play_duration = 0
33
-
34
- # Utility functions
35
- def load_tiles_and_text():
36
- """Load tile images and their corresponding markdown text from the root directory"""
37
- tiles = {}
38
- for file in os.listdir('.'):
39
- if file.endswith(('.png', '.jpg', '.jpeg')):
40
- tile_name = os.path.splitext(file)[0]
41
- # Load image
42
- img = Image.open(file)
43
- # Convert image to base64 for HTML embedding
44
- buffered = BytesIO()
45
- img.save(buffered, format="PNG")
46
- img_str = base64.b64encode(buffered.getvalue()).decode()
47
-
48
- # Check for corresponding markdown file
49
- md_file = f"{tile_name}.md"
50
- overlay_text = ""
51
- if os.path.exists(md_file):
52
- with open(md_file, 'r') as f:
53
- overlay_text = f.read().strip()
54
-
55
- tiles[tile_name] = {
56
- 'image': img_str,
57
- 'text': overlay_text
58
- }
59
- return tiles
60
-
61
- def generate_map(width=20, height=15):
62
- """Generate a random tile map"""
63
- tile_types = ['grass', 'water', 'rock']
64
- return [[random.choice(tile_types) for _ in range(width)] for _ in range(height)]
65
-
66
- def create_tile_html(tile_data, size=60):
67
- """Create HTML for a single tile with text overlay"""
68
- return f"""
69
- <div style="position: relative; width: {size}px; height: {size}px; display: inline-block;">
70
- <img src="data:image/png;base64,{tile_data['image']}"
71
- style="width: {size}px; height: {size}px; object-fit: cover;">
72
- <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;
73
- background-color: rgba(255, 255, 255, 0.7);
74
- display: flex; align-items: center; justify-content: center;
75
- text-align: center; font-size: 10px; padding: 2px;
76
- opacity: 0.8; pointer-events: none;">
77
- {tile_data['text']}
78
- </div>
79
- </div>
80
- """
81
-
82
- def create_game_board_html(tile_map, tiles, players):
83
- """Create HTML for the entire game board"""
84
- board_html = """
85
- <style>
86
- .game-board { line-height: 0; }
87
- .player-marker {
88
- position: absolute;
89
- width: 10px;
90
- height: 10px;
91
- background-color: red;
92
- border-radius: 50%;
93
- z-index: 2;
94
- }
95
- </style>
96
- <div class="game-board">
97
- """
98
 
99
- for y, row in enumerate(tile_map):
100
- for x, tile_type in enumerate(row):
101
- if tile_type in tiles:
102
- board_html += create_tile_html(tiles[tile_type])
103
- board_html += "<br>"
104
 
105
- # Add player markers
106
- for player_name, player_data in players.items():
107
- pos_x = player_data['position']['x'] * 60 + 25
108
- pos_y = player_data['position']['y'] * 60 + 25
109
- board_html += f"""
110
- <div class="player-marker" style="left: {pos_x}px; top: {pos_y}px;"
111
- title="{player_name}"></div>
112
- """
 
 
 
 
 
 
 
 
 
 
113
 
114
- board_html += "</div>"
115
- return board_html
116
-
117
- def save_game_state():
118
- """Save the current game state to a file"""
119
- state_file = Path("game_state.json")
120
- state_to_save = {
121
- 'players': st.session_state.game_state['players'],
122
- 'chat_messages': st.session_state.game_state['chat_messages'],
123
- 'tile_map': st.session_state.game_state['tile_map']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
- with open(state_file, 'w') as f:
126
- json.dump(state_to_save, f)
127
-
128
- def load_game_state():
129
- """Load the game state from file"""
130
- state_file = Path("game_state.json")
131
- if state_file.exists():
132
- with open(state_file, 'r') as f:
133
- loaded_state = json.load(f)
134
- st.session_state.game_state.update(loaded_state)
135
-
136
- def add_chat_message(player_name, message):
137
- """Add a message to the chat history"""
138
- timestamp = datetime.now().strftime("%H:%M:%S")
139
- st.session_state.game_state['chat_messages'].append({
140
- 'player': player_name,
141
- 'message': message,
142
- 'timestamp': timestamp
143
- })
144
- save_game_state()
145
-
146
- def format_duration(seconds):
147
- """Format duration in seconds to HH:MM:SS"""
148
- hours = seconds // 3600
149
- minutes = (seconds % 3600) // 60
150
- seconds = seconds % 60
151
- return f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}"
152
-
153
- def check_auto_refresh():
154
- """Check if it's time to refresh the page"""
155
- current_time = time.time()
156
- if current_time - st.session_state.last_refresh >= 5:
157
- st.session_state.last_refresh = current_time
158
- st.rerun()
159
-
160
- def update_play_duration():
161
- """Update the player's total play duration"""
162
- if st.session_state.start_time is not None:
163
- current_time = time.time()
164
- st.session_state.play_duration = int(current_time - st.session_state.start_time)
165
-
166
- # Main game UI
167
- def main():
168
- st.title("Multiplayer Tile Game")
169
-
170
- if st.session_state.player_name is None:
171
- with st.form("join_game"):
172
- player_name = st.text_input("Enter your name:")
173
- submitted = st.form_submit_button("Join Game")
174
- if submitted and player_name:
175
- st.session_state.player_name = player_name
176
- st.session_state.start_time = time.time()
177
- st.session_state.play_duration = 0
178
- st.session_state.game_state['players'][player_name] = {
179
- 'position': {'x': 0, 'y': 0},
180
- 'last_active': time.time(),
181
- 'score': 0
182
- }
183
- st.rerun()
184
-
185
- else:
186
- update_play_duration()
187
- st.sidebar.metric("Play Time", format_duration(st.session_state.play_duration))
188
 
189
- col1, col2 = st.columns([2, 1])
 
190
 
191
- with col1:
192
- st.subheader("Game Map")
193
- if not st.session_state.game_state['tile_map']:
194
- st.session_state.game_state['tile_map'] = generate_map()
195
-
196
- tiles = load_tiles_and_text()
197
- if tiles:
198
- # Generate and display the game board
199
- board_html = create_game_board_html(
200
- st.session_state.game_state['tile_map'],
201
- tiles,
202
- st.session_state.game_state['players']
203
- )
204
- components.html(board_html, height=600)
205
-
206
- # Movement controls
207
- if st.session_state.player_name:
208
- player = st.session_state.game_state['players'][st.session_state.player_name]
209
-
210
- cols = st.columns(4)
211
- if cols[0].button("←"):
212
- player['position']['x'] = max(0, player['position']['x'] - 1)
213
- if cols[1].button("↑"):
214
- player['position']['y'] = max(0, player['position']['y'] - 1)
215
- if cols[2].button("↓"):
216
- player['position']['y'] = min(14, player['position']['y'] + 1)
217
- if cols[3].button("→"):
218
- player['position']['x'] = min(19, player['position']['x'] + 1)
219
-
220
- st.write(f"Position: ({player['position']['x']}, {player['position']['y']})")
221
- else:
222
- st.warning("No image files found. Please add .png files and optional .md files with matching names.")
223
 
224
- with col2:
225
- st.subheader("Chat Room")
226
- chat_container = st.container()
227
- with chat_container:
228
- for message in st.session_state.game_state['chat_messages'][-50:]:
229
- st.text(f"[{message['timestamp']}] {message['player']}: {message['message']}")
230
-
231
- with st.form("chat_form", clear_on_submit=True):
232
- message = st.text_input("Message:")
233
- if st.form_submit_button("Send") and message:
234
- add_chat_message(st.session_state.player_name, message)
235
- st.rerun()
236
 
237
- if st.button("Leave Game"):
238
- if st.session_state.player_name in st.session_state.game_state['players']:
239
- del st.session_state.game_state['players'][st.session_state.player_name]
240
- st.session_state.player_name = None
241
- st.session_state.start_time = None
242
- st.session_state.play_duration = 0
243
- save_game_state()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  st.rerun()
245
 
246
- check_auto_refresh()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  if __name__ == "__main__":
249
- load_game_state()
250
  main()
 
1
  import streamlit as st
 
2
  import os
3
  import random
4
  import time
 
9
  import base64
10
  from io import BytesIO
11
 
12
+ # Fantasy name generator
13
+ def generate_fantasy_name():
14
+ prefixes = ['Aer', 'Bal', 'Cal', 'Dor', 'El', 'Fae', 'Gor', 'Hel', 'Il', 'Jor',
15
+ 'Kal', 'Lyr', 'Mel', 'Nym', 'Oro', 'Pyr', 'Qar', 'Ryn', 'Syl', 'Tyr']
16
+ suffixes = ['ian', 'or', 'ion', 'us', 'ix', 'ar', 'en', 'yr', 'el', 'an',
17
+ 'is', 'ax', 'on', 'ir', 'ex', 'az', 'er', 'eth', 'ys', 'ix']
18
+ return random.choice(prefixes) + random.choice(suffixes)
19
+
20
+ # Initialize session state
21
+ if 'initialized' not in st.session_state:
22
+ st.session_state.initialized = False
23
  st.session_state.game_state = {
24
  'players': {},
25
  'chat_messages': [],
26
  'tile_map': [],
27
+ 'last_sync': time.time()
28
  }
 
 
29
  st.session_state.player_name = None
30
+ st.session_state.character_stats = None
31
+
32
+ # Character Stats Generation
33
+ def roll_stats():
34
+ return {
35
+ 'STR': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
36
+ 'DEX': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
37
+ 'CON': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
38
+ 'INT': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
39
+ 'WIS': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
40
+ 'CHA': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]),
41
+ 'HP': random.randint(1, 20) * 2 + random.randint(1, 20),
42
+ 'MAX_HP': 40,
43
+ 'score': 0,
44
+ 'created_at': time.time()
45
+ }
46
 
47
+ def save_character_sheet(player_name, stats):
48
+ """Save character sheet as markdown"""
49
+ filepath = f"characters/{player_name}.md"
50
+ os.makedirs('characters', exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ markdown = f"""# {player_name}'s Character Sheet
 
 
 
 
53
 
54
+ ## Stats
55
+ - **STR**: {stats['STR']}
56
+ - **DEX**: {stats['DEX']}
57
+ - **CON**: {stats['CON']}
58
+ - **INT**: {stats['INT']}
59
+ - **WIS**: {stats['WIS']}
60
+ - **CHA**: {stats['CHA']}
61
+
62
+ ## Health
63
+ HP: {stats['HP']}/{stats['MAX_HP']}
64
+
65
+ ## Score
66
+ Current Score: {stats['score']}
67
+
68
+ ## Session Info
69
+ Created: {datetime.fromtimestamp(stats['created_at']).strftime('%Y-%m-%d %H:%M:%S')}
70
+ Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
71
+ """
72
 
73
+ with open(filepath, 'w') as f:
74
+ f.write(markdown)
75
+
76
+ def load_character_sheet(player_name):
77
+ """Load character sheet if it exists"""
78
+ filepath = f"characters/{player_name}.md"
79
+ if os.path.exists(filepath):
80
+ with open(filepath, 'r') as f:
81
+ return f.read()
82
+ return None
83
+
84
+ def create_game_js():
85
+ """Create JavaScript for real-time game updates"""
86
+ return """
87
+ <script>
88
+ const gameState = {
89
+ playerName: null,
90
+ position: { x: 0, y: 0 },
91
+ velocity: { x: 0, y: 0 },
92
+ lastUpdate: Date.now(),
93
+ needsSync: false
94
+ };
95
+
96
+ function updateClock() {
97
+ const now = new Date();
98
+ document.getElementById('game-clock').textContent =
99
+ now.toLocaleTimeString();
100
  }
101
+
102
+ function handleMovement(e) {
103
+ const speed = 5;
104
+ switch(e.key.toLowerCase()) {
105
+ case 'w': case 'arrowup':
106
+ gameState.velocity.y = -speed;
107
+ break;
108
+ case 's': case 'arrowdown':
109
+ gameState.velocity.y = speed;
110
+ break;
111
+ case 'a': case 'arrowleft':
112
+ gameState.velocity.x = -speed;
113
+ break;
114
+ case 'd': case 'arrowright':
115
+ gameState.velocity.x = speed;
116
+ break;
117
+ case 'x':
118
+ gameState.velocity = { x: 0, y: 0 };
119
+ break;
120
+ }
121
+ gameState.needsSync = true;
122
+ }
123
+
124
+ function updatePosition() {
125
+ const now = Date.now();
126
+ const delta = (now - gameState.lastUpdate) / 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ gameState.position.x += gameState.velocity.x * delta;
129
+ gameState.position.y += gameState.velocity.y * delta;
130
 
131
+ // Boundary checks
132
+ gameState.position.x = Math.max(0, Math.min(gameState.position.x, 19));
133
+ gameState.position.y = Math.max(0, Math.min(gameState.position.y, 14));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ gameState.lastUpdate = now;
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ // Update player marker position
138
+ const marker = document.querySelector('.player-marker');
139
+ if (marker) {
140
+ marker.style.left = `${gameState.position.x * 60 + 25}px`;
141
+ marker.style.top = `${gameState.position.y * 60 + 25}px`;
142
+ }
143
+ }
144
+
145
+ function syncWithServer() {
146
+ if (gameState.needsSync) {
147
+ // Send position to Streamlit
148
+ window.parent.postMessage({
149
+ type: 'streamlit:sync',
150
+ position: gameState.position
151
+ }, '*');
152
+ gameState.needsSync = false;
153
+ }
154
+ }
155
+
156
+ // Initialize game loop
157
+ setInterval(updatePosition, 16); // ~60 FPS
158
+ setInterval(syncWithServer, 5000); // Sync every 5 seconds
159
+ setInterval(updateClock, 1000); // Update clock every second
160
+
161
+ // Set up event listeners
162
+ document.addEventListener('keydown', handleMovement);
163
+ </script>
164
+ """
165
+
166
+ def main():
167
+ # Sidebar for player info and controls
168
+ st.sidebar.title("Player Info")
169
+
170
+ # Player name handling
171
+ if st.session_state.player_name is None:
172
+ default_name = generate_fantasy_name()
173
+ player_name = st.sidebar.text_input("Enter your name or use generated name:",
174
+ value=default_name)
175
+ if st.sidebar.button("Start Playing"):
176
+ st.session_state.player_name = player_name
177
+ if st.session_state.character_stats is None:
178
+ st.session_state.character_stats = roll_stats()
179
+ save_character_sheet(player_name, st.session_state.character_stats)
180
+ st.rerun()
181
+ else:
182
+ # Show current name and allow changes
183
+ new_name = st.sidebar.text_input("Your name:",
184
+ value=st.session_state.player_name)
185
+ if new_name != st.session_state.player_name:
186
+ old_name = st.session_state.player_name
187
+ st.session_state.player_name = new_name
188
+ # Rename character sheet
189
+ os.rename(f"characters/{old_name}.md",
190
+ f"characters/{new_name}.md")
191
  st.rerun()
192
 
193
+ # Display character sheet
194
+ character_sheet = load_character_sheet(st.session_state.player_name)
195
+ if character_sheet:
196
+ st.sidebar.markdown(character_sheet)
197
+
198
+ # Movement controls with emojis
199
+ st.sidebar.markdown("### Movement Controls")
200
+ move_cols = st.sidebar.columns(3)
201
+ move_cols[1].button("⬆️", key="up")
202
+ cols = st.sidebar.columns(3)
203
+ cols[0].button("⬅️", key="left")
204
+ cols[1].button("⬇️", key="down")
205
+ cols[2].button("➡️", key="right")
206
+ st.sidebar.button("🛑", key="stop")
207
+
208
+ # Main game area
209
+ st.title("Multiplayer Tile Game")
210
+
211
+ # Game clock
212
+ st.markdown('<div id="game-clock" style="font-size: 24px;"></div>',
213
+ unsafe_allow_html=True)
214
+
215
+ # Game board and JavaScript
216
+ if st.session_state.player_name:
217
+ st.components.v1.html(create_game_js(), height=600)
218
 
219
  if __name__ == "__main__":
 
220
  main()