@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,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
|
+
|