kaggle-environments 1.17.2__py2.py3-none-any.whl → 1.17.5__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kaggle-environments might be problematic. Click here for more details.

Files changed (30) hide show
  1. kaggle_environments/__init__.py +2 -2
  2. kaggle_environments/envs/open_spiel/__init__.py +0 -0
  3. kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
  4. kaggle_environments/envs/open_spiel/games/chess/chess.js +294 -0
  5. kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
  6. kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +296 -0
  7. kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
  8. kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy_test.py +57 -0
  9. kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
  10. kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
  11. kaggle_environments/envs/open_spiel/games/go/go_proxy.py +105 -0
  12. kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
  13. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
  14. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +101 -0
  15. kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
  16. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
  17. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
  18. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy_test.py +49 -0
  19. kaggle_environments/envs/open_spiel/html_playthrough_generator.py +30 -0
  20. kaggle_environments/envs/open_spiel/observation.py +133 -0
  21. kaggle_environments/envs/open_spiel/open_spiel.py +325 -224
  22. kaggle_environments/envs/open_spiel/proxy.py +139 -0
  23. kaggle_environments/envs/open_spiel/proxy_test.py +64 -0
  24. kaggle_environments/envs/open_spiel/test_open_spiel.py +23 -8
  25. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/METADATA +2 -2
  26. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/RECORD +30 -9
  27. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/WHEEL +0 -0
  28. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/entry_points.txt +0 -0
  29. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/licenses/LICENSE +0 -0
  30. {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ from .core import *
20
20
  from .main import http_request
21
21
  from . import errors
22
22
 
23
- __version__ = "1.17.2"
23
+ __version__ = "1.17.5"
24
24
 
25
25
  __all__ = ["Agent", "environments", "errors", "evaluate", "http_request",
26
26
  "make", "register", "utils", "__version__",
@@ -32,7 +32,7 @@ for name in listdir(utils.envs_path):
32
32
  try:
33
33
  env = import_module(f".envs.{name}.{name}", __name__)
34
34
  if name == "open_spiel":
35
- for env_name, env_dict in env.registered_open_spiel_envs.items():
35
+ for env_name, env_dict in env.ENV_REGISTRY.items():
36
36
  register(env_name, {
37
37
  "agents": env_dict.get("agents"),
38
38
  "html_renderer": env_dict.get("html_renderer"),
File without changes
File without changes
@@ -0,0 +1,294 @@
1
+ function renderer(options) {
2
+ const { environment, step, parent, interactive, isInteractive } = options;
3
+
4
+ // Chess-specific constants
5
+ const DEFAULT_NUM_ROWS = 8;
6
+ const DEFAULT_NUM_COLS = 8;
7
+ const PIECE_SVG_URLS = {
8
+ 'p': 'https://upload.wikimedia.org/wikipedia/commons/c/c7/Chess_pdt45.svg', // Black Pawn
9
+ 'r': 'https://upload.wikimedia.org/wikipedia/commons/f/ff/Chess_rdt45.svg', // Black Rook
10
+ 'n': 'https://upload.wikimedia.org/wikipedia/commons/e/ef/Chess_ndt45.svg', // Black Knight
11
+ 'b': 'https://upload.wikimedia.org/wikipedia/commons/9/98/Chess_bdt45.svg', // Black Bishop
12
+ 'q': 'https://upload.wikimedia.org/wikipedia/commons/4/47/Chess_qdt45.svg', // Black Queen
13
+ 'k': 'https://upload.wikimedia.org/wikipedia/commons/f/f0/Chess_kdt45.svg', // Black King
14
+ 'P': 'https://upload.wikimedia.org/wikipedia/commons/4/45/Chess_plt45.svg', // White Pawn
15
+ 'R': 'https://upload.wikimedia.org/wikipedia/commons/7/72/Chess_rlt45.svg', // White Rook
16
+ 'N': 'https://upload.wikimedia.org/wikipedia/commons/7/70/Chess_nlt45.svg', // White Knight
17
+ 'B': 'https://upload.wikimedia.org/wikipedia/commons/b/b1/Chess_blt45.svg', // White Bishop
18
+ 'Q': 'https://upload.wikimedia.org/wikipedia/commons/1/15/Chess_qlt45.svg', // White Queen
19
+ 'K': 'https://upload.wikimedia.org/wikipedia/commons/4/42/Chess_klt45.svg' // White King
20
+ };
21
+ const LIGHT_SQUARE_COLOR = '#f0d9b5';
22
+ const DARK_SQUARE_COLOR = '#b58863';
23
+
24
+ // Renderer state variables
25
+ let currentBoardElement = null;
26
+ let currentStatusTextElement = null;
27
+ let currentWinnerTextElement = null;
28
+ let currentMessageBoxElement = typeof document !== 'undefined' ? document.getElementById('messageBox') : null;
29
+ let currentRendererContainer = null;
30
+ let currentTitleElement = null;
31
+
32
+ function _showMessage(message, type = 'info', duration = 3000) {
33
+ if (typeof document === 'undefined' || !document.body) return;
34
+ if (!currentMessageBoxElement) {
35
+ currentMessageBoxElement = document.createElement('div');
36
+ currentMessageBoxElement.id = 'messageBox';
37
+ // Identical styling to the Connect Four renderer's message box
38
+ Object.assign(currentMessageBoxElement.style, {
39
+ position: 'fixed',
40
+ top: '10px',
41
+ left: '50%',
42
+ transform: 'translateX(-50%)',
43
+ padding: '0.75rem 1rem',
44
+ borderRadius: '0.375rem',
45
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
46
+ zIndex: '1000',
47
+ opacity: '0',
48
+ transition: 'opacity 0.3s ease-in-out, background-color 0.3s',
49
+ fontSize: '0.875rem',
50
+ fontFamily: "'Inter', sans-serif",
51
+ color: 'white'
52
+ });
53
+ document.body.appendChild(currentMessageBoxElement);
54
+ }
55
+ currentMessageBoxElement.textContent = message;
56
+ currentMessageBoxElement.style.backgroundColor = type === 'error' ? '#ef4444' : '#10b981';
57
+ currentMessageBoxElement.style.opacity = '1';
58
+ setTimeout(() => { if (currentMessageBoxElement) currentMessageBoxElement.style.opacity = '0'; }, duration);
59
+ }
60
+
61
+ function _ensureRendererElements(parentElementToClear, rows, cols) {
62
+ if (!parentElementToClear) return false;
63
+ parentElementToClear.innerHTML = '';
64
+
65
+ currentRendererContainer = document.createElement('div');
66
+ Object.assign(currentRendererContainer.style, {
67
+ display: 'flex',
68
+ flexDirection: 'column',
69
+ alignItems: 'center',
70
+ padding: '20px',
71
+ boxSizing: 'border-box',
72
+ width: '100%',
73
+ height: '100%',
74
+ fontFamily: "'Inter', sans-serif"
75
+ });
76
+
77
+ currentTitleElement = document.createElement('h1');
78
+ currentTitleElement.textContent = 'Chess';
79
+ // Identical styling to the Connect Four renderer's title
80
+ Object.assign(currentTitleElement.style, {
81
+ fontSize: '1.875rem',
82
+ fontWeight: 'bold',
83
+ marginBottom: '1rem',
84
+ textAlign: 'center',
85
+ color: '#2563eb'
86
+ });
87
+ currentRendererContainer.appendChild(currentTitleElement);
88
+
89
+ currentBoardElement = document.createElement('div');
90
+ Object.assign(currentBoardElement.style, {
91
+ display: 'grid',
92
+ gridTemplateColumns: `repeat(${cols}, 50px)`,
93
+ gridTemplateRows: `repeat(${rows}, 50px)`,
94
+ width: `${cols * 50}px`,
95
+ height: `${rows * 50}px`,
96
+ border: '2px solid #333'
97
+ });
98
+
99
+ for (let r = 0; r < rows; r++) {
100
+ for (let c = 0; c < cols; c++) {
101
+ const square = document.createElement('div');
102
+ square.id = `cell-${r}-${c}`;
103
+ Object.assign(square.style, {
104
+ width: '50px',
105
+ height: '50px',
106
+ backgroundColor: (r + c) % 2 === 0 ? LIGHT_SQUARE_COLOR : DARK_SQUARE_COLOR,
107
+ display: 'flex',
108
+ alignItems: 'center',
109
+ justifyContent: 'center',
110
+ });
111
+ currentBoardElement.appendChild(square);
112
+ }
113
+ }
114
+ currentRendererContainer.appendChild(currentBoardElement);
115
+
116
+ const statusContainer = document.createElement('div');
117
+ // Identical styling to the Connect Four renderer's status container
118
+ Object.assign(statusContainer.style, {
119
+ padding: '10px 15px',
120
+ backgroundColor: 'white',
121
+ borderRadius: '8px',
122
+ boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)',
123
+ textAlign: 'center',
124
+ width: 'auto',
125
+ minWidth: '200px',
126
+ maxWidth: '90vw',
127
+ marginTop: '20px'
128
+ });
129
+ currentRendererContainer.appendChild(statusContainer);
130
+
131
+ currentStatusTextElement = document.createElement('p');
132
+ Object.assign(currentStatusTextElement.style, {
133
+ fontSize: '1.1rem',
134
+ fontWeight: '600',
135
+ margin: '0 0 5px 0'
136
+ });
137
+ statusContainer.appendChild(currentStatusTextElement);
138
+
139
+ currentWinnerTextElement = document.createElement('p');
140
+ Object.assign(currentWinnerTextElement.style, {
141
+ fontSize: '1.25rem',
142
+ fontWeight: '700',
143
+ margin: '5px 0 0 0'
144
+ });
145
+ statusContainer.appendChild(currentWinnerTextElement);
146
+
147
+ parentElementToClear.appendChild(currentRendererContainer);
148
+
149
+ if (typeof document !== 'undefined' && !document.body.hasAttribute('data-renderer-initialized')) {
150
+ document.body.setAttribute('data-renderer-initialized', 'true');
151
+ }
152
+ return true;
153
+ }
154
+
155
+ function _parseFen(fen) {
156
+ if (!fen || typeof fen !== 'string') return null;
157
+
158
+ const [piecePlacement, activeColor, castling, enPassant, halfmoveClock, fullmoveNumber] = fen.split(' ');
159
+ const board = [];
160
+ const rows = piecePlacement.split('/');
161
+
162
+ for (const row of rows) {
163
+ const boardRow = [];
164
+ for (const char of row) {
165
+ if (isNaN(parseInt(char))) {
166
+ boardRow.push(char);
167
+ } else {
168
+ for (let i = 0; i < parseInt(char); i++) {
169
+ boardRow.push(null);
170
+ }
171
+ }
172
+ }
173
+ board.push(boardRow);
174
+ }
175
+
176
+ return {
177
+ board,
178
+ activeColor,
179
+ castling,
180
+ enPassant,
181
+ halfmoveClock,
182
+ fullmoveNumber
183
+ };
184
+ }
185
+
186
+
187
+ function _renderBoardDisplay(gameStateToDisplay, displayRows, displayCols) {
188
+ if (!currentBoardElement || !currentStatusTextElement || !currentWinnerTextElement) return;
189
+
190
+ // Clear board
191
+ for (let r = 0; r < displayRows; r++) {
192
+ for (let c = 0; c < displayCols; c++) {
193
+ const squareElement = currentBoardElement.querySelector(`#cell-${r}-${c}`);
194
+ if (squareElement) {
195
+ squareElement.innerHTML = '';
196
+ }
197
+ }
198
+ }
199
+
200
+ if (!gameStateToDisplay || !gameStateToDisplay.board) {
201
+ currentStatusTextElement.textContent = "Waiting for game data...";
202
+ currentWinnerTextElement.textContent = "";
203
+ return;
204
+ }
205
+
206
+
207
+ const { board, activeColor, is_terminal, winner } = gameStateToDisplay;
208
+
209
+ for (let r_data = 0; r_data < displayRows; r_data++) {
210
+ for (let c_data = 0; c_data < displayCols; c_data++) {
211
+ const piece = board[r_data][c_data];
212
+ const squareElement = currentBoardElement.querySelector(`#cell-${r_data}-${c_data}`);
213
+ if (squareElement && piece) {
214
+ const pieceImg = document.createElement('img');
215
+ pieceImg.src = PIECE_SVG_URLS[piece];
216
+ pieceImg.style.width = '45px';
217
+ pieceImg.style.height = '45px';
218
+ squareElement.appendChild(pieceImg);
219
+ }
220
+ }
221
+ }
222
+
223
+ currentStatusTextElement.innerHTML = '';
224
+ currentWinnerTextElement.innerHTML = '';
225
+ if (is_terminal) {
226
+ currentStatusTextElement.textContent = "Game Over!";
227
+ if (winner) {
228
+ if (String(winner).toLowerCase() === 'draw') {
229
+ currentWinnerTextElement.textContent = "It's a Draw!";
230
+ } else {
231
+ const winnerColor = String(winner).toLowerCase() === 'white' ? 'White' : 'Black';
232
+ currentWinnerTextElement.innerHTML = `Winner: <span style="font-weight: bold;">${winnerColor}</span>`;
233
+ }
234
+ } else {
235
+ currentWinnerTextElement.textContent = "Game ended.";
236
+ }
237
+ } else {
238
+ const playerColor = String(activeColor).toLowerCase() === 'w' ? 'White' : 'Black';
239
+ currentStatusTextElement.innerHTML = `Current Player: <span style="font-weight: bold;">${playerColor}</span>`;
240
+ }
241
+ }
242
+
243
+ // --- Main execution logic ---
244
+ if (!_ensureRendererElements(parent, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS)) {
245
+ if (parent && typeof parent.innerHTML !== 'undefined') {
246
+ parent.innerHTML = "<p style='color:red; font-family: sans-serif;'>Critical Error: Renderer element setup failed.</p>";
247
+ }
248
+ return;
249
+ }
250
+
251
+ if (!environment || !environment.steps || !environment.steps[step]) {
252
+ _renderBoardDisplay(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
253
+ if (currentStatusTextElement) currentStatusTextElement.textContent = "Initializing environment...";
254
+ return;
255
+ }
256
+
257
+ const currentStepAgents = environment.steps[step];
258
+ if (!currentStepAgents || !Array.isArray(currentStepAgents) || currentStepAgents.length === 0) {
259
+ _renderBoardDisplay(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
260
+ if (currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for agent data...";
261
+ return;
262
+ }
263
+
264
+ // In chess, observation is the same for both agents. We can take it from the first.
265
+ const agent = currentStepAgents[0];
266
+
267
+ if (!agent || typeof agent.observation === 'undefined') {
268
+ _renderBoardDisplay(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
269
+ if (currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for observation data...";
270
+ return;
271
+ }
272
+ const observationForRenderer = agent.observation;
273
+
274
+ let gameSpecificState = null;
275
+
276
+ if (observationForRenderer && typeof observationForRenderer.observation_string === 'string' && observationForRenderer.observation_string.trim() !== '') {
277
+ try {
278
+ const fen = observationForRenderer.observation_string;
279
+ const parsedFen = _parseFen(fen);
280
+ if (parsedFen) {
281
+ // Assuming `is_terminal` and `winner` are provided in the top-level observation
282
+ gameSpecificState = {
283
+ ...parsedFen,
284
+ is_terminal: observationForRenderer.is_terminal,
285
+ winner: observationForRenderer.winner
286
+ };
287
+ }
288
+ } catch (e) {
289
+ _showMessage("Error: Corrupted game state (obs_string).", 'error');
290
+ }
291
+ }
292
+
293
+ _renderBoardDisplay(gameSpecificState, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
294
+ }
@@ -0,0 +1,296 @@
1
+ function renderer(options) {
2
+ const { environment, step, parent, interactive, isInteractive } = options;
3
+
4
+ const DEFAULT_NUM_ROWS = 6;
5
+ const DEFAULT_NUM_COLS = 7;
6
+ const PLAYER_SYMBOLS = ['O', 'X']; // O: Player 0 (Yellow), X: Player 1 (Red)
7
+ const PLAYER_COLORS = ['#facc15', '#ef4444']; // Yellow for 'O', Red for 'X'
8
+ const EMPTY_CELL_COLOR = '#e5e7eb';
9
+ const BOARD_COLOR = '#3b82f6';
10
+
11
+ const SVG_NS = "http://www.w3.org/2000/svg";
12
+ const CELL_UNIT_SIZE = 100;
13
+ const CIRCLE_RADIUS = CELL_UNIT_SIZE * 0.42;
14
+ const SVG_VIEWBOX_WIDTH = DEFAULT_NUM_COLS * CELL_UNIT_SIZE;
15
+ const SVG_VIEWBOX_HEIGHT = DEFAULT_NUM_ROWS * CELL_UNIT_SIZE;
16
+
17
+ let currentBoardSvgElement = null;
18
+ let currentStatusTextElement = null;
19
+ let currentWinnerTextElement = null;
20
+ let currentMessageBoxElement = typeof document !== 'undefined' ? document.getElementById('messageBox') : null;
21
+ let currentRendererContainer = null;
22
+ let currentTitleElement = null;
23
+
24
+ function _showMessage(message, type = 'info', duration = 3000) {
25
+ if (typeof document === 'undefined' || !document.body) return;
26
+ if (!currentMessageBoxElement) {
27
+ currentMessageBoxElement = document.createElement('div');
28
+ currentMessageBoxElement.id = 'messageBox';
29
+ currentMessageBoxElement.style.position = 'fixed';
30
+ currentMessageBoxElement.style.top = '10px';
31
+ currentMessageBoxElement.style.left = '50%';
32
+ currentMessageBoxElement.style.transform = 'translateX(-50%)';
33
+ currentMessageBoxElement.style.padding = '0.75rem 1rem';
34
+ currentMessageBoxElement.style.borderRadius = '0.375rem';
35
+ currentMessageBoxElement.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
36
+ currentMessageBoxElement.style.zIndex = '1000';
37
+ currentMessageBoxElement.style.opacity = '0';
38
+ currentMessageBoxElement.style.transition = 'opacity 0.3s ease-in-out, background-color 0.3s';
39
+ currentMessageBoxElement.style.fontSize = '0.875rem';
40
+ currentMessageBoxElement.style.fontFamily = "'Inter', sans-serif";
41
+ document.body.appendChild(currentMessageBoxElement);
42
+ }
43
+ currentMessageBoxElement.textContent = message;
44
+ currentMessageBoxElement.style.backgroundColor = type === 'error' ? '#ef4444' : '#10b981';
45
+ currentMessageBoxElement.style.color = 'white';
46
+ currentMessageBoxElement.style.opacity = '1';
47
+ setTimeout(() => { if (currentMessageBoxElement) currentMessageBoxElement.style.opacity = '0'; }, duration);
48
+ }
49
+
50
+ function _ensureRendererElements(parentElementToClear, rows, cols) {
51
+ if (!parentElementToClear) return false;
52
+ parentElementToClear.innerHTML = '';
53
+
54
+ currentRendererContainer = document.createElement('div');
55
+ currentRendererContainer.style.display = 'flex';
56
+ currentRendererContainer.style.flexDirection = 'column';
57
+ currentRendererContainer.style.alignItems = 'center';
58
+ currentRendererContainer.style.padding = '20px';
59
+ currentRendererContainer.style.boxSizing = 'border-box';
60
+ currentRendererContainer.style.width = '100%';
61
+ currentRendererContainer.style.height = '100%';
62
+ currentRendererContainer.style.fontFamily = "'Inter', sans-serif";
63
+
64
+ currentTitleElement = document.createElement('h1');
65
+ currentTitleElement.textContent = 'Connect Four';
66
+ currentTitleElement.style.fontSize = '1.875rem';
67
+ currentTitleElement.style.fontWeight = 'bold';
68
+ currentTitleElement.style.marginBottom = '1rem';
69
+ currentTitleElement.style.textAlign = 'center';
70
+ currentTitleElement.style.color = '#2563eb';
71
+ currentRendererContainer.appendChild(currentTitleElement);
72
+
73
+ currentBoardSvgElement = document.createElementNS(SVG_NS, "svg");
74
+ currentBoardSvgElement.setAttribute("viewBox", `0 0 ${SVG_VIEWBOX_WIDTH} ${SVG_VIEWBOX_HEIGHT}`);
75
+ currentBoardSvgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
76
+ currentBoardSvgElement.style.width = "auto";
77
+ currentBoardSvgElement.style.maxWidth = "500px";
78
+ currentBoardSvgElement.style.maxHeight = `calc(100vh - 200px)`;
79
+ currentBoardSvgElement.style.aspectRatio = `${cols} / ${rows}`;
80
+ currentBoardSvgElement.style.display = "block";
81
+ currentBoardSvgElement.style.margin = "0 auto 20px auto";
82
+
83
+ const boardBgRect = document.createElementNS(SVG_NS, "rect");
84
+ boardBgRect.setAttribute("x", "0");
85
+ boardBgRect.setAttribute("y", "0");
86
+ boardBgRect.setAttribute("width", SVG_VIEWBOX_WIDTH.toString());
87
+ boardBgRect.setAttribute("height", SVG_VIEWBOX_HEIGHT.toString());
88
+ boardBgRect.setAttribute("fill", BOARD_COLOR);
89
+ boardBgRect.setAttribute("rx", (CELL_UNIT_SIZE * 0.1).toString());
90
+ currentBoardSvgElement.appendChild(boardBgRect);
91
+
92
+ // SVG Circles are created with (0,0) being top-left visual circle
93
+ for (let r_visual = 0; r_visual < rows; r_visual++) {
94
+ for (let c_visual = 0; c_visual < cols; c_visual++) {
95
+ const circle = document.createElementNS(SVG_NS, "circle");
96
+ const cx = c_visual * CELL_UNIT_SIZE + CELL_UNIT_SIZE / 2;
97
+ const cy = r_visual * CELL_UNIT_SIZE + CELL_UNIT_SIZE / 2;
98
+ circle.setAttribute("id", `cell-${r_visual}-${c_visual}`);
99
+ circle.setAttribute("cx", cx.toString());
100
+ circle.setAttribute("cy", cy.toString());
101
+ circle.setAttribute("r", CIRCLE_RADIUS.toString());
102
+ circle.setAttribute("fill", EMPTY_CELL_COLOR);
103
+ currentBoardSvgElement.appendChild(circle);
104
+ }
105
+ }
106
+ currentRendererContainer.appendChild(currentBoardSvgElement);
107
+
108
+ const statusContainer = document.createElement('div');
109
+ statusContainer.style.padding = '10px 15px';
110
+ statusContainer.style.backgroundColor = 'white';
111
+ statusContainer.style.borderRadius = '8px';
112
+ statusContainer.style.boxShadow = '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)';
113
+ statusContainer.style.textAlign = 'center';
114
+ statusContainer.style.width = 'auto';
115
+ statusContainer.style.minWidth = '200px';
116
+ statusContainer.style.maxWidth = '90vw';
117
+ currentRendererContainer.appendChild(statusContainer);
118
+
119
+ currentStatusTextElement = document.createElement('p');
120
+ currentStatusTextElement.style.fontSize = '1.1rem';
121
+ currentStatusTextElement.style.fontWeight = '600';
122
+ currentStatusTextElement.style.margin = '0 0 5px 0';
123
+ statusContainer.appendChild(currentStatusTextElement);
124
+
125
+ currentWinnerTextElement = document.createElement('p');
126
+ currentWinnerTextElement.style.fontSize = '1.25rem';
127
+ currentWinnerTextElement.style.fontWeight = '700';
128
+ currentWinnerTextElement.style.margin = '5px 0 0 0';
129
+ statusContainer.appendChild(currentWinnerTextElement);
130
+
131
+ parentElementToClear.appendChild(currentRendererContainer);
132
+
133
+ if (typeof document !== 'undefined' && !document.body.hasAttribute('data-renderer-initialized')) {
134
+ document.body.setAttribute('data-renderer-initialized', 'true');
135
+ }
136
+ return true;
137
+ }
138
+
139
+ function _renderBoardDisplay_svg(gameStateToDisplay, displayRows, displayCols) {
140
+ if (!currentBoardSvgElement || !currentStatusTextElement || !currentWinnerTextElement) return;
141
+
142
+ if (!gameStateToDisplay || typeof gameStateToDisplay.board !== 'object' || !Array.isArray(gameStateToDisplay.board) || gameStateToDisplay.board.length === 0) {
143
+ currentStatusTextElement.textContent = "Waiting for game data...";
144
+ currentWinnerTextElement.textContent = "";
145
+ for (let r_visual = 0; r_visual < displayRows; r_visual++) {
146
+ for (let c_visual = 0; c_visual < displayCols; c_visual++) {
147
+ const circleElement = currentBoardSvgElement.querySelector(`#cell-${r_visual}-${c_visual}`);
148
+ if (circleElement) {
149
+ circleElement.setAttribute("fill", EMPTY_CELL_COLOR);
150
+ }
151
+ }
152
+ }
153
+ return;
154
+ }
155
+
156
+ const { board, current_player, is_terminal, winner } = gameStateToDisplay;
157
+
158
+ for (let r_data = 0; r_data < displayRows; r_data++) {
159
+ const dataRow = board[r_data];
160
+ if (!dataRow || !Array.isArray(dataRow) || dataRow.length !== displayCols) {
161
+ // Error handling for malformed row
162
+ for (let c_fill = 0; c_fill < displayCols; c_fill++) {
163
+ // Determine visual row for error display. If r_data=0 is top data,
164
+ // and we want to flip, then this error is for visual row (displayRows-1)-0.
165
+ const visual_row_for_error = (displayRows - 1) - r_data;
166
+ const circleElement = currentBoardSvgElement.querySelector(`#cell-${visual_row_for_error}-${c_fill}`);
167
+ if (circleElement) circleElement.setAttribute("fill", '#FF00FF'); // Magenta for error
168
+ }
169
+ continue;
170
+ }
171
+
172
+ const visual_svg_row_index = (displayRows - 1) - r_data;
173
+
174
+ for (let c_data = 0; c_data < displayCols; c_data++) { // c_data iterates through columns of `board[r_data]`
175
+ const originalCellValue = dataRow[c_data];
176
+ const cellValueForComparison = String(originalCellValue).trim().toLowerCase();
177
+
178
+ // The column index for SVG is the same as c_data
179
+ const visual_svg_col_index = c_data;
180
+ const circleElement = currentBoardSvgElement.querySelector(`#cell-${visual_svg_row_index}-${visual_svg_col_index}`);
181
+
182
+ if (!circleElement) continue;
183
+
184
+ let fillColor = EMPTY_CELL_COLOR;
185
+ if (cellValueForComparison === "o") {
186
+ fillColor = PLAYER_COLORS[0]; // Yellow
187
+ } else if (cellValueForComparison === "x") {
188
+ fillColor = PLAYER_COLORS[1]; // Red
189
+ }
190
+ circleElement.setAttribute("fill", fillColor);
191
+ }
192
+ }
193
+
194
+ currentStatusTextElement.innerHTML = '';
195
+ currentWinnerTextElement.innerHTML = '';
196
+ if (is_terminal) {
197
+ currentStatusTextElement.textContent = "Game Over!";
198
+ if (winner !== null && winner !== undefined) {
199
+ if (String(winner).toLowerCase() === 'draw') {
200
+ currentWinnerTextElement.textContent = "It's a Draw!";
201
+ } else {
202
+ let winnerSymbolDisplay, winnerColorDisplay;
203
+ if (String(winner).toLowerCase() === "o") {
204
+ winnerSymbolDisplay = PLAYER_SYMBOLS[0];
205
+ winnerColorDisplay = PLAYER_COLORS[0];
206
+ } else if (String(winner).toLowerCase() === "x") {
207
+ winnerSymbolDisplay = PLAYER_SYMBOLS[1];
208
+ winnerColorDisplay = PLAYER_COLORS[1];
209
+ }
210
+ if (winnerSymbolDisplay) {
211
+ currentWinnerTextElement.innerHTML = `Player <span style="color: ${winnerColorDisplay}; font-weight: bold;">${winnerSymbolDisplay}</span> Wins!`;
212
+ } else {
213
+ currentWinnerTextElement.textContent = `Winner: ${String(winner).toUpperCase()}`;
214
+ }
215
+ }
216
+ } else { currentWinnerTextElement.textContent = "Game ended."; }
217
+ } else {
218
+ let playerSymbolToDisplay, playerColorToDisplay;
219
+ if (String(current_player).toLowerCase() === "o") {
220
+ playerSymbolToDisplay = PLAYER_SYMBOLS[0];
221
+ playerColorToDisplay = PLAYER_COLORS[0];
222
+ } else if (String(current_player).toLowerCase() === "x") {
223
+ playerSymbolToDisplay = PLAYER_SYMBOLS[1];
224
+ playerColorToDisplay = PLAYER_COLORS[1];
225
+ }
226
+ if (playerSymbolToDisplay) {
227
+ currentStatusTextElement.innerHTML = `Current Player: <span style="color: ${playerColorToDisplay}; font-weight: bold;">${playerSymbolToDisplay}</span>`;
228
+ } else {
229
+ currentStatusTextElement.textContent = "Waiting for player...";
230
+ }
231
+ }
232
+ }
233
+
234
+ // --- Main execution logic ---
235
+ if (!_ensureRendererElements(parent, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS)) {
236
+ if (parent && typeof parent.innerHTML !== 'undefined') {
237
+ parent.innerHTML = "<p style='color:red; font-family: sans-serif;'>Critical Error: Renderer element setup failed.</p>";
238
+ }
239
+ return;
240
+ }
241
+
242
+ if (!environment || !environment.steps || !environment.steps[step]) {
243
+ _renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
244
+ if(currentStatusTextElement) currentStatusTextElement.textContent = "Initializing environment...";
245
+ return;
246
+ }
247
+
248
+ const currentStepAgents = environment.steps[step];
249
+ if (!currentStepAgents || !Array.isArray(currentStepAgents) || currentStepAgents.length === 0) {
250
+ _renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
251
+ if(currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for agent data...";
252
+ return;
253
+ }
254
+
255
+ const gameMasterAgentIndex = currentStepAgents.length - 1;
256
+ const gameMasterAgent = currentStepAgents[gameMasterAgentIndex];
257
+
258
+ if (!gameMasterAgent || typeof gameMasterAgent.observation === 'undefined') {
259
+ _renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
260
+ if(currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for observation data...";
261
+ return;
262
+ }
263
+ const observationForRenderer = gameMasterAgent.observation;
264
+
265
+ let gameSpecificState = null;
266
+
267
+ if (observationForRenderer && typeof observationForRenderer.observation_string === 'string' && observationForRenderer.observation_string.trim() !== '') {
268
+ try {
269
+ gameSpecificState = JSON.parse(observationForRenderer.observation_string);
270
+ } catch (e) {
271
+ _showMessage("Error: Corrupted game state (obs_string).", 'error');
272
+ }
273
+ }
274
+
275
+ if (!gameSpecificState && observationForRenderer && typeof observationForRenderer.json === 'string' && observationForRenderer.json.trim() !== '') {
276
+ try {
277
+ gameSpecificState = JSON.parse(observationForRenderer.json);
278
+ } catch (e) {
279
+ _showMessage("Error: Corrupted game state (json).", 'error');
280
+ }
281
+ }
282
+
283
+ if (!gameSpecificState && observationForRenderer &&
284
+ Array.isArray(observationForRenderer.board) &&
285
+ typeof observationForRenderer.current_player !== 'undefined'
286
+ ) {
287
+ if( (observationForRenderer.board.length === DEFAULT_NUM_ROWS &&
288
+ (observationForRenderer.board.length === 0 ||
289
+ (Array.isArray(observationForRenderer.board[0]) && observationForRenderer.board[0].length === DEFAULT_NUM_COLS)))
290
+ ){
291
+ gameSpecificState = observationForRenderer;
292
+ }
293
+ }
294
+
295
+ _renderBoardDisplay_svg(gameSpecificState, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
296
+ }