Skip to content

Commit a73daaa

Browse files
Soopranoclaude
andcommitted
feat(phase-34e): parser de /analisis lee Winamax (3er dialecto)
Winamax (export PT4) sin romper iPoker ni GG/Stars: - Acciones SIN dos puntos ('Atenea. posts small blind 40'): se intenta primero 'Nombre: verbo' y, si falla o el colon pertenece a una descripción con ' : ' (ej. 'Two pairs : 5 and 4'), se usa un match anclado al verbo conocido. - Header 'Winamax …' con ciegas en '(40/80)' (parse genérico de parénhesis-slash, cubre también GG 'Level1(10/20)'); botón en 'Table:' (con dos puntos). - Board pegado sin espacio '[4d Js Td][7c]' (parseAllCardsInBrackets ya lo cubre). - Winner 'collected X from main/side pot' y summary 'won X' sin paréntesis. - shows con descripción entre paréntesis al final. Verificado con node: iPoker + 2 GG + 3 Winamax (incl. all-in 3-way y split pots) — hero/board/pots/botón/ganadores/shows/sugerencias correctos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 53f1535 commit a73daaa

1 file changed

Lines changed: 55 additions & 19 deletions

File tree

‎src/utils/handHistory.ts‎

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,23 @@ export function parseHandHistory(raw: string): HandParseResult {
258258
const line = rawLine.trim();
259259
if (!line) continue;
260260

261-
// ── Header (iPoker "GAME #…" / GG-Stars "Poker Hand #…") ────────────────
262-
if (line.startsWith('GAME #') || line.startsWith('Poker Hand #')) {
261+
// ── Header (iPoker "GAME #…" / GG-Stars "Poker Hand #…" / Winamax) ───────
262+
if (
263+
line.startsWith('GAME #') ||
264+
line.startsWith('Poker Hand #') ||
265+
line.startsWith('Winamax')
266+
) {
263267
const idMatch = line.match(/#(\S+?)[\s:]/);
264268
gameId = idMatch ? idMatch[1]! : null;
265269
const dateMatch = line.match(
266270
/(\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2})/,
267271
);
268272
dateTime = dateMatch ? dateMatch[1]! : null;
269-
// GG/Stars carry blinds in the level, e.g. "Level1(10/20)".
270-
const level = line.match(/Level\d+\s*\((\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)\)/i);
271-
if (level) {
272-
smallBlind = Number(level[1]);
273-
bigBlind = Number(level[2]);
273+
// Blinds in parens: GG "Level1(10/20)", Winamax "no limit (40/80)".
274+
const blinds = line.match(/\((\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)\)/);
275+
if (blinds) {
276+
smallBlind = Number(blinds[1]);
277+
bigBlind = Number(blinds[2]);
274278
}
275279
continue;
276280
}
@@ -286,8 +290,11 @@ export function parseHandHistory(raw: string): HandParseResult {
286290
continue;
287291
}
288292

289-
// GG/Stars table line: "Table '36270' 3-max Seat #1 is the button"
290-
if (line.startsWith('Table ')) {
293+
// Table line with the button seat:
294+
// GG: "Table '36270' 3-max Seat #1 is the button"
295+
// Winamax: "Table: 'Expresso(…)#0' 3-max (real money) Seat #3 is the button"
296+
// (iPoker "Table Info:" was already handled above.)
297+
if (line.startsWith('Table')) {
291298
const btn = line.match(/Seat #(\d+) is the button/i);
292299
if (btn) buttonSeat = Number(btn[1]);
293300
continue;
@@ -360,8 +367,8 @@ export function parseHandHistory(raw: string): HandParseResult {
360367
continue;
361368
}
362369

363-
// ── Winner via "name collected X from pot" (GG showdown body) ───────────
364-
const collected = line.match(/^(.+?)\s+collected\s+([\d.,]+)\s+from pot/i);
370+
// ── Winner via "name collected X from [main/side] pot" (GG / Winamax) ────
371+
const collected = line.match(/^(.+?)\s+collected\s+([\d.,]+)\s+from\b/i);
365372
if (collected && !inSummary) {
366373
const amt = parseAmount(collected[2]!);
367374
if (amt) addWinner(collected[1]!.trim(), amt.amount);
@@ -402,18 +409,43 @@ export function parseHandHistory(raw: string): HandParseResult {
402409
const desc = line.match(/\bwith\s+(.+)$/i);
403410
addShow(name, parseAllCardsInBrackets(line) ?? [], desc ? desc[1]!.trim() : '');
404411
}
405-
const won = line.match(/\bwon\s+\(([\d.,]+)\)/i);
412+
// GG "won (540)" / Winamax "won 1438" (with or without parens).
413+
const won = line.match(/\bwon\s+\(?([\d.,]+)/i);
406414
if (won) addWinner(name, Number(won[1]!.replace(/,/g, '')));
407415
continue;
408416
}
409417
continue;
410418
}
411419

412-
// ── Action lines: "<name>: <verb> [amount]" ───────────────────────────
413-
const actionMatch = line.match(/^(.+?):\s+(.+)$/);
414-
if (actionMatch) {
415-
const actor = actionMatch[1]!.trim();
416-
const rest = actionMatch[2]!.trim();
420+
// ── Action lines ──────────────────────────────────────────────────────
421+
// iPoker/GG/Stars use "Name: verb …"; Winamax omits the colon
422+
// ("Atenea. posts small blind 40"). Try the colon form first, then fall
423+
// back to a verb-anchored match for the colon-less dialect.
424+
// Only treat a colon line as an action if what follows the colon is a known
425+
// verb — Winamax hand descriptions contain " : " (e.g. "Two pairs : 5 and
426+
// 4") which would otherwise false-match the "Name: rest" shape.
427+
const colonRaw = line.match(/^(.+?):\s+(.+)$/);
428+
const colonMatch =
429+
colonRaw &&
430+
/^(posts?|raises?|calls?|checks?|folds?|bets?|shows?|wins?)\b/i.test(
431+
colonRaw[2]!,
432+
)
433+
? colonRaw
434+
: null;
435+
const wmxMatch = colonMatch
436+
? null
437+
: line.match(
438+
/^(\S.*?)\s+(posts|raises|calls|checks|folds|bets|shows)\b\s*(.*)$/i,
439+
);
440+
const actor = colonMatch
441+
? colonMatch[1]!.trim()
442+
: wmxMatch
443+
? wmxMatch[1]!.trim()
444+
: null;
445+
if (actor !== null) {
446+
const rest = colonMatch
447+
? colonMatch[2]!.trim()
448+
: `${wmxMatch![2]} ${wmxMatch![3] ?? ''}`.trim();
417449
const lower = rest.toLowerCase();
418450

419451
if (lower.startsWith('post')) {
@@ -432,10 +464,14 @@ export function parseHandHistory(raw: string): HandParseResult {
432464
pushDecision(actor, 'fold', 0);
433465
continue;
434466
}
435-
// GG all-in runout shows cards inline: "Hero: shows [9c Qh]"
467+
// Cards shown inline (GG all-in runout / Winamax show down):
468+
// "Hero: shows [9c Qh]" · "Atenea. shows [5h 5c] (Two pairs : 5 and 4)"
436469
if (lower.startsWith('show')) {
437470
const cards = parseAllCardsInBrackets(line);
438-
if (cards) addShow(actor, cards, '');
471+
if (cards) {
472+
const parenDesc = line.match(/\(([^)]*)\)\s*$/);
473+
addShow(actor, cards, parenDesc ? parenDesc[1]!.trim() : '');
474+
}
439475
continue;
440476
}
441477

0 commit comments

Comments
 (0)