DarshanScripts commited on
Commit
50cc6ab
ยท
verified ยท
1 Parent(s): bbb09fe

Upload stratego\web\components\interactive_board.py with huggingface_hub

Browse files
stratego//web//components//interactive_board.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Interactive board component - Chess-like click-to-move interface"""
2
+ import streamlit as st
3
+ from stratego.utils.parsing import extract_legal_moves
4
+
5
+
6
+ # Piece emoji mapping
7
+ PIECE_EMOJI = {
8
+ "fl": "๐Ÿšฉ", # Flag
9
+ "bm": "๐Ÿ’ฃ", # Bomb
10
+ "ms": "๐Ÿ‘‘", # Marshal
11
+ "gn": "โญ", # General
12
+ "mn": "โ›๏ธ", # Miner
13
+ "sc": "๐Ÿƒ", # Scout
14
+ "sp": "๐Ÿ•ต๏ธ", # Spy
15
+ "sr": "๐Ÿช–", # Sergeant
16
+ "lt": "๐Ÿ“", # Lieutenant
17
+ "cp": "๐ŸŽ–๏ธ", # Captain
18
+ "mx": "โ“", # Unknown enemy
19
+ ".": " ", # Empty
20
+ "~": "๐Ÿ’ง", # Lake
21
+ }
22
+
23
+
24
+ def render_interactive_board(game_controller):
25
+ """
26
+ Render an interactive chess-like board where:
27
+ 1. Click a piece to select it
28
+ 2. See available moves highlighted
29
+ 3. Click destination to move
30
+ """
31
+ if not game_controller:
32
+ st.error("Game not initialized")
33
+ return None
34
+
35
+ board = game_controller.get_board_display()
36
+ lines = board.split("\n")
37
+
38
+ # Find where board starts
39
+ board_start = 0
40
+ for i, line in enumerate(lines):
41
+ if line.strip().startswith("0"):
42
+ board_start = i + 1
43
+ break
44
+
45
+ board_lines = lines[board_start:]
46
+
47
+ # Extract board data
48
+ size = game_controller.size
49
+ board_data = {}
50
+
51
+ for line in board_lines:
52
+ if not line.strip() or len(line) < 3:
53
+ continue
54
+ row_label = line[0] if line and line[0].isalpha() else None
55
+ if not row_label:
56
+ continue
57
+
58
+ rest = line[2:].strip()
59
+ pieces = rest.split()
60
+
61
+ if len(pieces) >= size:
62
+ for col, piece in enumerate(pieces[:size]):
63
+ board_data[(row_label, col)] = piece
64
+
65
+ # Initialize session state for piece selection
66
+ if "selected_piece" not in st.session_state:
67
+ st.session_state.selected_piece = None
68
+ if "available_moves_for_piece" not in st.session_state:
69
+ st.session_state.available_moves_for_piece = []
70
+
71
+ st.markdown("## โš”๏ธ Stratego Battle Board")
72
+ st.markdown(
73
+ "*Click a piece to select it, then click a highlighted square to move*"
74
+ )
75
+
76
+ # Get all legal moves for reference
77
+ all_legal_moves = game_controller.get_legal_moves()
78
+
79
+ # Create board HTML
80
+ html = create_interactive_board_html(
81
+ board_data,
82
+ size,
83
+ game_controller,
84
+ st.session_state.selected_piece,
85
+ st.session_state.available_moves_for_piece,
86
+ all_legal_moves
87
+ )
88
+
89
+ st.markdown(html, unsafe_allow_html=True)
90
+
91
+ # Handle piece/move selection with buttons
92
+ selected_move = handle_board_interaction(
93
+ board_data, size, game_controller, all_legal_moves
94
+ )
95
+
96
+ return selected_move
97
+
98
+
99
+ def create_interactive_board_html(
100
+ board_data, size, game_controller, selected_piece, available_moves, all_legal_moves
101
+ ) -> str:
102
+ """Create beautiful interactive board HTML"""
103
+
104
+ html = """
105
+ <style>
106
+ .board-wrapper {
107
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
108
+ padding: 20px;
109
+ border-radius: 15px;
110
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
111
+ display: inline-block;
112
+ margin: 20px 0;
113
+ }
114
+
115
+ .board-grid {
116
+ display: grid;
117
+ grid-template-columns: 40px repeat(""" + str(size) + """, 50px);
118
+ grid-template-rows: 40px repeat(""" + str(size) + """, 50px);
119
+ gap: 2px;
120
+ background-color: #1a1a2e;
121
+ padding: 10px;
122
+ border-radius: 10px;
123
+ }
124
+
125
+ .col-header {
126
+ background: linear-gradient(180deg, #3d5a80 0%, #2a5298 100%);
127
+ color: white;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ font-weight: bold;
132
+ font-size: 14px;
133
+ border-radius: 5px;
134
+ }
135
+
136
+ .row-header {
137
+ background: linear-gradient(90deg, #3d5a80 0%, #2a5298 100%);
138
+ color: white;
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: center;
142
+ font-weight: bold;
143
+ font-size: 14px;
144
+ border-radius: 5px;
145
+ }
146
+
147
+ .cell {
148
+ width: 50px;
149
+ height: 50px;
150
+ display: flex;
151
+ flex-direction: column;
152
+ align-items: center;
153
+ justify-content: center;
154
+ font-size: 20px;
155
+ border-radius: 8px;
156
+ cursor: pointer;
157
+ transition: all 0.2s ease;
158
+ border: 1px solid rgba(255,255,255,0.1);
159
+ position: relative;
160
+ }
161
+
162
+ .cell-empty {
163
+ background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
164
+ }
165
+
166
+ .cell-your-piece {
167
+ background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
168
+ box-shadow: 0 4px 15px rgba(52, 152, 219, 0.4);
169
+ }
170
+
171
+ .cell-your-piece:hover {
172
+ background: linear-gradient(135deg, #5dade2 0%, #3498db 100%);
173
+ transform: scale(1.08);
174
+ }
175
+
176
+ .cell-selected {
177
+ background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%) !important;
178
+ box-shadow: 0 0 20px rgba(243, 156, 18, 0.8) !important;
179
+ border: 2px solid #fff !important;
180
+ }
181
+
182
+ .cell-available-move {
183
+ background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%) !important;
184
+ box-shadow: 0 0 15px rgba(46, 204, 113, 0.6) !important;
185
+ border: 2px dashed #fff !important;
186
+ }
187
+
188
+ .cell-available-move::after {
189
+ content: "โ—";
190
+ font-size: 30px;
191
+ color: rgba(255,255,255,0.6);
192
+ position: absolute;
193
+ }
194
+
195
+ .cell-enemy-piece {
196
+ background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
197
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
198
+ }
199
+
200
+ .cell-enemy-piece:hover {
201
+ background: linear-gradient(135deg, #ec7063 0%, #e74c3c 100%);
202
+ transform: scale(1.08);
203
+ }
204
+
205
+ .cell-lake {
206
+ background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
207
+ animation: wave 3s ease-in-out infinite;
208
+ }
209
+
210
+ @keyframes wave {
211
+ 0%, 100% { opacity: 0.7; }
212
+ 50% { opacity: 1; }
213
+ }
214
+
215
+ .coordinate-label {
216
+ position: absolute;
217
+ bottom: 2px;
218
+ right: 4px;
219
+ font-size: 8px;
220
+ color: rgba(255,255,255,0.3);
221
+ font-weight: bold;
222
+ }
223
+ </style>
224
+
225
+ <div class="board-wrapper">
226
+ <div class="board-grid">
227
+ """
228
+
229
+ # Column headers (0-9)
230
+ html += '<div class="col-header"></div>' # Corner
231
+ for col in range(size):
232
+ html += f'<div class="col-header">{col}</div>'
233
+
234
+ # Rows - REVERSED for proper orientation (J at bottom, A at top visually)
235
+ # But we need to think about this - human should be at bottom
236
+ # In standard game: Player 0 (human) is at rows A-D (top)
237
+ # But for chess-like play, human should be at bottom
238
+ # So we display rows in reverse order (J down to A) so human setup is at bottom
239
+ rows = [chr(ord('A') + i) for i in range(size)]
240
+
241
+ for row_idx, row in enumerate(reversed(rows)): # Reverse to show A at bottom
242
+ # Row header
243
+ html += f'<div class="row-header">{row}</div>'
244
+
245
+ # Cells
246
+ for col in range(size):
247
+ piece = board_data.get((row, col), ".")
248
+ cell_key = f"{row}-{col}"
249
+
250
+ # Determine cell class and emoji
251
+ if piece == "~":
252
+ cell_class = "cell cell-lake"
253
+ emoji = PIECE_EMOJI.get(piece, "๐Ÿ’ง")
254
+ elif piece == ".":
255
+ cell_class = "cell cell-empty"
256
+ emoji = ""
257
+ elif piece == "?":
258
+ cell_class = "cell cell-enemy-piece"
259
+ emoji = PIECE_EMOJI.get(piece, "โ“")
260
+ else:
261
+ cell_class = "cell cell-your-piece"
262
+ emoji = PIECE_EMOJI.get(piece, "๐ŸŽ–๏ธ")
263
+
264
+ # Check if this is selected or available move
265
+ if cell_key == selected_piece:
266
+ cell_class += " cell-selected"
267
+ elif cell_key in available_moves:
268
+ cell_class += " cell-available-move"
269
+
270
+ html += f'<div class="{cell_class}" data-cell="{cell_key}">{emoji}<span class="coordinate-label">{row}{col}</span></div>'
271
+
272
+ html += """
273
+ </div>
274
+ </div>
275
+ """
276
+
277
+ return html
278
+
279
+
280
+ def handle_board_interaction(board_data, size, game_controller, all_legal_moves):
281
+ """Handle click-based board interaction"""
282
+
283
+ if not game_controller.is_human_turn():
284
+ return None
285
+
286
+ st.markdown("---")
287
+ st.markdown("### ๐ŸŽฎ Make Your Move")
288
+
289
+ # Get legal moves with their source and destination
290
+ legal_move_dict = {} # Maps destination to source
291
+ for move in all_legal_moves:
292
+ # Move format: "[A0 B0]"
293
+ parts = move.replace("[", "").replace("]", "").split()
294
+ if len(parts) == 2:
295
+ src, dst = parts
296
+ dst_key = f"{dst[0]}-{int(dst[1])}"
297
+ legal_move_dict[dst_key] = move
298
+
299
+ # Show instructions
300
+ col1, col2, col3 = st.columns(3)
301
+ with col1:
302
+ st.info(f"๐Ÿ“ Legal moves: {len(all_legal_moves)}")
303
+ with col2:
304
+ st.caption("Step 1: Click a piece")
305
+ with col3:
306
+ st.caption("Step 2: Click destination")
307
+
308
+ # Create columns for move buttons
309
+ st.markdown("**Or click a move directly:**")
310
+ cols_per_row = 6
311
+ cols = st.columns(cols_per_row)
312
+
313
+ for idx, move in enumerate(all_legal_moves):
314
+ col_idx = idx % cols_per_row
315
+ with cols[col_idx]:
316
+ if st.button(
317
+ f"๐ŸŽฒ {move}",
318
+ use_container_width=True,
319
+ key=f"move_btn_{move}_{idx}",
320
+ ):
321
+ return move.strip().upper()
322
+
323
+ return None