@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,152 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for Six-Plus Hold'em showdown evaluator
4
+ *
5
+ * Six-Plus Hold'em uses a 36-card deck and different hand rankings:
6
+ * - Flush beats Full House
7
+ * - Three of a Kind beats Straight
8
+ */
9
+
10
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
11
+ import { Card, Suit, Rank, HandType } from "../../cards";
12
+ import { SixPlusShowdownEvaluator } from "../../poker/six_plus_showdown";
13
+
14
+ // Helper function to create cards
15
+ function createCard(rank: string, suit: string): Card {
16
+ return new Card(suit, rank);
17
+ }
18
+
19
+ describe("SixPlusShowdownEvaluator", () => {
20
+ test("should rank Flush higher than Full House", () => {
21
+ const evaluator = new SixPlusShowdownEvaluator();
22
+
23
+ // Flush: A♠ K♠ Q♠ J♠ 10♠
24
+ const flushHole = new Array<Card>(2);
25
+ flushHole[0] = createCard(Rank.ACE, Suit.SPADES);
26
+ flushHole[1] = createCard(Rank.KING, Suit.SPADES);
27
+ const flushCommunity = new Array<Card>(5);
28
+ flushCommunity[0] = createCard(Rank.QUEEN, Suit.SPADES);
29
+ flushCommunity[1] = createCard(Rank.JACK, Suit.SPADES);
30
+ flushCommunity[2] = createCard(Rank.TEN, Suit.SPADES);
31
+ flushCommunity[3] = createCard(Rank.NINE, Suit.SPADES);
32
+ flushCommunity[4] = createCard(Rank.EIGHT, Suit.SPADES);
33
+
34
+ // Full House: A♠ A♥ A♦ K♠ K♥
35
+ const fullHouseHole = new Array<Card>(2);
36
+ fullHouseHole[0] = createCard(Rank.ACE, Suit.SPADES);
37
+ fullHouseHole[1] = createCard(Rank.ACE, Suit.HEARTS);
38
+ const fullHouseCommunity = new Array<Card>(5);
39
+ fullHouseCommunity[0] = createCard(Rank.ACE, Suit.DIAMONDS);
40
+ fullHouseCommunity[1] = createCard(Rank.KING, Suit.SPADES);
41
+ fullHouseCommunity[2] = createCard(Rank.KING, Suit.HEARTS);
42
+ fullHouseCommunity[3] = createCard(Rank.QUEEN, Suit.HEARTS);
43
+ fullHouseCommunity[4] = createCard(Rank.JACK, Suit.HEARTS);
44
+
45
+ const flushRank = evaluator.evaluateHand(flushHole, flushCommunity);
46
+ const fullHouseRank = evaluator.evaluateHand(fullHouseHole, fullHouseCommunity);
47
+
48
+ // In Six-Plus, Flush should beat Full House
49
+ const comparison = evaluator.compareHands(flushRank, fullHouseRank);
50
+ expect(comparison).equal(1); // Flush wins
51
+ });
52
+
53
+ test("should rank Three of a Kind higher than Straight", () => {
54
+ const evaluator = new SixPlusShowdownEvaluator();
55
+
56
+ // Three of a Kind: A♠ A♥ A♦ K♠ Q♠ (no flush/straight possible)
57
+ const tripsHole = new Array<Card>(2);
58
+ tripsHole[0] = createCard(Rank.ACE, Suit.SPADES);
59
+ tripsHole[1] = createCard(Rank.ACE, Suit.HEARTS);
60
+ const tripsCommunity = new Array<Card>(5);
61
+ tripsCommunity[0] = createCard(Rank.ACE, Suit.DIAMONDS);
62
+ tripsCommunity[1] = createCard(Rank.KING, Suit.HEARTS);
63
+ tripsCommunity[2] = createCard(Rank.QUEEN, Suit.CLUBS);
64
+ tripsCommunity[3] = createCard(Rank.JACK, Suit.DIAMONDS);
65
+ tripsCommunity[4] = createCard(Rank.TEN, Suit.HEARTS);
66
+
67
+ // Straight: A♠ K♠ Q♠ J♠ 10♠ (but this is actually a flush, so use different setup)
68
+ // Straight: 9♠ 8♠ 7♠ 6♠ 5♠
69
+ const straightHole = new Array<Card>(2);
70
+ straightHole[0] = createCard(Rank.NINE, Suit.SPADES);
71
+ straightHole[1] = createCard(Rank.EIGHT, Suit.HEARTS);
72
+ const straightCommunity = new Array<Card>(5);
73
+ straightCommunity[0] = createCard(Rank.SEVEN, Suit.DIAMONDS);
74
+ straightCommunity[1] = createCard(Rank.SIX, Suit.CLUBS);
75
+ straightCommunity[2] = createCard(Rank.FIVE, Suit.SPADES);
76
+ straightCommunity[3] = createCard(Rank.FOUR, Suit.HEARTS);
77
+ straightCommunity[4] = createCard(Rank.TWO, Suit.HEARTS);
78
+
79
+ const tripsRank = evaluator.evaluateHand(tripsHole, tripsCommunity);
80
+ const straightRank = evaluator.evaluateHand(straightHole, straightCommunity);
81
+
82
+ // In Six-Plus, Three of a Kind should beat Straight
83
+ const comparison = evaluator.compareHands(tripsRank, straightRank);
84
+ expect(comparison).equal(1); // Three of a Kind wins
85
+ });
86
+
87
+ test("should handle showdown with multiple players", () => {
88
+ const evaluator = new SixPlusShowdownEvaluator();
89
+
90
+ // Player 0: Flush (should win in Six-Plus)
91
+ const holeCards0 = new Array<Card>(2);
92
+ holeCards0[0] = createCard(Rank.ACE, Suit.SPADES);
93
+ holeCards0[1] = createCard(Rank.KING, Suit.SPADES);
94
+
95
+ // Player 1: Full House (should lose to Flush in Six-Plus)
96
+ const holeCards1 = new Array<Card>(2);
97
+ holeCards1[0] = createCard(Rank.ACE, Suit.HEARTS);
98
+ holeCards1[1] = createCard(Rank.ACE, Suit.DIAMONDS);
99
+
100
+ const community = new Array<Card>(5);
101
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
102
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
103
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
104
+ community[3] = createCard(Rank.ACE, Suit.CLUBS);
105
+ community[4] = createCard(Rank.KING, Suit.HEARTS);
106
+
107
+ const holeCardsMap = new Map<i32, Card[]>();
108
+ holeCardsMap.set(0, holeCards0);
109
+ holeCardsMap.set(1, holeCards1);
110
+
111
+ const result = evaluator.compareHandsShowdown(holeCardsMap, community);
112
+
113
+ // Player 0 should win with Flush (beats Full House in Six-Plus)
114
+ expect(result.winners.length).equal(1);
115
+ expect(result.winners[0]).equal(0);
116
+ });
117
+
118
+ test("should preserve standard rankings for other hands", () => {
119
+ const evaluator = new SixPlusShowdownEvaluator();
120
+
121
+ // Royal Flush should still beat everything
122
+ const royalFlushHole = new Array<Card>(2);
123
+ royalFlushHole[0] = createCard(Rank.ACE, Suit.SPADES);
124
+ royalFlushHole[1] = createCard(Rank.KING, Suit.SPADES);
125
+ const royalFlushCommunity = new Array<Card>(5);
126
+ royalFlushCommunity[0] = createCard(Rank.QUEEN, Suit.SPADES);
127
+ royalFlushCommunity[1] = createCard(Rank.JACK, Suit.SPADES);
128
+ royalFlushCommunity[2] = createCard(Rank.TEN, Suit.SPADES);
129
+ royalFlushCommunity[3] = createCard(Rank.NINE, Suit.SPADES);
130
+ royalFlushCommunity[4] = createCard(Rank.EIGHT, Suit.SPADES);
131
+
132
+ // Flush (high in Six-Plus, but not Royal Flush) - use lower non-consecutive cards
133
+ const flushHole = new Array<Card>(2);
134
+ flushHole[0] = createCard(Rank.ACE, Suit.HEARTS);
135
+ flushHole[1] = createCard(Rank.KING, Suit.HEARTS);
136
+ const flushCommunity = new Array<Card>(5);
137
+ flushCommunity[0] = createCard(Rank.QUEEN, Suit.HEARTS);
138
+ flushCommunity[1] = createCard(Rank.JACK, Suit.HEARTS);
139
+ flushCommunity[2] = createCard(Rank.SEVEN, Suit.HEARTS); // Changed from TEN to break straight
140
+ flushCommunity[3] = createCard(Rank.FIVE, Suit.HEARTS); // Changed from NINE to break straight
141
+ flushCommunity[4] = createCard(Rank.THREE, Suit.HEARTS); // Changed from SIX to break straight
142
+
143
+ const royalRank = evaluator.evaluateHand(royalFlushHole, royalFlushCommunity);
144
+ const flushRank = evaluator.evaluateHand(flushHole, flushCommunity);
145
+
146
+ // Royal Flush should still beat Flush
147
+ // Royal Flush (type 10, maps to 10 in Six-Plus) > Flush (type 6, maps to 7 in Six-Plus)
148
+ const comparison = evaluator.compareHands(royalRank, flushRank);
149
+ expect(comparison).equal(1); // Royal Flush wins
150
+ });
151
+ });
152
+
@@ -0,0 +1,384 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for stakes and betting utilities
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import { Stakes, AnteType, BettingRoundState, PokerSeatBase } from "../../poker/poker_game_types";
8
+ import {
9
+ calculateAnteAmount,
10
+ postAntes,
11
+ postBlinds,
12
+ getNextActingSeat,
13
+ validateBuyIn,
14
+ processBuyIn,
15
+ isBettingRoundComplete
16
+ } from "../../poker/poker_game_utils";
17
+
18
+ // ============================================================================
19
+ // calculateAnteAmount Tests
20
+ // ============================================================================
21
+
22
+ describe("calculateAnteAmount", () => {
23
+ test("should return 0 for NONE ante type", () => {
24
+ const stakes = new Stakes(10, 20, 5);
25
+ const ante = calculateAnteAmount(stakes, AnteType.NONE, 0.0);
26
+ expect(ante).equal(0);
27
+ });
28
+
29
+ test("should return fixed ante amount for FIXED type", () => {
30
+ const stakes = new Stakes(10, 20, 5);
31
+ const ante = calculateAnteAmount(stakes, AnteType.FIXED, 0.0);
32
+ expect(ante).equal(5);
33
+ });
34
+
35
+ test("should calculate percentage ante for PERCENTAGE type", () => {
36
+ const stakes = new Stakes(10, 20, 0);
37
+ const ante = calculateAnteAmount(stakes, AnteType.PERCENTAGE, 10.0); // 10% of BB
38
+ expect(ante).equal(2); // 10% of 20 = 2
39
+ });
40
+
41
+ test("should handle zero big blind for percentage ante", () => {
42
+ const stakes = new Stakes(10, 0, 0);
43
+ const ante = calculateAnteAmount(stakes, AnteType.PERCENTAGE, 10.0);
44
+ expect(ante).equal(0);
45
+ });
46
+ });
47
+
48
+ // ============================================================================
49
+ // postAntes Tests
50
+ // ============================================================================
51
+
52
+ describe("postAntes", () => {
53
+ test("should post antes for all players in hand", () => {
54
+ const stakes = new Stakes(10, 20, 5);
55
+ const bettingState = new BettingRoundState();
56
+
57
+ const seats = new Array<PokerSeatBase>(3);
58
+ seats[0] = new PokerSeatBase(0, "player1", 100);
59
+ seats[0].inHand = true;
60
+ seats[1] = new PokerSeatBase(1, "player2", 100);
61
+ seats[1].inHand = true;
62
+ seats[2] = new PokerSeatBase(2, null, 0); // Empty seat
63
+ seats[2].inHand = false;
64
+
65
+ const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
66
+
67
+ expect(result.totalAnteCollected).equal(10); // 2 players * 5 ante
68
+ expect(result.seats[0].stack).equal(95); // 100 - 5
69
+ expect(result.seats[1].stack).equal(95); // 100 - 5
70
+ expect(bettingState.getContributionThisRound(0)).equal(5);
71
+ expect(bettingState.getContributionThisRound(1)).equal(5);
72
+ });
73
+
74
+ test("should handle all-in antes", () => {
75
+ const stakes = new Stakes(10, 20, 5);
76
+ const bettingState = new BettingRoundState();
77
+
78
+ const seats = new Array<PokerSeatBase>(2);
79
+ seats[0] = new PokerSeatBase(0, "player1", 3); // Less than ante
80
+ seats[0].inHand = true;
81
+ seats[1] = new PokerSeatBase(1, "player2", 100);
82
+ seats[1].inHand = true;
83
+
84
+ const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
85
+
86
+ expect(result.totalAnteCollected).equal(8); // 3 + 5
87
+ expect(result.seats[0].stack).equal(0);
88
+ expect(result.seats[0].allIn).equal(true);
89
+ expect(result.seats[1].stack).equal(95);
90
+ expect(bettingState.getContributionThisRound(0)).equal(3);
91
+ expect(bettingState.getContributionThisRound(1)).equal(5);
92
+ });
93
+
94
+ test("should skip players not in hand", () => {
95
+ const stakes = new Stakes(10, 20, 5);
96
+ const bettingState = new BettingRoundState();
97
+
98
+ const seats = new Array<PokerSeatBase>(2);
99
+ seats[0] = new PokerSeatBase(0, "player1", 100);
100
+ seats[0].inHand = false; // Not in hand
101
+ seats[1] = new PokerSeatBase(1, "player2", 100);
102
+ seats[1].inHand = true;
103
+
104
+ const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
105
+
106
+ expect(result.totalAnteCollected).equal(5); // Only player2
107
+ expect(result.seats[0].stack).equal(100); // Unchanged
108
+ expect(result.seats[1].stack).equal(95);
109
+ });
110
+ });
111
+
112
+ // ============================================================================
113
+ // postBlinds Tests
114
+ // ============================================================================
115
+
116
+ describe("postBlinds", () => {
117
+ test("should post small blind and big blind", () => {
118
+ const stakes = new Stakes(10, 20, 0);
119
+ const bettingState = new BettingRoundState();
120
+
121
+ const seats = new Array<PokerSeatBase>(3);
122
+ seats[0] = new PokerSeatBase(0, "sb", 100);
123
+ seats[0].inHand = true;
124
+ seats[1] = new PokerSeatBase(1, "bb", 100);
125
+ seats[1].inHand = true;
126
+ seats[2] = new PokerSeatBase(2, "other", 100);
127
+ seats[2].inHand = true;
128
+
129
+ const result = postBlinds(seats, stakes, 0, 1, bettingState);
130
+
131
+ expect(result.seats[0].stack).equal(90); // 100 - 10 SB
132
+ expect(result.seats[1].stack).equal(80); // 100 - 20 BB
133
+ expect(result.seats[2].stack).equal(100); // Unchanged
134
+ expect(result.currentBetToMatch).equal(20); // BB amount
135
+ expect(bettingState.getContributionThisRound(0)).equal(10);
136
+ expect(bettingState.getContributionThisRound(1)).equal(20);
137
+ });
138
+
139
+ test("should handle all-in small blind", () => {
140
+ const stakes = new Stakes(10, 20, 0);
141
+ const bettingState = new BettingRoundState();
142
+
143
+ const seats = new Array<PokerSeatBase>(2);
144
+ seats[0] = new PokerSeatBase(0, "sb", 5); // Less than SB
145
+ seats[0].inHand = true;
146
+ seats[1] = new PokerSeatBase(1, "bb", 100);
147
+ seats[1].inHand = true;
148
+
149
+ const result = postBlinds(seats, stakes, 0, 1, bettingState);
150
+
151
+ expect(result.seats[0].stack).equal(0);
152
+ expect(result.seats[0].allIn).equal(true);
153
+ expect(bettingState.getContributionThisRound(0)).equal(5);
154
+ });
155
+
156
+ test("should handle all-in big blind", () => {
157
+ const stakes = new Stakes(10, 20, 0);
158
+ const bettingState = new BettingRoundState();
159
+
160
+ const seats = new Array<PokerSeatBase>(2);
161
+ seats[0] = new PokerSeatBase(0, "sb", 100);
162
+ seats[0].inHand = true;
163
+ seats[1] = new PokerSeatBase(1, "bb", 15); // Less than BB
164
+ seats[1].inHand = true;
165
+
166
+ const result = postBlinds(seats, stakes, 0, 1, bettingState);
167
+
168
+ expect(result.seats[1].stack).equal(0);
169
+ expect(result.seats[1].allIn).equal(true);
170
+ expect(result.currentBetToMatch).equal(15); // Actual BB posted
171
+ expect(bettingState.getContributionThisRound(1)).equal(15);
172
+ });
173
+ });
174
+
175
+ // ============================================================================
176
+ // getNextActingSeat Tests
177
+ // ============================================================================
178
+
179
+ describe("getNextActingSeat", () => {
180
+ test("should return first to act preflop (after BB)", () => {
181
+ const bettingState = new BettingRoundState();
182
+ bettingState.currentBetToMatch = 20;
183
+
184
+ const seats = new Array<PokerSeatBase>(4);
185
+ seats[0] = new PokerSeatBase(0, "button", 100);
186
+ seats[0].inHand = true;
187
+ seats[1] = new PokerSeatBase(1, "sb", 100);
188
+ seats[1].inHand = true;
189
+ seats[2] = new PokerSeatBase(2, "bb", 100);
190
+ seats[2].inHand = true;
191
+ seats[3] = new PokerSeatBase(3, "utg", 100);
192
+ seats[3].inHand = true;
193
+
194
+ // Preflop: action starts after BB (button + 3)
195
+ const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
196
+ expect(nextSeat).equal(3); // UTG (button + 3)
197
+ });
198
+
199
+ test("should return first to act postflop (after button)", () => {
200
+ const bettingState = new BettingRoundState();
201
+ bettingState.currentBetToMatch = 0;
202
+
203
+ const seats = new Array<PokerSeatBase>(3);
204
+ seats[0] = new PokerSeatBase(0, "button", 100);
205
+ seats[0].inHand = true;
206
+ seats[1] = new PokerSeatBase(1, "sb", 100);
207
+ seats[1].inHand = true;
208
+ seats[2] = new PokerSeatBase(2, "bb", 100);
209
+ seats[2].inHand = true;
210
+
211
+ // Postflop: action starts after button (button + 1)
212
+ const nextSeat = getNextActingSeat(seats, 0, false, bettingState);
213
+ expect(nextSeat).equal(1); // SB (button + 1)
214
+ });
215
+
216
+ test("should skip folded players", () => {
217
+ const bettingState = new BettingRoundState();
218
+ bettingState.currentBetToMatch = 20;
219
+
220
+ const seats = new Array<PokerSeatBase>(3);
221
+ seats[0] = new PokerSeatBase(0, "button", 100);
222
+ seats[0].inHand = true;
223
+ seats[1] = new PokerSeatBase(1, "sb", 100);
224
+ seats[1].inHand = true;
225
+ seats[2] = new PokerSeatBase(2, "bb", 100);
226
+ seats[2].inHand = false; // Folded
227
+
228
+ const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
229
+ // Should wrap around to button since BB is folded
230
+ expect(nextSeat >= 0).equal(true);
231
+ });
232
+
233
+ test("should skip all-in players", () => {
234
+ const bettingState = new BettingRoundState();
235
+ bettingState.currentBetToMatch = 20;
236
+
237
+ const seats = new Array<PokerSeatBase>(3);
238
+ seats[0] = new PokerSeatBase(0, "button", 100);
239
+ seats[0].inHand = true;
240
+ seats[1] = new PokerSeatBase(1, "sb", 100);
241
+ seats[1].inHand = true;
242
+ seats[1].allIn = true; // All-in
243
+ seats[2] = new PokerSeatBase(2, "bb", 100);
244
+ seats[2].inHand = true;
245
+
246
+ const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
247
+ // Should skip all-in SB (seat 1)
248
+ // Preflop: button (0) + 3 = 3, but only 3 seats (0,1,2), so wraps to 0
249
+ // Then skips 0 (button), 1 (all-in), finds 2 (BB)
250
+ expect(nextSeat >= 0).equal(true);
251
+ expect(nextSeat !== 1).equal(true); // Should not be all-in SB
252
+ });
253
+ });
254
+
255
+ // ============================================================================
256
+ // validateBuyIn Tests
257
+ // ============================================================================
258
+
259
+ describe("validateBuyIn", () => {
260
+ test("should validate buy-in within range", () => {
261
+ expect(validateBuyIn(100, 50, 200)).equal(true);
262
+ expect(validateBuyIn(50, 50, 200)).equal(true); // Min
263
+ expect(validateBuyIn(200, 50, 200)).equal(true); // Max
264
+ });
265
+
266
+ test("should reject buy-in below minimum", () => {
267
+ expect(validateBuyIn(49, 50, 200)).equal(false);
268
+ });
269
+
270
+ test("should reject buy-in above maximum", () => {
271
+ expect(validateBuyIn(201, 50, 200)).equal(false);
272
+ });
273
+ });
274
+
275
+ // ============================================================================
276
+ // processBuyIn Tests
277
+ // ============================================================================
278
+
279
+ describe("processBuyIn", () => {
280
+ test("should process valid buy-in", () => {
281
+ const seat = new PokerSeatBase(0, "player1", 0);
282
+ const updatedSeat = processBuyIn(seat, 100, 50, 200);
283
+
284
+ expect(updatedSeat !== null).equal(true);
285
+ if (updatedSeat !== null) {
286
+ expect(updatedSeat.stack).equal(100);
287
+ }
288
+ });
289
+
290
+ test("should return null for invalid buy-in", () => {
291
+ const seat = new PokerSeatBase(0, "player1", 0);
292
+ const updatedSeat = processBuyIn(seat, 30, 50, 200);
293
+
294
+ expect(updatedSeat === null).equal(true);
295
+ });
296
+
297
+ test("should add to existing stack", () => {
298
+ const seat = new PokerSeatBase(0, "player1", 50);
299
+ const updatedSeat = processBuyIn(seat, 100, 50, 200);
300
+
301
+ expect(updatedSeat !== null).equal(true);
302
+ if (updatedSeat !== null) {
303
+ expect(updatedSeat.stack).equal(150); // 50 + 100
304
+ }
305
+ });
306
+ });
307
+
308
+ // ============================================================================
309
+ // isBettingRoundComplete Tests
310
+ // ============================================================================
311
+
312
+ describe("isBettingRoundComplete", () => {
313
+ test("should return true when all players have matched bet", () => {
314
+ const bettingState = new BettingRoundState();
315
+ bettingState.currentBetToMatch = 20;
316
+
317
+ const seats = new Array<PokerSeatBase>(2);
318
+ seats[0] = new PokerSeatBase(0, "player1", 100);
319
+ seats[0].inHand = true;
320
+ seats[0].hasActedThisRound = true;
321
+ seats[1] = new PokerSeatBase(1, "player2", 100);
322
+ seats[1].inHand = true;
323
+ seats[1].hasActedThisRound = true;
324
+
325
+ bettingState.contribThisRound.set(0, 20);
326
+ bettingState.contribThisRound.set(1, 20);
327
+
328
+ expect(isBettingRoundComplete(seats, bettingState)).equal(true);
329
+ });
330
+
331
+ test("should return false when player hasn't matched bet", () => {
332
+ const bettingState = new BettingRoundState();
333
+ bettingState.currentBetToMatch = 20;
334
+
335
+ const seats = new Array<PokerSeatBase>(2);
336
+ seats[0] = new PokerSeatBase(0, "player1", 100);
337
+ seats[0].inHand = true;
338
+ seats[0].hasActedThisRound = true;
339
+ seats[1] = new PokerSeatBase(1, "player2", 100);
340
+ seats[1].inHand = true;
341
+ seats[1].hasActedThisRound = true;
342
+
343
+ bettingState.contribThisRound.set(0, 20);
344
+ bettingState.contribThisRound.set(1, 10); // Hasn't matched
345
+
346
+ expect(isBettingRoundComplete(seats, bettingState)).equal(false);
347
+ });
348
+
349
+ test("should return false when player hasn't acted", () => {
350
+ const bettingState = new BettingRoundState();
351
+ bettingState.currentBetToMatch = 20;
352
+
353
+ const seats = new Array<PokerSeatBase>(2);
354
+ seats[0] = new PokerSeatBase(0, "player1", 100);
355
+ seats[0].inHand = true;
356
+ seats[0].hasActedThisRound = true;
357
+ seats[1] = new PokerSeatBase(1, "player2", 100);
358
+ seats[1].inHand = true;
359
+ seats[1].hasActedThisRound = false; // Hasn't acted
360
+
361
+ bettingState.contribThisRound.set(0, 20);
362
+ bettingState.contribThisRound.set(1, 20);
363
+
364
+ expect(isBettingRoundComplete(seats, bettingState)).equal(false);
365
+ });
366
+
367
+ test("should return true with only one active player", () => {
368
+ const bettingState = new BettingRoundState();
369
+ bettingState.currentBetToMatch = 20;
370
+
371
+ const seats = new Array<PokerSeatBase>(2);
372
+ seats[0] = new PokerSeatBase(0, "player1", 100);
373
+ seats[0].inHand = true;
374
+ seats[0].hasActedThisRound = true; // Has acted
375
+ seats[1] = new PokerSeatBase(1, "player2", 100);
376
+ seats[1].inHand = false; // Folded
377
+
378
+ // Player 1 has matched (or doesn't need to match if no bet)
379
+ bettingState.contribThisRound.set(0, 20);
380
+
381
+ expect(isBettingRoundComplete(seats, bettingState)).equal(true);
382
+ });
383
+ });
384
+