Skip to content

Commit a4d141e

Browse files
committed
feat: parse PokerStars hands
1 parent 81dd8b5 commit a4d141e

21 files changed

+993
-77
lines changed

src/__fixtures__/hands/pokerstars.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
export const HAND_CASH_NO_SHOWDOWN = `*********** # 2 **************
2+
PokerStars Hand #247743239964: Hold'em No Limit ($0.25/$0.50 USD) - 2023/12/28 22:34:00 ET
3+
Table 'Sirona' 6-max Seat #5 is the button
4+
Seat 1: natek526 ($35.21 in chips)
5+
Seat 2: PennStateWins ($53.85 in chips)
6+
Seat 3: mhuggins ($50 in chips)
7+
Seat 4: NutritionalFact ($80.17 in chips)
8+
Seat 5: maketheca11 ($73.33 in chips)
9+
Seat 6: omgmuffins ($49.50 in chips)
10+
omgmuffins: posts small blind $0.25
11+
natek526: posts big blind $0.50
12+
*** HOLE CARDS ***
13+
Dealt to mhuggins [3s Qh]
14+
PennStateWins: folds
15+
mhuggins: folds
16+
NutritionalFact: raises $0.50 to $1
17+
maketheca11: folds
18+
omgmuffins: calls $0.75
19+
natek526: folds
20+
*** FLOP *** [9d 3d Qc]
21+
omgmuffins: checks
22+
NutritionalFact: bets $1.19
23+
omgmuffins: calls $1.19
24+
*** TURN *** [9d 3d Qc] [Qd]
25+
omgmuffins: checks
26+
NutritionalFact: checks
27+
*** RIVER *** [9d 3d Qc Qd] [Js]
28+
omgmuffins: bets $3.50
29+
NutritionalFact: folds
30+
Uncalled bet ($3.50) returned to omgmuffins
31+
omgmuffins collected $4.64 from pot
32+
omgmuffins: doesn't show hand
33+
*** SUMMARY ***
34+
Total pot $4.88 | Rake $0.24
35+
Board [9d 3d Qc Qd Js]
36+
Seat 1: natek526 (big blind) folded before Flop
37+
Seat 2: PennStateWins folded before Flop (didn't bet)
38+
Seat 3: mhuggins folded before Flop (didn't bet)
39+
Seat 4: NutritionalFact folded on the River
40+
Seat 5: maketheca11 (button) folded before Flop (didn't bet)
41+
Seat 6: omgmuffins (small blind) collected ($4.64)`;
42+
43+
export const HAND_CASH_ALL_IN = `*********** # 97 **************
44+
PokerStars Hand #247743232339: Hold'em No Limit ($0.50/$1.00 USD) - 2023/12/28 22:13:14 ET
45+
Table 'Skat' 6-max Seat #4 is the button
46+
Seat 1: gvillian03 ($23.76 in chips)
47+
Seat 2: mhuggins ($258.53 in chips)
48+
Seat 3: Schwalke ($156.36 in chips)
49+
Seat 4: $$Top_Boy$$ OG ($97 in chips)
50+
Seat 5: MadOreos ($176.06 in chips)
51+
Seat 6: MiltonBradley ($105.75 in chips)
52+
MadOreos: posts small blind $0.50
53+
MiltonBradley: posts big blind $1
54+
*** HOLE CARDS ***
55+
Dealt to mhuggins [As 8c]
56+
gvillian03: raises $1.50 to $2.50
57+
mhuggins: folds
58+
Schwalke: folds
59+
$$Top_Boy$$ OG: folds
60+
MadOreos: raises $10 to $12.50
61+
MiltonBradley: folds
62+
gvillian03: calls $10
63+
*** FLOP *** [Th 6d 6c]
64+
MadOreos: bets $16.30
65+
gvillian03: calls $11.26 and is all-in
66+
Uncalled bet ($5.04) returned to MadOreos
67+
*** TURN *** [Th 6d 6c] [Kh]
68+
*** RIVER *** [Th 6d 6c Kh] [7h]
69+
*** SHOW DOWN ***
70+
MadOreos: shows [Ad Qd] (a pair of Sixes)
71+
gvillian03: shows [5h Ah] (a flush, Ace high)
72+
gvillian03 collected $46.09 from pot
73+
*** SUMMARY ***
74+
Total pot $48.52 | Rake $2.43
75+
Board [Th 6d 6c Kh 7h]
76+
Seat 1: gvillian03 showed [5h Ah] and won ($46.09) with a flush, Ace high
77+
Seat 2: mhuggins folded before Flop (didn't bet)
78+
Seat 3: Schwalke folded before Flop (didn't bet)
79+
Seat 4: $$Top_Boy$$ OG (button) folded before Flop (didn't bet)
80+
Seat 5: MadOreos (small blind) showed [Ad Qd] and lost with a pair of Sixes
81+
Seat 6: MiltonBradley (big blind) folded before Flop`;
82+
83+
export const HAND_TOURNAMENT_BOUNTY_AND_PLACEMENT = `*********** # 238 **************
84+
PokerStars Hand #247678901194: Tournament #3429918960, $4.55+$4.55+$0.90 USD Hold'em No Limit - Level XXVI (2000/4000) - 2023/12/25 18:48:09 ET
85+
Table '3429918960 5' 9-max Seat #1 is the button
86+
Seat 1: mhuggins (755175 in chips, $148.95 bounty)
87+
Seat 2: hammy6955 (184825 in chips, $38.62 bounty)
88+
mhuggins: posts the ante 600
89+
hammy6955: posts the ante 600
90+
mhuggins: posts small blind 2000
91+
hammy6955: posts big blind 4000
92+
*** HOLE CARDS ***
93+
Dealt to mhuggins [4s 2c]
94+
mhuggins: raises 8000 to 12000
95+
hammy6955: raises 24000 to 36000
96+
mhuggins: calls 24000
97+
*** FLOP *** [Jd 4h 7c]
98+
hammy6955: bets 24000
99+
mhuggins: calls 24000
100+
*** TURN *** [Jd 4h 7c] [4c]
101+
hammy6955: bets 124225 and is all-in
102+
mhuggins: calls 124225
103+
*** RIVER *** [Jd 4h 7c 4c] [Qd]
104+
*** SHOW DOWN ***
105+
hammy6955: shows [Ah Th] (a pair of Fours)
106+
mhuggins: shows [4s 2c] (three of a kind, Fours)
107+
mhuggins collected 369650 from pot
108+
mhuggins wins $19.31 for eliminating hammy6955 and their own bounty increases by $19.31 to $168.26
109+
hammy6955 finished the tournament in 2nd place and received $150.40.
110+
mhuggins wins the tournament and receives $150.56 - congratulations!
111+
*** SUMMARY ***
112+
Total pot 369650 | Rake 0
113+
Board [Jd 4h 7c 4c Qd]
114+
Seat 1: mhuggins (button) (small blind) showed [4s 2c] and won (369650) with three of a kind, Fours
115+
Seat 2: hammy6955 (big blind) showed [Ah Th] and lost with a pair of Fours`;

