@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.
Files changed (52) hide show
  1. package/README.md +354 -0
  2. package/dist/constants/deck.const.d.ts +3 -0
  3. package/dist/constants/deck.const.d.ts.map +1 -0
  4. package/dist/constants/deck.const.js +13 -0
  5. package/dist/constants/deck.const.js.map +1 -0
  6. package/dist/evaluator/nlh.evaluator.d.ts +2 -0
  7. package/dist/evaluator/nlh.evaluator.d.ts.map +1 -0
  8. package/dist/evaluator/nlh.evaluator.js +8 -0
  9. package/dist/evaluator/nlh.evaluator.js.map +1 -0
  10. package/dist/evaluator/omaha.evaluator.d.ts +6 -0
  11. package/dist/evaluator/omaha.evaluator.d.ts.map +1 -0
  12. package/dist/evaluator/omaha.evaluator.js +23 -0
  13. package/dist/evaluator/omaha.evaluator.js.map +1 -0
  14. package/dist/index.d.ts +14 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +44 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/simulator/deck.d.ts +3 -0
  19. package/dist/simulator/deck.d.ts.map +1 -0
  20. package/dist/simulator/deck.js +16 -0
  21. package/dist/simulator/deck.js.map +1 -0
  22. package/dist/simulator/monteCarlo.d.ts +11 -0
  23. package/dist/simulator/monteCarlo.d.ts.map +1 -0
  24. package/dist/simulator/monteCarlo.js +54 -0
  25. package/dist/simulator/monteCarlo.js.map +1 -0
  26. package/dist/types/card.type.d.ts +8 -0
  27. package/dist/types/card.type.d.ts.map +1 -0
  28. package/dist/types/card.type.js +3 -0
  29. package/dist/types/card.type.js.map +1 -0
  30. package/dist/types/odds.interface.d.ts +25 -0
  31. package/dist/types/odds.interface.d.ts.map +1 -0
  32. package/dist/types/odds.interface.js +3 -0
  33. package/dist/types/odds.interface.js.map +1 -0
  34. package/dist/types/variant.type.d.ts +2 -0
  35. package/dist/types/variant.type.d.ts.map +1 -0
  36. package/dist/types/variant.type.js +3 -0
  37. package/dist/types/variant.type.js.map +1 -0
  38. package/dist/utils/combination.d.ts +2 -0
  39. package/dist/utils/combination.d.ts.map +1 -0
  40. package/dist/utils/combination.js +21 -0
  41. package/dist/utils/combination.js.map +1 -0
  42. package/package.json +31 -0
  43. package/src/constants/deck.const.ts +11 -0
  44. package/src/evaluator/nlh.evaluator.ts +5 -0
  45. package/src/evaluator/omaha.evaluator.ts +20 -0
  46. package/src/index.ts +62 -0
  47. package/src/simulator/deck.ts +13 -0
  48. package/src/simulator/monteCarlo.ts +72 -0
  49. package/src/types/card.type.ts +7 -0
  50. package/src/types/odds.interface.ts +28 -0
  51. package/src/types/variant.type.ts +1 -0
  52. 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,3 @@
1
+ export declare const FULL_DECK: string[];
2
+ export declare const HOLE_CARD_COUNT: Record<string, number>;
3
+ //# sourceMappingURL=deck.const.d.ts.map
@@ -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,2 @@
1
+ export declare function evalNLH(holeCards: string[], boardCards: string[]): number;
2
+ //# sourceMappingURL=nlh.evaluator.d.ts.map
@@ -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"}
@@ -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,3 @@
1
+ export declare function buildRemainingDeck(knownCards: string[]): string[];
2
+ export declare function shuffleDeck(deck: string[]): void;
3
+ //# sourceMappingURL=deck.d.ts.map
@@ -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,8 @@
1
+ /**
2
+ * Standard card notation: rank + suit
3
+ * Ranks: A K Q J T 9 8 7 6 5 4 3 2
4
+ * Suits: s (spades) h (hearts) d (diamonds) c (clubs)
5
+ * Example: "As", "Kh", "Td", "2c"
6
+ */
7
+ export type Card = string;
8
+ //# sourceMappingURL=card.type.d.ts.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=card.type.js.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=odds.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odds.interface.js","sourceRoot":"","sources":["../../src/types/odds.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export type Variant = 'NLH' | 'PLO4' | 'PLO5' | 'PLO6';
2
+ //# sourceMappingURL=variant.type.d.ts.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=variant.type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.type.js","sourceRoot":"","sources":["../../src/types/variant.type.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare function combinations<T>(set: T[], k: number): T[][];
2
+ //# sourceMappingURL=combination.d.ts.map
@@ -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,5 @@
1
+ import { evalHand } from 'poker-evaluator';
2
+
3
+ export function evalNLH(holeCards: string[], boardCards: string[]): number {
4
+ return evalHand([...holeCards, ...boardCards]).value;
5
+ }
@@ -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,7 @@
1
+ /**
2
+ * Standard card notation: rank + suit
3
+ * Ranks: A K Q J T 9 8 7 6 5 4 3 2
4
+ * Suits: s (spades) h (hearts) d (diamonds) c (clubs)
5
+ * Example: "As", "Kh", "Td", "2c"
6
+ */
7
+ export type Card = string;
@@ -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
+ }