@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.
Files changed (48) hide show
  1. package/README.md +722 -0
  2. package/as-test.config.js +36 -0
  3. package/asconfig.json +22 -0
  4. package/assembly/__tests__/blackjack/actions/common.spec.ts +180 -0
  5. package/assembly/__tests__/blackjack/actions/dealer_scenarios.spec.ts +452 -0
  6. package/assembly/__tests__/blackjack/actions/double.spec.ts +128 -0
  7. package/assembly/__tests__/blackjack/actions/edge_cases.spec.ts +1041 -0
  8. package/assembly/__tests__/blackjack/actions/insurance.spec.ts +39 -0
  9. package/assembly/__tests__/blackjack/actions/split.spec.ts +96 -0
  10. package/assembly/__tests__/blackjack/actions/stand.spec.ts +103 -0
  11. package/assembly/__tests__/blackjack/actions/surrender.spec.ts +89 -0
  12. package/assembly/__tests__/blackjack/actions/test.ts +18 -0
  13. package/assembly/__tests__/blackjack/rules.spec.ts +231 -0
  14. package/assembly/__tests__/deck/deck.spec.ts +551 -0
  15. package/assembly/__tests__/deck/shoe.spec.ts +410 -0
  16. package/assembly/__tests__/poker/betting_round.spec.ts +103 -0
  17. package/assembly/__tests__/poker/omaha.spec.ts +171 -0
  18. package/assembly/__tests__/poker/pots.spec.ts +255 -0
  19. package/assembly/__tests__/poker/showdown.spec.ts +324 -0
  20. package/assembly/__tests__/poker/six_plus.spec.ts +152 -0
  21. package/assembly/__tests__/poker/stakes.spec.ts +384 -0
  22. package/assembly/__tests__/poker/stud.spec.ts +190 -0
  23. package/assembly/__tests__/poker/test.ts +13 -0
  24. package/assembly/__tests__/test.ts +11 -0
  25. package/assembly/blackjack/actions.ts +191 -0
  26. package/assembly/blackjack/blackjack.ts +571 -0
  27. package/assembly/blackjack/rules.ts +11 -0
  28. package/assembly/cardgames.ts +314 -0
  29. package/assembly/cards.ts +314 -0
  30. package/assembly/cashgames/cash_game_types.ts +142 -0
  31. package/assembly/cashgames/cash_game_utils.ts +223 -0
  32. package/assembly/cashgames/index.ts +10 -0
  33. package/assembly/deck/deck.ts +744 -0
  34. package/assembly/deck/index.ts +9 -0
  35. package/assembly/index.ts +28 -0
  36. package/assembly/poker/index.ts +17 -0
  37. package/assembly/poker/omaha_evaluator.ts +121 -0
  38. package/assembly/poker/poker_game_types.ts +233 -0
  39. package/assembly/poker/poker_game_utils.ts +671 -0
  40. package/assembly/poker/showdown.ts +106 -0
  41. package/assembly/poker/showdown_evaluator.ts +225 -0
  42. package/assembly/poker/six_plus_showdown.ts +96 -0
  43. package/assembly/poker/stud_evaluator.ts +60 -0
  44. package/assembly/poker/variant_utils.ts +51 -0
  45. package/assembly/poker/variants.ts +182 -0
  46. package/assembly/poker.ts +307 -0
  47. package/package.json +51 -0
  48. package/tsconfig.json +16 -0
@@ -0,0 +1,9 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Deck Management Module
4
+ *
5
+ * Re-exports all deck utilities
6
+ */
7
+
8
+ export * from "./deck";
9
+
@@ -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
+