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,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)
|