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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Change Go state and action string representations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ... import proxy
|
|
7
|
+
import pyspiel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GoState(proxy.State):
|
|
11
|
+
"""Go state proxy."""
|
|
12
|
+
|
|
13
|
+
def _player_string(self, player: int) -> str:
|
|
14
|
+
if player < 0:
|
|
15
|
+
return pyspiel.PlayerId(player).name.lower()
|
|
16
|
+
elif player == 0:
|
|
17
|
+
return 'B'
|
|
18
|
+
elif player == 1:
|
|
19
|
+
return 'W'
|
|
20
|
+
else:
|
|
21
|
+
raise ValueError(f'Invalid player: {player}')
|
|
22
|
+
|
|
23
|
+
def _board_string_to_dict(self, board_string: str) -> dict:
|
|
24
|
+
lines = board_string.strip().splitlines()
|
|
25
|
+
if len(lines) < 3:
|
|
26
|
+
raise ValueError("Input string is too short to be a valid board.")
|
|
27
|
+
# The last line contains the column labels (e.g., "ABC...")
|
|
28
|
+
column_labels = lines[-1].strip()
|
|
29
|
+
board_rows = lines[2:-1]
|
|
30
|
+
board_size = len(column_labels)
|
|
31
|
+
if len(board_rows) != board_size:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Board dimension mismatch: {len(column_labels)} columns "
|
|
34
|
+
f"but {len(board_rows)} rows."
|
|
35
|
+
)
|
|
36
|
+
grid = []
|
|
37
|
+
symbol_map = {
|
|
38
|
+
'+': '.',
|
|
39
|
+
'X': 'B',
|
|
40
|
+
'O': 'W'
|
|
41
|
+
}
|
|
42
|
+
for i, row_line in enumerate(board_rows):
|
|
43
|
+
row_number = board_size - i
|
|
44
|
+
try:
|
|
45
|
+
board_content = row_line.split(maxsplit=1)[1]
|
|
46
|
+
except IndexError:
|
|
47
|
+
raise ValueError(f"Malformed board row: '{row_line}'")
|
|
48
|
+
current_row_list = []
|
|
49
|
+
for j, stone_char in enumerate(board_content):
|
|
50
|
+
col_letter = column_labels[j]
|
|
51
|
+
coordinate = f"{col_letter}{row_number}"
|
|
52
|
+
# TODO
|
|
53
|
+
point_dict = {coordinate: symbol_map.get(stone_char, '?')}
|
|
54
|
+
current_row_list.append(point_dict)
|
|
55
|
+
grid.append(current_row_list)
|
|
56
|
+
return grid
|
|
57
|
+
|
|
58
|
+
def state_dict(self) -> dict[str, Any]:
|
|
59
|
+
clone_state = self.get_game().__wrapped__.new_initial_state()
|
|
60
|
+
action_strs = []
|
|
61
|
+
for action in self.history():
|
|
62
|
+
action_strs.append(clone_state.action_to_string(action))
|
|
63
|
+
clone_state.apply_action(action)
|
|
64
|
+
prev_move = None if not action_strs else action_strs[-1]
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
'board_size': self.get_game().get_parameters()['board_size'],
|
|
68
|
+
'komi': self.get_game().get_parameters()['komi'],
|
|
69
|
+
'current_player_to_move': self._player_string(self.current_player()),
|
|
70
|
+
'move_number': len(self.history()) + 1,
|
|
71
|
+
'previous_move_a1': prev_move,
|
|
72
|
+
'board_grid': self._board_string_to_dict(self.__wrapped__.__str__()),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def to_json(self) -> str:
|
|
76
|
+
return json.dumps(self.state_dict())
|
|
77
|
+
|
|
78
|
+
def observation_string(self, player: int) -> str:
|
|
79
|
+
return self.observation_json(player)
|
|
80
|
+
|
|
81
|
+
def observation_json(self, player: int) -> str:
|
|
82
|
+
del player
|
|
83
|
+
return self.to_json()
|
|
84
|
+
|
|
85
|
+
def __str__(self):
|
|
86
|
+
return self.to_json()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class GoGame(proxy.Game):
|
|
90
|
+
"""Go game proxy."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, params: Any | None = None):
|
|
93
|
+
params = params or {}
|
|
94
|
+
wrapped = pyspiel.load_game('go', params)
|
|
95
|
+
super().__init__(
|
|
96
|
+
wrapped,
|
|
97
|
+
short_name='go_proxy',
|
|
98
|
+
long_name='Go (proxy)',
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def new_initial_state(self, *args) -> GoState:
|
|
102
|
+
return GoState(self.__wrapped__.new_initial_state(*args), game=self)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
pyspiel.register_game(GoGame().get_type(), GoGame)
|
|
File without changes
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
function renderer(options) {
|
|
2
|
+
const { environment, step, parent, interactive, isInteractive } = options;
|
|
3
|
+
|
|
4
|
+
const DEFAULT_NUM_ROWS = 3;
|
|
5
|
+
const DEFAULT_NUM_COLS = 3;
|
|
6
|
+
const PLAYER_SYMBOLS = ['O', 'X'];
|
|
7
|
+
const PLAYER_COLORS = ['#000000', '#000000'];
|
|
8
|
+
const BOARD_BACKGROUND_COLOR = '#f0f0f0';
|
|
9
|
+
const GRID_LINE_COLOR = '#cccccc';
|
|
10
|
+
const MARK_COLOR = '#000000';
|
|
11
|
+
const KAGGLE_BLUE = '#20BEFF';
|
|
12
|
+
|
|
13
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
14
|
+
const CELL_UNIT_SIZE = 100;
|
|
15
|
+
const MARK_THICKNESS = CELL_UNIT_SIZE * 0.08;
|
|
16
|
+
const O_MARK_RADIUS = CELL_UNIT_SIZE * 0.32;
|
|
17
|
+
const X_MARK_ARM_LENGTH = CELL_UNIT_SIZE * 0.30;
|
|
18
|
+
|
|
19
|
+
const SVG_VIEWBOX_WIDTH = DEFAULT_NUM_COLS * CELL_UNIT_SIZE;
|
|
20
|
+
const SVG_VIEWBOX_HEIGHT = DEFAULT_NUM_ROWS * CELL_UNIT_SIZE;
|
|
21
|
+
|
|
22
|
+
let currentBoardSvgElement = null;
|
|
23
|
+
let currentStatusTextElement = null;
|
|
24
|
+
let currentWinnerTextElement = null;
|
|
25
|
+
let currentMessageBoxElement = typeof document !== 'undefined' ? document.getElementById('messageBox') : null;
|
|
26
|
+
let currentRendererContainer = null;
|
|
27
|
+
let currentTitleElement = null;
|
|
28
|
+
|
|
29
|
+
function _showMessage(message, type = 'info', duration = 3000) {
|
|
30
|
+
if (typeof document === 'undefined' || !document.body) return;
|
|
31
|
+
if (!currentMessageBoxElement) {
|
|
32
|
+
currentMessageBoxElement = document.createElement('div');
|
|
33
|
+
currentMessageBoxElement.id = 'messageBox';
|
|
34
|
+
currentMessageBoxElement.style.position = 'fixed';
|
|
35
|
+
currentMessageBoxElement.style.top = '20px';
|
|
36
|
+
currentMessageBoxElement.style.left = '50%';
|
|
37
|
+
currentMessageBoxElement.style.transform = 'translateX(-50%)';
|
|
38
|
+
currentMessageBoxElement.style.padding = '0.75rem 1.25rem';
|
|
39
|
+
currentMessageBoxElement.style.borderRadius = '0.375rem';
|
|
40
|
+
currentMessageBoxElement.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)';
|
|
41
|
+
currentMessageBoxElement.style.zIndex = '10000';
|
|
42
|
+
currentMessageBoxElement.style.opacity = '0';
|
|
43
|
+
currentMessageBoxElement.style.transition = 'opacity 0.3s ease-in-out, background-color 0.3s, top 0.3s';
|
|
44
|
+
currentMessageBoxElement.style.fontSize = '0.9rem';
|
|
45
|
+
currentMessageBoxElement.style.fontFamily = "'Inter', sans-serif";
|
|
46
|
+
currentMessageBoxElement.style.maxWidth = '90%';
|
|
47
|
+
currentMessageBoxElement.style.textAlign = 'center';
|
|
48
|
+
document.body.appendChild(currentMessageBoxElement);
|
|
49
|
+
}
|
|
50
|
+
currentMessageBoxElement.textContent = message;
|
|
51
|
+
currentMessageBoxElement.style.backgroundColor = type === 'error' ? '#ef4444' : '#10b981';
|
|
52
|
+
currentMessageBoxElement.style.color = 'white';
|
|
53
|
+
currentMessageBoxElement.style.opacity = '1';
|
|
54
|
+
currentMessageBoxElement.style.top = '20px';
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
if (currentMessageBoxElement) {
|
|
57
|
+
currentMessageBoxElement.style.opacity = '0';
|
|
58
|
+
}
|
|
59
|
+
}, duration);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _ensureRendererElements(parentElementToClear, rows, cols) {
|
|
63
|
+
if (!parentElementToClear) {
|
|
64
|
+
console.error("Parent element to clear is null or undefined.");
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
parentElementToClear.innerHTML = '';
|
|
68
|
+
|
|
69
|
+
currentRendererContainer = document.createElement('div');
|
|
70
|
+
currentRendererContainer.style.display = 'flex';
|
|
71
|
+
currentRendererContainer.style.flexDirection = 'column';
|
|
72
|
+
currentRendererContainer.style.alignItems = 'center';
|
|
73
|
+
currentRendererContainer.style.padding = '20px';
|
|
74
|
+
currentRendererContainer.style.boxSizing = 'border-box';
|
|
75
|
+
currentRendererContainer.style.width = '100%';
|
|
76
|
+
currentRendererContainer.style.height = '100%';
|
|
77
|
+
currentRendererContainer.style.fontFamily = "'Inter', sans-serif";
|
|
78
|
+
|
|
79
|
+
currentTitleElement = document.createElement('h1');
|
|
80
|
+
currentTitleElement.textContent = 'Tic Tac Toe';
|
|
81
|
+
currentTitleElement.style.fontSize = '1.875rem';
|
|
82
|
+
currentTitleElement.style.fontWeight = 'bold';
|
|
83
|
+
currentTitleElement.style.marginBottom = '1rem';
|
|
84
|
+
currentTitleElement.style.textAlign = 'center';
|
|
85
|
+
currentTitleElement.style.color = KAGGLE_BLUE;
|
|
86
|
+
currentRendererContainer.appendChild(currentTitleElement);
|
|
87
|
+
|
|
88
|
+
currentBoardSvgElement = document.createElementNS(SVG_NS, "svg");
|
|
89
|
+
currentBoardSvgElement.setAttribute("viewBox", `0 0 ${SVG_VIEWBOX_WIDTH} ${SVG_VIEWBOX_HEIGHT}`);
|
|
90
|
+
currentBoardSvgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
91
|
+
currentBoardSvgElement.style.width = "auto";
|
|
92
|
+
currentBoardSvgElement.style.maxWidth = "300px";
|
|
93
|
+
currentBoardSvgElement.style.maxHeight = `calc(100vh - 280px)`;
|
|
94
|
+
currentBoardSvgElement.style.aspectRatio = `${cols} / ${rows}`;
|
|
95
|
+
currentBoardSvgElement.style.display = "block";
|
|
96
|
+
currentBoardSvgElement.style.margin = "0 auto 20px auto";
|
|
97
|
+
currentBoardSvgElement.style.backgroundColor = BOARD_BACKGROUND_COLOR;
|
|
98
|
+
currentBoardSvgElement.style.borderRadius = "8px";
|
|
99
|
+
|
|
100
|
+
for (let i = 1; i < rows; i++) {
|
|
101
|
+
const line = document.createElementNS(SVG_NS, "line");
|
|
102
|
+
line.setAttribute("x1", "0");
|
|
103
|
+
line.setAttribute("y1", (i * CELL_UNIT_SIZE).toString());
|
|
104
|
+
line.setAttribute("x2", SVG_VIEWBOX_WIDTH.toString());
|
|
105
|
+
line.setAttribute("y2", (i * CELL_UNIT_SIZE).toString());
|
|
106
|
+
line.setAttribute("stroke", GRID_LINE_COLOR);
|
|
107
|
+
line.setAttribute("stroke-width", "2");
|
|
108
|
+
currentBoardSvgElement.appendChild(line);
|
|
109
|
+
}
|
|
110
|
+
for (let i = 1; i < cols; i++) {
|
|
111
|
+
const line = document.createElementNS(SVG_NS, "line");
|
|
112
|
+
line.setAttribute("x1", (i * CELL_UNIT_SIZE).toString());
|
|
113
|
+
line.setAttribute("y1", "0");
|
|
114
|
+
line.setAttribute("x2", (i * CELL_UNIT_SIZE).toString());
|
|
115
|
+
line.setAttribute("y2", SVG_VIEWBOX_HEIGHT.toString());
|
|
116
|
+
line.setAttribute("stroke", GRID_LINE_COLOR);
|
|
117
|
+
line.setAttribute("stroke-width", "2");
|
|
118
|
+
currentBoardSvgElement.appendChild(line);
|
|
119
|
+
}
|
|
120
|
+
currentRendererContainer.appendChild(currentBoardSvgElement);
|
|
121
|
+
|
|
122
|
+
const statusContainer = document.createElement('div');
|
|
123
|
+
statusContainer.style.padding = '10px 15px';
|
|
124
|
+
statusContainer.style.backgroundColor = 'white';
|
|
125
|
+
statusContainer.style.borderRadius = '8px';
|
|
126
|
+
statusContainer.style.boxShadow = '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)';
|
|
127
|
+
statusContainer.style.textAlign = 'center';
|
|
128
|
+
statusContainer.style.width = 'auto';
|
|
129
|
+
statusContainer.style.minWidth = '220px';
|
|
130
|
+
statusContainer.style.maxWidth = '90vw';
|
|
131
|
+
currentRendererContainer.appendChild(statusContainer);
|
|
132
|
+
|
|
133
|
+
currentStatusTextElement = document.createElement('p');
|
|
134
|
+
currentStatusTextElement.style.fontSize = '1.1rem';
|
|
135
|
+
currentStatusTextElement.style.fontWeight = '600';
|
|
136
|
+
currentStatusTextElement.style.color = '#333333';
|
|
137
|
+
currentStatusTextElement.style.margin = '0 0 5px 0';
|
|
138
|
+
statusContainer.appendChild(currentStatusTextElement);
|
|
139
|
+
|
|
140
|
+
currentWinnerTextElement = document.createElement('p');
|
|
141
|
+
currentWinnerTextElement.style.fontSize = '1.25rem';
|
|
142
|
+
currentWinnerTextElement.style.fontWeight = '700';
|
|
143
|
+
currentWinnerTextElement.style.color = '#333333';
|
|
144
|
+
currentWinnerTextElement.style.margin = '5px 0 0 0';
|
|
145
|
+
statusContainer.appendChild(currentWinnerTextElement);
|
|
146
|
+
|
|
147
|
+
parentElementToClear.appendChild(currentRendererContainer);
|
|
148
|
+
|
|
149
|
+
if (typeof document !== 'undefined' && !document.body.hasAttribute('data-renderer-initialized')) {
|
|
150
|
+
_showMessage("Renderer initialized (Tic Tac Toe).", "info", 1500);
|
|
151
|
+
document.body.setAttribute('data-renderer-initialized', 'true');
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function _renderBoardDisplay_svg(gameStateToDisplay, displayRows, displayCols) {
|
|
157
|
+
if (!currentBoardSvgElement || !currentStatusTextElement || !currentWinnerTextElement) {
|
|
158
|
+
console.error("Rendering elements not ready. This should not happen if _ensureRendererElements succeeded.");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const existingMarks = currentBoardSvgElement.querySelectorAll(".game-mark");
|
|
163
|
+
existingMarks.forEach(mark => mark.remove());
|
|
164
|
+
|
|
165
|
+
if (!gameStateToDisplay || typeof gameStateToDisplay.board !== 'object' || !Array.isArray(gameStateToDisplay.board) || gameStateToDisplay.board.length !== (displayRows * displayCols)) {
|
|
166
|
+
currentStatusTextElement.textContent = "Waiting for player...";
|
|
167
|
+
currentWinnerTextElement.textContent = "";
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { board, current_player, is_terminal, winner } = gameStateToDisplay;
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < board.length; i++) {
|
|
174
|
+
const cellValue = board[i];
|
|
175
|
+
if (cellValue === null || cellValue === undefined || String(cellValue).trim() === '') {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const row = Math.floor(i / displayCols);
|
|
179
|
+
const col = i % displayCols;
|
|
180
|
+
const cx = col * CELL_UNIT_SIZE + CELL_UNIT_SIZE / 2;
|
|
181
|
+
const cy = row * CELL_UNIT_SIZE + CELL_UNIT_SIZE / 2;
|
|
182
|
+
const cellValueForComparison = String(cellValue).trim().toLowerCase();
|
|
183
|
+
|
|
184
|
+
if (cellValueForComparison === "o") {
|
|
185
|
+
const markO = document.createElementNS(SVG_NS, "circle");
|
|
186
|
+
markO.setAttribute("cx", cx.toString());
|
|
187
|
+
markO.setAttribute("cy", cy.toString());
|
|
188
|
+
markO.setAttribute("r", O_MARK_RADIUS.toString());
|
|
189
|
+
markO.setAttribute("stroke", MARK_COLOR);
|
|
190
|
+
markO.setAttribute("stroke-width", MARK_THICKNESS.toString());
|
|
191
|
+
markO.setAttribute("fill", "none");
|
|
192
|
+
markO.classList.add("game-mark");
|
|
193
|
+
currentBoardSvgElement.appendChild(markO);
|
|
194
|
+
} else if (cellValueForComparison === "x") {
|
|
195
|
+
const line1 = document.createElementNS(SVG_NS, "line");
|
|
196
|
+
line1.setAttribute("x1", (cx - X_MARK_ARM_LENGTH).toString());
|
|
197
|
+
line1.setAttribute("y1", (cy - X_MARK_ARM_LENGTH).toString());
|
|
198
|
+
line1.setAttribute("x2", (cx + X_MARK_ARM_LENGTH).toString());
|
|
199
|
+
line1.setAttribute("y2", (cy + X_MARK_ARM_LENGTH).toString());
|
|
200
|
+
line1.setAttribute("stroke", MARK_COLOR);
|
|
201
|
+
line1.setAttribute("stroke-width", MARK_THICKNESS.toString());
|
|
202
|
+
line1.classList.add("game-mark");
|
|
203
|
+
currentBoardSvgElement.appendChild(line1);
|
|
204
|
+
|
|
205
|
+
const line2 = document.createElementNS(SVG_NS, "line");
|
|
206
|
+
line2.setAttribute("x1", (cx + X_MARK_ARM_LENGTH).toString());
|
|
207
|
+
line2.setAttribute("y1", (cy - X_MARK_ARM_LENGTH).toString());
|
|
208
|
+
line2.setAttribute("x2", (cx - X_MARK_ARM_LENGTH).toString());
|
|
209
|
+
line2.setAttribute("y2", (cy + X_MARK_ARM_LENGTH).toString());
|
|
210
|
+
line2.setAttribute("stroke", MARK_COLOR);
|
|
211
|
+
line2.setAttribute("stroke-width", MARK_THICKNESS.toString());
|
|
212
|
+
line2.classList.add("game-mark");
|
|
213
|
+
currentBoardSvgElement.appendChild(line2);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
currentStatusTextElement.innerHTML = '';
|
|
218
|
+
currentWinnerTextElement.innerHTML = '';
|
|
219
|
+
const spanStyle = 'font-weight: bold; color: ' + MARK_COLOR + ';';
|
|
220
|
+
|
|
221
|
+
if (is_terminal) {
|
|
222
|
+
currentStatusTextElement.textContent = '';
|
|
223
|
+
if (winner !== null && winner !== undefined) {
|
|
224
|
+
if (String(winner).toLowerCase() === 'draw') {
|
|
225
|
+
currentWinnerTextElement.textContent = "It's a Draw!";
|
|
226
|
+
} else {
|
|
227
|
+
let winnerSymbolDisplay;
|
|
228
|
+
if (String(winner).toLowerCase() === "o") { winnerSymbolDisplay = PLAYER_SYMBOLS[0]; }
|
|
229
|
+
else if (String(winner).toLowerCase() === "x") { winnerSymbolDisplay = PLAYER_SYMBOLS[1]; }
|
|
230
|
+
if (winnerSymbolDisplay) {
|
|
231
|
+
currentWinnerTextElement.innerHTML = 'Player <span style="' + spanStyle + '">' + winnerSymbolDisplay + '</span> Wins!';
|
|
232
|
+
} else {
|
|
233
|
+
currentWinnerTextElement.textContent = `Winner: ${String(winner).toUpperCase()}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else { currentWinnerTextElement.textContent = "Game Over!"; }
|
|
237
|
+
} else {
|
|
238
|
+
let playerSymbolToDisplay;
|
|
239
|
+
if (String(current_player).toLowerCase() === "o") { playerSymbolToDisplay = PLAYER_SYMBOLS[0]; }
|
|
240
|
+
else if (String(current_player).toLowerCase() === "x") { playerSymbolToDisplay = PLAYER_SYMBOLS[1]; }
|
|
241
|
+
if (playerSymbolToDisplay) {
|
|
242
|
+
currentStatusTextElement.innerHTML = 'Current Player: <span style="' + spanStyle + '">' + playerSymbolToDisplay + '</span>';
|
|
243
|
+
} else {
|
|
244
|
+
currentStatusTextElement.textContent = "Waiting for player...";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!_ensureRendererElements(parent, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS)) {
|
|
250
|
+
if (parent && typeof parent.innerHTML !== 'undefined') {
|
|
251
|
+
parent.innerHTML = "<p style='color:red; font-family: sans-serif;'>Critical Error: Renderer element setup failed.</p>";
|
|
252
|
+
}
|
|
253
|
+
console.error("Renderer element setup failed and parent could not be updated.");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!environment || !environment.steps || !environment.steps[step]) {
|
|
258
|
+
_renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
|
|
259
|
+
if(currentStatusTextElement) currentStatusTextElement.textContent = "Initializing environment...";
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const currentStepAgents = environment.steps[step];
|
|
264
|
+
if (!currentStepAgents || !Array.isArray(currentStepAgents) || currentStepAgents.length === 0) {
|
|
265
|
+
_renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
|
|
266
|
+
if(currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for agent data...";
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const environmentAgentIndex = currentStepAgents.length - 1;
|
|
271
|
+
const environmentAgent = currentStepAgents[environmentAgentIndex];
|
|
272
|
+
|
|
273
|
+
if (!environmentAgent || typeof environmentAgent.observation === 'undefined') {
|
|
274
|
+
_renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
|
|
275
|
+
if(currentStatusTextElement) currentStatusTextElement.textContent = "Waiting for observation data...";
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const observationForBoardState = environmentAgent.observation;
|
|
280
|
+
let gameSpecificState = null;
|
|
281
|
+
|
|
282
|
+
if (observationForBoardState) {
|
|
283
|
+
let boardArray = null;
|
|
284
|
+
let currentPlayerForState = null;
|
|
285
|
+
|
|
286
|
+
if (typeof observationForBoardState.observation_string === 'string' && observationForBoardState.observation_string.trim().startsWith('{')) {
|
|
287
|
+
try {
|
|
288
|
+
const parsedState = JSON.parse(observationForBoardState.observation_string);
|
|
289
|
+
if (parsedState && Array.isArray(parsedState.board)) {
|
|
290
|
+
boardArray = parsedState.board;
|
|
291
|
+
}
|
|
292
|
+
if (parsedState && typeof parsedState.current_player === 'string') {
|
|
293
|
+
currentPlayerForState = parsedState.current_player;
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
_showMessage("Error parsing game state from observation string.", 'error');
|
|
297
|
+
console.error("Failed to parse observation_string JSON:", e);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!boardArray) {
|
|
302
|
+
boardArray = [];
|
|
303
|
+
for (let i = 0; i < DEFAULT_NUM_ROWS * DEFAULT_NUM_COLS; i++) {
|
|
304
|
+
boardArray.push(null);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!currentPlayerForState) {
|
|
309
|
+
if (observationForBoardState.current_player === 0) { currentPlayerForState = 'x'; }
|
|
310
|
+
else if (observationForBoardState.current_player === 1) { currentPlayerForState = 'o'; }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const isTerminal = !!observationForBoardState.is_terminal;
|
|
314
|
+
let winnerForState = null;
|
|
315
|
+
|
|
316
|
+
if (isTerminal) {
|
|
317
|
+
const finalRewards = window.kaggle && window.kaggle.rewards;
|
|
318
|
+
if (finalRewards && Array.isArray(finalRewards)) {
|
|
319
|
+
if (finalRewards[0] === 1.0) winnerForState = 'x';
|
|
320
|
+
else if (finalRewards[1] === 1.0) winnerForState = 'o';
|
|
321
|
+
else if (finalRewards[0] === 0.0 && finalRewards[1] === 0.0) winnerForState = 'draw';
|
|
322
|
+
}
|
|
323
|
+
if (observationForBoardState.current_player === -2 && winnerForState === null) {
|
|
324
|
+
winnerForState = 'draw';
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
gameSpecificState = {
|
|
329
|
+
board: boardArray,
|
|
330
|
+
current_player: currentPlayerForState,
|
|
331
|
+
is_terminal: isTerminal,
|
|
332
|
+
winner: winnerForState
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!gameSpecificState) {
|
|
337
|
+
_renderBoardDisplay_svg(null, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
|
|
338
|
+
if(currentStatusTextElement) currentStatusTextElement.textContent = "Error processing game state.";
|
|
339
|
+
_showMessage("Error: Game state could not be parsed correctly.", 'error');
|
|
340
|
+
console.error("Could not determine gameSpecificState from:", observationForBoardState);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_renderBoardDisplay_svg(gameSpecificState, DEFAULT_NUM_ROWS, DEFAULT_NUM_COLS);
|
|
345
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Change Tic Tac Toe state and action string representations.
|
|
2
|
+
|
|
3
|
+
Intended as an example of how to subclass proxy.Game and proxy.State.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
game = TicTacToeGame()
|
|
7
|
+
state = game.new_initial_state()
|
|
8
|
+
state.apply_action(state.legal_actions()[0])
|
|
9
|
+
print(state)
|
|
10
|
+
|
|
11
|
+
Shows the board in a more readable format:
|
|
12
|
+
x | . | .
|
|
13
|
+
-----------
|
|
14
|
+
. | . | .
|
|
15
|
+
-----------
|
|
16
|
+
. | . | .
|
|
17
|
+
|
|
18
|
+
Also remaps action strings to following format: <row,col>
|
|
19
|
+
|
|
20
|
+
Adds to_json() and action_to_json() methods as symbolic representation.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from ... import proxy
|
|
27
|
+
import pyspiel
|
|
28
|
+
|
|
29
|
+
NUM_COLS = 3
|
|
30
|
+
NUM_ROWS = 3
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TicTacToeState(proxy.State):
|
|
34
|
+
"""Tic Tac Toe state proxy."""
|
|
35
|
+
|
|
36
|
+
def observation_string(self, player: int) -> str:
|
|
37
|
+
del player # Unused.
|
|
38
|
+
return str(self) # Perfect information game, return state as observation.
|
|
39
|
+
|
|
40
|
+
def to_json(self) -> str:
|
|
41
|
+
board = "".join(str(self).split())
|
|
42
|
+
assert len(board) == 9
|
|
43
|
+
board = list(board)
|
|
44
|
+
board = [None if cell == "." else cell for cell in board]
|
|
45
|
+
if self.is_terminal():
|
|
46
|
+
current_player = None
|
|
47
|
+
elif board.count("x") > board.count("o"):
|
|
48
|
+
current_player = "o"
|
|
49
|
+
else:
|
|
50
|
+
current_player = "x"
|
|
51
|
+
return json.dumps(dict(board=board, current_player=current_player))
|
|
52
|
+
|
|
53
|
+
def _action_to_string(self, player: int, action: int) -> str:
|
|
54
|
+
del player # Unused.
|
|
55
|
+
row, col = divmod(action, NUM_COLS)
|
|
56
|
+
return f'<{row},{col}>'
|
|
57
|
+
|
|
58
|
+
def action_to_json(self, *args: int) -> str:
|
|
59
|
+
match len(args):
|
|
60
|
+
case 1:
|
|
61
|
+
action = args[0]
|
|
62
|
+
return self._action_to_json(self.current_player(), action)
|
|
63
|
+
case 2:
|
|
64
|
+
return self._action_to_json(args[0], args[1])
|
|
65
|
+
case _:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f'Invalid args, expected (player) or (player, action), got: {args}'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _action_to_json(self, player: int, action: int) -> str:
|
|
71
|
+
row, col = divmod(action, NUM_COLS)
|
|
72
|
+
player_str = None
|
|
73
|
+
if player == 0:
|
|
74
|
+
player_str = "x"
|
|
75
|
+
elif player == 1:
|
|
76
|
+
player_str = "o"
|
|
77
|
+
return json.dumps({'player': player_str, 'row': int(row), 'col': int(col)})
|
|
78
|
+
|
|
79
|
+
def observation_json(self, player: int) -> str:
|
|
80
|
+
del player # Unused.
|
|
81
|
+
return self.to_json()
|
|
82
|
+
|
|
83
|
+
def observation_string(self, player: int) -> str:
|
|
84
|
+
return self.observation_json(player)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TicTacToeGame(proxy.Game):
|
|
88
|
+
"""Tic Tac Toe game proxy."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, params: Any | None = None):
|
|
91
|
+
del params
|
|
92
|
+
wrapped = pyspiel.load_game('tic_tac_toe()')
|
|
93
|
+
super().__init__(
|
|
94
|
+
wrapped, short_name='tic_tac_toe_proxy', long_name='Tic Tac Toe (proxy)'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def new_initial_state(self, *args) -> TicTacToeState:
|
|
98
|
+
return TicTacToeState(self.__wrapped__.new_initial_state(*args), game=self)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
pyspiel.register_game(TicTacToeGame().get_type(), TicTacToeGame)
|
|
File without changes
|