@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,142 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Shared types for cash games
4
+ *
5
+ * Standardized interfaces for buy-in, rake, rebuy, and seat management
6
+ */
7
+
8
+ /**
9
+ * Rake configuration for cash games
10
+ */
11
+ export class RakeConfig {
12
+ percentage: f64 = 0.0; // Rake percentage (e.g., 5.0 for 5%)
13
+ cap: i64 = 0; // Maximum rake per pot (0 = no cap)
14
+
15
+ constructor(percentage: f64 = 0.0, cap: i64 = 0) {
16
+ this.percentage = percentage;
17
+ this.cap = cap;
18
+ }
19
+
20
+ clone(): RakeConfig {
21
+ return new RakeConfig(this.percentage, this.cap);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Buy-in range configuration
27
+ */
28
+ export class BuyInRange {
29
+ min: string = "0"; // Minimum buy-in amount (BigInt string)
30
+ max: string = "0"; // Maximum buy-in amount (BigInt string)
31
+
32
+ constructor(min: string = "0", max: string = "0") {
33
+ this.min = min;
34
+ this.max = max;
35
+ }
36
+
37
+ clone(): BuyInRange {
38
+ return new BuyInRange(this.min, this.max);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Rebuy configuration
44
+ */
45
+ export class RebuyConfig {
46
+ allowed: bool = true; // Whether rebuys are allowed
47
+ cooldownMs: i64 = 0; // Cooldown between rebuys (0 = no cooldown)
48
+ maxRebuys: i32 = -1; // Maximum rebuys per player (-1 = unlimited)
49
+ autoRebuy: bool = false; // Auto-rebuy when stack reaches 0
50
+
51
+ constructor(
52
+ allowed: bool = true,
53
+ cooldownMs: i64 = 0,
54
+ maxRebuys: i32 = -1,
55
+ autoRebuy: bool = false
56
+ ) {
57
+ this.allowed = allowed;
58
+ this.cooldownMs = cooldownMs;
59
+ this.maxRebuys = maxRebuys;
60
+ this.autoRebuy = autoRebuy;
61
+ }
62
+
63
+ clone(): RebuyConfig {
64
+ return new RebuyConfig(this.allowed, this.cooldownMs, this.maxRebuys, this.autoRebuy);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Cash game configuration
70
+ * Combines all cash game settings
71
+ */
72
+ export class CashGameConfig {
73
+ buyInRange: BuyInRange = new BuyInRange();
74
+ rakeConfig: RakeConfig = new RakeConfig();
75
+ rebuyConfig: RebuyConfig = new RebuyConfig();
76
+
77
+ constructor(
78
+ buyInRange: BuyInRange | null = null,
79
+ rakeConfig: RakeConfig | null = null,
80
+ rebuyConfig: RebuyConfig | null = null
81
+ ) {
82
+ this.buyInRange = buyInRange !== null ? buyInRange : new BuyInRange();
83
+ this.rakeConfig = rakeConfig !== null ? rakeConfig : new RakeConfig();
84
+ this.rebuyConfig = rebuyConfig !== null ? rebuyConfig : new RebuyConfig();
85
+ }
86
+
87
+ clone(): CashGameConfig {
88
+ return new CashGameConfig(
89
+ this.buyInRange.clone(),
90
+ this.rakeConfig.clone(),
91
+ this.rebuyConfig.clone()
92
+ );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Base interface for cash game seats
98
+ * Games should extend this with game-specific fields
99
+ */
100
+ export class CashGameSeatBase {
101
+ seatId: i32 = 0;
102
+ playerId: string | null = null;
103
+ stack: string = "0"; // Current stack (BigInt string)
104
+ buyInAmount: string = "0"; // Original buy-in amount (BigInt string)
105
+ lastRebuyAt: i64 = 0; // Timestamp of last rebuy
106
+ rebuyCount: i32 = 0; // Number of rebuys this player has made
107
+
108
+ constructor(
109
+ seatId: i32 = 0,
110
+ playerId: string | null = null,
111
+ stack: string = "0",
112
+ buyInAmount: string = "0"
113
+ ) {
114
+ this.seatId = seatId;
115
+ this.playerId = playerId;
116
+ this.stack = stack;
117
+ this.buyInAmount = buyInAmount;
118
+ this.lastRebuyAt = 0;
119
+ this.rebuyCount = 0;
120
+ }
121
+
122
+ isEmpty(): bool {
123
+ const pid = this.playerId;
124
+ if (pid === null) {
125
+ return true;
126
+ }
127
+ return pid.length === 0;
128
+ }
129
+
130
+ clone(): CashGameSeatBase {
131
+ const seat = new CashGameSeatBase(
132
+ this.seatId,
133
+ this.playerId,
134
+ this.stack,
135
+ this.buyInAmount
136
+ );
137
+ seat.lastRebuyAt = this.lastRebuyAt;
138
+ seat.rebuyCount = this.rebuyCount;
139
+ return seat;
140
+ }
141
+ }
142
+
@@ -0,0 +1,223 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Shared utilities for cash games
4
+ *
5
+ * Standardized functions for buy-in validation, rake calculation, rebuy eligibility, etc.
6
+ */
7
+
8
+ import { BigInt } from "@arcanahq/core/assembly/primitives/bigint";
9
+ import { is_valid_u256_string, parse_u256_string } from "@arcanahq/core/assembly/primitives/conversions";
10
+ import { CashGameSeatBase } from "./cash_game_types";
11
+
12
+ /**
13
+ * Validate buy-in amount is within min/max range
14
+ * @param amount Buy-in amount as string (BigInt)
15
+ * @param minBuyIn Minimum buy-in as string (BigInt)
16
+ * @param maxBuyIn Maximum buy-in as string (BigInt)
17
+ * @returns true if valid, false otherwise
18
+ */
19
+ export function validateBuyInAmount(amount: string, minBuyIn: string, maxBuyIn: string): bool {
20
+ // Validate amount format
21
+ if (!is_valid_u256_string(amount)) {
22
+ return false;
23
+ }
24
+
25
+ const amountBigInt = parse_u256_string(amount);
26
+ if (amountBigInt.lte(BigInt.ZERO)) {
27
+ return false;
28
+ }
29
+
30
+ // Validate min/max format
31
+ if (!is_valid_u256_string(minBuyIn) || !is_valid_u256_string(maxBuyIn)) {
32
+ return false;
33
+ }
34
+
35
+ const minBigInt = parse_u256_string(minBuyIn);
36
+ const maxBigInt = parse_u256_string(maxBuyIn);
37
+
38
+ // Validate range
39
+ if (minBigInt.lte(BigInt.ZERO) || maxBigInt.lte(BigInt.ZERO)) {
40
+ return false;
41
+ }
42
+
43
+ if (minBigInt.gt(maxBigInt)) {
44
+ return false;
45
+ }
46
+
47
+ // Check amount is within range
48
+ if (amountBigInt.lt(minBigInt) || amountBigInt.gt(maxBigInt)) {
49
+ return false;
50
+ }
51
+
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Calculate rake for a pot amount
57
+ * @param potAmount Pot amount as i64
58
+ * @param rakePercentage Rake percentage (e.g., 5.0 for 5%)
59
+ * @param rakeCap Maximum rake per pot (0 = no cap)
60
+ * @returns Rake amount as i64
61
+ */
62
+ export function calculateRake(potAmount: i64, rakePercentage: f64, rakeCap: i64): i64 {
63
+ if (rakePercentage <= 0.0) {
64
+ return 0;
65
+ }
66
+
67
+ // Calculate rake as percentage
68
+ const rake = <i64>(<f64>potAmount * rakePercentage / 100.0);
69
+
70
+ // Apply cap if set
71
+ if (rakeCap > 0 && rake > rakeCap) {
72
+ return rakeCap;
73
+ }
74
+
75
+ return rake;
76
+ }
77
+
78
+ /**
79
+ * Calculate rake for a pot amount (BigInt string version)
80
+ * @param potAmount Pot amount as string (BigInt)
81
+ * @param rakePercentage Rake percentage (e.g., 5.0 for 5%)
82
+ * @param rakeCap Maximum rake per pot as string (BigInt, "0" = no cap)
83
+ * @returns Rake amount as string (BigInt)
84
+ */
85
+ export function calculateRakeString(potAmount: string, rakePercentage: f64, rakeCap: string): string {
86
+ if (rakePercentage <= 0.0) {
87
+ return "0";
88
+ }
89
+
90
+ if (!is_valid_u256_string(potAmount)) {
91
+ return "0";
92
+ }
93
+
94
+ const potBigInt = parse_u256_string(potAmount);
95
+ if (potBigInt.lte(BigInt.ZERO)) {
96
+ return "0";
97
+ }
98
+
99
+ // For percentage calculation, we need to use fixed-point math
100
+ // Calculate percentage using BigInt operations
101
+ // potAmount * percentage / 100
102
+ // Since we can't do floating point with BigInt directly, we'll use integer math:
103
+ // Multiply by percentage (as integer * 100), then divide by 10000
104
+ // e.g., 5% = 500 / 10000
105
+ const percentageInt = <i64>(rakePercentage * 100.0); // e.g., 5.0% = 500
106
+ const percentageBigInt = BigInt.fromString(percentageInt.toString());
107
+ const divisorBigInt = BigInt.fromString("10000");
108
+
109
+ // Calculate: pot * percentage / 10000
110
+ const rakeBigInt = potBigInt.mul(percentageBigInt).div(divisorBigInt);
111
+
112
+ // Apply cap if set
113
+ if (is_valid_u256_string(rakeCap) && rakeCap !== "0") {
114
+ const capBigInt = parse_u256_string(rakeCap);
115
+ if (capBigInt.gt(BigInt.ZERO) && rakeBigInt.gt(capBigInt)) {
116
+ return capBigInt.toString();
117
+ }
118
+ }
119
+
120
+ return rakeBigInt.toString();
121
+ }
122
+
123
+ /**
124
+ * Check if a player can rebuy
125
+ * @param seat Seat to check
126
+ * @param gamePhase Current game phase
127
+ * @param rebuyAllowed Whether rebuys are allowed
128
+ * @param cooldownMs Cooldown between rebuys
129
+ * @param nowMs Current timestamp in milliseconds
130
+ * @returns true if rebuy is allowed, false otherwise
131
+ */
132
+ export function canRebuy(
133
+ seat: CashGameSeatBase,
134
+ gamePhase: string,
135
+ rebuyAllowed: bool,
136
+ cooldownMs: i64,
137
+ nowMs: i64
138
+ ): bool {
139
+ if (!rebuyAllowed) {
140
+ return false;
141
+ }
142
+
143
+ // Must have a player
144
+ if (seat.isEmpty()) {
145
+ return false;
146
+ }
147
+
148
+ // Check cooldown
149
+ if (cooldownMs > 0 && seat.lastRebuyAt > 0) {
150
+ const timeSinceRebuy = nowMs - seat.lastRebuyAt;
151
+ if (timeSinceRebuy < cooldownMs) {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ // Check if stack is zero or very low (for rebuy eligibility)
157
+ const stackBigInt = parse_u256_string(seat.stack);
158
+ if (stackBigInt.gt(BigInt.ZERO)) {
159
+ // For top-up, not rebuy - use canTopUp instead
160
+ return false;
161
+ }
162
+
163
+ // Can rebuy if stack is zero and not in active hand
164
+ // Game-specific phases should be checked by caller
165
+ return true;
166
+ }
167
+
168
+ /**
169
+ * Check if a player can top up
170
+ * @param seat Seat to check
171
+ * @param topUpAmount Amount to top up as string (BigInt)
172
+ * @param maxBuyIn Maximum buy-in as string (BigInt)
173
+ * @param gamePhase Current game phase
174
+ * @returns true if top-up is allowed, false otherwise
175
+ */
176
+ export function canTopUp(
177
+ seat: CashGameSeatBase,
178
+ topUpAmount: string,
179
+ maxBuyIn: string,
180
+ gamePhase: string
181
+ ): bool {
182
+ // Must have a player
183
+ if (seat.isEmpty()) {
184
+ return false;
185
+ }
186
+
187
+ // Validate top-up amount
188
+ if (!is_valid_u256_string(topUpAmount)) {
189
+ return false;
190
+ }
191
+
192
+ const topUpBigInt = parse_u256_string(topUpAmount);
193
+ if (topUpBigInt.lte(BigInt.ZERO)) {
194
+ return false;
195
+ }
196
+
197
+ // Check current stack + top-up doesn't exceed max buy-in
198
+ const stackBigInt = parse_u256_string(seat.stack);
199
+ const maxBigInt = parse_u256_string(maxBuyIn);
200
+
201
+ const newStack = stackBigInt.add(topUpBigInt);
202
+ if (newStack.gt(maxBigInt)) {
203
+ return false;
204
+ }
205
+
206
+ // Can top up if not in active hand
207
+ // Game-specific phases should be checked by caller
208
+ return true;
209
+ }
210
+
211
+ /**
212
+ * Initialize cash game seats array
213
+ * @param maxSeats Maximum number of seats
214
+ * @returns Array of empty seats with seat IDs 0 to maxSeats-1
215
+ */
216
+ export function initializeCashGameSeats(maxSeats: i32): CashGameSeatBase[] {
217
+ const seats = new Array<CashGameSeatBase>(maxSeats);
218
+ for (let i = 0; i < maxSeats; i++) {
219
+ seats[i] = new CashGameSeatBase(i, null, "0", "0");
220
+ }
221
+ return seats;
222
+ }
223
+
@@ -0,0 +1,10 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Cash Games Module
4
+ *
5
+ * Re-exports cash game types and utilities
6
+ */
7
+
8
+ export * from "./cash_game_types";
9
+ export * from "./cash_game_utils";
10
+