@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,255 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for pot management utilities
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import { Pot, PokerRakeConfig } from "../../poker/poker_game_types";
8
+ import {
9
+ constructSidePots,
10
+ calculatePokerRake,
11
+ calculateRakeSimple,
12
+ distributePot,
13
+ distributePotSimple,
14
+ splitPotForRuns,
15
+ splitAllPotsForRuns,
16
+ lockPots,
17
+ getTotalPotAmount
18
+ } from "../../poker/poker_game_utils";
19
+
20
+ // ============================================================================
21
+ // constructSidePots Tests
22
+ // ============================================================================
23
+
24
+ describe("constructSidePots", () => {
25
+ test("should create single pot when all contributions equal", () => {
26
+ const contributions = new Map<i32, i64>();
27
+ contributions.set(0, 100);
28
+ contributions.set(1, 100);
29
+ contributions.set(2, 100);
30
+
31
+ const pots = constructSidePots(contributions, 0);
32
+
33
+ expect(pots.length).equal(1);
34
+ expect(pots[0].amount).equal(300);
35
+ expect(pots[0].eligibleSeats.length).equal(3);
36
+ });
37
+
38
+ test("should create side pots for all-in scenarios", () => {
39
+ const contributions = new Map<i32, i64>();
40
+ contributions.set(0, 100);
41
+ contributions.set(1, 200); // All-in
42
+ contributions.set(2, 100);
43
+
44
+ const pots = constructSidePots(contributions, 0);
45
+
46
+ expect(pots.length).equal(2);
47
+ // First pot: all players contribute 100
48
+ expect(pots[0].amount).equal(300); // 3 players * 100
49
+ expect(pots[0].eligibleSeats.length).equal(3);
50
+ // Second pot: only player 1 contributes additional 100
51
+ expect(pots[1].amount).equal(100);
52
+ expect(pots[1].eligibleSeats.length).equal(1);
53
+ expect(pots[1].eligibleSeats[0]).equal(1);
54
+ });
55
+
56
+ test("should handle multiple all-in levels", () => {
57
+ const contributions = new Map<i32, i64>();
58
+ contributions.set(0, 50);
59
+ contributions.set(1, 100);
60
+ contributions.set(2, 200);
61
+
62
+ const pots = constructSidePots(contributions, 0);
63
+
64
+ expect(pots.length).equal(3);
65
+ // Pot 1: all contribute 50
66
+ expect(pots[0].amount).equal(150);
67
+ expect(pots[0].eligibleSeats.length).equal(3);
68
+ // Pot 2: players 1 and 2 contribute additional 50
69
+ expect(pots[1].amount).equal(100);
70
+ expect(pots[1].eligibleSeats.length).equal(2);
71
+ // Pot 3: only player 2 contributes additional 100
72
+ expect(pots[2].amount).equal(100);
73
+ expect(pots[2].eligibleSeats.length).equal(1);
74
+ });
75
+
76
+ test("should return empty array for no contributions", () => {
77
+ const contributions = new Map<i32, i64>();
78
+ const pots = constructSidePots(contributions, 0);
79
+ expect(pots.length).equal(0);
80
+ });
81
+ });
82
+
83
+ // ============================================================================
84
+ // calculatePokerRake Tests
85
+ // ============================================================================
86
+
87
+ describe("calculatePokerRake", () => {
88
+ test("should calculate rake as percentage", () => {
89
+ const rakeConfig = new PokerRakeConfig(5.0, 0); // 5% rake, no cap
90
+ const rake = calculatePokerRake(100, rakeConfig);
91
+ expect(rake).equal(5); // 5% of 100
92
+ });
93
+
94
+ test("should apply rake cap", () => {
95
+ const rakeConfig = new PokerRakeConfig(10.0, 5); // 10% rake, cap at 5
96
+ const rake = calculatePokerRake(100, rakeConfig);
97
+ expect(rake).equal(5); // Capped at 5, not 10
98
+ });
99
+
100
+ test("should return 0 for zero percentage", () => {
101
+ const rakeConfig = new PokerRakeConfig(0.0, 0);
102
+ const rake = calculatePokerRake(100, rakeConfig);
103
+ expect(rake).equal(0);
104
+ });
105
+
106
+ test("should handle fractional rake", () => {
107
+ const rakeConfig = new PokerRakeConfig(2.5, 0); // 2.5% rake
108
+ const rake = calculatePokerRake(100, rakeConfig);
109
+ expect(rake).equal(2); // Rounded down
110
+ });
111
+ });
112
+
113
+ // ============================================================================
114
+ // calculateRakeSimple Tests
115
+ // ============================================================================
116
+
117
+ describe("calculateRakeSimple", () => {
118
+ test("should calculate rake with percentage and cap", () => {
119
+ const rake = calculateRakeSimple(100, 5.0, 10);
120
+ expect(rake).equal(5);
121
+ });
122
+
123
+ test("should apply cap", () => {
124
+ const rake = calculateRakeSimple(1000, 10.0, 50); // 10% = 100, capped at 50
125
+ expect(rake).equal(50);
126
+ });
127
+ });
128
+
129
+ // ============================================================================
130
+ // splitPotForRuns Tests
131
+ // ============================================================================
132
+
133
+ describe("splitPotForRuns", () => {
134
+ test("should split pot evenly for 2 runs", () => {
135
+ const pot = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(0));
136
+ const splitPot = splitPotForRuns(pot, 2);
137
+
138
+ expect(splitPot.runCountForPot).equal(2);
139
+ expect(splitPot.splitAmountsPerRun.length).equal(2);
140
+ expect(splitPot.splitAmountsPerRun[0] + splitPot.splitAmountsPerRun[1]).equal(100);
141
+ });
142
+
143
+ test("should handle remainder for odd splits", () => {
144
+ const pot = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(0));
145
+ const splitPot = splitPotForRuns(pot, 3);
146
+
147
+ expect(splitPot.runCountForPot).equal(3);
148
+ expect(splitPot.splitAmountsPerRun.length).equal(3);
149
+ // 100 / 3 = 33 remainder 1, so first run gets extra chip
150
+ const total = splitPot.splitAmountsPerRun[0] + splitPot.splitAmountsPerRun[1] + splitPot.splitAmountsPerRun[2];
151
+ expect(total).equal(100);
152
+ });
153
+
154
+ test("should not split for runCount <= 1", () => {
155
+ const pot = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(0));
156
+ const splitPot = splitPotForRuns(pot, 1);
157
+
158
+ expect(splitPot.runCountForPot).equal(1);
159
+ expect(splitPot.splitAmountsPerRun.length).equal(1);
160
+ expect(splitPot.splitAmountsPerRun[0]).equal(100);
161
+ });
162
+ });
163
+
164
+ // ============================================================================
165
+ // distributePot Tests
166
+ // ============================================================================
167
+
168
+ describe("distributePot", () => {
169
+ test("should distribute pot to single winner", () => {
170
+ const pot = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(1));
171
+ pot.splitAmountsPerRun[0] = 100;
172
+ const winners = new Array<i32>(1);
173
+ winners[0] = 0;
174
+ const rakeConfig = new PokerRakeConfig(5.0, 0);
175
+
176
+ const result = distributePot(pot, winners, 0, 0, rakeConfig);
177
+
178
+ expect(result.rake).equal(5); // 5% of 100
179
+ expect(result.payouts.has(0)).equal(true);
180
+ expect(result.payouts.get(0)).equal(95); // 100 - 5 rake
181
+ });
182
+
183
+ test("should distribute pot to multiple winners", () => {
184
+ const pot = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(1));
185
+ pot.splitAmountsPerRun[0] = 100;
186
+ const winners = new Array<i32>(2);
187
+ winners[0] = 0;
188
+ winners[1] = 1;
189
+ const rakeConfig = new PokerRakeConfig(0.0, 0); // No rake
190
+
191
+ const result = distributePot(pot, winners, 0, 0, rakeConfig);
192
+
193
+ expect(result.rake).equal(0);
194
+ expect(result.payouts.has(0)).equal(true);
195
+ expect(result.payouts.has(1)).equal(true);
196
+ // Each gets 50 (100 / 2)
197
+ expect(result.payouts.get(0) + result.payouts.get(1)).equal(100);
198
+ });
199
+
200
+ test("should handle odd chips", () => {
201
+ const pot = new Pot(0, 101, new Array<i32>(0), false, 1, new Array<i64>(1));
202
+ pot.splitAmountsPerRun[0] = 101;
203
+ const winners = new Array<i32>(2);
204
+ winners[0] = 0;
205
+ winners[1] = 1;
206
+ const rakeConfig = new PokerRakeConfig(0.0, 0);
207
+
208
+ const result = distributePot(pot, winners, 0, 0, rakeConfig);
209
+
210
+ // 101 / 2 = 50 remainder 1, first winner gets extra chip
211
+ const total = result.payouts.get(0) + result.payouts.get(1);
212
+ expect(total).equal(101);
213
+ });
214
+ });
215
+
216
+ // ============================================================================
217
+ // lockPots Tests
218
+ // ============================================================================
219
+
220
+ describe("lockPots", () => {
221
+ test("should lock all pots", () => {
222
+ const pots = new Array<Pot>(2);
223
+ pots[0] = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(0));
224
+ pots[1] = new Pot(1, 50, new Array<i32>(0), false, 1, new Array<i64>(0));
225
+
226
+ const lockedPots = lockPots(pots);
227
+
228
+ expect(lockedPots.length).equal(2);
229
+ expect(lockedPots[0].locked).equal(true);
230
+ expect(lockedPots[1].locked).equal(true);
231
+ });
232
+ });
233
+
234
+ // ============================================================================
235
+ // getTotalPotAmount Tests
236
+ // ============================================================================
237
+
238
+ describe("getTotalPotAmount", () => {
239
+ test("should sum all pot amounts", () => {
240
+ const pots = new Array<Pot>(3);
241
+ pots[0] = new Pot(0, 100, new Array<i32>(0), false, 1, new Array<i64>(0));
242
+ pots[1] = new Pot(1, 50, new Array<i32>(0), false, 1, new Array<i64>(0));
243
+ pots[2] = new Pot(2, 25, new Array<i32>(0), false, 1, new Array<i64>(0));
244
+
245
+ const total = getTotalPotAmount(pots);
246
+ expect(total).equal(175); // 100 + 50 + 25
247
+ });
248
+
249
+ test("should return 0 for empty array", () => {
250
+ const pots = new Array<Pot>(0);
251
+ const total = getTotalPotAmount(pots);
252
+ expect(total).equal(0);
253
+ });
254
+ });
255
+
@@ -0,0 +1,324 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for poker showdown utilities
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import { Card, Suit, Rank, HandType } from "../../cards";
8
+ import {
9
+ compareHandsShowdown,
10
+ compareFiveCardHands,
11
+ getPlayerHandRank,
12
+ getPlayerBestHand,
13
+ compareTwoHands
14
+ } from "../../poker/showdown";
15
+
16
+ // Helper function to create cards
17
+ function createCard(rank: string, suit: string): Card {
18
+ return new Card(suit, rank);
19
+ }
20
+
21
+ // ============================================================================
22
+ // compareHandsShowdown Tests
23
+ // ============================================================================
24
+
25
+ describe("compareHandsShowdown", () => {
26
+ test("should determine single winner with best hand", () => {
27
+ // Player 0: Royal Flush
28
+ const holeCards1 = new Array<Card>(2);
29
+ holeCards1[0] = createCard(Rank.ACE, Suit.SPADES);
30
+ holeCards1[1] = createCard(Rank.KING, Suit.SPADES);
31
+
32
+ // Player 1: Pair of 2s (using different suits to avoid flush/straight)
33
+ const holeCards2 = new Array<Card>(2);
34
+ holeCards2[0] = createCard(Rank.TWO, Suit.HEARTS);
35
+ holeCards2[1] = createCard(Rank.TWO, Suit.DIAMONDS);
36
+
37
+ // Community: Q♠ J♠ 10♠ 9♠ 8♠ (completes royal flush for player 0)
38
+ // For player 1, best hand is pair of 2s with Q, J, 10 kickers
39
+ const community = new Array<Card>(5);
40
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
41
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
42
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
43
+ community[3] = createCard(Rank.NINE, Suit.SPADES);
44
+ community[4] = createCard(Rank.EIGHT, Suit.SPADES);
45
+
46
+ const holeCardsMap = new Map<i32, Card[]>();
47
+ holeCardsMap.set(0, holeCards1);
48
+ holeCardsMap.set(1, holeCards2);
49
+
50
+ const result = compareHandsShowdown(holeCardsMap, community);
51
+
52
+ expect(result.winners.length).equal(1);
53
+ expect(result.winners[0]).equal(0); // Player 0 wins with royal flush
54
+ expect(result.handRanks.has(0)).equal(true);
55
+ expect(result.handRanks.has(1)).equal(true);
56
+ const rank0 = result.handRanks.get(0);
57
+ const rank1 = result.handRanks.get(1);
58
+ expect(rank0.handType).equal(HandType.ROYAL_FLUSH);
59
+ // Player 1's best 5-card hand from 2♥ 2♦ and Q♠ J♠ 10♠ 9♠ 8♠ is actually a straight flush (8♠ 9♠ 10♠ J♠ Q♠)
60
+ // So we need to change the test to expect STRAIGHT_FLUSH instead of PAIR
61
+ expect(rank1.handType).equal(HandType.STRAIGHT_FLUSH);
62
+ });
63
+
64
+ test("should handle ties", () => {
65
+ // Both players have same pair of Aces with same kickers
66
+ const holeCards1 = new Array<Card>(2);
67
+ holeCards1[0] = createCard(Rank.ACE, Suit.SPADES);
68
+ holeCards1[1] = createCard(Rank.ACE, Suit.HEARTS);
69
+
70
+ const holeCards2 = new Array<Card>(2);
71
+ holeCards2[0] = createCard(Rank.ACE, Suit.DIAMONDS);
72
+ holeCards2[1] = createCard(Rank.ACE, Suit.CLUBS);
73
+
74
+ // Community: K♥ Q♦ J♣ 10♥ 9♦ (mixed suits, no flush/straight possible)
75
+ const community = new Array<Card>(5);
76
+ community[0] = createCard(Rank.KING, Suit.HEARTS);
77
+ community[1] = createCard(Rank.QUEEN, Suit.DIAMONDS);
78
+ community[2] = createCard(Rank.JACK, Suit.CLUBS);
79
+ community[3] = createCard(Rank.TEN, Suit.HEARTS);
80
+ community[4] = createCard(Rank.NINE, Suit.DIAMONDS);
81
+
82
+ const holeCardsMap = new Map<i32, Card[]>();
83
+ holeCardsMap.set(0, holeCards1);
84
+ holeCardsMap.set(1, holeCards2);
85
+
86
+ const result = compareHandsShowdown(holeCardsMap, community);
87
+
88
+ // Both have same hand (pair of Aces with same kickers: K, Q, J)
89
+ expect(result.winners.length).equal(2);
90
+ if (result.winners.length >= 1) {
91
+ expect(result.winners[0] === 0 || result.winners[0] === 1).equal(true);
92
+ }
93
+ if (result.winners.length >= 2) {
94
+ expect(result.winners[1] === 0 || result.winners[1] === 1).equal(true);
95
+ }
96
+ });
97
+
98
+ test("should handle multiple players", () => {
99
+ // Player 0: Straight (5-6-7-8-9)
100
+ const holeCards1 = new Array<Card>(2);
101
+ holeCards1[0] = createCard(Rank.FIVE, Suit.HEARTS);
102
+ holeCards1[1] = createCard(Rank.SIX, Suit.HEARTS);
103
+
104
+ // Player 1: Flush (all spades)
105
+ const holeCards2 = new Array<Card>(2);
106
+ holeCards2[0] = createCard(Rank.TWO, Suit.SPADES);
107
+ holeCards2[1] = createCard(Rank.THREE, Suit.SPADES);
108
+
109
+ // Player 2: Pair of 2s
110
+ const holeCards3 = new Array<Card>(2);
111
+ holeCards3[0] = createCard(Rank.TWO, Suit.HEARTS);
112
+ holeCards3[1] = createCard(Rank.TWO, Suit.DIAMONDS);
113
+
114
+ // Community: 7♠ 8♠ 9♠ 10♠ 2♣
115
+ // Player 0: 5♥ 6♥ 7♠ 8♠ 9♠ = Straight (5-6-7-8-9)
116
+ // Player 1: 2♠ 3♠ 7♠ 8♠ 9♠ = Flush (all spades)
117
+ // Player 2: 2♥ 2♦ 7♠ 8♠ 9♠ 10♠ 2♣ = Pair of 2s
118
+ const community = new Array<Card>(5);
119
+ community[0] = createCard(Rank.SEVEN, Suit.SPADES);
120
+ community[1] = createCard(Rank.EIGHT, Suit.SPADES);
121
+ community[2] = createCard(Rank.NINE, Suit.SPADES);
122
+ community[3] = createCard(Rank.TEN, Suit.SPADES);
123
+ community[4] = createCard(Rank.TWO, Suit.CLUBS);
124
+
125
+ const holeCardsMap = new Map<i32, Card[]>();
126
+ holeCardsMap.set(0, holeCards1);
127
+ holeCardsMap.set(1, holeCards2);
128
+ holeCardsMap.set(2, holeCards3);
129
+
130
+ const result = compareHandsShowdown(holeCardsMap, community);
131
+
132
+ // Player 1 has flush (best), then player 0 has straight, then player 2 has pair
133
+ expect(result.winners.length).equal(1);
134
+ expect(result.winners[0]).equal(1); // Player 1 wins with flush
135
+ });
136
+
137
+ test("should return empty winners for no hands", () => {
138
+ const holeCardsMap = new Map<i32, Card[]>();
139
+ const community = new Array<Card>(5);
140
+
141
+ const result = compareHandsShowdown(holeCardsMap, community);
142
+
143
+ expect(result.winners.length).equal(0);
144
+ expect(result.handRanks.size).equal(0);
145
+ });
146
+ });
147
+
148
+ // ============================================================================
149
+ // compareFiveCardHands Tests
150
+ // ============================================================================
151
+
152
+ describe("compareFiveCardHands", () => {
153
+ test("should compare 5-card hands", () => {
154
+ // Player 1: Royal Flush
155
+ const hand1 = new Array<Card>(5);
156
+ hand1[0] = createCard(Rank.ACE, Suit.SPADES);
157
+ hand1[1] = createCard(Rank.KING, Suit.SPADES);
158
+ hand1[2] = createCard(Rank.QUEEN, Suit.SPADES);
159
+ hand1[3] = createCard(Rank.JACK, Suit.SPADES);
160
+ hand1[4] = createCard(Rank.TEN, Suit.SPADES);
161
+
162
+ // Player 2: Pair
163
+ const hand2 = new Array<Card>(5);
164
+ hand2[0] = createCard(Rank.ACE, Suit.HEARTS);
165
+ hand2[1] = createCard(Rank.ACE, Suit.DIAMONDS);
166
+ hand2[2] = createCard(Rank.KING, Suit.HEARTS);
167
+ hand2[3] = createCard(Rank.QUEEN, Suit.HEARTS);
168
+ hand2[4] = createCard(Rank.JACK, Suit.HEARTS);
169
+
170
+ const handsMap = new Map<i32, Card[]>();
171
+ handsMap.set(0, hand1);
172
+ handsMap.set(1, hand2);
173
+
174
+ const result = compareFiveCardHands(handsMap);
175
+
176
+ expect(result.winners.length).equal(1);
177
+ expect(result.winners[0]).equal(0); // Player 1 wins
178
+ });
179
+
180
+ test("should skip invalid hands (not 5 cards)", () => {
181
+ const hand1 = new Array<Card>(5);
182
+ hand1[0] = createCard(Rank.ACE, Suit.SPADES);
183
+ hand1[1] = createCard(Rank.KING, Suit.SPADES);
184
+ hand1[2] = createCard(Rank.QUEEN, Suit.SPADES);
185
+ hand1[3] = createCard(Rank.JACK, Suit.SPADES);
186
+ hand1[4] = createCard(Rank.TEN, Suit.SPADES);
187
+
188
+ const hand2 = new Array<Card>(2); // Invalid: only 2 cards
189
+ hand2[0] = createCard(Rank.ACE, Suit.HEARTS);
190
+ hand2[1] = createCard(Rank.KING, Suit.HEARTS);
191
+
192
+ const handsMap = new Map<i32, Card[]>();
193
+ handsMap.set(0, hand1);
194
+ handsMap.set(1, hand2);
195
+
196
+ const result = compareFiveCardHands(handsMap);
197
+
198
+ // Only player 1 should be evaluated
199
+ expect(result.winners.length).equal(1);
200
+ expect(result.winners[0]).equal(0);
201
+ expect(result.handRanks.has(1)).equal(false);
202
+ });
203
+ });
204
+
205
+ // ============================================================================
206
+ // getPlayerHandRank Tests
207
+ // ============================================================================
208
+
209
+ describe("getPlayerHandRank", () => {
210
+ test("should return hand rank for player", () => {
211
+ const holeCards = new Array<Card>(2);
212
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
213
+ holeCards[1] = createCard(Rank.ACE, Suit.HEARTS);
214
+
215
+ // Community cards that don't create flush/straight (mixed suits, no consecutive ranks)
216
+ const community = new Array<Card>(5);
217
+ community[0] = createCard(Rank.KING, Suit.HEARTS);
218
+ community[1] = createCard(Rank.QUEEN, Suit.DIAMONDS);
219
+ community[2] = createCard(Rank.JACK, Suit.CLUBS);
220
+ community[3] = createCard(Rank.SEVEN, Suit.HEARTS); // Changed from TEN to break straight
221
+ community[4] = createCard(Rank.THREE, Suit.DIAMONDS); // Changed from NINE to break straight
222
+
223
+ const rank = getPlayerHandRank(holeCards, community);
224
+
225
+ expect(rank.handType).equal(HandType.PAIR); // Pair of Aces
226
+ });
227
+ });
228
+
229
+ // ============================================================================
230
+ // getPlayerBestHand Tests
231
+ // ============================================================================
232
+
233
+ describe("getPlayerBestHand", () => {
234
+ test("should return best 5-card hand", () => {
235
+ const holeCards = new Array<Card>(2);
236
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
237
+ holeCards[1] = createCard(Rank.KING, Suit.SPADES);
238
+
239
+ const community = new Array<Card>(5);
240
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
241
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
242
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
243
+ community[3] = createCard(Rank.NINE, Suit.HEARTS);
244
+ community[4] = createCard(Rank.EIGHT, Suit.HEARTS);
245
+
246
+ const bestHand = getPlayerBestHand(holeCards, community);
247
+
248
+ expect(bestHand.length).equal(5);
249
+ // Should be the straight: A♠ K♠ Q♠ J♠ 10♠
250
+ });
251
+ });
252
+
253
+ // ============================================================================
254
+ // compareTwoHands Tests
255
+ // ============================================================================
256
+
257
+ describe("compareTwoHands", () => {
258
+ test("should return 1 when first hand wins", () => {
259
+ // Player 1: Royal Flush
260
+ const holeCards1 = new Array<Card>(2);
261
+ holeCards1[0] = createCard(Rank.ACE, Suit.SPADES);
262
+ holeCards1[1] = createCard(Rank.KING, Suit.SPADES);
263
+
264
+ // Player 2: Pair
265
+ const holeCards2 = new Array<Card>(2);
266
+ holeCards2[0] = createCard(Rank.TWO, Suit.HEARTS);
267
+ holeCards2[1] = createCard(Rank.TWO, Suit.DIAMONDS);
268
+
269
+ const community = new Array<Card>(5);
270
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
271
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
272
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
273
+ community[3] = createCard(Rank.NINE, Suit.SPADES);
274
+ community[4] = createCard(Rank.EIGHT, Suit.SPADES);
275
+
276
+ const result = compareTwoHands(holeCards1, holeCards2, community);
277
+ expect(result).equal(1); // Player 1 wins
278
+ });
279
+
280
+ test("should return -1 when second hand wins", () => {
281
+ // Player 1: Pair
282
+ const holeCards1 = new Array<Card>(2);
283
+ holeCards1[0] = createCard(Rank.TWO, Suit.HEARTS);
284
+ holeCards1[1] = createCard(Rank.TWO, Suit.DIAMONDS);
285
+
286
+ // Player 2: Royal Flush
287
+ const holeCards2 = new Array<Card>(2);
288
+ holeCards2[0] = createCard(Rank.ACE, Suit.SPADES);
289
+ holeCards2[1] = createCard(Rank.KING, Suit.SPADES);
290
+
291
+ const community = new Array<Card>(5);
292
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
293
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
294
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
295
+ community[3] = createCard(Rank.NINE, Suit.SPADES);
296
+ community[4] = createCard(Rank.EIGHT, Suit.SPADES);
297
+
298
+ const result = compareTwoHands(holeCards1, holeCards2, community);
299
+ expect(result).equal(-1); // Player 2 wins
300
+ });
301
+
302
+ test("should return 0 when hands tie", () => {
303
+ // Both players have same pair of Aces with same kickers
304
+ const holeCards1 = new Array<Card>(2);
305
+ holeCards1[0] = createCard(Rank.ACE, Suit.SPADES);
306
+ holeCards1[1] = createCard(Rank.ACE, Suit.HEARTS);
307
+
308
+ const holeCards2 = new Array<Card>(2);
309
+ holeCards2[0] = createCard(Rank.ACE, Suit.DIAMONDS);
310
+ holeCards2[1] = createCard(Rank.ACE, Suit.CLUBS);
311
+
312
+ // Community cards that don't create flush/straight
313
+ const community = new Array<Card>(5);
314
+ community[0] = createCard(Rank.KING, Suit.HEARTS);
315
+ community[1] = createCard(Rank.QUEEN, Suit.DIAMONDS);
316
+ community[2] = createCard(Rank.JACK, Suit.CLUBS);
317
+ community[3] = createCard(Rank.TEN, Suit.HEARTS);
318
+ community[4] = createCard(Rank.NINE, Suit.DIAMONDS);
319
+
320
+ const result = compareTwoHands(holeCards1, holeCards2, community);
321
+ expect(result).equal(0); // Tie - both have pair of Aces with same kickers
322
+ });
323
+ });
324
+