@creatiosoft/poker-odds-calculator 1.0.0
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.
- package/README.md +354 -0
- package/dist/constants/deck.const.d.ts +3 -0
- package/dist/constants/deck.const.d.ts.map +1 -0
- package/dist/constants/deck.const.js +13 -0
- package/dist/constants/deck.const.js.map +1 -0
- package/dist/evaluator/nlh.evaluator.d.ts +2 -0
- package/dist/evaluator/nlh.evaluator.d.ts.map +1 -0
- package/dist/evaluator/nlh.evaluator.js +8 -0
- package/dist/evaluator/nlh.evaluator.js.map +1 -0
- package/dist/evaluator/omaha.evaluator.d.ts +6 -0
- package/dist/evaluator/omaha.evaluator.d.ts.map +1 -0
- package/dist/evaluator/omaha.evaluator.js +23 -0
- package/dist/evaluator/omaha.evaluator.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/simulator/deck.d.ts +3 -0
- package/dist/simulator/deck.d.ts.map +1 -0
- package/dist/simulator/deck.js +16 -0
- package/dist/simulator/deck.js.map +1 -0
- package/dist/simulator/monteCarlo.d.ts +11 -0
- package/dist/simulator/monteCarlo.d.ts.map +1 -0
- package/dist/simulator/monteCarlo.js +54 -0
- package/dist/simulator/monteCarlo.js.map +1 -0
- package/dist/types/card.type.d.ts +8 -0
- package/dist/types/card.type.d.ts.map +1 -0
- package/dist/types/card.type.js +3 -0
- package/dist/types/card.type.js.map +1 -0
- package/dist/types/odds.interface.d.ts +25 -0
- package/dist/types/odds.interface.d.ts.map +1 -0
- package/dist/types/odds.interface.js +3 -0
- package/dist/types/odds.interface.js.map +1 -0
- package/dist/types/variant.type.d.ts +2 -0
- package/dist/types/variant.type.d.ts.map +1 -0
- package/dist/types/variant.type.js +3 -0
- package/dist/types/variant.type.js.map +1 -0
- package/dist/utils/combination.d.ts +2 -0
- package/dist/utils/combination.d.ts.map +1 -0
- package/dist/utils/combination.js +21 -0
- package/dist/utils/combination.js.map +1 -0
- package/package.json +31 -0
- package/src/constants/deck.const.ts +11 -0
- package/src/evaluator/nlh.evaluator.ts +5 -0
- package/src/evaluator/omaha.evaluator.ts +20 -0
- package/src/index.ts +62 -0
- package/src/simulator/deck.ts +13 -0
- package/src/simulator/monteCarlo.ts +72 -0
- package/src/types/card.type.ts +7 -0
- package/src/types/odds.interface.ts +28 -0
- package/src/types/variant.type.ts +1 -0
- package/src/utils/combination.ts +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# @creatiosoft/poker-odds-calculator
|
|
2
|
+
|
|
3
|
+
A TypeScript package that calculates winning probability for poker hands across all major variants using **Monte Carlo simulation**.
|
|
4
|
+
|
|
5
|
+
Supports **NLH**, **PLO4**, **PLO5**, and **PLO6**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [API Reference](#api-reference)
|
|
14
|
+
- [Understanding the Result](#understanding-the-result)
|
|
15
|
+
- [What is Monte Carlo Simulation?](#what-is-monte-carlo-simulation)
|
|
16
|
+
- [How We Use It](#how-we-use-it)
|
|
17
|
+
- [Why Not Use poker-evaluator's Built-in Odds?](#why-not-use-poker-evaluators-built-in-odds)
|
|
18
|
+
- [Supported Variants](#supported-variants)
|
|
19
|
+
- [Card Format](#card-format)
|
|
20
|
+
- [Accuracy vs Iterations](#accuracy-vs-iterations)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @creatiosoft/poker-odds-calculator
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { calculateOdds, calculateTableOdds } from '@creatiosoft/poker-odds-calculator';
|
|
36
|
+
|
|
37
|
+
// How likely am I to win with pocket Aces against 2 unknown opponents?
|
|
38
|
+
const result = calculateOdds({
|
|
39
|
+
variant: 'NLH',
|
|
40
|
+
holeCards: ['Ah', 'As'],
|
|
41
|
+
communityCards: [],
|
|
42
|
+
playerCount: 3,
|
|
43
|
+
iterations: 5000
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(result.winRate); // e.g. 0.73 → 73% chance to win outright
|
|
47
|
+
console.log(result.splitRates); // e.g. [{ rate: 0.01, ways: 2 }] → 1% chance to split with 1 player
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## API Reference
|
|
53
|
+
|
|
54
|
+
### `calculateOdds(input)` — Single Player
|
|
55
|
+
|
|
56
|
+
Use this when **only your cards are known** and opponents' cards are random (the most common real-time table scenario).
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
calculateOdds({
|
|
60
|
+
variant: 'PLO4', // game variant (see Supported Variants)
|
|
61
|
+
holeCards: ['Ah','As','Kh','Kd'], // your hole cards
|
|
62
|
+
communityCards: ['2c','7h','Jd'], // board cards dealt so far (0–5)
|
|
63
|
+
playerCount: 3, // total players including you
|
|
64
|
+
iterations: 5000 // Monte Carlo cycles (optional, default 1000)
|
|
65
|
+
}): PlayerOdds
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Returns:** `PlayerOdds` — your odds only (player 0).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `calculateTableOdds(input)` — All Players Known
|
|
73
|
+
|
|
74
|
+
Use this when **all hole cards are visible** — e.g. a run-it-out, a showdown analysis, or a training tool.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
calculateTableOdds({
|
|
78
|
+
variant: 'NLH',
|
|
79
|
+
hands: [
|
|
80
|
+
['Ah', 'As'], // player 0
|
|
81
|
+
['Kh', 'Kd'], // player 1
|
|
82
|
+
['Qh', 'Qd'], // player 2
|
|
83
|
+
],
|
|
84
|
+
communityCards: ['2c', '7h', 'Jd'],
|
|
85
|
+
iterations: 5000
|
|
86
|
+
}): TableOdds
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Returns:** `TableOdds` — an array of `PlayerOdds`, one per player, in the same order as `hands`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Understanding the Result
|
|
94
|
+
|
|
95
|
+
### `PlayerOdds`
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{
|
|
99
|
+
winRate: 0.72, // fraction of simulated games this player wins outright
|
|
100
|
+
splitRates: [
|
|
101
|
+
{ rate: 0.03, ways: 2 }, // 3% of games end in a 2-way split
|
|
102
|
+
{ rate: 0.01, ways: 3 }, // 1% of games end in a 3-way split
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### `winRate`
|
|
108
|
+
The fraction of iterations where **this player won the entire pot outright** — no ties, no splits. A value of `0.72` means the player wins 72 out of every 100 simulated hands.
|
|
109
|
+
|
|
110
|
+
#### `splitRates`
|
|
111
|
+
An array describing **how often the pot is split** and **how many players share it**.
|
|
112
|
+
|
|
113
|
+
| Field | Type | Meaning |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `rate` | `number` | Fraction of iterations where the pot was split this way |
|
|
116
|
+
| `ways` | `number` | How many players split the pot (2 = heads-up tie, 3 = three-way tie, …) |
|
|
117
|
+
|
|
118
|
+
**Example — reading a split result:**
|
|
119
|
+
```
|
|
120
|
+
winRate: 0.68
|
|
121
|
+
splitRates: [{ rate: 0.04, ways: 2 }]
|
|
122
|
+
```
|
|
123
|
+
This means:
|
|
124
|
+
- 68% of the time → you win the whole pot
|
|
125
|
+
- 4% of the time → you and exactly one other player tie (split 50/50)
|
|
126
|
+
- 28% of the time → you lose
|
|
127
|
+
|
|
128
|
+
**Expected pot share formula:**
|
|
129
|
+
```
|
|
130
|
+
expectedShare = winRate + Σ (splitRate.rate / splitRate.ways)
|
|
131
|
+
= 0.68 + (0.04 / 2)
|
|
132
|
+
= 0.68 + 0.02
|
|
133
|
+
= 0.70 → you expect 70% of the pot on average
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `TableOdds`
|
|
137
|
+
```typescript
|
|
138
|
+
{
|
|
139
|
+
players: [PlayerOdds, PlayerOdds, ...] // same order as the hands[] you passed in
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `winRate` values across all players **do not sum to 1** — splits account for the remainder. But `winRate + splitContribution` summed across all players **does** equal 1 (the whole pot is always distributed).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## What is Monte Carlo Simulation?
|
|
148
|
+
|
|
149
|
+
Monte Carlo simulation is a technique for estimating probabilities by **running a scenario thousands of times with random inputs** and counting outcomes.
|
|
150
|
+
|
|
151
|
+
In poker, the number of possible combinations of cards is astronomically large. For example, dealing 2 unknown opponents on a blank board has over 1 billion possible runouts. Calculating exact probabilities by enumerating every combination would take too long for real-time use.
|
|
152
|
+
|
|
153
|
+
**Monte Carlo solves this by sampling:**
|
|
154
|
+
|
|
155
|
+
> Instead of checking all 1 billion possibilities, deal out the remaining cards randomly 5,000 times. Count how often you win. 5,000 samples gives you a result accurate to within ~1%.
|
|
156
|
+
|
|
157
|
+
### Analogy
|
|
158
|
+
|
|
159
|
+
Imagine you want to know the probability of rolling two dice and getting a sum of 7. You could:
|
|
160
|
+
- **Exact method:** list all 36 combinations, count the 6 that sum to 7 → 6/36 = 16.7%
|
|
161
|
+
- **Monte Carlo:** roll the dice 10,000 times, count the 7s → you'll get roughly 16.7%
|
|
162
|
+
|
|
163
|
+
For poker, the "exact method" is too slow for real-time use. Monte Carlo gives us a fast, accurate-enough answer.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## How We Use It
|
|
168
|
+
|
|
169
|
+
Here is exactly what happens inside each call to `calculateOdds`:
|
|
170
|
+
|
|
171
|
+
### Step 1 — Build the Remaining Deck
|
|
172
|
+
|
|
173
|
+
We start with a full 52-card deck and remove every card that is already known (your hole cards + any community cards already dealt).
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
Full deck (52 cards)
|
|
177
|
+
− your hole cards (2–6 cards)
|
|
178
|
+
− community cards already on board (0–5 cards)
|
|
179
|
+
= remaining deck (41–50 cards)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Step 2 — Run N Iterations
|
|
183
|
+
|
|
184
|
+
For each iteration:
|
|
185
|
+
|
|
186
|
+
**1. Shuffle** the remaining deck using the Fisher-Yates algorithm (the same unbiased shuffle used in `poker-evaluator`).
|
|
187
|
+
|
|
188
|
+
**2. Deal** cards to fill in the unknowns:
|
|
189
|
+
- Each opponent gets hole cards dealt from the top of the shuffled deck
|
|
190
|
+
- The community cards are completed to 5 if not already
|
|
191
|
+
|
|
192
|
+
**3. Evaluate** each player's best hand using the correct rule for the variant (see below).
|
|
193
|
+
|
|
194
|
+
**4. Find the winner** — the player with the highest hand value wins. If multiple players have equal values, the pot is split.
|
|
195
|
+
|
|
196
|
+
**5. Record** the result — increment that player's win or split counter.
|
|
197
|
+
|
|
198
|
+
### Step 3 — Calculate Rates
|
|
199
|
+
|
|
200
|
+
After all iterations:
|
|
201
|
+
```
|
|
202
|
+
winRate = wins[player] / totalIterations
|
|
203
|
+
splitRate = splits[player][ways-2] / totalIterations
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Hand Evaluation per Variant
|
|
207
|
+
|
|
208
|
+
This is where our package differs from `poker-evaluator`'s built-in odds calculator:
|
|
209
|
+
|
|
210
|
+
| Variant | Rule | How we evaluate |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| NLH | Best 5 from any 7 cards | Pass all 7 cards to `evalHand` — it picks the best 5 |
|
|
213
|
+
| PLO4 | Exactly 2 hole + 3 board | Generate all C(4,2)=6 hole pairs × C(5,3)=10 board triples = **60 combinations**, evaluate each, take the best |
|
|
214
|
+
| PLO5 | Exactly 2 hole + 3 board | C(5,2)=10 × C(5,3)=10 = **100 combinations** per player |
|
|
215
|
+
| PLO6 | Exactly 2 hole + 3 board | C(6,2)=15 × C(5,3)=10 = **150 combinations** per player |
|
|
216
|
+
|
|
217
|
+
For NLH the library's own `evalHand` does the work. For PLO we enumerate every legal 2+3 combination ourselves, call `evalHand` on each 5-card hand, and take the highest value.
|
|
218
|
+
|
|
219
|
+
### Visual Walkthrough — One Iteration (PLO4, 2 Players)
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Your hand : [Ah, As, Kh, Kd] (known)
|
|
223
|
+
Board : [2c, 7h, Jd] (known, flop)
|
|
224
|
+
Deck left : 45 cards
|
|
225
|
+
|
|
226
|
+
Shuffle deck → [Qc, 3h, 9s, Tc, 6d, ...]
|
|
227
|
+
|
|
228
|
+
Deal opponent: Qc, 3h (first 2 from shuffled deck)
|
|
229
|
+
Complete board: 9s, Tc (next 2 cards → board = [2c,7h,Jd,9s,Tc])
|
|
230
|
+
|
|
231
|
+
Evaluate YOUR hand (PLO4 — must use exactly 2 hole + 3 board):
|
|
232
|
+
Ah+As + 2c+7h+Jd → evalHand([Ah,As,2c,7h,Jd]) → one pair aces
|
|
233
|
+
Ah+As + 2c+7h+9s → evalHand([Ah,As,2c,7h,9s]) → one pair aces
|
|
234
|
+
Ah+As + 2c+7h+Tc → evalHand([Ah,As,2c,7h,Tc]) → one pair aces
|
|
235
|
+
Ah+Kh + 2c+7h+Jd → evalHand([Ah,Kh,2c,7h,Jd]) → high card
|
|
236
|
+
Ah+As + Jd+9s+Tc → evalHand([Ah,As,Jd,9s,Tc]) → one pair aces
|
|
237
|
+
... (60 total combos)
|
|
238
|
+
Best value for you → one pair aces (value: 3553)
|
|
239
|
+
|
|
240
|
+
Evaluate OPPONENT hand [Qc, 3h] (PLO4):
|
|
241
|
+
Qc+3h + 2c+7h+Jd → high card queen
|
|
242
|
+
... (60 combos)
|
|
243
|
+
Best value for opponent → high card queen (value: 1210)
|
|
244
|
+
|
|
245
|
+
3553 > 1210 → YOU WIN this iteration
|
|
246
|
+
wins[0]++
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Repeat 5000 times → `winRate = wins[0] / 5000`
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Why Not Use poker-evaluator's Built-in Odds?
|
|
254
|
+
|
|
255
|
+
`poker-evaluator` ships `winningOddsForPlayer` and `winningOddsForTable` but they are **NLH-only**. Two lines in their source code make this impossible to reuse for PLO:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// poker-evaluator source — hardcoded 2 hole cards, no way to change
|
|
259
|
+
var card1 = numHands[p][0] ?? startingDeck[deckPosition++];
|
|
260
|
+
var card2 = numHands[p][1] ?? startingDeck[deckPosition++];
|
|
261
|
+
holeCards.push([card1, card2]); // ← always 2 cards, PLO needs 4/5/6
|
|
262
|
+
|
|
263
|
+
// NLH rule: best 5 from any 7
|
|
264
|
+
evalHand([...hand, ...communityCards]).value // ← wrong for PLO
|
|
265
|
+
// PLO rule: must use exactly 2 from hole + 3 from board
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
We **reuse** `evalHand` (the fast O(1) lookup table) and write our own loop around it that correctly handles variable hole card counts and the PLO 2+3 constraint.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Supported Variants
|
|
273
|
+
|
|
274
|
+
| Variant | Hole Cards | Evaluation Rule |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `NLH` | 2 | Best 5 from any 7 (hole + board) |
|
|
277
|
+
| `PLO4` | 4 | Exactly 2 from hole + exactly 3 from board |
|
|
278
|
+
| `PLO5` | 5 | Exactly 2 from hole + exactly 3 from board |
|
|
279
|
+
| `PLO6` | 6 | Exactly 2 from hole + exactly 3 from board |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Card Format
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
<rank><suit>
|
|
287
|
+
|
|
288
|
+
Ranks : A K Q J T 9 8 7 6 5 4 3 2
|
|
289
|
+
Suits : s (spades) h (hearts) d (diamonds) c (clubs)
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
'As' → Ace of Spades
|
|
293
|
+
'Kh' → King of Hearts
|
|
294
|
+
'Td' → Ten of Diamonds
|
|
295
|
+
'2c' → Two of Clubs
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Accuracy vs Iterations
|
|
301
|
+
|
|
302
|
+
Both `winRate` and `splitRates` become more accurate as you increase `iterations`. More iterations = smaller margin of error but slower response.
|
|
303
|
+
|
|
304
|
+
| Iterations | Typical error | Time (NLH) | Time (PLO4) | Recommended for |
|
|
305
|
+
|---:|---:|---:|---:|---|
|
|
306
|
+
| 1 000 | ±2–3% | ~13 ms | ~200 ms | Development / unit tests |
|
|
307
|
+
| 5 000 | ±1% | ~44 ms | ~990 ms | Real-time display at the table |
|
|
308
|
+
| 10 000 | ±0.5% | ~100 ms | ~2 000 ms | High-accuracy analysis |
|
|
309
|
+
| 100 000 | ±0.1% | ~1 s | ~20 s | Pre-computation / batch jobs |
|
|
310
|
+
|
|
311
|
+
For **real-time use** in a running game, 5 000 iterations is the recommended balance — fast enough to feel instant for NLH, and accurate to within 1%.
|
|
312
|
+
|
|
313
|
+
For PLO at real-time speed, consider running the simulation in a **Node.js worker thread** so it does not block the main event loop.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Full Example
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { calculateOdds, calculateTableOdds } from '@creatiosoft/poker-odds-calculator';
|
|
321
|
+
|
|
322
|
+
// ── Example 1: Your hand only (most common) ──────────────────────────────────
|
|
323
|
+
|
|
324
|
+
const myOdds = calculateOdds({
|
|
325
|
+
variant: 'PLO4',
|
|
326
|
+
holeCards: ['Ah', 'As', 'Kh', 'Kd'], // double-suited aces and kings
|
|
327
|
+
communityCards: ['2c', '7h', 'Jd'], // flop is out
|
|
328
|
+
playerCount: 3, // you + 2 opponents
|
|
329
|
+
iterations: 5000
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
console.log(`Win rate : ${(myOdds.winRate * 100).toFixed(1)}%`);
|
|
333
|
+
myOdds.splitRates.forEach(s =>
|
|
334
|
+
console.log(`${s.ways}-way split: ${(s.rate * 100).toFixed(1)}%`)
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// ── Example 2: Full table (all hands known) ──────────────────────────────────
|
|
338
|
+
|
|
339
|
+
const table = calculateTableOdds({
|
|
340
|
+
variant: 'NLH',
|
|
341
|
+
hands: [
|
|
342
|
+
['Ah', 'As'], // player 0 — pocket aces
|
|
343
|
+
['Kh', 'Kd'], // player 1 — pocket kings
|
|
344
|
+
],
|
|
345
|
+
communityCards: [], // preflop
|
|
346
|
+
iterations: 10000
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
table.players.forEach((p, i) =>
|
|
350
|
+
console.log(`Player ${i}: ${(p.winRate * 100).toFixed(1)}% win`)
|
|
351
|
+
);
|
|
352
|
+
// Player 0: 81.2% win
|
|
353
|
+
// Player 1: 17.8% win
|
|
354
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deck.const.d.ts","sourceRoot":"","sources":["../../src/constants/deck.const.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,EAAE,MAAM,EAA8C,CAAC;AAE7E,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKlD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOLE_CARD_COUNT = exports.FULL_DECK = void 0;
|
|
4
|
+
const RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'];
|
|
5
|
+
const SUITS = ['c', 'd', 'h', 's'];
|
|
6
|
+
exports.FULL_DECK = RANKS.flatMap(r => SUITS.map(s => r + s));
|
|
7
|
+
exports.HOLE_CARD_COUNT = {
|
|
8
|
+
NLH: 2,
|
|
9
|
+
PLO4: 4,
|
|
10
|
+
PLO5: 5,
|
|
11
|
+
PLO6: 6,
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=deck.const.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deck.const.js","sourceRoot":"","sources":["../../src/constants/deck.const.ts"],"names":[],"mappings":";;;AAAA,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAChF,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEtB,QAAA,SAAS,GAAa,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEhE,QAAA,eAAe,GAA2B;IACrD,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;CACR,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nlh.evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluator/nlh.evaluator.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evalNLH = evalNLH;
|
|
4
|
+
const poker_evaluator_1 = require("poker-evaluator");
|
|
5
|
+
function evalNLH(holeCards, boardCards) {
|
|
6
|
+
return (0, poker_evaluator_1.evalHand)([...holeCards, ...boardCards]).value;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=nlh.evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nlh.evaluator.js","sourceRoot":"","sources":["../../src/evaluator/nlh.evaluator.ts"],"names":[],"mappings":";;AAEA,0BAEC;AAJD,qDAA2C;AAE3C,SAAgB,OAAO,CAAC,SAAmB,EAAE,UAAoB;IAC/D,OAAO,IAAA,0BAAQ,EAAC,CAAC,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluates an Omaha hand (PLO4/5/6) using the mandatory 2-from-hole + 3-from-board rule.
|
|
3
|
+
* Returns the highest hand value (higher = stronger).
|
|
4
|
+
*/
|
|
5
|
+
export declare function evalOmaha(holeCards: string[], boardCards: string[]): number;
|
|
6
|
+
//# sourceMappingURL=omaha.evaluator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omaha.evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluator/omaha.evaluator.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAY3E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evalOmaha = evalOmaha;
|
|
4
|
+
const poker_evaluator_1 = require("poker-evaluator");
|
|
5
|
+
const combination_1 = require("../utils/combination");
|
|
6
|
+
/**
|
|
7
|
+
* Evaluates an Omaha hand (PLO4/5/6) using the mandatory 2-from-hole + 3-from-board rule.
|
|
8
|
+
* Returns the highest hand value (higher = stronger).
|
|
9
|
+
*/
|
|
10
|
+
function evalOmaha(holeCards, boardCards) {
|
|
11
|
+
const holeCombos = (0, combination_1.combinations)(holeCards, 2);
|
|
12
|
+
const boardCombos = (0, combination_1.combinations)(boardCards, 3);
|
|
13
|
+
let bestValue = -1;
|
|
14
|
+
for (const hc of holeCombos) {
|
|
15
|
+
for (const bc of boardCombos) {
|
|
16
|
+
const value = (0, poker_evaluator_1.evalHand)([...hc, ...bc]).value;
|
|
17
|
+
if (value > bestValue)
|
|
18
|
+
bestValue = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return bestValue;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=omaha.evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omaha.evaluator.js","sourceRoot":"","sources":["../../src/evaluator/omaha.evaluator.ts"],"names":[],"mappings":";;AAOA,8BAYC;AAnBD,qDAA2C;AAC3C,sDAAoD;AAEpD;;;GAGG;AACH,SAAgB,SAAS,CAAC,SAAmB,EAAE,UAAoB;IACjE,MAAM,UAAU,GAAG,IAAA,0BAAY,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAA,0BAAY,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAEhD,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;IACnB,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;QAC5B,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAA,0BAAQ,EAAC,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YAC7C,IAAI,KAAK,GAAG,SAAS;gBAAE,SAAS,GAAG,KAAK,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CalculateOddsInput, CalculateTableOddsInput, PlayerOdds, TableOdds } from './types/odds.interface';
|
|
2
|
+
export type { Variant } from './types/variant.type';
|
|
3
|
+
export type { Card } from './types/card.type';
|
|
4
|
+
export type { Split, PlayerOdds, TableOdds, CalculateOddsInput, CalculateTableOddsInput, } from './types/odds.interface';
|
|
5
|
+
/**
|
|
6
|
+
* Calculates win odds for a single player against N-1 unknown opponents.
|
|
7
|
+
* The caller's hole cards are known; all other hands are randomly simulated.
|
|
8
|
+
*/
|
|
9
|
+
export declare function calculateOdds(input: CalculateOddsInput): PlayerOdds;
|
|
10
|
+
/**
|
|
11
|
+
* Calculates win odds for all players when all hole cards are known (e.g. a run-it-out scenario).
|
|
12
|
+
*/
|
|
13
|
+
export declare function calculateTableOdds(input: CalculateTableOddsInput): TableOdds;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,UAAU,EACV,SAAS,EACV,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,YAAY,EACV,KAAK,EACL,UAAU,EACV,SAAS,EACT,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAShC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAiBnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAU5E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateOdds = calculateOdds;
|
|
4
|
+
exports.calculateTableOdds = calculateTableOdds;
|
|
5
|
+
const monteCarlo_1 = require("./simulator/monteCarlo");
|
|
6
|
+
const DEFAULT_ITERATIONS = {
|
|
7
|
+
NLH: 1000,
|
|
8
|
+
PLO4: 500,
|
|
9
|
+
PLO5: 300,
|
|
10
|
+
PLO6: 200,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Calculates win odds for a single player against N-1 unknown opponents.
|
|
14
|
+
* The caller's hole cards are known; all other hands are randomly simulated.
|
|
15
|
+
*/
|
|
16
|
+
function calculateOdds(input) {
|
|
17
|
+
const iterations = input.iterations ?? DEFAULT_ITERATIONS[input.variant];
|
|
18
|
+
const knownHands = [
|
|
19
|
+
input.holeCards,
|
|
20
|
+
...Array(input.playerCount - 1).fill([]),
|
|
21
|
+
];
|
|
22
|
+
const result = (0, monteCarlo_1.runMonteCarlo)({
|
|
23
|
+
variant: input.variant,
|
|
24
|
+
knownHands,
|
|
25
|
+
community: input.communityCards,
|
|
26
|
+
playerCount: input.playerCount,
|
|
27
|
+
iterations,
|
|
28
|
+
});
|
|
29
|
+
return result.players[0];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Calculates win odds for all players when all hole cards are known (e.g. a run-it-out scenario).
|
|
33
|
+
*/
|
|
34
|
+
function calculateTableOdds(input) {
|
|
35
|
+
const iterations = input.iterations ?? DEFAULT_ITERATIONS[input.variant];
|
|
36
|
+
return (0, monteCarlo_1.runMonteCarlo)({
|
|
37
|
+
variant: input.variant,
|
|
38
|
+
knownHands: input.hands,
|
|
39
|
+
community: input.communityCards,
|
|
40
|
+
playerCount: input.hands.length,
|
|
41
|
+
iterations,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AA6BA,sCAiBC;AAKD,gDAUC;AA7DD,uDAAuD;AAkBvD,MAAM,kBAAkB,GAA2B;IACjD,GAAG,EAAG,IAAI;IACV,IAAI,EAAG,GAAG;IACV,IAAI,EAAG,GAAG;IACV,IAAI,EAAG,GAAG;CACX,CAAC;AAEF;;;GAGG;AACH,SAAgB,aAAa,CAAC,KAAyB;IACrD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAEzE,MAAM,UAAU,GAAG;QACjB,KAAK,CAAC,SAAS;QACf,GAAG,KAAK,CAAW,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;KACnD,CAAC;IAEF,MAAM,MAAM,GAAG,IAAA,0BAAa,EAAC;QAC3B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,UAAU;QACV,SAAS,EAAE,KAAK,CAAC,cAAc;QAC/B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,UAAU;KACX,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,KAA8B;IAC/D,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAEzE,OAAO,IAAA,0BAAa,EAAC;QACnB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,UAAU,EAAE,KAAK,CAAC,KAAK;QACvB,SAAS,EAAE,KAAK,CAAC,cAAc;QAC/B,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;QAC/B,UAAU;KACX,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deck.d.ts","sourceRoot":"","sources":["../../src/simulator/deck.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGjE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAKhD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRemainingDeck = buildRemainingDeck;
|
|
4
|
+
exports.shuffleDeck = shuffleDeck;
|
|
5
|
+
const deck_const_1 = require("../constants/deck.const");
|
|
6
|
+
function buildRemainingDeck(knownCards) {
|
|
7
|
+
const known = new Set(knownCards.map(c => c.toLowerCase()));
|
|
8
|
+
return deck_const_1.FULL_DECK.filter(card => !known.has(card.toLowerCase()));
|
|
9
|
+
}
|
|
10
|
+
function shuffleDeck(deck) {
|
|
11
|
+
for (let i = deck.length - 1; i > 0; i--) {
|
|
12
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
13
|
+
[deck[i], deck[j]] = [deck[j], deck[i]];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=deck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deck.js","sourceRoot":"","sources":["../../src/simulator/deck.ts"],"names":[],"mappings":";;AAEA,gDAGC;AAED,kCAKC;AAZD,wDAAoD;AAEpD,SAAgB,kBAAkB,CAAC,UAAoB;IACrD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,sBAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,WAAW,CAAC,IAAc;IACxC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Variant } from '../types/variant.type';
|
|
2
|
+
import { TableOdds } from '../types/odds.interface';
|
|
3
|
+
export interface SimulationConfig {
|
|
4
|
+
variant: Variant;
|
|
5
|
+
knownHands: string[][];
|
|
6
|
+
community: string[];
|
|
7
|
+
playerCount: number;
|
|
8
|
+
iterations: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function runMonteCarlo(config: SimulationConfig): TableOdds;
|
|
11
|
+
//# sourceMappingURL=monteCarlo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monteCarlo.d.ts","sourceRoot":"","sources":["../../src/simulator/monteCarlo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAMpD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAwDjE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMonteCarlo = runMonteCarlo;
|
|
4
|
+
const deck_const_1 = require("../constants/deck.const");
|
|
5
|
+
const deck_1 = require("./deck");
|
|
6
|
+
const nlh_evaluator_1 = require("../evaluator/nlh.evaluator");
|
|
7
|
+
const omaha_evaluator_1 = require("../evaluator/omaha.evaluator");
|
|
8
|
+
function runMonteCarlo(config) {
|
|
9
|
+
const { variant, knownHands, community, playerCount, iterations } = config;
|
|
10
|
+
const holeCount = deck_const_1.HOLE_CARD_COUNT[variant];
|
|
11
|
+
const t0 = performance.now();
|
|
12
|
+
const allKnown = [...community, ...knownHands.flat()];
|
|
13
|
+
const deck = (0, deck_1.buildRemainingDeck)(allKnown);
|
|
14
|
+
const wins = new Array(playerCount).fill(0);
|
|
15
|
+
const splits = Array.from({ length: playerCount }, () => new Array(playerCount - 1).fill(0));
|
|
16
|
+
for (let i = 0; i < iterations; i++) {
|
|
17
|
+
(0, deck_1.shuffleDeck)(deck);
|
|
18
|
+
let pos = 0;
|
|
19
|
+
const hands = [];
|
|
20
|
+
for (let p = 0; p < playerCount; p++) {
|
|
21
|
+
const known = knownHands[p] ?? [];
|
|
22
|
+
const needed = holeCount - known.length;
|
|
23
|
+
hands.push([...known, ...deck.slice(pos, pos + needed)]);
|
|
24
|
+
pos += needed;
|
|
25
|
+
}
|
|
26
|
+
const board = [...community];
|
|
27
|
+
while (board.length < 5)
|
|
28
|
+
board.push(deck[pos++]);
|
|
29
|
+
const values = hands.map(hand => variant === 'NLH' ? (0, nlh_evaluator_1.evalNLH)(hand, board) : (0, omaha_evaluator_1.evalOmaha)(hand, board));
|
|
30
|
+
const best = Math.max(...values);
|
|
31
|
+
const winners = values.reduce((acc, v, idx) => {
|
|
32
|
+
if (v === best)
|
|
33
|
+
acc.push(idx);
|
|
34
|
+
return acc;
|
|
35
|
+
}, []);
|
|
36
|
+
if (winners.length === 1) {
|
|
37
|
+
wins[winners[0]]++;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
winners.forEach(idx => splits[idx][winners.length - 2]++);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const elapsed = (performance.now() - t0).toFixed(1);
|
|
44
|
+
console.log(`[${variant}] ${iterations} iterations, ${playerCount} players → ${elapsed}ms`);
|
|
45
|
+
return {
|
|
46
|
+
players: wins.map((w, p) => ({
|
|
47
|
+
winRate: w / iterations,
|
|
48
|
+
splitRates: splits[p]
|
|
49
|
+
.map((s, i) => ({ rate: s / iterations, ways: i + 2 }))
|
|
50
|
+
.filter(s => s.rate > 0),
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=monteCarlo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monteCarlo.js","sourceRoot":"","sources":["../../src/simulator/monteCarlo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.type.d.ts","sourceRoot":"","sources":["../../src/types/card.type.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.type.js","sourceRoot":"","sources":["../../src/types/card.type.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Split {
|
|
2
|
+
rate: number;
|
|
3
|
+
ways: number;
|
|
4
|
+
}
|
|
5
|
+
export interface PlayerOdds {
|
|
6
|
+
winRate: number;
|
|
7
|
+
splitRates: Split[];
|
|
8
|
+
}
|
|
9
|
+
export interface TableOdds {
|
|
10
|
+
players: PlayerOdds[];
|
|
11
|
+
}
|
|
12
|
+
export interface CalculateOddsInput {
|
|
13
|
+
variant: import('./variant.type').Variant;
|
|
14
|
+
holeCards: string[];
|
|
15
|
+
communityCards: string[];
|
|
16
|
+
playerCount: number;
|
|
17
|
+
iterations?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CalculateTableOddsInput {
|
|
20
|
+
variant: import('./variant.type').Variant;
|
|
21
|
+
hands: string[][];
|
|
22
|
+
communityCards: string[];
|
|
23
|
+
iterations?: number;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=odds.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"odds.interface.d.ts","sourceRoot":"","sources":["../../src/types/odds.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,KAAK,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,gBAAgB,EAAE,OAAO,CAAC;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,gBAAgB,EAAE,OAAO,CAAC;IAC1C,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"odds.interface.js","sourceRoot":"","sources":["../../src/types/odds.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant.type.d.ts","sourceRoot":"","sources":["../../src/types/variant.type.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant.type.js","sourceRoot":"","sources":["../../src/types/variant.type.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combination.d.ts","sourceRoot":"","sources":["../../src/utils/combination.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,CAc1D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.combinations = combinations;
|
|
4
|
+
function combinations(set, k) {
|
|
5
|
+
if (k > set.length || k <= 0)
|
|
6
|
+
return [];
|
|
7
|
+
if (k === set.length)
|
|
8
|
+
return [set];
|
|
9
|
+
if (k === 1)
|
|
10
|
+
return set.map(item => [item]);
|
|
11
|
+
const result = [];
|
|
12
|
+
for (let i = 0; i <= set.length - k; i++) {
|
|
13
|
+
const head = set.slice(i, i + 1);
|
|
14
|
+
const tailCombos = combinations(set.slice(i + 1), k - 1);
|
|
15
|
+
for (const tail of tailCombos) {
|
|
16
|
+
result.push([...head, ...tail]);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=combination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combination.js","sourceRoot":"","sources":["../../src/utils/combination.ts"],"names":[],"mappings":";;AAAA,oCAcC;AAdD,SAAgB,YAAY,CAAI,GAAQ,EAAE,CAAS;IACjD,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAU,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACjC,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@creatiosoft/poker-odds-calculator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Monte Carlo poker odds calculator for NLH, PLO4, PLO5, and PLO6",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"author": "Creatiosoft",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:coverage": "jest --coverage",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": ["poker", "odds", "calculator", "nlh", "plo", "omaha", "monte-carlo"],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"poker-evaluator": "^2.1.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/jest": "^29.5.0",
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"jest": "^29.5.0",
|
|
28
|
+
"ts-jest": "^29.1.0",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'];
|
|
2
|
+
const SUITS = ['c', 'd', 'h', 's'];
|
|
3
|
+
|
|
4
|
+
export const FULL_DECK: string[] = RANKS.flatMap(r => SUITS.map(s => r + s));
|
|
5
|
+
|
|
6
|
+
export const HOLE_CARD_COUNT: Record<string, number> = {
|
|
7
|
+
NLH: 2,
|
|
8
|
+
PLO4: 4,
|
|
9
|
+
PLO5: 5,
|
|
10
|
+
PLO6: 6,
|
|
11
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { evalHand } from 'poker-evaluator';
|
|
2
|
+
import { combinations } from '../utils/combination';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Evaluates an Omaha hand (PLO4/5/6) using the mandatory 2-from-hole + 3-from-board rule.
|
|
6
|
+
* Returns the highest hand value (higher = stronger).
|
|
7
|
+
*/
|
|
8
|
+
export function evalOmaha(holeCards: string[], boardCards: string[]): number {
|
|
9
|
+
const holeCombos = combinations(holeCards, 2);
|
|
10
|
+
const boardCombos = combinations(boardCards, 3);
|
|
11
|
+
|
|
12
|
+
let bestValue = -1;
|
|
13
|
+
for (const hc of holeCombos) {
|
|
14
|
+
for (const bc of boardCombos) {
|
|
15
|
+
const value = evalHand([...hc, ...bc]).value;
|
|
16
|
+
if (value > bestValue) bestValue = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return bestValue;
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { runMonteCarlo } from './simulator/monteCarlo';
|
|
2
|
+
import {
|
|
3
|
+
CalculateOddsInput,
|
|
4
|
+
CalculateTableOddsInput,
|
|
5
|
+
PlayerOdds,
|
|
6
|
+
TableOdds,
|
|
7
|
+
} from './types/odds.interface';
|
|
8
|
+
|
|
9
|
+
export type { Variant } from './types/variant.type';
|
|
10
|
+
export type { Card } from './types/card.type';
|
|
11
|
+
export type {
|
|
12
|
+
Split,
|
|
13
|
+
PlayerOdds,
|
|
14
|
+
TableOdds,
|
|
15
|
+
CalculateOddsInput,
|
|
16
|
+
CalculateTableOddsInput,
|
|
17
|
+
} from './types/odds.interface';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_ITERATIONS: Record<string, number> = {
|
|
20
|
+
NLH: 1000,
|
|
21
|
+
PLO4: 500,
|
|
22
|
+
PLO5: 300,
|
|
23
|
+
PLO6: 200,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculates win odds for a single player against N-1 unknown opponents.
|
|
28
|
+
* The caller's hole cards are known; all other hands are randomly simulated.
|
|
29
|
+
*/
|
|
30
|
+
export function calculateOdds(input: CalculateOddsInput): PlayerOdds {
|
|
31
|
+
const iterations = input.iterations ?? DEFAULT_ITERATIONS[input.variant];
|
|
32
|
+
|
|
33
|
+
const knownHands = [
|
|
34
|
+
input.holeCards,
|
|
35
|
+
...Array<string[]>(input.playerCount - 1).fill([]),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const result = runMonteCarlo({
|
|
39
|
+
variant: input.variant,
|
|
40
|
+
knownHands,
|
|
41
|
+
community: input.communityCards,
|
|
42
|
+
playerCount: input.playerCount,
|
|
43
|
+
iterations,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return result.players[0];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calculates win odds for all players when all hole cards are known (e.g. a run-it-out scenario).
|
|
51
|
+
*/
|
|
52
|
+
export function calculateTableOdds(input: CalculateTableOddsInput): TableOdds {
|
|
53
|
+
const iterations = input.iterations ?? DEFAULT_ITERATIONS[input.variant];
|
|
54
|
+
|
|
55
|
+
return runMonteCarlo({
|
|
56
|
+
variant: input.variant,
|
|
57
|
+
knownHands: input.hands,
|
|
58
|
+
community: input.communityCards,
|
|
59
|
+
playerCount: input.hands.length,
|
|
60
|
+
iterations,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FULL_DECK } from '../constants/deck.const';
|
|
2
|
+
|
|
3
|
+
export function buildRemainingDeck(knownCards: string[]): string[] {
|
|
4
|
+
const known = new Set(knownCards.map(c => c.toLowerCase()));
|
|
5
|
+
return FULL_DECK.filter(card => !known.has(card.toLowerCase()));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function shuffleDeck(deck: string[]): void {
|
|
9
|
+
for (let i = deck.length - 1; i > 0; i--) {
|
|
10
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
11
|
+
[deck[i], deck[j]] = [deck[j], deck[i]];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Variant } from '../types/variant.type';
|
|
2
|
+
import { TableOdds } from '../types/odds.interface';
|
|
3
|
+
import { HOLE_CARD_COUNT } from '../constants/deck.const';
|
|
4
|
+
import { buildRemainingDeck, shuffleDeck } from './deck';
|
|
5
|
+
import { evalNLH } from '../evaluator/nlh.evaluator';
|
|
6
|
+
import { evalOmaha } from '../evaluator/omaha.evaluator';
|
|
7
|
+
|
|
8
|
+
export interface SimulationConfig {
|
|
9
|
+
variant: Variant;
|
|
10
|
+
knownHands: string[][];
|
|
11
|
+
community: string[];
|
|
12
|
+
playerCount: number;
|
|
13
|
+
iterations: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function runMonteCarlo(config: SimulationConfig): TableOdds {
|
|
17
|
+
const { variant, knownHands, community, playerCount, iterations } = config;
|
|
18
|
+
const holeCount = HOLE_CARD_COUNT[variant];
|
|
19
|
+
const t0 = performance.now();
|
|
20
|
+
|
|
21
|
+
const allKnown = [...community, ...knownHands.flat()];
|
|
22
|
+
const deck = buildRemainingDeck(allKnown);
|
|
23
|
+
|
|
24
|
+
const wins = new Array<number>(playerCount).fill(0);
|
|
25
|
+
const splits = Array.from({ length: playerCount }, () =>
|
|
26
|
+
new Array<number>(playerCount - 1).fill(0)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < iterations; i++) {
|
|
30
|
+
shuffleDeck(deck);
|
|
31
|
+
let pos = 0;
|
|
32
|
+
|
|
33
|
+
const hands: string[][] = [];
|
|
34
|
+
for (let p = 0; p < playerCount; p++) {
|
|
35
|
+
const known = knownHands[p] ?? [];
|
|
36
|
+
const needed = holeCount - known.length;
|
|
37
|
+
hands.push([...known, ...deck.slice(pos, pos + needed)]);
|
|
38
|
+
pos += needed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const board = [...community];
|
|
42
|
+
while (board.length < 5) board.push(deck[pos++]);
|
|
43
|
+
|
|
44
|
+
const values = hands.map(hand =>
|
|
45
|
+
variant === 'NLH' ? evalNLH(hand, board) : evalOmaha(hand, board)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const best = Math.max(...values);
|
|
49
|
+
const winners = values.reduce<number[]>((acc, v, idx) => {
|
|
50
|
+
if (v === best) acc.push(idx);
|
|
51
|
+
return acc;
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
if (winners.length === 1) {
|
|
55
|
+
wins[winners[0]]++;
|
|
56
|
+
} else {
|
|
57
|
+
winners.forEach(idx => splits[idx][winners.length - 2]++);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const elapsed = (performance.now() - t0).toFixed(1);
|
|
62
|
+
console.log(`[${variant}] ${iterations} iterations, ${playerCount} players → ${elapsed}ms`);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
players: wins.map((w, p) => ({
|
|
66
|
+
winRate: w / iterations,
|
|
67
|
+
splitRates: splits[p]
|
|
68
|
+
.map((s, i) => ({ rate: s / iterations, ways: i + 2 }))
|
|
69
|
+
.filter(s => s.rate > 0),
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Split {
|
|
2
|
+
rate: number;
|
|
3
|
+
ways: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PlayerOdds {
|
|
7
|
+
winRate: number;
|
|
8
|
+
splitRates: Split[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TableOdds {
|
|
12
|
+
players: PlayerOdds[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CalculateOddsInput {
|
|
16
|
+
variant: import('./variant.type').Variant;
|
|
17
|
+
holeCards: string[];
|
|
18
|
+
communityCards: string[];
|
|
19
|
+
playerCount: number;
|
|
20
|
+
iterations?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CalculateTableOddsInput {
|
|
24
|
+
variant: import('./variant.type').Variant;
|
|
25
|
+
hands: string[][];
|
|
26
|
+
communityCards: string[];
|
|
27
|
+
iterations?: number;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Variant = 'NLH' | 'PLO4' | 'PLO5' | 'PLO6';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function combinations<T>(set: T[], k: number): T[][] {
|
|
2
|
+
if (k > set.length || k <= 0) return [];
|
|
3
|
+
if (k === set.length) return [set];
|
|
4
|
+
if (k === 1) return set.map(item => [item]);
|
|
5
|
+
|
|
6
|
+
const result: T[][] = [];
|
|
7
|
+
for (let i = 0; i <= set.length - k; i++) {
|
|
8
|
+
const head = set.slice(i, i + 1);
|
|
9
|
+
const tailCombos = combinations(set.slice(i + 1), k - 1);
|
|
10
|
+
for (const tail of tailCombos) {
|
|
11
|
+
result.push([...head, ...tail]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|