@pokertools/evaluator 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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/core/evaluator.d.ts +3 -0
- package/dist/core/evaluator.js +164 -0
- package/dist/core/hash.d.ts +8 -0
- package/dist/core/hash.js +22 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +69 -0
- package/dist/models/hand-rank.d.ts +21 -0
- package/dist/models/hand-rank.js +54 -0
- package/dist/tables/bit-masks.d.ts +2 -0
- package/dist/tables/bit-masks.js +15 -0
- package/dist/tables/dp.d.ts +3 -0
- package/dist/tables/dp.js +267 -0
- package/dist/tables/flush.d.ts +1 -0
- package/dist/tables/flush.js +363 -0
- package/dist/tables/no-flush-5.d.ts +1 -0
- package/dist/tables/no-flush-5.js +388 -0
- package/dist/tables/no-flush-6.d.ts +1 -0
- package/dist/tables/no-flush-6.js +1121 -0
- package/dist/tables/no-flush-7.d.ts +1 -0
- package/dist/tables/no-flush-7.js +2895 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/card.d.ts +19 -0
- package/dist/utils/card.js +79 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Aurelius
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @pokertools/evaluator
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@pokertools/evaluator)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A lightning-fast, strongly-typed Poker Hand Evaluator for Node.js and the browser.
|
|
7
|
+
|
|
8
|
+
Capable of evaluating **over 16 million 7-card hands per second** on a standard CPU. This library uses a Perfect Hash algorithm (based on Cactus Kev/Paul Senzee) optimized specifically for the V8 JavaScript engine.
|
|
9
|
+
|
|
10
|
+
## 🚀 Features
|
|
11
|
+
|
|
12
|
+
- **Extreme Performance:** ~17M evaluations/sec (7-card hands).
|
|
13
|
+
- **Zero Garbage Collection:** Uses static memory buffers to prevent GC overhead during Monte Carlo simulations.
|
|
14
|
+
- **Flexible:** Supports 5, 6, and 7 card hands.
|
|
15
|
+
- **TypeScript:** Written in strict TypeScript with full type definitions.
|
|
16
|
+
- **Lightweight:** Zero runtime dependencies.
|
|
17
|
+
|
|
18
|
+
## ⚡ Benchmarks
|
|
19
|
+
|
|
20
|
+
Comparison of 7-card hand evaluation speed (Node.js V8):
|
|
21
|
+
|
|
22
|
+
| Library | Input Type | Speed (Hands/Sec) | Relative Speed |
|
|
23
|
+
| :------------------------ | :---------- | :---------------- | :------------- |
|
|
24
|
+
| **@pokertools/evaluator** | **Integer** | **~17,900,000** | **100%** |
|
|
25
|
+
| phe | Integer | ~16,550,000 | 92.5% |
|
|
26
|
+
| poker-evaluator | String | ~1,390,000 | 7.7% |
|
|
27
|
+
| pokersolver | String | ~73,000 | 0.4% |
|
|
28
|
+
|
|
29
|
+
### Raw Output
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
🃏 Starting Benchmark: 1000 random 7-card hands per cycle
|
|
33
|
+
----------------------------------------------------------------
|
|
34
|
+
phe (Int) | 16,574,257 hands/sec | ±2.26%
|
|
35
|
+
poker-evaluator (Str) | 1,375,495 hands/sec | ±0.33%
|
|
36
|
+
pokersolver (Str) | 70,980 hands/sec | ±0.70%
|
|
37
|
+
@pokertools (Int) | 17,915,292 hands/sec | ±1.56%
|
|
38
|
+
----------------------------------------------------------------
|
|
39
|
+
🚀 WINNER: @pokertools (Int)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
_Benchmarks run on an M1 Air. Higher is better._
|
|
43
|
+
|
|
44
|
+
## 📦 Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @pokertools/evaluator
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 📖 Usage
|
|
51
|
+
|
|
52
|
+
### 1. Basic Usage (Strings)
|
|
53
|
+
|
|
54
|
+
If you are building a UI or simple game logic, string inputs are easiest to work with.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { evaluateBoard, rankBoard, rankDescription } from "@pokertools/evaluator";
|
|
58
|
+
|
|
59
|
+
// 1. Get a raw strength score (lower is better)
|
|
60
|
+
const score = evaluateBoard("Ah Kh Qh Jh Th 2c 3c");
|
|
61
|
+
console.log(score); // 1 (Royal Flush is the lowest/best number)
|
|
62
|
+
|
|
63
|
+
// 2. Get the Rank Category (Enum)
|
|
64
|
+
const rank = rankBoard("Ah As Ks Kd Qs Qd 2c");
|
|
65
|
+
console.log(rankDescription(rank)); // "Two Pair"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. High-Performance Usage (Integers)
|
|
69
|
+
|
|
70
|
+
If you are building an Equity Calculator or AI Solver, you should convert cards to integers **once** and pass integers around your system. This creates a 12x performance boost by skipping string parsing.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { evaluate, getCardCode, rank, HandRank } from "@pokertools/evaluator";
|
|
74
|
+
|
|
75
|
+
// Convert strings to integers once
|
|
76
|
+
const holeCards = [getCardCode("As"), getCardCode("Ah")];
|
|
77
|
+
const board = [getCardCode("Ks"), getCardCode("Kh"), getCardCode("Qs")];
|
|
78
|
+
|
|
79
|
+
// Combine arrays (Spread operator is fast enough for small arrays)
|
|
80
|
+
const hand = [...holeCards, ...board];
|
|
81
|
+
|
|
82
|
+
// Evaluate
|
|
83
|
+
const strength = evaluate(hand);
|
|
84
|
+
|
|
85
|
+
// Check Rank
|
|
86
|
+
if (rank(hand) === HandRank.FullHouse) {
|
|
87
|
+
console.log("We have a boat!");
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 📚 API Reference
|
|
92
|
+
|
|
93
|
+
### Core Functions
|
|
94
|
+
|
|
95
|
+
#### `evaluate(codes: number[]): number`
|
|
96
|
+
|
|
97
|
+
The fastest evaluation method. Accepts an array of 5, 6, or 7 integers. Returns a raw score (lower is better).
|
|
98
|
+
|
|
99
|
+
- Royal Flush: 1
|
|
100
|
+
- ...
|
|
101
|
+
- Worst High Card: 7462
|
|
102
|
+
|
|
103
|
+
#### `evaluateStrings(cards: string[]): number`
|
|
104
|
+
|
|
105
|
+
Helper to evaluate an array of strings like `['Ah', 'Td', ...]`.
|
|
106
|
+
|
|
107
|
+
#### `evaluateBoard(board: string): number`
|
|
108
|
+
|
|
109
|
+
Helper to evaluate a space-separated string like `"Ah Td 2c"`.
|
|
110
|
+
|
|
111
|
+
### Ranking Helpers
|
|
112
|
+
|
|
113
|
+
#### `rank(codes: number[]): HandRank`
|
|
114
|
+
|
|
115
|
+
Returns the `HandRank` enum (0-8) for a set of card integers.
|
|
116
|
+
|
|
117
|
+
#### `HandRank` (Enum)
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
enum HandRank {
|
|
121
|
+
StraightFlush = 0,
|
|
122
|
+
FourOfAKind = 1,
|
|
123
|
+
FullHouse = 2,
|
|
124
|
+
Flush = 3,
|
|
125
|
+
Straight = 4,
|
|
126
|
+
ThreeOfAKind = 5,
|
|
127
|
+
TwoPair = 6,
|
|
128
|
+
OnePair = 7,
|
|
129
|
+
HighCard = 8,
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `rankDescription(rank: HandRank): string`
|
|
134
|
+
|
|
135
|
+
Returns human-readable strings like "Full House" or "High Card".
|
|
136
|
+
|
|
137
|
+
### Card Encoding
|
|
138
|
+
|
|
139
|
+
#### `getCardCode(cardStr: string): number`
|
|
140
|
+
|
|
141
|
+
Converts a card string (e.g., `"Ah"`) into the optimized integer format used by this library.
|
|
142
|
+
|
|
143
|
+
#### `stringifyCardCode(code: number): string`
|
|
144
|
+
|
|
145
|
+
Converts an integer back into a readable string.
|
|
146
|
+
|
|
147
|
+
## 🧮 Algorithm
|
|
148
|
+
|
|
149
|
+
This library implements the **Perfect Hash** algorithm.
|
|
150
|
+
|
|
151
|
+
1. It first checks for a Flush using a bitmask OR operation.
|
|
152
|
+
2. If no flush, it calculates a unique prime-product hash (Quinary) based on the rank counts.
|
|
153
|
+
3. This hash is used as an index into a pre-computed DAG (Directed Acyclic Graph) lookup table to immediately return the hand strength.
|
|
154
|
+
|
|
155
|
+
This approach avoids expensive sorting or pattern matching operations found in slower libraries.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluate5Cards = evaluate5Cards;
|
|
4
|
+
exports.evaluate6Cards = evaluate6Cards;
|
|
5
|
+
exports.evaluate7Cards = evaluate7Cards;
|
|
6
|
+
const bit_masks_1 = require("../tables/bit-masks");
|
|
7
|
+
const dp_1 = require("../tables/dp");
|
|
8
|
+
const flush_1 = require("../tables/flush");
|
|
9
|
+
const no_flush_5_1 = require("../tables/no-flush-5");
|
|
10
|
+
const no_flush_6_1 = require("../tables/no-flush-6");
|
|
11
|
+
const no_flush_7_1 = require("../tables/no-flush-7");
|
|
12
|
+
const hash_1 = require("./hash");
|
|
13
|
+
/**
|
|
14
|
+
* Static buffers to prevent garbage collection overhead during hot loop evaluations.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
* @warning NOT THREAD-SAFE / NOT RE-ENTRANT
|
|
18
|
+
*
|
|
19
|
+
* These static arrays are reused across all evaluate() calls within the same
|
|
20
|
+
* JavaScript context to eliminate GC pressure during Monte Carlo simulations.
|
|
21
|
+
*
|
|
22
|
+
* **Thread Safety Implications:**
|
|
23
|
+
* - Safe for standard Node.js/Browser single-threaded execution
|
|
24
|
+
* - Safe for async/await code (each await yields control)
|
|
25
|
+
* - NOT safe if called recursively (don't call evaluate() from within evaluate())
|
|
26
|
+
* - NOT safe with SharedArrayBuffer or true multi-threaded contexts
|
|
27
|
+
* - NOT safe if multiple evaluate() calls are interleaved in the same tick
|
|
28
|
+
*
|
|
29
|
+
* **Performance Trade-off:**
|
|
30
|
+
* Using static buffers provides ~12% speed improvement (17M vs 15M hands/sec)
|
|
31
|
+
* by avoiding array allocations in the hot path. The non-reentrancy is acceptable
|
|
32
|
+
* because poker hand evaluation is a synchronous, non-recursive operation.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // ✅ SAFE: Sequential evaluation
|
|
36
|
+
* const score1 = evaluate(hand1);
|
|
37
|
+
* const score2 = evaluate(hand2);
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // ✅ SAFE: Async is OK (yields between calls)
|
|
41
|
+
* for (const hand of hands) {
|
|
42
|
+
* const score = evaluate(hand);
|
|
43
|
+
* await saveToDatabase(score);
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // ❌ UNSAFE: Recursive call
|
|
48
|
+
* function badIdea(cards) {
|
|
49
|
+
* if (cards.length > 7) {
|
|
50
|
+
* return evaluate(cards.slice(0, 7)); // Corrupts static buffers!
|
|
51
|
+
* }
|
|
52
|
+
* return evaluate(cards);
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
55
|
+
const suitBinary = [0, 0, 0, 0];
|
|
56
|
+
const quinary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
57
|
+
/**
|
|
58
|
+
* Fast reset of static buffers.
|
|
59
|
+
* Manual index assignment is faster than .fill(0).
|
|
60
|
+
*/
|
|
61
|
+
function resetBuffers() {
|
|
62
|
+
suitBinary[0] = 0;
|
|
63
|
+
suitBinary[1] = 0;
|
|
64
|
+
suitBinary[2] = 0;
|
|
65
|
+
suitBinary[3] = 0;
|
|
66
|
+
quinary[0] = 0;
|
|
67
|
+
quinary[1] = 0;
|
|
68
|
+
quinary[2] = 0;
|
|
69
|
+
quinary[3] = 0;
|
|
70
|
+
quinary[4] = 0;
|
|
71
|
+
quinary[5] = 0;
|
|
72
|
+
quinary[6] = 0;
|
|
73
|
+
quinary[7] = 0;
|
|
74
|
+
quinary[8] = 0;
|
|
75
|
+
quinary[9] = 0;
|
|
76
|
+
quinary[10] = 0;
|
|
77
|
+
quinary[11] = 0;
|
|
78
|
+
quinary[12] = 0;
|
|
79
|
+
}
|
|
80
|
+
function evaluate5Cards(cards) {
|
|
81
|
+
const c1 = cards[0], c2 = cards[1], c3 = cards[2], c4 = cards[3], c5 = cards[4];
|
|
82
|
+
resetBuffers();
|
|
83
|
+
// 1. Calculate Suit Hash
|
|
84
|
+
const suitHash = bit_masks_1.SUITBIT_BY_ID[c1] +
|
|
85
|
+
bit_masks_1.SUITBIT_BY_ID[c2] +
|
|
86
|
+
bit_masks_1.SUITBIT_BY_ID[c3] +
|
|
87
|
+
bit_masks_1.SUITBIT_BY_ID[c4] +
|
|
88
|
+
bit_masks_1.SUITBIT_BY_ID[c5];
|
|
89
|
+
// 2. Populate Rank Frequency (Quinary)
|
|
90
|
+
quinary[c1 >> 2]++;
|
|
91
|
+
quinary[c2 >> 2]++;
|
|
92
|
+
quinary[c3 >> 2]++;
|
|
93
|
+
quinary[c4 >> 2]++;
|
|
94
|
+
quinary[c5 >> 2]++;
|
|
95
|
+
// 3. Check for Flush using DP Table
|
|
96
|
+
if (dp_1.SUITS_HASH[suitHash]) {
|
|
97
|
+
suitBinary[c1 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c1];
|
|
98
|
+
suitBinary[c2 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c2];
|
|
99
|
+
suitBinary[c3 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c3];
|
|
100
|
+
suitBinary[c4 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c4];
|
|
101
|
+
suitBinary[c5 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c5];
|
|
102
|
+
return flush_1.FLUSH_LOOKUP[suitBinary[dp_1.SUITS_HASH[suitHash] - 1]];
|
|
103
|
+
}
|
|
104
|
+
// 4. Check Hash Table
|
|
105
|
+
const hash = (0, hash_1.hashQuinary)(quinary, 13, 5);
|
|
106
|
+
return no_flush_5_1.NO_FLUSH_5[hash];
|
|
107
|
+
}
|
|
108
|
+
function evaluate6Cards(cards) {
|
|
109
|
+
const c1 = cards[0], c2 = cards[1], c3 = cards[2], c4 = cards[3], c5 = cards[4], c6 = cards[5];
|
|
110
|
+
resetBuffers();
|
|
111
|
+
const suitHash = bit_masks_1.SUITBIT_BY_ID[c1] +
|
|
112
|
+
bit_masks_1.SUITBIT_BY_ID[c2] +
|
|
113
|
+
bit_masks_1.SUITBIT_BY_ID[c3] +
|
|
114
|
+
bit_masks_1.SUITBIT_BY_ID[c4] +
|
|
115
|
+
bit_masks_1.SUITBIT_BY_ID[c5] +
|
|
116
|
+
bit_masks_1.SUITBIT_BY_ID[c6];
|
|
117
|
+
quinary[c1 >> 2]++;
|
|
118
|
+
quinary[c2 >> 2]++;
|
|
119
|
+
quinary[c3 >> 2]++;
|
|
120
|
+
quinary[c4 >> 2]++;
|
|
121
|
+
quinary[c5 >> 2]++;
|
|
122
|
+
quinary[c6 >> 2]++;
|
|
123
|
+
if (dp_1.SUITS_HASH[suitHash]) {
|
|
124
|
+
suitBinary[c1 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c1];
|
|
125
|
+
suitBinary[c2 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c2];
|
|
126
|
+
suitBinary[c3 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c3];
|
|
127
|
+
suitBinary[c4 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c4];
|
|
128
|
+
suitBinary[c5 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c5];
|
|
129
|
+
suitBinary[c6 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c6];
|
|
130
|
+
return flush_1.FLUSH_LOOKUP[suitBinary[dp_1.SUITS_HASH[suitHash] - 1]];
|
|
131
|
+
}
|
|
132
|
+
const hash = (0, hash_1.hashQuinary)(quinary, 13, 6);
|
|
133
|
+
return no_flush_6_1.NO_FLUSH_6[hash];
|
|
134
|
+
}
|
|
135
|
+
function evaluate7Cards(cards) {
|
|
136
|
+
const c1 = cards[0], c2 = cards[1], c3 = cards[2], c4 = cards[3], c5 = cards[4], c6 = cards[5], c7 = cards[6];
|
|
137
|
+
resetBuffers();
|
|
138
|
+
const suitHash = bit_masks_1.SUITBIT_BY_ID[c1] +
|
|
139
|
+
bit_masks_1.SUITBIT_BY_ID[c2] +
|
|
140
|
+
bit_masks_1.SUITBIT_BY_ID[c3] +
|
|
141
|
+
bit_masks_1.SUITBIT_BY_ID[c4] +
|
|
142
|
+
bit_masks_1.SUITBIT_BY_ID[c5] +
|
|
143
|
+
bit_masks_1.SUITBIT_BY_ID[c6] +
|
|
144
|
+
bit_masks_1.SUITBIT_BY_ID[c7];
|
|
145
|
+
quinary[c1 >> 2]++;
|
|
146
|
+
quinary[c2 >> 2]++;
|
|
147
|
+
quinary[c3 >> 2]++;
|
|
148
|
+
quinary[c4 >> 2]++;
|
|
149
|
+
quinary[c5 >> 2]++;
|
|
150
|
+
quinary[c6 >> 2]++;
|
|
151
|
+
quinary[c7 >> 2]++;
|
|
152
|
+
if (dp_1.SUITS_HASH[suitHash]) {
|
|
153
|
+
suitBinary[c1 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c1];
|
|
154
|
+
suitBinary[c2 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c2];
|
|
155
|
+
suitBinary[c3 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c3];
|
|
156
|
+
suitBinary[c4 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c4];
|
|
157
|
+
suitBinary[c5 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c5];
|
|
158
|
+
suitBinary[c6 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c6];
|
|
159
|
+
suitBinary[c7 & 0x3] |= bit_masks_1.BINARIES_BY_ID[c7];
|
|
160
|
+
return flush_1.FLUSH_LOOKUP[suitBinary[dp_1.SUITS_HASH[suitHash] - 1]];
|
|
161
|
+
}
|
|
162
|
+
const hash = (0, hash_1.hashQuinary)(quinary, 13, 7);
|
|
163
|
+
return no_flush_7_1.NO_FLUSH_7[hash];
|
|
164
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perfect Hash algorithm for quinary sums.
|
|
3
|
+
*
|
|
4
|
+
* @param q - The frequency array of ranks (count of 2s, count of 3s, etc.)
|
|
5
|
+
* @param len - Number of ranks (always 13)
|
|
6
|
+
* @param k - Number of cards (5, 6, or 7)
|
|
7
|
+
*/
|
|
8
|
+
export declare function hashQuinary(q: number[], len: number, k: number): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashQuinary = hashQuinary;
|
|
4
|
+
const dp_1 = require("../tables/dp");
|
|
5
|
+
/**
|
|
6
|
+
* Perfect Hash algorithm for quinary sums.
|
|
7
|
+
*
|
|
8
|
+
* @param q - The frequency array of ranks (count of 2s, count of 3s, etc.)
|
|
9
|
+
* @param len - Number of ranks (always 13)
|
|
10
|
+
* @param k - Number of cards (5, 6, or 7)
|
|
11
|
+
*/
|
|
12
|
+
function hashQuinary(q, len, k) {
|
|
13
|
+
let sum = 0;
|
|
14
|
+
for (let i = 0; i < len; i++) {
|
|
15
|
+
sum += dp_1.DP_MATRIX[q[i]][len - i - 1][k];
|
|
16
|
+
k -= q[i];
|
|
17
|
+
// Optimization: if we have accounted for all cards (k=0), we can stop early.
|
|
18
|
+
if (k <= 0)
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
return sum;
|
|
22
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HandRank } from "./models/hand-rank";
|
|
2
|
+
export { HandRank, HAND_RANK_DESCRIPTIONS } from "./models/hand-rank";
|
|
3
|
+
export { stringifyCardCode, getCardCode, getCardCodes } from "./utils/card";
|
|
4
|
+
/**
|
|
5
|
+
* Primary entry point.
|
|
6
|
+
* Evaluates 5, 6, or 7 card integer codes.
|
|
7
|
+
* Returns a strength score (lower is better).
|
|
8
|
+
*
|
|
9
|
+
* @param codes Array of card integers (e.g. generated by getCardCode)
|
|
10
|
+
*/
|
|
11
|
+
export declare function evaluate(codes: number[]): number;
|
|
12
|
+
/**
|
|
13
|
+
* Evaluates an array of card strings (e.g. ['As', 'Kd', ...]).
|
|
14
|
+
* Slightly slower than `evaluate` due to parsing overhead.
|
|
15
|
+
*/
|
|
16
|
+
export declare function evaluateStrings(cards: string[]): number;
|
|
17
|
+
/**
|
|
18
|
+
* Evaluates a board string (e.g. "As Kd Qh ...").
|
|
19
|
+
*/
|
|
20
|
+
export declare function evaluateBoard(board: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the HandRank (enum 0-8) for a given set of card integers.
|
|
23
|
+
* Useful for categorizing hands (e.g. "Full House").
|
|
24
|
+
*/
|
|
25
|
+
export declare function rank(codes: number[]): HandRank;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the HandRank (enum 0-8) for a given board string.
|
|
28
|
+
*/
|
|
29
|
+
export declare function rankBoard(board: string): HandRank;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the human-readable name of the hand rank (e.g. "Straight Flush").
|
|
32
|
+
*/
|
|
33
|
+
export declare function rankDescription(rank: HandRank): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCardCodes = exports.getCardCode = exports.stringifyCardCode = exports.HAND_RANK_DESCRIPTIONS = exports.HandRank = void 0;
|
|
4
|
+
exports.evaluate = evaluate;
|
|
5
|
+
exports.evaluateStrings = evaluateStrings;
|
|
6
|
+
exports.evaluateBoard = evaluateBoard;
|
|
7
|
+
exports.rank = rank;
|
|
8
|
+
exports.rankBoard = rankBoard;
|
|
9
|
+
exports.rankDescription = rankDescription;
|
|
10
|
+
const evaluator_1 = require("./core/evaluator");
|
|
11
|
+
const card_1 = require("./utils/card");
|
|
12
|
+
const hand_rank_1 = require("./models/hand-rank");
|
|
13
|
+
// Export Constants and Enums for consumers
|
|
14
|
+
var hand_rank_2 = require("./models/hand-rank");
|
|
15
|
+
Object.defineProperty(exports, "HandRank", { enumerable: true, get: function () { return hand_rank_2.HandRank; } });
|
|
16
|
+
Object.defineProperty(exports, "HAND_RANK_DESCRIPTIONS", { enumerable: true, get: function () { return hand_rank_2.HAND_RANK_DESCRIPTIONS; } });
|
|
17
|
+
var card_2 = require("./utils/card");
|
|
18
|
+
Object.defineProperty(exports, "stringifyCardCode", { enumerable: true, get: function () { return card_2.stringifyCardCode; } });
|
|
19
|
+
Object.defineProperty(exports, "getCardCode", { enumerable: true, get: function () { return card_2.getCardCode; } });
|
|
20
|
+
Object.defineProperty(exports, "getCardCodes", { enumerable: true, get: function () { return card_2.getCardCodes; } });
|
|
21
|
+
/**
|
|
22
|
+
* Primary entry point.
|
|
23
|
+
* Evaluates 5, 6, or 7 card integer codes.
|
|
24
|
+
* Returns a strength score (lower is better).
|
|
25
|
+
*
|
|
26
|
+
* @param codes Array of card integers (e.g. generated by getCardCode)
|
|
27
|
+
*/
|
|
28
|
+
function evaluate(codes) {
|
|
29
|
+
const len = codes.length;
|
|
30
|
+
if (len === 7)
|
|
31
|
+
return (0, evaluator_1.evaluate7Cards)(codes);
|
|
32
|
+
if (len === 6)
|
|
33
|
+
return (0, evaluator_1.evaluate6Cards)(codes);
|
|
34
|
+
if (len === 5)
|
|
35
|
+
return (0, evaluator_1.evaluate5Cards)(codes);
|
|
36
|
+
throw new Error(`Evaluator requires 5, 6, or 7 cards. Received ${len}.`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Evaluates an array of card strings (e.g. ['As', 'Kd', ...]).
|
|
40
|
+
* Slightly slower than `evaluate` due to parsing overhead.
|
|
41
|
+
*/
|
|
42
|
+
function evaluateStrings(cards) {
|
|
43
|
+
return evaluate((0, card_1.getCardCodes)(cards));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Evaluates a board string (e.g. "As Kd Qh ...").
|
|
47
|
+
*/
|
|
48
|
+
function evaluateBoard(board) {
|
|
49
|
+
return evaluate((0, card_1.getBoardCodes)(board));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the HandRank (enum 0-8) for a given set of card integers.
|
|
53
|
+
* Useful for categorizing hands (e.g. "Full House").
|
|
54
|
+
*/
|
|
55
|
+
function rank(codes) {
|
|
56
|
+
return (0, hand_rank_1.getHandRank)(evaluate(codes));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the HandRank (enum 0-8) for a given board string.
|
|
60
|
+
*/
|
|
61
|
+
function rankBoard(board) {
|
|
62
|
+
return (0, hand_rank_1.getHandRank)(evaluateBoard(board));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns the human-readable name of the hand rank (e.g. "Straight Flush").
|
|
66
|
+
*/
|
|
67
|
+
function rankDescription(rank) {
|
|
68
|
+
return hand_rank_1.HAND_RANK_DESCRIPTIONS[rank];
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Poker Hand Ranks.
|
|
3
|
+
* Using const enum for inlining performance.
|
|
4
|
+
*/
|
|
5
|
+
export declare const enum HandRank {
|
|
6
|
+
StraightFlush = 0,
|
|
7
|
+
FourOfAKind = 1,
|
|
8
|
+
FullHouse = 2,
|
|
9
|
+
Flush = 3,
|
|
10
|
+
Straight = 4,
|
|
11
|
+
ThreeOfAKind = 5,
|
|
12
|
+
TwoPair = 6,
|
|
13
|
+
OnePair = 7,
|
|
14
|
+
HighCard = 8
|
|
15
|
+
}
|
|
16
|
+
export declare const HAND_RANK_DESCRIPTIONS: Readonly<Record<HandRank, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Converts a raw evaluator score into a HandRank category.
|
|
19
|
+
* Thresholds are based on the specific hash algorithm used.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getHandRank(val: number): HandRank;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HAND_RANK_DESCRIPTIONS = exports.HandRank = void 0;
|
|
4
|
+
exports.getHandRank = getHandRank;
|
|
5
|
+
/**
|
|
6
|
+
* Standard Poker Hand Ranks.
|
|
7
|
+
* Using const enum for inlining performance.
|
|
8
|
+
*/
|
|
9
|
+
var HandRank;
|
|
10
|
+
(function (HandRank) {
|
|
11
|
+
HandRank[HandRank["StraightFlush"] = 0] = "StraightFlush";
|
|
12
|
+
HandRank[HandRank["FourOfAKind"] = 1] = "FourOfAKind";
|
|
13
|
+
HandRank[HandRank["FullHouse"] = 2] = "FullHouse";
|
|
14
|
+
HandRank[HandRank["Flush"] = 3] = "Flush";
|
|
15
|
+
HandRank[HandRank["Straight"] = 4] = "Straight";
|
|
16
|
+
HandRank[HandRank["ThreeOfAKind"] = 5] = "ThreeOfAKind";
|
|
17
|
+
HandRank[HandRank["TwoPair"] = 6] = "TwoPair";
|
|
18
|
+
HandRank[HandRank["OnePair"] = 7] = "OnePair";
|
|
19
|
+
HandRank[HandRank["HighCard"] = 8] = "HighCard";
|
|
20
|
+
})(HandRank || (exports.HandRank = HandRank = {}));
|
|
21
|
+
exports.HAND_RANK_DESCRIPTIONS = {
|
|
22
|
+
[0 /* HandRank.StraightFlush */]: "Straight Flush",
|
|
23
|
+
[1 /* HandRank.FourOfAKind */]: "Four of a Kind",
|
|
24
|
+
[2 /* HandRank.FullHouse */]: "Full House",
|
|
25
|
+
[3 /* HandRank.Flush */]: "Flush",
|
|
26
|
+
[4 /* HandRank.Straight */]: "Straight",
|
|
27
|
+
[5 /* HandRank.ThreeOfAKind */]: "Three of a Kind",
|
|
28
|
+
[6 /* HandRank.TwoPair */]: "Two Pair",
|
|
29
|
+
[7 /* HandRank.OnePair */]: "One Pair",
|
|
30
|
+
[8 /* HandRank.HighCard */]: "High Card",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Converts a raw evaluator score into a HandRank category.
|
|
34
|
+
* Thresholds are based on the specific hash algorithm used.
|
|
35
|
+
*/
|
|
36
|
+
function getHandRank(val) {
|
|
37
|
+
if (val > 6185)
|
|
38
|
+
return 8 /* HandRank.HighCard */; // 1277 high cards
|
|
39
|
+
if (val > 3325)
|
|
40
|
+
return 7 /* HandRank.OnePair */; // 2860 one pairs
|
|
41
|
+
if (val > 2467)
|
|
42
|
+
return 6 /* HandRank.TwoPair */; // 858 two pairs
|
|
43
|
+
if (val > 1609)
|
|
44
|
+
return 5 /* HandRank.ThreeOfAKind */; // 858 three-kinds
|
|
45
|
+
if (val > 1599)
|
|
46
|
+
return 4 /* HandRank.Straight */; // 10 straights
|
|
47
|
+
if (val > 322)
|
|
48
|
+
return 3 /* HandRank.Flush */; // 1277 flushes
|
|
49
|
+
if (val > 166)
|
|
50
|
+
return 2 /* HandRank.FullHouse */; // 156 full houses
|
|
51
|
+
if (val > 10)
|
|
52
|
+
return 1 /* HandRank.FourOfAKind */; // 156 four-kinds
|
|
53
|
+
return 0 /* HandRank.StraightFlush */; // 10 straight-flushes
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUITBIT_BY_ID = exports.BINARIES_BY_ID = void 0;
|
|
4
|
+
exports.BINARIES_BY_ID = [
|
|
5
|
+
0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x8, 0x8, 0x8, 0x8, 0x10, 0x10, 0x10,
|
|
6
|
+
0x10, 0x20, 0x20, 0x20, 0x20, 0x40, 0x40, 0x40, 0x40, 0x80, 0x80, 0x80, 0x80, 0x100, 0x100, 0x100,
|
|
7
|
+
0x100, 0x200, 0x200, 0x200, 0x200, 0x400, 0x400, 0x400, 0x400, 0x800, 0x800, 0x800, 0x800, 0x1000,
|
|
8
|
+
0x1000, 0x1000, 0x1000,
|
|
9
|
+
];
|
|
10
|
+
exports.SUITBIT_BY_ID = [
|
|
11
|
+
0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1,
|
|
12
|
+
0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8,
|
|
13
|
+
0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40, 0x200, 0x1, 0x8, 0x40,
|
|
14
|
+
0x200,
|
|
15
|
+
];
|