src/grammar/PokerStars.g4

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
grammar PokerStars;
2+
3+
// file entry
4+
handHistory: line | ((line EOL)+ line);
5+
line:
6+
( lineHandIndex
7+
| lineCashGameMeta
8+
| lineTournamentMeta
9+
| lineTableMeta
10+
| linePlayer
11+
| linePostSmallBlind
12+
| linePostBigBlind
13+
| linePostAnte
14+
| lineStreet
15+
| lineDeal
16+
| lineAction
17+
| lineUncalled
18+
| lineShowdown
19+
| lineResult
20+
| lineTotalPot
21+
| lineBoard
22+
| lineActionSummary
23+
| lineAwardBounty
24+
| lineTournamentPlacement
25+
| lineMisc
26+
);
27+
lineHandIndex: '*'+ '#' INT '*'+;
28+
lineCashGameMeta: 'PokerStars' 'Hand' '#' handNumber ':' variant bettingStructure '(' blinds currency ')' '-' timestamp;
29+
lineTournamentMeta: 'PokerStars' 'Hand' '#' handNumber ':' 'Tournament' '#' tournamentNumber ',' buyIn ('+' bountyChip)? '+' entryFee currency variant bettingStructure '-' 'Level' levelNumber '(' blinds ')' '-' timestamp;
30+
lineTableMeta: 'Table' '\'' tableName '\'' tableSize 'Seat' '#' INT 'is' 'the' 'button';
31+
linePlayer: 'Seat' INT ':' playerName '(' playerChips playerBounty? ')';
32+
linePostSmallBlind: playerName ':' 'posts' 'small' 'blind' currencyValue;
33+
linePostBigBlind: playerName ':' 'posts' 'big' 'blind' currencyValue;
34+
linePostAnte: playerName ':' 'posts' 'the' 'ante' currencyValue;
35+
lineStreet: '*'+ STREET_HEADING '*'+ cardCollection*;
36+
lineDeal: 'Dealt' 'to' playerName cardCollection;
37+
lineAction: playerName ':' action;
38+
lineUncalled: 'Uncalled' 'bet' '(' currencyValue ')' 'returned' 'to' playerName;
39+
lineResult: playerName 'collected' currencyValue 'from' 'pot';
40+
lineShowdown: playerName ':'
41+
( 'doesn\'t' 'show' 'hand'
42+
| 'mucks' 'hand'
43+
| 'shows' cardCollection '(' handStrength ('-' handStrengthKicker)? ')'
44+
);
45+
lineTotalPot: 'Total' 'pot' currencyValue '|' 'Rake' currencyValue;
46+
lineBoard: 'Board' cardCollection;
47+
lineActionSummary: 'Seat' INT ':' playerName '(' position ')' actionSummary;
48+
lineAwardBounty: playerName 'wins' currencyValue 'for' 'eliminating' playerName 'and' 'their' 'own' 'bounty' 'increases' 'by' currencyValue 'to' currencyValue;
49+
lineTournamentPlacement: playerName 'finished' 'the' 'tournament' 'in' placement ('nd' | 'st' | 'rd' | 'th') 'place' 'and' 'received' currencyValue '.';
50+
lineMisc:
51+
( playerName 'leaves' 'the' 'table'
52+
| playerName 'joins' 'the' 'table' 'at' 'seat' '#' INT
53+
| playerName 'will' 'be' 'allowed' 'to' 'play' 'after' 'the' 'button'
54+
);
55+
56+
handNumber: INT;
57+
tournamentNumber: INT;
58+
variant: 'Hold\'em';
59+
bettingStructure: 'No Limit';
60+
blinds: currencyValue '/' currencyValue;
61+
currencyValue: '$' DECIMAL;
62+
currency: WORD;
63+
timestamp: INT '/' INT '/' INT INT ':' INT ':' INT timezone;
64+
timezone: WORD;
65+
tableName: (~'\'')+;
66+
tableSize: INT '-' 'max';
67+
playerName: USERNAME;
68+
playerChips: currencyValue 'in' 'chips';
69+
playerBounty: ',' currencyValue 'in' 'bounty';
70+
cardCollection: '[' cards ']';
71+
cards: CARD+;
72+
position: 'small blind' | 'big blind' | 'dealer';
73+
actionSummary:
74+
( 'folded' 'before' 'Flop' ('(' 'didn\t' 'bet' ')')? |
75+
| 'folded' 'on' 'the' ('Flop' | 'Turn' | 'River')
76+
| 'collected' '(' currencyValue ')'
77+
| 'showed' cardCollection 'and' 'won' '(' currencyValue ')' 'with' handStrength
78+
| 'showed' cardCollection 'and' 'lost' 'with' handStrength
79+
| 'mucked' cardCollection?
80+
);
81+
action:
82+
( actionBet
83+
| actionCall
84+
| actionCheck
85+
| actionFold
86+
| actionRaise
87+
);
88+
actionBet: 'bets' currencyValue allIn?;
89+
actionCall: 'calls' currencyValue allIn?;
90+
actionCheck: 'checks';
91+
actionFold: 'folds';
92+
actionRaise: 'raises' currencyValue 'to' currencyValue allIn?;
93+
allIn: 'and' 'is' 'all-in';
94+
handStrength:
95+
( 'high' 'card' WORD
96+
| 'a' 'pair' 'of' WORD
97+
| 'two' 'pair' ',' WORD+
98+
| 'three' 'of' 'a' 'kind' ',' WORD
99+
| 'a' 'straight' ',' WORD+
100+
| 'a' 'flush' ',' WORD+
101+
| 'a' 'full' 'house' ',' WORD+
102+
| 'four' 'of' 'a' 'kind' ',' WORD // TODO: determine if this is correct
103+
| 'a' 'straight' 'flush' ',' WORD // TODO: determine if this is correct
104+
| 'a' 'royal' 'flush' ',' WORD // TODO: determine if this is correct
105+
);
106+
handStrengthKicker: WORD ('+' WORD)? 'kicker';
107+
buyIn: currencyValue;
108+
bountyChip: currencyValue;
109+
entryFee: currencyValue;
110+
levelNumber: ROMAN_NUMERALS;
111+
placement: INT;
112+
113+
INT: [0-9]+;
114+
DECIMAL: (INT ',')* INT ('.' INT)?;
115+
USERNAME: [a-z0-9$_ ]+;
116+
STREET_HEADING: 'HOLE CARDS' | 'FLOP' | 'TURN' | 'RIVER' | 'SHOW DOWN' | 'SUMMARY';
117+
ROMAN_NUMERALS: [ivxlcdm]+;
118+
CARD: [2-9tjqka][cdhs];
119+
WORD: [a-z]+;
120+
EOL: '\r' | '\n' | '\r\n';
121+
WS: [ \t]+ -> skip;

