Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: parse tournament hands #5

Merged
merged 4 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ Add `@poker-apprentice/hand-history-parser` as a dependency.

### `parseHand`

This promise-based function can be used to parse hand histories from any [support poker site](#supported-poker-sites). To use it, simply pass the contents of an individual hand history.
This promise-based function can be used to parse hand histories from any [support poker site](#supported-poker-sites). To use it, simply pass the contents of an individual hand history along with the filename.

```ts
// assumes `hand` is a string containing the hand history file contents
const handHistory = parseHand(hand)
// Assumes `hand` is a string containing the hand history file contents & `filename`
// is a string containing the hand history filename.
const handHistory = parseHand({ hand, filename })
.then((handHistory) => console.log(handHistory))
.catch((err) => console.error(err));
```

If preferred, an async/await implementation can be used instead.

```ts
// assumes `hand` is a string containing the hand history file contents
// Assumes `hand` is a string containing the hand history file contents & `filename`
// is a string containing the hand history filename.
try {
const handHistory = await parseHand(hand);
const handHistory = await parseHand({ hand, filename });
console.log(handHistory);
} catch (err) {
console.error(err);
Expand Down Expand Up @@ -274,9 +276,9 @@ This package currently supports the following poker sites & networks:

| Site | Network | Cash Games | Tournaments | Hold'em | Omaha | Omaha-8 | Stud | Currencies |
| -------- | -------- | :--------: | :---------: | :-----: | :-----: | :-----: | :--: | ---------- |
| Bodog | Ignition | ✅ | ❌ | ✅ | ✅ | ✅ | N/A | USD |
| Bovada | Ignition | ✅ | ❌ | ✅ | ✅ | ✅ | N/A | USD |
| Ignition | Ignition | ✅ | ❌ | ✅ | ✅ | ✅ | N/A | USD |
| Bodog | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |
| Bovada | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |
| Ignition | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |

The parser is built in a way that it is relatively straightforward to extend with new poker sites. The main thing that is missing for this to happen is a combination of sample hand histories to implement against & time.

Expand All @@ -298,17 +300,18 @@ The architecture is as straightforward as possible, with the most complex part i
```bash
yarn install
```
1. Add a `.g4` grammar file under `grammar/[PokerSite].g4`.
1. Add a `.g4` grammar file under `grammar/[PokerNetwork].g4`.
It is recommended that this file be parsed by different types of lines that appear within the file. These lines can typically be broken down into one of: game metadata, player metadata, & player actions. These three types of information comprise the data that must be returned by a new hand history parser.
1. Build all ANTLR grammar files:
```bash
yarn build:grammar
```
1. Add a visitor for your poker site under `networks/[pokersite]/[PokerSite]HandHistoryVisitor.ts`.
1. Add a visitor for your poker site under `networks/[pokernetwork]/[PokerNetwork]HandHistoryVisitor.ts`.
The visitor is responsible for returning a value representing the current node that is being parsed bym ANTLR. Ideally each line being parsed can return an object representing one of the three types of metadata outlined above. However, it may be necessary to utilize a custom return type that may or may not wrap the shared `Action`, `Player`, and `GameInfo` types depending on the poker site's hand history structure.
1. Add a parser for your poker site under `networks/[pokersite]/parseHand.ts`.
1. Add a parser for your poker site under `networks/[pokernetwork]/parseHand.ts`.
This function is intended to delegate to the Visitor class implemented above, massaging the return value into a `HandHistory` object as a return value.
1. Include tests for common & uncommon parsing scenarios that demonstrate the `parseHand` function is working as intended. Common & uncommon scenarios include:
1. Add an if-condition for your poker site within `parseSite.ts`.
1. Include tests for common & uncommon parsing scenarios that demonstrate the `parseHand` and `parseSite` functions are working as intended. Common & uncommon scenarios include for the `parseHand` function include:
- Multiple variants (e.g.: Hold'em, Omaha, Omaha-8, etc.),
- Multiple betting structures (i.e.: limit, no-limit, pot-limit, spread-limit, cap-limit),
- Multiple currencies,
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"@poker-apprentice/types": "^1.4.0",
"antlr4ts": "^0.5.0-alpha.4",
"assert-never": "^1.2.1",
"bignumber.js": "^9.1.2"
"bignumber.js": "^9.1.2",
"lodash": "^4.17.21"
},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.22.10",
Expand All @@ -55,6 +56,7 @@
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-terser": "^0.4.3",
"@types/jest": "^29.5.4",
"@types/lodash": "^4.14.202",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"antlr4ts-cli": "^0.5.0-alpha.4",
Expand Down
56 changes: 56 additions & 0 deletions src/__fixtures__/hands/bovada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,59 @@ Seat+2: UTG lost with One pair [5s Ad-Ad Ac Kh 7h 6s]
Seat+3: UTG+1 Folded before the FLOP
Seat+4: Dealer Folded before the FLOP
Seat+6: Big Blind $14.82 with One pair [As Qs-As Ac Kh Qs 7h]`;

export const HAND_TOURNAMENT = `Bovada Hand #4561202298: OMAHA HiLo Tournament #47895702 TBL#2, Normal- Level 10 (200/400) - 2022-06-25 22:24:22
Seat 30: UTG+2 (4,619 in chips)
Seat 37: UTG+3 (6,282 in chips)
Seat 86: Dealer (4,300 in chips)
Seat 16: Small Blind (11,003 in chips)
Seat 89: Big Blind [ME] (3,000 in chips)
Seat 61: UTG (5,714 in chips)
Seat 68: UTG+1 (15,486 in chips)
Dealer : Set dealer [4]
UTG+2 : Ante chip 40
UTG+3 : Ante chip 40
Dealer : Ante chip 40
Small Blind : Ante chip 40
Big Blind [ME] : Ante chip 40
UTG : Ante chip 40
UTG+1 : Ante chip 40
Small Blind : Small blind 200
Big Blind [ME] : Big blind 400
*** HOLE CARDS ***
UTG+2 : Card dealt to a spot [9d 5c 2c Td]
UTG+3 : Card dealt to a spot [4c Ad Th 8h]
Dealer : Card dealt to a spot [6h Ac 3s 7s]
Small Blind : Card dealt to a spot [Qd 3d Ts 7d]
Big Blind [ME] : Card dealt to a spot [Js As Jd 5s]
UTG : Card dealt to a spot [6s 8c 7c Ah]
UTG+1 : Card dealt to a spot [8s 9c 2h 3h]
UTG : Folds
UTG+1 : Folds
UTG+2 : Raises 800 to 800
UTG+3 : Folds
Dealer : Fold(Blind Disconnected)
Small Blind : Sit out
Small Blind : Folds(timeout)
Small Blind : Re-join
Big Blind [ME] : Call 400
*** FLOP *** [2d 4d Qh]
Big Blind [ME] : Checks
UTG+2 : Bets 2080
Big Blind [ME] : All-in(raise) 2160 to 2160
UTG+2 : Call 80
*** TURN *** [2d 4d Qh] [Ks]
*** RIVER *** [2d 4d Qh Ks] [Kh]
UTG+2 : Showdown [2c Td 2d Ks Kh] (Two pair)
Big Blind [ME] : Showdown [Js Jd Qh Ks Kh] (Two pair)
Big Blind [ME] : Hand Result 6400
*** SUMMARY ***
Total Pot(6400)
Board [2d 4d Qh Ks Kh]
Seat+30: UTG+2 lose with Two pair [9d 5c 2c Td-2c Td 2d Ks Kh]
Seat+37: UTG+3 Folded on the FLOP
Seat+86: Dealer Folded on the FLOP
Seat+16: Small Blind Folded on the FLOP
Seat+89: Big Blind HI 6400 with Two pair [Js As Jd 5s-Js Jd Qh Ks Kh]
Seat+61: UTG Folded on the FLOP
Seat+68: UTG+1 Folded on the FLOP`;
30 changes: 19 additions & 11 deletions src/grammar/Ignition.g4
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ grammar Ignition;
// file entry
handHistory: line | ((line EOL)+ line);
line:
( lineMeta
( lineMetaCash
| lineMetaTournament
| linePlayer
| lineDealer
| lineSmallBlind
Expand All @@ -23,11 +24,12 @@ line:
);

// lines of text
lineMeta : site 'Hand' '#' handNumber fastFold? ('TBL#' | 'ID#') tableNumber variant bettingStructure '-' timestamp;
lineMetaCash : site 'Hand' '#' handNumber fastFold? ('TBL#' | 'ID#') tableNumber variant bettingStructure '-' timestamp;
lineMetaTournament: site 'Hand' '#' handNumber COLON variant 'Tournament' '#' tournamentNumber 'TBL#' tableNumber ',' tournamentSpeed '-' 'Level' tournamentLevel '(' chipCount '/' chipCount ')' '-' timestamp;
linePlayer : 'Seat' seatNumber COLON position ME? '(' chipCount 'in chips)';
lineDealer : ('Dealer' ME? COLON)? 'Set dealer' ('[' INT ']')?;
lineSmallBlind : ('Small Blind' | 'Dealer') ME? COLON 'Small Blind' chipCount;
lineBigBlind : 'Big Blind' ME? COLON 'Big blind' chipCount;
lineSmallBlind : ('Small Blind' | 'Dealer') ME? COLON ('Small Blind' | 'Small blind') chipCount;
lineBigBlind : 'Big Blind' ME? COLON ('Big Blind' | 'Big blind') chipCount;
linePost : position ME? COLON 'Posts' DEAD? 'chip' chipCount;
lineStreet : '***' STREET '***' boardSections?;
lineHandsDealt : position ME? COLON 'Card dealt to a spot' hand;
Expand All @@ -39,8 +41,10 @@ lineMisc :
| 'Seat sit out'
| 'Seat stand'
| 'Seat re-join'
| 'Re-join'
| 'Table enter user'
| 'Table leave user'
| 'Sit out'
| 'Sitout' forcedActionReason
| 'Enter' forcedActionReason
| 'Leave' forcedActionReason
Expand All @@ -49,7 +53,7 @@ lineAction : position ME? COLON action;
lineMuck : position ME? COLON 'Does not show' hand '(' handStrength ')';
lineUncalled : position ME? COLON 'Return uncalled portion of bet' chipCount;
lineShowdown : position ME? COLON showdownAction hand? '(' handStrength ')';
lineResult : position ME? COLON 'Hand result' ('-' SIDEPOT)? chipCount;
lineResult : position ME? COLON ('Hand Result' | 'Hand result') ('-' SIDEPOT)? chipCount;
lineTotalPot : 'Total Pot' '(' chipCount ')';
lineBoard : 'Board' board;
lineActionSummary:
Expand All @@ -67,13 +71,16 @@ lineActionSummary:
handNumber : INT;
seatNumber : INT;
tableNumber : INT;
tournamentNumber: INT;
tournamentLevel: INT;
tournamentSpeed: 'Normal' | 'Turbo';
timestamp : INT '-' INT '-' INT INT ':' INT ':' INT;
site : 'Bodog' | 'Bovada' | 'Ignition';
variant : 'HOLDEM' | 'OMAHA' | 'OMAHA HiLo' | 'HOLDEMZonePoker' | 'OMAHAZonePoker';
bettingStructure : 'No Limit' | 'Pot Limit' | 'Limit';
fastFold : FASTFOLD;
position : 'Small Blind' | 'Big Blind' | 'UTG' | 'UTG+1' | 'UTG+2' | 'Dealer';
chipCount : '$' value;
position : 'Small Blind' | 'Big Blind' | 'UTG' | 'UTG+1' | 'UTG+2' | 'UTG+3' | 'UTG+4' | 'UTG+5' | 'Dealer';
chipCount : '$'? value;
value : (INT ',')* INT ('.' INT)?;
board : '[' cards ']';
boardSections : board+;
Expand All @@ -82,19 +89,20 @@ handAndBoard : '[' cards '-' cards ']';
cards : card+;
card : CARD;
handStrength : 'High Card' | 'One pair' | 'Two pair' | 'Three of a kind' | 'Straight' | 'Flush' | 'Full House' | 'Four of a kind' | 'Straight Flush' | 'Royal Straight Flush';
action : actionFold | actionCheck | actionBet | actionCall | actionRaise | actionAllIn | actionAllInRaise;
actionFold : 'Folds' (forcedActionReason | '& shows' hand)?;
action : actionFold | actionCheck | actionBet | actionCall | actionRaise | actionAllIn | actionAllInRaise | actionAnte;
actionFold : ('Fold' | 'Folds') (forcedActionReason | '& shows' hand)?;
actionCheck : 'Checks' forcedActionReason?;
actionBet : 'Bets' chipCount;
actionCall : 'Calls' chipCount;
actionCall : ('Call' | 'Calls') chipCount;
actionRaise : 'Raises' chipCount 'to' chipCount;
actionAllIn : 'All-in' chipCount;
actionAllInRaise : 'All-in' '(raise)' chipCount 'to' chipCount;
actionAnte : 'Ante chip' chipCount;
forcedActionReason : '(' ~')'* ')';
showdownAction: 'Showdown' | 'Mucks';
winHighResult: chipCount 'with' handStrength (hand | handAndBoard);
winLowResult : chipCount (hand | handAndBoard);
loseResult : 'lost with' handStrength (hand | handAndBoard);
loseResult : ('lose with' | 'lost with') handStrength (hand | handAndBoard);

// lexer rules
ME : '[ME]';
Expand Down
24 changes: 0 additions & 24 deletions src/grammar/Site.g4

This file was deleted.

27 changes: 0 additions & 27 deletions src/networks/all/SiteSwitchVisitor.ts

This file was deleted.

7 changes: 7 additions & 0 deletions src/networks/ignition/IgnitionActionVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ export class IgnitionActionVisitor
return [{ type: 'raise', playerName, amount, totalBet, isAllIn: false }];
}

const anteAction = action.actionAnte();
if (anteAction) {
const amount = new IgnitionChipCountVisitor().visit(anteAction.chipCount()).toString();
const playerName = ctx.position().text;
return [{ type: 'post', postType: 'ante', playerName, amount }];
}

throw new NotImplementedError();
}
}
Loading