@arcanahq/cardgames 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 +722 -0
- package/as-test.config.js +36 -0
- package/asconfig.json +22 -0
- package/assembly/__tests__/blackjack/actions/common.spec.ts +180 -0
- package/assembly/__tests__/blackjack/actions/dealer_scenarios.spec.ts +452 -0
- package/assembly/__tests__/blackjack/actions/double.spec.ts +128 -0
- package/assembly/__tests__/blackjack/actions/edge_cases.spec.ts +1041 -0
- package/assembly/__tests__/blackjack/actions/insurance.spec.ts +39 -0
- package/assembly/__tests__/blackjack/actions/split.spec.ts +96 -0
- package/assembly/__tests__/blackjack/actions/stand.spec.ts +103 -0
- package/assembly/__tests__/blackjack/actions/surrender.spec.ts +89 -0
- package/assembly/__tests__/blackjack/actions/test.ts +18 -0
- package/assembly/__tests__/blackjack/rules.spec.ts +231 -0
- package/assembly/__tests__/deck/deck.spec.ts +551 -0
- package/assembly/__tests__/deck/shoe.spec.ts +410 -0
- package/assembly/__tests__/poker/betting_round.spec.ts +103 -0
- package/assembly/__tests__/poker/omaha.spec.ts +171 -0
- package/assembly/__tests__/poker/pots.spec.ts +255 -0
- package/assembly/__tests__/poker/showdown.spec.ts +324 -0
- package/assembly/__tests__/poker/six_plus.spec.ts +152 -0
- package/assembly/__tests__/poker/stakes.spec.ts +384 -0
- package/assembly/__tests__/poker/stud.spec.ts +190 -0
- package/assembly/__tests__/poker/test.ts +13 -0
- package/assembly/__tests__/test.ts +11 -0
- package/assembly/blackjack/actions.ts +191 -0
- package/assembly/blackjack/blackjack.ts +571 -0
- package/assembly/blackjack/rules.ts +11 -0
- package/assembly/cardgames.ts +314 -0
- package/assembly/cards.ts +314 -0
- package/assembly/cashgames/cash_game_types.ts +142 -0
- package/assembly/cashgames/cash_game_utils.ts +223 -0
- package/assembly/cashgames/index.ts +10 -0
- package/assembly/deck/deck.ts +744 -0
- package/assembly/deck/index.ts +9 -0
- package/assembly/index.ts +28 -0
- package/assembly/poker/index.ts +17 -0
- package/assembly/poker/omaha_evaluator.ts +121 -0
- package/assembly/poker/poker_game_types.ts +233 -0
- package/assembly/poker/poker_game_utils.ts +671 -0
- package/assembly/poker/showdown.ts +106 -0
- package/assembly/poker/showdown_evaluator.ts +225 -0
- package/assembly/poker/six_plus_showdown.ts +96 -0
- package/assembly/poker/stud_evaluator.ts +60 -0
- package/assembly/poker/variant_utils.ts +51 -0
- package/assembly/poker/variants.ts +182 -0
- package/assembly/poker.ts +307 -0
- package/package.json +51 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Card Games Library
|
|
4
|
+
*
|
|
5
|
+
* Provides reusable card game logic and blackjack action processing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export card game utilities
|
|
9
|
+
export * from "./cards";
|
|
10
|
+
export * from "./cardgames";
|
|
11
|
+
export * from "./poker";
|
|
12
|
+
|
|
13
|
+
// Re-export blackjack-specific modules
|
|
14
|
+
export * from "./blackjack/blackjack";
|
|
15
|
+
export * from "./blackjack/rules";
|
|
16
|
+
export * from "./blackjack/actions";
|
|
17
|
+
|
|
18
|
+
// Re-export cash game utilities
|
|
19
|
+
export * from "./cashgames";
|
|
20
|
+
|
|
21
|
+
// Re-export deck management
|
|
22
|
+
export * from "./deck";
|
|
23
|
+
|
|
24
|
+
// Re-export poker game utilities
|
|
25
|
+
export * from "./poker/poker_game_types";
|
|
26
|
+
export * from "./poker/poker_game_utils";
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Poker Game Module
|
|
4
|
+
*
|
|
5
|
+
* Re-exports all poker game utilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from "./poker_game_types";
|
|
9
|
+
export * from "./poker_game_utils";
|
|
10
|
+
export * from "./showdown_evaluator";
|
|
11
|
+
export * from "./showdown";
|
|
12
|
+
export * from "./six_plus_showdown";
|
|
13
|
+
export * from "./variants";
|
|
14
|
+
export * from "./stud_evaluator";
|
|
15
|
+
export * from "./omaha_evaluator";
|
|
16
|
+
export * from "./variant_utils";
|
|
17
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Omaha Poker Showdown Evaluator
|
|
4
|
+
*
|
|
5
|
+
* Evaluator for Omaha and Omaha Hi-Lo variants.
|
|
6
|
+
* In Omaha, players receive 4 hole cards and must use exactly 2 of them
|
|
7
|
+
* combined with exactly 3 community cards to make their best 5-card hand.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Card, HandRank } from "../cards";
|
|
11
|
+
import { ShowdownEvaluator } from "./showdown_evaluator";
|
|
12
|
+
import { evaluateHand } from "../poker";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Omaha showdown evaluator
|
|
16
|
+
* Players must use exactly 2 hole cards and 3 community cards
|
|
17
|
+
*/
|
|
18
|
+
export class OmahaShowdownEvaluator extends ShowdownEvaluator {
|
|
19
|
+
/**
|
|
20
|
+
* Evaluate an Omaha hand
|
|
21
|
+
* Must use exactly 2 hole cards and 3 community cards
|
|
22
|
+
*
|
|
23
|
+
* @param holeCards 4 hole cards (must use exactly 2)
|
|
24
|
+
* @param communityCards 5 community cards (must use exactly 3)
|
|
25
|
+
*/
|
|
26
|
+
evaluateHand(holeCards: Card[], communityCards: Card[]): HandRank {
|
|
27
|
+
if (holeCards.length !== 4 || communityCards.length !== 5) {
|
|
28
|
+
// Invalid hand structure for Omaha
|
|
29
|
+
return new HandRank(1, []); // Return high card as fallback
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Try all combinations of 2 hole cards and 3 community cards
|
|
33
|
+
let bestRank: HandRank | null = null;
|
|
34
|
+
|
|
35
|
+
// All combinations of 2 hole cards
|
|
36
|
+
for (let i = 0; i < holeCards.length - 1; i++) {
|
|
37
|
+
for (let j = i + 1; j < holeCards.length; j++) {
|
|
38
|
+
const twoHole = new Array<Card>(2);
|
|
39
|
+
twoHole[0] = holeCards[i];
|
|
40
|
+
twoHole[1] = holeCards[j];
|
|
41
|
+
|
|
42
|
+
// All combinations of 3 community cards
|
|
43
|
+
for (let k = 0; k < communityCards.length - 2; k++) {
|
|
44
|
+
for (let l = k + 1; l < communityCards.length - 1; l++) {
|
|
45
|
+
for (let m = l + 1; m < communityCards.length; m++) {
|
|
46
|
+
const threeCommunity = new Array<Card>(3);
|
|
47
|
+
threeCommunity[0] = communityCards[k];
|
|
48
|
+
threeCommunity[1] = communityCards[l];
|
|
49
|
+
threeCommunity[2] = communityCards[m];
|
|
50
|
+
|
|
51
|
+
// Evaluate this 5-card combination
|
|
52
|
+
const rank = evaluateHand(twoHole, threeCommunity);
|
|
53
|
+
|
|
54
|
+
if (bestRank === null || rank.compare(bestRank) > 0) {
|
|
55
|
+
bestRank = rank;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return bestRank !== null ? bestRank : new HandRank(1, []);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get best 5-card hand for Omaha
|
|
68
|
+
* Returns the 2 hole cards + 3 community cards that form the best hand
|
|
69
|
+
*/
|
|
70
|
+
getBestFiveCards(holeCards: Card[], communityCards: Card[]): Card[] {
|
|
71
|
+
if (holeCards.length !== 4 || communityCards.length !== 5) {
|
|
72
|
+
return new Array<Card>(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let bestRank: HandRank | null = null;
|
|
76
|
+
let bestCards: Card[] | null = null;
|
|
77
|
+
|
|
78
|
+
// Try all combinations of 2 hole cards and 3 community cards
|
|
79
|
+
for (let i = 0; i < holeCards.length - 1; i++) {
|
|
80
|
+
for (let j = i + 1; j < holeCards.length; j++) {
|
|
81
|
+
const twoHole = new Array<Card>(2);
|
|
82
|
+
twoHole[0] = holeCards[i];
|
|
83
|
+
twoHole[1] = holeCards[j];
|
|
84
|
+
|
|
85
|
+
for (let k = 0; k < communityCards.length - 2; k++) {
|
|
86
|
+
for (let l = k + 1; l < communityCards.length - 1; l++) {
|
|
87
|
+
for (let m = l + 1; m < communityCards.length; m++) {
|
|
88
|
+
const threeCommunity = new Array<Card>(3);
|
|
89
|
+
threeCommunity[0] = communityCards[k];
|
|
90
|
+
threeCommunity[1] = communityCards[l];
|
|
91
|
+
threeCommunity[2] = communityCards[m];
|
|
92
|
+
|
|
93
|
+
const rank = evaluateHand(twoHole, threeCommunity);
|
|
94
|
+
|
|
95
|
+
if (bestRank === null || rank.compare(bestRank) > 0) {
|
|
96
|
+
bestRank = rank;
|
|
97
|
+
// Combine the cards
|
|
98
|
+
bestCards = new Array<Card>(5);
|
|
99
|
+
bestCards[0] = twoHole[0];
|
|
100
|
+
bestCards[1] = twoHole[1];
|
|
101
|
+
bestCards[2] = threeCommunity[0];
|
|
102
|
+
bestCards[3] = threeCommunity[1];
|
|
103
|
+
bestCards[4] = threeCommunity[2];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return bestCards !== null ? bestCards : new Array<Card>(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Compare hands (same as standard)
|
|
116
|
+
*/
|
|
117
|
+
compareHands(hand1: HandRank, hand2: HandRank): i32 {
|
|
118
|
+
return hand1.compare(hand2);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Poker Game Types
|
|
4
|
+
*
|
|
5
|
+
* Generic types for poker-style card games including blinds, antes, pots, and rake
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stakes configuration for poker games
|
|
10
|
+
* Defines small blind, big blind, and ante amounts
|
|
11
|
+
*/
|
|
12
|
+
export class Stakes {
|
|
13
|
+
sb: i64 = 0; // Small blind
|
|
14
|
+
bb: i64 = 0; // Big blind
|
|
15
|
+
ante: i64 = 0; // Ante per player
|
|
16
|
+
|
|
17
|
+
constructor(sb: i64 = 0, bb: i64 = 0, ante: i64 = 0) {
|
|
18
|
+
this.sb = sb;
|
|
19
|
+
this.bb = bb;
|
|
20
|
+
this.ante = ante;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
clone(): Stakes {
|
|
24
|
+
return new Stakes(this.sb, this.bb, this.ante);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ante type configuration
|
|
30
|
+
*/
|
|
31
|
+
export class AnteType {
|
|
32
|
+
static readonly NONE: string = "NONE";
|
|
33
|
+
static readonly FIXED: string = "FIXED"; // Fixed ante per player
|
|
34
|
+
static readonly PERCENTAGE: string = "PERCENTAGE"; // Ante as percentage of big blind
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pot structure for poker games
|
|
39
|
+
* Supports side pots and run-it multiple times
|
|
40
|
+
*/
|
|
41
|
+
export class Pot {
|
|
42
|
+
potId: i32 = 0;
|
|
43
|
+
amount: i64 = 0;
|
|
44
|
+
eligibleSeats: i32[] = []; // Seat IDs eligible to win this pot
|
|
45
|
+
locked: bool = false; // Whether pot is locked (no further contributions)
|
|
46
|
+
runCountForPot: i32 = 1; // Number of runs for run-it multiple times
|
|
47
|
+
splitAmountsPerRun: i64[] = []; // Amount per run if split
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
potId: i32 = 0,
|
|
51
|
+
amount: i64 = 0,
|
|
52
|
+
eligibleSeats: i32[] = new Array<i32>(0),
|
|
53
|
+
locked: bool = false,
|
|
54
|
+
runCountForPot: i32 = 1,
|
|
55
|
+
splitAmountsPerRun: i64[] = new Array<i64>(0)
|
|
56
|
+
) {
|
|
57
|
+
this.potId = potId;
|
|
58
|
+
this.amount = amount;
|
|
59
|
+
this.eligibleSeats = eligibleSeats;
|
|
60
|
+
this.locked = locked;
|
|
61
|
+
this.runCountForPot = runCountForPot;
|
|
62
|
+
this.splitAmountsPerRun = splitAmountsPerRun;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
clone(): Pot {
|
|
66
|
+
const clonedEligibleSeats = new Array<i32>(this.eligibleSeats.length);
|
|
67
|
+
for (let i = 0; i < this.eligibleSeats.length; i++) {
|
|
68
|
+
clonedEligibleSeats[i] = this.eligibleSeats[i];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const clonedSplitAmounts = new Array<i64>(this.splitAmountsPerRun.length);
|
|
72
|
+
for (let i = 0; i < this.splitAmountsPerRun.length; i++) {
|
|
73
|
+
clonedSplitAmounts[i] = this.splitAmountsPerRun[i];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new Pot(
|
|
77
|
+
this.potId,
|
|
78
|
+
this.amount,
|
|
79
|
+
clonedEligibleSeats,
|
|
80
|
+
this.locked,
|
|
81
|
+
this.runCountForPot,
|
|
82
|
+
clonedSplitAmounts
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Rake configuration for poker games
|
|
89
|
+
* Note: Uses same structure as cashgames RakeConfig but with additional poker-specific options
|
|
90
|
+
*/
|
|
91
|
+
export class PokerRakeConfig {
|
|
92
|
+
percentage: f64 = 0.0; // Rake percentage (e.g., 5.0 for 5%)
|
|
93
|
+
cap: i64 = 0; // Maximum rake per pot (0 = no cap)
|
|
94
|
+
noFlopNoDrop: bool = false; // No rake if hand doesn't reach flop
|
|
95
|
+
|
|
96
|
+
constructor(percentage: f64 = 0.0, cap: i64 = 0, noFlopNoDrop: bool = false) {
|
|
97
|
+
this.percentage = percentage;
|
|
98
|
+
this.cap = cap;
|
|
99
|
+
this.noFlopNoDrop = noFlopNoDrop;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
clone(): PokerRakeConfig {
|
|
103
|
+
return new PokerRakeConfig(this.percentage, this.cap, this.noFlopNoDrop);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Result of pot distribution
|
|
109
|
+
*/
|
|
110
|
+
export class PotDistributionResult {
|
|
111
|
+
payouts: Map<i32, i64>; // Map of seat ID to payout amount
|
|
112
|
+
rake: i64; // Rake amount deducted
|
|
113
|
+
|
|
114
|
+
constructor(payouts: Map<i32, i64>, rake: i64) {
|
|
115
|
+
this.payouts = payouts;
|
|
116
|
+
this.rake = rake;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Betting round state for poker games
|
|
122
|
+
* Tracks contributions, current bet, and action state
|
|
123
|
+
*/
|
|
124
|
+
export class BettingRoundState {
|
|
125
|
+
contribThisRound: Map<i32, i64>; // Contributions this betting round (seat ID -> amount)
|
|
126
|
+
contribTotal: Map<i32, i64>; // Total contributions this hand (seat ID -> amount)
|
|
127
|
+
currentBetToMatch: i64 = 0; // Current bet amount that must be matched
|
|
128
|
+
minRaise: i64 = 0; // Minimum raise amount
|
|
129
|
+
lastFullRaiseSize: i64 = 0; // Size of last full raise
|
|
130
|
+
lastAggressorSeatId: i32 = -1; // Seat ID of last player to bet/raise
|
|
131
|
+
actingSeatId: i32 = -1; // Seat ID of player currently to act
|
|
132
|
+
|
|
133
|
+
constructor() {
|
|
134
|
+
this.contribThisRound = new Map<i32, i64>();
|
|
135
|
+
this.contribTotal = new Map<i32, i64>();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get contribution for a seat this round
|
|
140
|
+
*/
|
|
141
|
+
getContributionThisRound(seatId: i32): i64 {
|
|
142
|
+
return this.contribThisRound.has(seatId) ? this.contribThisRound.get(seatId) : 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get total contribution for a seat this hand
|
|
147
|
+
*/
|
|
148
|
+
getTotalContribution(seatId: i32): i64 {
|
|
149
|
+
return this.contribTotal.has(seatId) ? this.contribTotal.get(seatId) : 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Calculate amount needed to call for a seat
|
|
154
|
+
*/
|
|
155
|
+
calculateToCall(seatId: i32): i64 {
|
|
156
|
+
const contrib = this.getContributionThisRound(seatId);
|
|
157
|
+
const toCall = this.currentBetToMatch - contrib;
|
|
158
|
+
return toCall > 0 ? toCall : 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reset betting round state for next street
|
|
163
|
+
*/
|
|
164
|
+
resetRound(): void {
|
|
165
|
+
this.contribThisRound = new Map<i32, i64>();
|
|
166
|
+
this.currentBetToMatch = 0;
|
|
167
|
+
this.lastFullRaiseSize = 0;
|
|
168
|
+
this.lastAggressorSeatId = -1;
|
|
169
|
+
this.actingSeatId = -1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
clone(): BettingRoundState {
|
|
173
|
+
const cloned = new BettingRoundState();
|
|
174
|
+
cloned.currentBetToMatch = this.currentBetToMatch;
|
|
175
|
+
cloned.minRaise = this.minRaise;
|
|
176
|
+
cloned.lastFullRaiseSize = this.lastFullRaiseSize;
|
|
177
|
+
cloned.lastAggressorSeatId = this.lastAggressorSeatId;
|
|
178
|
+
cloned.actingSeatId = this.actingSeatId;
|
|
179
|
+
|
|
180
|
+
// Clone maps
|
|
181
|
+
const contribThisRoundKeys = this.contribThisRound.keys();
|
|
182
|
+
for (let i = 0; i < contribThisRoundKeys.length; i++) {
|
|
183
|
+
const key = contribThisRoundKeys[i];
|
|
184
|
+
cloned.contribThisRound.set(key, this.contribThisRound.get(key));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const contribTotalKeys = this.contribTotal.keys();
|
|
188
|
+
for (let i = 0; i < contribTotalKeys.length; i++) {
|
|
189
|
+
const key = contribTotalKeys[i];
|
|
190
|
+
cloned.contribTotal.set(key, this.contribTotal.get(key));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return cloned;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Base interface for poker seat
|
|
199
|
+
* Games should extend this with game-specific fields
|
|
200
|
+
*/
|
|
201
|
+
export class PokerSeatBase {
|
|
202
|
+
seatId: i32 = 0;
|
|
203
|
+
playerId: string | null = null;
|
|
204
|
+
stack: i64 = 0; // Current stack
|
|
205
|
+
inHand: bool = false; // Whether player is in the current hand
|
|
206
|
+
allIn: bool = false; // Whether player is all-in
|
|
207
|
+
hasActedThisRound: bool = false; // Whether player has acted this betting round
|
|
208
|
+
lastAction: string = ""; // Last action taken (FOLD, CHECK, CALL, BET, RAISE, etc.)
|
|
209
|
+
|
|
210
|
+
constructor(seatId: i32 = 0, playerId: string | null = null, stack: i64 = 0) {
|
|
211
|
+
this.seatId = seatId;
|
|
212
|
+
this.playerId = playerId;
|
|
213
|
+
this.stack = stack;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
isEmpty(): bool {
|
|
217
|
+
const pid = this.playerId;
|
|
218
|
+
if (pid === null) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return pid.length === 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
clone(): PokerSeatBase {
|
|
225
|
+
const seat = new PokerSeatBase(this.seatId, this.playerId, this.stack);
|
|
226
|
+
seat.inHand = this.inHand;
|
|
227
|
+
seat.allIn = this.allIn;
|
|
228
|
+
seat.hasActedThisRound = this.hasActedThisRound;
|
|
229
|
+
seat.lastAction = this.lastAction;
|
|
230
|
+
return seat;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|