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.
- kaggle_environments/__init__.py +2 -2
- kaggle_environments/envs/open_spiel/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/chess/chess.js +294 -0
- kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +296 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy_test.py +57 -0
- kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
- kaggle_environments/envs/open_spiel/games/go/go_proxy.py +105 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +101 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy_test.py +49 -0
- kaggle_environments/envs/open_spiel/html_playthrough_generator.py +30 -0
- kaggle_environments/envs/open_spiel/observation.py +133 -0
- kaggle_environments/envs/open_spiel/open_spiel.py +325 -224
- kaggle_environments/envs/open_spiel/proxy.py +139 -0
- kaggle_environments/envs/open_spiel/proxy_test.py +64 -0
- kaggle_environments/envs/open_spiel/test_open_spiel.py +23 -8
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/METADATA +2 -2
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/RECORD +30 -9
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/WHEEL +0 -0
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/entry_points.txt +0 -0
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/licenses/LICENSE +0 -0
- {kaggle_environments-1.17.2.dist-info → kaggle_environments-1.17.5.dist-info}/top_level.txt +0 -0
kaggle_environments/__init__.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
}
|
|
File without changes
|
|
@@ -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
|
+
}
|