src/networks/ignition/IgnitionActionVisitor.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor
22
import { LineActionContext } from '~/grammar/IgnitionParser';
33
import { IgnitionVisitor } from '~/grammar/IgnitionVisitor';
44
import { Action } from '~/types';
5-
import { IgnitionChipCountVisitor } from './IgnitionChipCountVisitor';
5+
import { getChipCount } from './getChipCount';
66
import { NotImplementedError } from './types';
77

88
export class IgnitionActionVisitor
@@ -22,7 +22,7 @@ export class IgnitionActionVisitor
2222

2323
const allInAction = action.actionAllIn();
2424
if (allInAction) {
25-
const amount = new IgnitionChipCountVisitor().visit(allInAction.chipCount()).toString();
25+
const amount = getChipCount(allInAction.chipCount());
2626
const playerName = ctx.position().text;
2727

2828
// This all-in action could actually represent a bet or a call. Unfortunately,
@@ -34,26 +34,22 @@ export class IgnitionActionVisitor
3434

3535
const allInRaiseAction = action.actionAllInRaise();
3636
if (allInRaiseAction) {
37-
const amount = new IgnitionChipCountVisitor()
38-
.visit(allInRaiseAction.chipCount()[0])
39-
.toString();
40-
const totalBet = new IgnitionChipCountVisitor()
41-
.visit(allInRaiseAction.chipCount()[1])
42-
.toString();
37+
const amount = getChipCount(allInRaiseAction.chipCount()[0]);
38+
const totalBet = getChipCount(allInRaiseAction.chipCount()[1]);
4339
const playerName = ctx.position().text;
4440
return [{ type: 'raise', playerName, amount, totalBet, isAllIn: true }];
4541
}
4642

4743
const betAction = action.actionBet();
4844
if (betAction) {
49-
const amount = new IgnitionChipCountVisitor().visit(betAction.chipCount()).toString();
45+
const amount = getChipCount(betAction.chipCount());
5046
const playerName = ctx.position().text;
5147
return [{ type: 'bet', playerName, amount, isAllIn: false }];
5248
}
5349

5450
const callAction = action.actionCall();
5551
if (callAction) {
56-
const amount = new IgnitionChipCountVisitor().visit(callAction.chipCount()).toString();
52+
const amount = getChipCount(callAction.chipCount());
5753
const playerName = ctx.position().text;
5854
return [{ type: 'call', playerName, amount, isAllIn: false }];
5955
}
@@ -72,15 +68,15 @@ export class IgnitionActionVisitor
7268

7369
const raiseAction = action.actionRaise();
7470
if (raiseAction) {
75-
const amount = new IgnitionChipCountVisitor().visit(raiseAction.chipCount()[0]).toString();
76-
const totalBet = new IgnitionChipCountVisitor().visit(raiseAction.chipCount()[1]).toString();
71+
const amount = getChipCount(raiseAction.chipCount()[0]);
72+
const totalBet = getChipCount(raiseAction.chipCount()[1]);
7773
const playerName = ctx.position().text;
7874
return [{ type: 'raise', playerName, amount, totalBet, isAllIn: false }];
7975
}
8076

8177
const anteAction = action.actionAnte();
8278
if (anteAction) {
83-
const amount = new IgnitionChipCountVisitor().visit(anteAction.chipCount()).toString();
79+
const amount = getChipCount(anteAction.chipCount());
8480
const playerName = ctx.position().text;
8581
return [{ type: 'post', postType: 'ante', playerName, amount }];
8682
}

src/networks/ignition/IgnitionChipCountVisitor.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)