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
@@ -0,0 +1,431 @@
1
+ function renderer(options) {
2
+ // --- Existing Elements and Style Injection (Unchanged) ---
3
+ const elements = {
4
+ pokerTableContainer: null,
5
+ pokerTable: null,
6
+ communityCardsContainer: null,
7
+ potDisplay: null,
8
+ playerPods: [],
9
+ dealerButton: null,
10
+ diagnosticHeader: null,
11
+ gameMessageArea: null,
12
+ };
13
+
14
+ function _injectStyles(passedOptions) {
15
+ if (typeof document === 'undefined' || window.__poker_styles_injected) {
16
+ return;
17
+ }
18
+ const style = document.createElement('style');
19
+ style.textContent = `
20
+ .poker-renderer-host {
21
+ width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
22
+ font-family: 'Inter', sans-serif; background-color: #2d3748; color: #fff;
23
+ overflow: hidden; padding: 1rem; box-sizing: border-box;
24
+ }
25
+ .poker-table-container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
26
+ .poker-table {
27
+ width: clamp(400px, 85vw, 850px); height: clamp(220px, 48vw, 450px);
28
+ background-color: #006400; border-radius: 225px; position: relative;
29
+ border: 12px solid #5c3a21; box-shadow: 0 0 25px rgba(0,0,0,0.6);
30
+ display: flex; align-items: center; justify-content: center;
31
+ }
32
+ .player-pod {
33
+ background-color: rgba(0, 0, 0, 0.75); border: 1px solid #4a5568; border-radius: 0.75rem;
34
+ padding: 0.6rem 0.8rem; color: white; text-align: center; position: absolute;
35
+ min-width: 120px; max-width: 160px; box-shadow: 0 3px 12px rgba(0,0,0,0.35);
36
+ transform: translateX(-50%); display: flex; flex-direction: column; justify-content: space-between;
37
+ min-height: 130px;
38
+ }
39
+ .player-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; margin-bottom: 0.25rem; font-size: 0.9rem;}
40
+ .player-stack { font-size: 0.8rem; color: #facc15; margin-bottom: 0.25rem; }
41
+ .player-cards-container { margin: 0.25rem 0; min-height: 70px; display: flex; justify-content: center; align-items:center;}
42
+ .player-status { font-size: 0.75rem; color: #9ca3af; min-height: 1.1em; margin-top: 0.25rem; }
43
+ .card {
44
+ display: inline-flex; flex-direction: column; justify-content: center; align-items: center;
45
+ width: 48px; height: 68px; border: 1px solid #999; border-radius: 0.375rem; margin: 0 3px;
46
+ background-color: white; color: black; font-weight: bold; text-align: center; overflow: hidden; position: relative;
47
+ }
48
+ .card-rank { font-size: 1.8rem; line-height: 1; display: block; margin-top: 2px; }
49
+ .card-suit { font-size: 1.5rem; line-height: 1; display: block; }
50
+ .card-red .card-suit { color: #c0392b; } .card-black .card-suit { color: #1a202c; }
51
+ .card-back {
52
+ background-color: #2b6cb0;
53
+ background-image: linear-gradient(45deg, rgba(255,255,255,0.1) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.1) 75%, rgba(255,255,255,0.1)),
54
+ linear-gradient(-45deg, rgba(255,255,255,0.1) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.1) 75%, rgba(255,255,255,0.1));
55
+ background-size: 10px 10px; border: 2px solid #63b3ed;
56
+ }
57
+ .card-back .card-rank, .card-back .card-suit { display: none; }
58
+ .community-cards-area { text-align: center; z-index: 10; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
59
+ .community-cards-container { min-height: 75px; display: flex; justify-content: center; align-items:center; margin-bottom: 0.5rem; }
60
+ .community-cards-container .card { width: 52px; height: 72px; }
61
+ .community-cards-container .card-rank { font-size: 2rem; } .community-cards-container .card-suit { font-size: 1.7rem; }
62
+ .pot-display { font-size: 1.1rem; font-weight: bold; color: #facc15; }
63
+ .bet-display {
64
+ display: inline-block; min-width: 55px; padding: 4px 8px; border-radius: 12px;
65
+ background-color: #1a202c; color: #f1c40f; font-size: 0.8rem; line-height: 1.4;
66
+ text-align: center; margin-top: 4px; border: 1.5px solid #f1c40f; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
67
+ }
68
+ .blind-indicator { font-size: 0.7rem; color: #a0aec0; margin-top: 3px; }
69
+ .dealer-button {
70
+ width: 36px; height: 36px; background-color: #f0f0f0; color: #333; border-radius: 50%;
71
+ text-align: center; line-height: 36px; font-weight: bold; font-size: 1rem; position: absolute;
72
+ border: 2px solid #888; box-shadow: 0 1px 3px rgba(0,0,0,0.3); z-index: 5;
73
+ }
74
+ .pos-player0-sb { bottom: -55px; left: 50%; }
75
+ .pos-player1-bb { top: -55px; left: 50%; }
76
+ .dealer-sb { bottom: -15px; left: calc(50% + 95px); transform: translateX(-50%); }
77
+ .current-player-turn-highlight { border: 2px solid #f1c40f !important; box-shadow: 0 0 15px #f1c40f, 0 3px 12px rgba(0,0,0,0.35) !important; }
78
+ #game-message-area { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); background-color: rgba(0,0,0,0.6); padding: 5px 10px; border-radius: 5px; font-size: 0.9rem; z-index: 20;}
79
+
80
+ @media (max-width: 768px) {
81
+ .poker-table { width: clamp(350px, 90vw, 700px); height: clamp(180px, 48vw, 350px); border-radius: 175px; }
82
+ .pos-player0-sb { bottom: -50px; } .pos-player1-bb { top: -50px; }
83
+ .dealer-sb { left: calc(50% + 85px); bottom: -12px; }
84
+ .player-pod { min-width: 110px; max-width: 150px; padding: 0.5rem 0.7rem; min-height: 120px; }
85
+ .card { width: 44px; height: 62px; } .card-rank { font-size: 1.6rem; } .card-suit { font-size: 1.3rem; }
86
+ .community-cards-container .card { width: 48px; height: 68px; }
87
+ .community-cards-container .card-rank { font-size: 1.8rem;} .community-cards-container .card-suit { font-size: 1.5rem;}
88
+ }
89
+ @media (max-width: 600px) {
90
+ .poker-table { width: clamp(300px, 90vw, 500px); height: clamp(160px, 50vw, 250px); border-radius: 125px; }
91
+ .player-pod { min-width: 100px; max-width: 140px; padding: 0.4rem 0.5rem; font-size: 0.85rem; min-height: 110px;}
92
+ .player-name { font-size: 0.85rem;} .player-stack { font-size: 0.75rem; }
93
+ .card { width: 40px; height: 58px; margin: 0 2px; } .card-rank { font-size: 1.4rem; } .card-suit { font-size: 1.2rem; }
94
+ .community-cards-container .card { width: 42px; height: 60px; }
95
+ .community-cards-container .card-rank { font-size: 1.5rem;} .community-cards-container .card-suit { font-size: 1.3rem;}
96
+ .bet-display { font-size: 0.75rem; } .pos-player0-sb { bottom: -45px; } .pos-player1-bb { top: -45px; }
97
+ .dealer-button { width: 32px; height: 32px; line-height: 32px; font-size: 0.9rem;}
98
+ .dealer-sb { bottom: -8px; left: calc(50% + 75px); }
99
+ }
100
+ @media (max-width: 400px) {
101
+ .poker-table { width: clamp(280px, 95vw, 380px); height: clamp(150px, 55vw, 200px); border-radius: 100px; border-width: 8px; }
102
+ .player-pod { min-width: 90px; max-width: 120px; padding: 0.3rem 0.4rem; min-height: 100px;}
103
+ .player-name { font-size: 0.8rem;} .player-stack { font-size: 0.7rem; }
104
+ .card { width: 36px; height: 52px; margin: 0 1px; } .card-rank { font-size: 1.2rem; } .card-suit { font-size: 1rem; }
105
+ .community-cards-container .card { width: 38px; height: 55px; }
106
+ .community-cards-container .card-rank { font-size: 1.3rem;} .community-cards-container .card-suit { font-size: 1.1rem;}
107
+ .dealer-button { width: 28px; height: 28px; line-height: 28px; font-size: 0.8rem;}
108
+ .pos-player0-sb { bottom: -40px; } .pos-player1-bb { top: -40px; }
109
+ .dealer-sb { bottom: -5px; left: calc(50% + 65px); }
110
+ }
111
+ `;
112
+ const parentForStyles = passedOptions && passedOptions.parent ? passedOptions.parent.ownerDocument.head : document.head;
113
+ if (parentForStyles && !parentForStyles.querySelector('style[data-poker-renderer-styles]')) {
114
+ style.setAttribute('data-poker-renderer-styles', 'true');
115
+ parentForStyles.appendChild(style);
116
+ }
117
+ window.__poker_styles_injected = true;
118
+ }
119
+
120
+ function acpcCardToDisplay(acpcCard) {
121
+ if (!acpcCard || acpcCard.length < 2) return { rank: '?', suitSymbol: '', original: acpcCard };
122
+ const rankChar = acpcCard[0].toUpperCase();
123
+ const suitChar = acpcCard[1].toLowerCase();
124
+ const rankMap = { 'T': '10', 'J': 'J', 'Q': 'Q', 'K': 'K', 'A': 'A' };
125
+ const suitMap = { 's': '♠', 'h': '♥', 'd': '♦', 'c': '♣' };
126
+ const rank = rankMap[rankChar] || rankChar;
127
+ const suitSymbol = suitMap[suitChar] || '';
128
+ return { rank, suitSymbol, original: acpcCard };
129
+ }
130
+
131
+ function createCardElement(cardStr, isHidden = false) {
132
+ const cardDiv = document.createElement('div');
133
+ cardDiv.classList.add('card');
134
+ if (isHidden || !cardStr || cardStr === '?' || cardStr === "??") {
135
+ cardDiv.classList.add('card-back');
136
+ } else {
137
+ const { rank, suitSymbol } = acpcCardToDisplay(cardStr);
138
+ const rankSpan = document.createElement('span');
139
+ rankSpan.classList.add('card-rank');
140
+ rankSpan.textContent = rank;
141
+ cardDiv.appendChild(rankSpan);
142
+ const suitSpan = document.createElement('span');
143
+ suitSpan.classList.add('card-suit');
144
+ suitSpan.textContent = suitSymbol;
145
+ cardDiv.appendChild(suitSpan);
146
+ if (suitSymbol === '♥' || suitSymbol === '♦') cardDiv.classList.add('card-red');
147
+ else if (suitSymbol === '♠' || suitSymbol === '♣') cardDiv.classList.add('card-black');
148
+ }
149
+ return cardDiv;
150
+ }
151
+
152
+ function _ensurePokerTableElements(parentElement, passedOptions) {
153
+ if (!parentElement) return false;
154
+ parentElement.innerHTML = '';
155
+ parentElement.classList.add('poker-renderer-host');
156
+
157
+ elements.diagnosticHeader = document.createElement('h1');
158
+ elements.diagnosticHeader.id = 'poker-renderer-diagnostic-header';
159
+ elements.diagnosticHeader.textContent = "Poker Table Initialized (Live Data)";
160
+ elements.diagnosticHeader.style.cssText = "color: lime; background-color: black; padding: 5px; font-size: 12px; position: absolute; top: 0px; left: 0px; z-index: 10001; display: none;"; // Hidden by default
161
+ parentElement.appendChild(elements.diagnosticHeader);
162
+
163
+ elements.gameMessageArea = document.createElement('div');
164
+ elements.gameMessageArea.id = 'game-message-area';
165
+ parentElement.appendChild(elements.gameMessageArea);
166
+
167
+ elements.pokerTableContainer = document.createElement('div');
168
+ elements.pokerTableContainer.className = 'poker-table-container';
169
+ parentElement.appendChild(elements.pokerTableContainer);
170
+
171
+ elements.pokerTable = document.createElement('div');
172
+ elements.pokerTable.className = 'poker-table';
173
+ elements.pokerTableContainer.appendChild(elements.pokerTable);
174
+
175
+ const communityArea = document.createElement('div');
176
+ communityArea.className = 'community-cards-area';
177
+ elements.pokerTable.appendChild(communityArea);
178
+
179
+ elements.communityCardsContainer = document.createElement('div');
180
+ elements.communityCardsContainer.className = 'community-cards-container';
181
+ communityArea.appendChild(elements.communityCardsContainer);
182
+
183
+ elements.potDisplay = document.createElement('div');
184
+ elements.potDisplay.className = 'pot-display';
185
+ communityArea.appendChild(elements.potDisplay);
186
+
187
+ elements.playerPods = [];
188
+ for (let i = 0; i < 2; i++) {
189
+ const playerPod = document.createElement('div');
190
+ playerPod.className = `player-pod ${i === 0 ? 'pos-player0-sb' : 'pos-player1-bb'}`;
191
+ playerPod.innerHTML = `
192
+ <div class="player-name">Player ${i}</div>
193
+ <div class="player-stack">$0.00</div>
194
+ <div class="player-cards-container"></div>
195
+ <div class="bet-display" style="display:none;">$0.00</div>
196
+ <div class="player-status">(${i === 0 ? 'SB' : 'BB'})</div>
197
+ `;
198
+ elements.pokerTable.appendChild(playerPod);
199
+ elements.playerPods.push(playerPod);
200
+ }
201
+
202
+ elements.dealerButton = document.createElement('div');
203
+ elements.dealerButton.className = 'dealer-button dealer-sb';
204
+ elements.dealerButton.textContent = 'D';
205
+ elements.dealerButton.style.display = 'none';
206
+ elements.pokerTable.appendChild(elements.dealerButton);
207
+ return true;
208
+ }
209
+
210
+
211
+ // --- REVISED PARSING LOGIC ---
212
+ function _parseKagglePokerState(options) {
213
+ const { environment, step } = options;
214
+ const numPlayers = 2; // Assuming 2 players based on logs
215
+
216
+ // --- Default State ---
217
+ const defaultUIData = {
218
+ players: Array(numPlayers).fill(null).map((_, i) => ({
219
+ id: `player${i}`,
220
+ name: `Player ${i}`,
221
+ stack: 0,
222
+ cards: [], // Will be filled with nulls or cards
223
+ currentBet: 0,
224
+ position: i === 0 ? "SB" : "BB",
225
+ isDealer: i === 0,
226
+ isTurn: false,
227
+ status: "Waiting...",
228
+ reward: null
229
+ })),
230
+ communityCards: [],
231
+ pot: 0,
232
+ isTerminal: false,
233
+ gameMessage: "Initializing...",
234
+ rawObservation: null, // For debugging
235
+ };
236
+
237
+ // --- Step Validation ---
238
+ if (!environment || !environment.steps || !environment.steps[step]) {
239
+ return defaultUIData;
240
+ }
241
+ const currentStepAgents = environment.steps[step];
242
+ if (!currentStepAgents || currentStepAgents.length < numPlayers) {
243
+ defaultUIData.gameMessage = "Waiting for agent data...";
244
+ return defaultUIData;
245
+ }
246
+
247
+ // --- Observation Extraction & Merging ---
248
+ let obsP0 = null, obsP1 = null;
249
+ try {
250
+ obsP0 = JSON.parse(currentStepAgents[0].observation.observation_string);
251
+ obsP1 = JSON.parse(currentStepAgents[1].observation.observation_string);
252
+ } catch (e) {
253
+ defaultUIData.gameMessage = "Error parsing observation JSON.";
254
+ return defaultUIData;
255
+ }
256
+
257
+ if (!obsP0) {
258
+ defaultUIData.gameMessage = "Waiting for valid game state...";
259
+ return defaultUIData;
260
+ }
261
+
262
+ // --- Combine observations into a single, reliable state object ---
263
+ const combinedState = { ...obsP0 }; // Start with Player 0's data
264
+ // Player hands are split across observations. We need to merge them.
265
+ combinedState.player_hands = [
266
+ // Take the real hand from P0's obs
267
+ obsP0.player_hands[0].length > 0 ? obsP0.player_hands[0] : [],
268
+ // Take the real hand from P1's obs
269
+ obsP1.player_hands[1].length > 0 ? obsP1.player_hands[1] : []
270
+ ];
271
+
272
+ defaultUIData.rawObservation = combinedState;
273
+
274
+ // --- Populate UI Data from Combined State ---
275
+ const {
276
+ pot_size,
277
+ player_contributions,
278
+ starting_stacks,
279
+ player_hands,
280
+ board_cards,
281
+ current_player,
282
+ betting_history,
283
+ } = combinedState;
284
+
285
+ const isTerminal = current_player === "terminal";
286
+ defaultUIData.isTerminal = isTerminal;
287
+ defaultUIData.pot = pot_size || 0;
288
+ defaultUIData.communityCards = board_cards || [];
289
+
290
+
291
+ // --- Update Player Pods ---
292
+ for (let i = 0; i < numPlayers; i++) {
293
+ const pData = defaultUIData.players[i];
294
+ const contribution = player_contributions ? player_contributions[i] : 0;
295
+ const startStack = starting_stacks ? starting_stacks[i] : 0;
296
+
297
+ pData.currentBet = contribution;
298
+ pData.stack = startStack - contribution;
299
+ pData.cards = (player_hands[i] || []).map(c => c === "??" ? null : c);
300
+ pData.isTurn = String(i) === String(current_player);
301
+ pData.status = pData.position; // Default status
302
+
303
+ if (isTerminal) {
304
+ const reward = environment.rewards ? environment.rewards[i] : null;
305
+ pData.reward = reward;
306
+ if (reward > 0) pData.status = "Winner!";
307
+ else if (reward < 0) pData.status = "Loser";
308
+ else pData.status = "Game Over";
309
+ } else if (pData.isTurn) {
310
+ pData.status = "Thinking...";
311
+ } else if (pData.stack === 0 && pData.currentBet > 0) {
312
+ pData.status = "All-in";
313
+ }
314
+ }
315
+
316
+ // Handle folded player status
317
+ if (!isTerminal && betting_history && betting_history.includes('f')) {
318
+ // A simple fold check: the player who didn't make the last action and isn't the current player might have folded.
319
+ // This is a simplification. A more robust parser would track the betting sequence.
320
+ const lastAction = betting_history.slice(-1);
321
+ if (lastAction === 'f') {
322
+ // Find who is NOT the current player
323
+ const nonCurrentPlayerIndex = current_player === '0' ? 1 : 0;
324
+ // If they are not all-in, they folded.
325
+ if (defaultUIData.players[nonCurrentPlayerIndex].status !== 'All-in') {
326
+ defaultUIData.players[nonCurrentPlayerIndex].status = "Folded";
327
+ }
328
+ }
329
+ }
330
+
331
+
332
+ // --- Set Game Message ---
333
+ if (isTerminal) {
334
+ const winnerIndex = environment.rewards ? environment.rewards.findIndex(r => r > 0) : -1;
335
+ if (winnerIndex !== -1) {
336
+ defaultUIData.gameMessage = `Player ${winnerIndex} wins!`;
337
+ } else {
338
+ defaultUIData.gameMessage = "Game Over.";
339
+ }
340
+ } else if (current_player === "chance") {
341
+ defaultUIData.gameMessage = `Dealing...`;
342
+ } else {
343
+ defaultUIData.gameMessage = `Player ${current_player}'s turn.`;
344
+ }
345
+
346
+ return defaultUIData;
347
+ }
348
+
349
+
350
+ // --- RENDERER UI LOGIC (Unchanged) ---
351
+ function _renderPokerTableUI(data, passedOptions) {
352
+ if (!elements.pokerTable || !data) return;
353
+ const { players, communityCards, pot, isTerminal, gameMessage } = data;
354
+
355
+ if (elements.diagnosticHeader && data.rawObservation) {
356
+ // Optional: Show diagnostics for debugging
357
+ // elements.diagnosticHeader.textContent = `[${passedOptions.step}] P_TURN:${data.rawObservation.current_player} POT:${data.pot}`;
358
+ // elements.diagnosticHeader.style.display = 'block';
359
+ }
360
+ if (elements.gameMessageArea) {
361
+ elements.gameMessageArea.textContent = gameMessage;
362
+ }
363
+
364
+ elements.communityCardsContainer.innerHTML = '';
365
+ if (communityCards && communityCards.length > 0) {
366
+ communityCards.forEach(cardStr => {
367
+ elements.communityCardsContainer.appendChild(createCardElement(cardStr));
368
+ });
369
+ }
370
+
371
+ elements.potDisplay.textContent = `Pot: $${pot}`;
372
+
373
+ players.forEach((playerData, index) => {
374
+ const playerPod = elements.playerPods[index];
375
+ if (!playerPod) return;
376
+
377
+ playerPod.querySelector('.player-name').textContent = playerData.name;
378
+ playerPod.querySelector('.player-stack').textContent = `$${playerData.stack}`;
379
+
380
+ const playerCardsContainer = playerPod.querySelector('.player-cards-container');
381
+ playerCardsContainer.innerHTML = '';
382
+
383
+ // In heads-up, we show both hands at the end.
384
+ const showCards = isTerminal || (playerData.cards && !playerData.cards.includes(null));
385
+
386
+ (playerData.cards || [null, null]).forEach(cardStr => {
387
+ playerCardsContainer.appendChild(createCardElement(cardStr, !showCards && cardStr !== null));
388
+ });
389
+
390
+ const betDisplay = playerPod.querySelector('.bet-display');
391
+ if (playerData.currentBet > 0) {
392
+ betDisplay.textContent = `$${playerData.currentBet}`;
393
+ betDisplay.style.display = 'inline-block';
394
+ } else {
395
+ betDisplay.style.display = 'none';
396
+ }
397
+
398
+ playerPod.querySelector('.player-status').textContent = playerData.status;
399
+
400
+ if (playerData.isTurn && !isTerminal) {
401
+ playerPod.classList.add('current-player-turn-highlight');
402
+ } else {
403
+ playerPod.classList.remove('current-player-turn-highlight');
404
+ }
405
+ });
406
+
407
+ const dealerPlayer = players.find(p => p.isDealer);
408
+ if (elements.dealerButton) {
409
+ elements.dealerButton.style.display = dealerPlayer ? 'block' : 'none';
410
+ }
411
+ }
412
+
413
+ // --- MAIN EXECUTION LOGIC ---
414
+ const { parent } = options;
415
+ if (!parent) {
416
+ console.error("Renderer: Parent element not provided.");
417
+ return;
418
+ }
419
+
420
+ _injectStyles(options);
421
+
422
+ if (!_ensurePokerTableElements(parent, options)) {
423
+ console.error("Renderer: Failed to ensure poker table elements.");
424
+ parent.innerHTML = '<p style="color:red;">Error: Could not create poker table structure.</p>';
425
+ return;
426
+ }
427
+
428
+ // Use the revised parsing logic
429
+ const uiData = _parseKagglePokerState(options);
430
+ _renderPokerTableUI(uiData, options);
431
+ }
@@ -0,0 +1,159 @@
1
+ """Change Universal Poker state and action string representations."""
2
+
3
+ import json
4
+ import re
5
+ from typing import Any
6
+
7
+ from ... import proxy
8
+ import pyspiel
9
+
10
+
11
+ class UniversalPokerState(proxy.State):
12
+ """Universal Poker state proxy."""
13
+
14
+ def _player_string(self, player: int) -> str:
15
+ if player == pyspiel.PlayerId.CHANCE:
16
+ return "chance"
17
+ elif player == pyspiel.PlayerId.TERMINAL:
18
+ return "terminal"
19
+ if player < 0:
20
+ return pyspiel.PlayerId(player).name.lower()
21
+ else:
22
+ return str(player)
23
+
24
+ def _state_dict(self) -> dict[str, Any]:
25
+ params = self.get_game().get_parameters()
26
+ blinds = params["blind"].strip().split()
27
+ blinds = [int(blind) for blind in blinds]
28
+ assert len(blinds) == self.num_players()
29
+ starting_stacks = params["stack"].strip().split()
30
+ starting_stacks = [int(stack) for stack in starting_stacks]
31
+ assert len(starting_stacks) == self.num_players()
32
+ state_str = self.to_string()
33
+ state_lines = state_str.split("\n")
34
+ player_hands = []
35
+ for i in range(self.num_players()):
36
+ for line in state_lines:
37
+ if line.startswith(f"P{i} Cards:"):
38
+ hand = line.split(":")[1].strip()
39
+ player_hands.append([hand[i:i+2] for i in range(0, len(hand), 2)])
40
+ assert len(player_hands) == self.num_players()
41
+ board_cards = None
42
+ for line in state_lines:
43
+ if line.startswith("BoardCards"):
44
+ board_cards_str = line.removeprefix("BoardCards").strip()
45
+ board_cards = [
46
+ board_cards_str[i:i+2] for i in range(0, len(board_cards_str), 2)
47
+ ]
48
+ assert board_cards is not None
49
+ pattern = r"P\d+:\s*(\d+)"
50
+ player_contributions = []
51
+ for line in state_lines:
52
+ if line.startswith("Spent:"):
53
+ matches = re.findall(pattern, line)
54
+ player_contributions = [int(match) for match in matches]
55
+ assert len(player_contributions) == self.num_players()
56
+ acpc_state = None
57
+ betting_history = None
58
+ for line in state_lines:
59
+ if line.startswith("ACPC State:"):
60
+ acpc_state = line.split("ACPC State:")[1].strip()
61
+ betting_history = acpc_state.split(":")[2]
62
+ assert acpc_state is not None
63
+ assert betting_history is not None
64
+
65
+ state_dict = {}
66
+ state_dict["acpc_state"] = acpc_state
67
+ state_dict["current_player"] = self._player_string(self.current_player())
68
+ state_dict["blinds"] = blinds
69
+ state_dict["betting_history"] = betting_history
70
+ state_dict["player_contributions"] = player_contributions
71
+ state_dict["pot_size"] = sum(player_contributions)
72
+ state_dict["starting_stacks"] = starting_stacks
73
+ state_dict["player_hands"] = player_hands
74
+ state_dict["board_cards"] = board_cards
75
+ return state_dict
76
+
77
+ def to_json(self) -> str:
78
+ return json.dumps(self._state_dict())
79
+
80
+ def _action_to_string(self, player: int, action: int) -> str:
81
+ if player == pyspiel.PlayerId.CHANCE:
82
+ return f"deal {action}" # TODO(jhtschultz): Add card.
83
+ if action == 0:
84
+ return "fold"
85
+ elif action == 1:
86
+ if 0 in self.legal_actions():
87
+ return "call"
88
+ else:
89
+ return "check"
90
+ else:
91
+ return f"raise{action}"
92
+
93
+ def action_to_json(self, action: int) -> str:
94
+ action_str = self._action_to_string(self.current_player(), action)
95
+ return json.dumps({"action": action_str})
96
+
97
+ def observation_dict(self, player: int) -> dict[str, Any]:
98
+ state_dict = self._state_dict()
99
+ for i in range(self.num_players()):
100
+ if i == player:
101
+ continue
102
+ state_dict["player_hands"][i] = ["??", "??"]
103
+ del state_dict["acpc_state"]
104
+ return state_dict
105
+
106
+ def observation_json(self, player: int) -> str:
107
+ return json.dumps(self.observation_dict(player))
108
+
109
+ def observation_string(self, player: int) -> str:
110
+ return self.observation_json(player)
111
+
112
+ def __str__(self):
113
+ return self.to_json()
114
+
115
+
116
+ def _strip_empty_kwargs(input_string):
117
+ try:
118
+ open_paren_index = input_string.index('(')
119
+ close_paren_index = input_string.rindex(')')
120
+ except ValueError:
121
+ return input_string
122
+
123
+ function_name = input_string[:open_paren_index]
124
+ args_string = input_string[open_paren_index + 1:close_paren_index]
125
+ args_list = args_string.split(',')
126
+ non_empty_args = []
127
+ for arg in args_list:
128
+ parts = arg.split('=', 1)
129
+ if len(parts) > 1 and parts[1]:
130
+ non_empty_args.append(arg)
131
+ elif len(parts) == 1 and parts[0]:
132
+ non_empty_args.append(arg)
133
+ new_args_string = ','.join(non_empty_args)
134
+ return f"{function_name}({new_args_string})"
135
+
136
+
137
+ class UniversalPokerGame(proxy.Game):
138
+ """Universal Poker game proxy."""
139
+
140
+ def __init__(self, params: Any | None = None):
141
+ params = params or {}
142
+ wrapped = pyspiel.load_game("universal_poker", params)
143
+ super().__init__(
144
+ wrapped,
145
+ short_name="universal_poker_proxy",
146
+ long_name="Universal Poker (proxy)",
147
+ )
148
+
149
+ def __str__(self):
150
+ s = _strip_empty_kwargs(self.__wrapped__.__str__())
151
+ return s.split("(")[0] + "_proxy(" + s.split("(")[1]
152
+
153
+ def new_initial_state(self, *args) -> UniversalPokerState:
154
+ return UniversalPokerState(
155
+ self.__wrapped__.new_initial_state(*args),
156
+ game=self,
157
+ )
158
+
159
+ pyspiel.register_game(UniversalPokerGame().get_type(), UniversalPokerGame)
@@ -0,0 +1,49 @@
1
+ """Test for proxied Universal Poker game."""
2
+
3
+ import json
4
+ import random
5
+
6
+ from absl.testing import absltest
7
+ from absl.testing import parameterized
8
+ import pyspiel
9
+ from . import universal_poker_proxy as universal_poker
10
+
11
+
12
+ class UniversalPokerTest(parameterized.TestCase):
13
+
14
+ def test_game_is_registered(self):
15
+ game = pyspiel.load_game('universal_poker_proxy')
16
+ self.assertIsInstance(game, universal_poker.UniversalPokerGame)
17
+
18
+ def test_game_parameters(self):
19
+ game = pyspiel.load_game("universal_poker_proxy(betting=nolimit,numPlayers=2,stack=20000 20000,numRounds=4,blind=50 100,firstPlayer=2 1 1 1,numSuits=4,numRanks=13,numHoleCards=2,numBoardCards=0 3 1 1,bettingAbstraction=fullgame)")
20
+ game_params = game.get_parameters()
21
+ self.assertEqual(
22
+ game_params,
23
+ {
24
+ 'betting': 'nolimit',
25
+ 'bettingAbstraction': 'fullgame',
26
+ 'blind': '50 100',
27
+ 'boardCards': '',
28
+ 'firstPlayer': '2 1 1 1',
29
+ 'handReaches': '',
30
+ 'maxRaises': '',
31
+ 'numBoardCards': '0 3 1 1',
32
+ 'numHoleCards': 2,
33
+ 'numPlayers': 2,
34
+ 'numRanks': 13,
35
+ 'numRounds': 4,
36
+ 'numSuits': 4,
37
+ 'potSize': 0,
38
+ 'raiseSize': '100 100',
39
+ 'stack': '20000 20000',
40
+ }
41
+ )
42
+
43
+ def test_random_sim(self):
44
+ game = universal_poker.UniversalPokerGame()
45
+ pyspiel.random_sim_test(game, num_sims=10, serialize=False, verbose=False)
46
+
47
+
48
+ if __name__ == '__main__':
49
+ absltest.main()
@@ -0,0 +1,30 @@
1
+ import os
2
+ import random
3
+ from kaggle_environments import make
4
+ import pyspiel
5
+
6
+ open_spiel_game_name = "connect_four"
7
+ game = pyspiel.load_game(open_spiel_game_name)
8
+ game_type = game.get_type()
9
+ environment_name = f"open_spiel_{game_type.short_name}"
10
+ agents_to_run = ["random"] * game.num_players()
11
+ replay_width = 500
12
+ replay_height = 450
13
+ debug_mode = True
14
+ env = make(environment_name, debug=debug_mode)
15
+
16
+ print(f"Running game with agents: {agents_to_run}...")
17
+ env.run(agents_to_run)
18
+ print("Game finished.")
19
+
20
+ print("Generating HTML replay...")
21
+ html_replay = env.render(mode="html", width=replay_width, height=replay_height)
22
+
23
+ output_html_file = f"kaggle_environments/envs/open_spiel/{environment_name}_game_replay.html"
24
+ print(f"Saving replay to: '{output_html_file}'")
25
+ with open(output_html_file, "w", encoding="utf-8") as f:
26
+ f.write(html_replay)
27
+
28
+ print("-" * 20)
29
+ print(f"Successfully generated replay: {os.path.abspath(output_html_file)}")
30
+ print("-" * 20)