@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,410 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for multi-deck shoe functionality
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import {
8
+ ShoeConfig,
9
+ DeckConfig,
10
+ dealCardFromShoe,
11
+ CardIndexMapper
12
+ } from "../../deck/deck";
13
+ import { Card, Suit, Rank } from "../../cards";
14
+
15
+ // ============================================================================
16
+ // ShoeConfig Tests
17
+ // ============================================================================
18
+
19
+ describe("ShoeConfig", () => {
20
+ test("should create single deck shoe", () => {
21
+ const shoe = ShoeConfig.standard(1);
22
+ expect(shoe.numDecks).equal(1);
23
+ expect(shoe.deckConfig.totalCards).equal(52);
24
+ expect(shoe.getShoeSize()).equal(52);
25
+ });
26
+
27
+ test("should create 2-deck shoe", () => {
28
+ const shoe = ShoeConfig.standard(2);
29
+ expect(shoe.numDecks).equal(2);
30
+ expect(shoe.deckConfig.totalCards).equal(52);
31
+ expect(shoe.getShoeSize()).equal(104); // 2 * 52
32
+ });
33
+
34
+ test("should create 4-deck shoe", () => {
35
+ const shoe = ShoeConfig.standard(4);
36
+ expect(shoe.numDecks).equal(4);
37
+ expect(shoe.deckConfig.totalCards).equal(52);
38
+ expect(shoe.getShoeSize()).equal(208); // 4 * 52
39
+ });
40
+
41
+ test("should create 6-deck shoe", () => {
42
+ const shoe = ShoeConfig.standard(6);
43
+ expect(shoe.numDecks).equal(6);
44
+ expect(shoe.deckConfig.totalCards).equal(52);
45
+ expect(shoe.getShoeSize()).equal(312); // 6 * 52
46
+ });
47
+
48
+ test("should create 8-deck shoe", () => {
49
+ const shoe = ShoeConfig.standard(8);
50
+ expect(shoe.numDecks).equal(8);
51
+ expect(shoe.deckConfig.totalCards).equal(52);
52
+ expect(shoe.getShoeSize()).equal(416); // 8 * 52
53
+ });
54
+
55
+ test("should create custom number of decks", () => {
56
+ const shoe = ShoeConfig.standard(10);
57
+ expect(shoe.numDecks).equal(10);
58
+ expect(shoe.getShoeSize()).equal(520); // 10 * 52
59
+ });
60
+
61
+ test("should create Spanish 21 with any number of decks", () => {
62
+ const shoe1 = ShoeConfig.spanish21(1);
63
+ expect(shoe1.numDecks).equal(1);
64
+ expect(shoe1.getShoeSize()).equal(48); // 1 * 48
65
+
66
+ const shoe6 = ShoeConfig.spanish21(6);
67
+ expect(shoe6.numDecks).equal(6);
68
+ expect(shoe6.getShoeSize()).equal(288); // 6 * 48
69
+
70
+ const shoe12 = ShoeConfig.spanish21(12);
71
+ expect(shoe12.numDecks).equal(12);
72
+ expect(shoe12.getShoeSize()).equal(576); // 12 * 48
73
+ });
74
+
75
+ test("should create shoe with custom deck config using withDeck", () => {
76
+ const deckConfig = DeckConfig.standard();
77
+ const shoe = ShoeConfig.withDeck(deckConfig, 4);
78
+ expect(shoe.numDecks).equal(4);
79
+ expect(shoe.getShoeSize()).equal(208); // 4 * 52
80
+ });
81
+
82
+ test("should create Spanish 21 shoe with withDeck", () => {
83
+ const spanishDeck = DeckConfig.spanish21();
84
+ const shoe = ShoeConfig.withDeck(spanishDeck, 6);
85
+ expect(shoe.numDecks).equal(6);
86
+ expect(shoe.deckConfig.totalCards).equal(48);
87
+ expect(shoe.getShoeSize()).equal(288); // 6 * 48
88
+ });
89
+
90
+ test("should support backward compatibility with custom method", () => {
91
+ const deckConfig = DeckConfig.standard();
92
+ const shoe = ShoeConfig.custom(deckConfig, 4);
93
+ expect(shoe.numDecks).equal(4);
94
+ expect(shoe.getShoeSize()).equal(208); // 4 * 52
95
+ });
96
+
97
+ test("should create shoe using constructor directly", () => {
98
+ const deckConfig = DeckConfig.standard();
99
+ const shoe = new ShoeConfig(deckConfig, 3);
100
+ expect(shoe.numDecks).equal(3);
101
+ expect(shoe.getShoeSize()).equal(156); // 3 * 52
102
+ });
103
+
104
+ // Backward compatibility tests
105
+ test("should support deprecated convenience methods", () => {
106
+ const single = ShoeConfig.singleDeck();
107
+ expect(single.numDecks).equal(1);
108
+
109
+ const six = ShoeConfig.sixDeck();
110
+ expect(six.numDecks).equal(6);
111
+
112
+ const eight = ShoeConfig.eightDeck();
113
+ expect(eight.numDecks).equal(8);
114
+ });
115
+ });
116
+
117
+ // ============================================================================
118
+ // createUnshuffledShoeIndices Tests
119
+ // ============================================================================
120
+
121
+ describe("createUnshuffledShoeIndices", () => {
122
+ test("should create indices for single deck shoe", () => {
123
+ const shoe = ShoeConfig.singleDeck();
124
+ const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
125
+
126
+ expect(indices.length).equal(52);
127
+ // First card should be 2 of Spades (index 0)
128
+ expect(indices[0]).equal(0);
129
+ // Last card should be Ace of Clubs (index 51)
130
+ expect(indices[51]).equal(51);
131
+ });
132
+
133
+ test("should create indices for 6-deck shoe", () => {
134
+ const shoe = ShoeConfig.sixDeck();
135
+ const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
136
+
137
+ expect(indices.length).equal(312); // 6 * 52
138
+
139
+ // Each deck should have the same card order
140
+ // First deck: indices 0-51
141
+ expect(indices[0]).equal(0); // 2♠
142
+ expect(indices[51]).equal(51); // A♣
143
+
144
+ // Second deck: indices 52-103 (same order)
145
+ expect(indices[52]).equal(0); // 2♠
146
+ expect(indices[103]).equal(51); // A♣
147
+
148
+ // Third deck: indices 104-155
149
+ expect(indices[104]).equal(0); // 2♠
150
+ expect(indices[155]).equal(51); // A♣
151
+
152
+ // Sixth deck: indices 260-311
153
+ expect(indices[260]).equal(0); // 2♠
154
+ expect(indices[311]).equal(51); // A♣
155
+ });
156
+
157
+ test("should create indices for 8-deck shoe", () => {
158
+ const shoe = ShoeConfig.eightDeck();
159
+ const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
160
+
161
+ expect(indices.length).equal(416); // 8 * 52
162
+
163
+ // Verify each deck has same order
164
+ for (let deck = 0; deck < 8; deck++) {
165
+ const deckStart = deck * 52;
166
+ expect(indices[deckStart]).equal(0); // First card of each deck
167
+ expect(indices[deckStart + 51]).equal(51); // Last card of each deck
168
+ }
169
+ });
170
+
171
+ test("should create indices for Spanish 21 6-deck shoe", () => {
172
+ const shoe = ShoeConfig.spanish21SixDeck();
173
+ const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
174
+
175
+ expect(indices.length).equal(288); // 6 * 48
176
+
177
+ // Verify structure
178
+ for (let deck = 0; deck < 6; deck++) {
179
+ const deckStart = deck * 48;
180
+ expect(indices[deckStart]).equal(0); // First card of each deck
181
+ expect(indices[deckStart + 47]).equal(47); // Last card of each deck
182
+ }
183
+ });
184
+ });
185
+
186
+ // ============================================================================
187
+ // dealCardFromShoe Tests
188
+ // ============================================================================
189
+
190
+ describe("dealCardFromShoe", () => {
191
+ test("should deal cards from 6-deck shoe", () => {
192
+ const shoe = ShoeConfig.sixDeck();
193
+ const shuffleId = "test-shuffle-6deck";
194
+ const shuffleSalt = "test-salt";
195
+
196
+ // Deal first card
197
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
198
+ expect(card1.suit.length).greaterThan(0); // Verify card is valid
199
+
200
+ // Deal second card
201
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 1, shoe);
202
+ expect(card2.suit.length).greaterThan(0); // Verify card is valid
203
+
204
+ // Cards should be different (shuffled)
205
+ expect(card1.equals(card2)).equal(false);
206
+ });
207
+
208
+ test("should deal cards from 8-deck shoe", () => {
209
+ const shoe = ShoeConfig.eightDeck();
210
+ const shuffleId = "test-shuffle-8deck";
211
+ const shuffleSalt = "test-salt";
212
+
213
+ // Deal multiple cards
214
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
215
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 1, shoe);
216
+ const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 2, shoe);
217
+
218
+ expect(card1.suit.length).greaterThan(0);
219
+ expect(card2.suit.length).greaterThan(0);
220
+ expect(card3.suit.length).greaterThan(0);
221
+
222
+ // All should be different
223
+ expect(card1.equals(card2)).equal(false);
224
+ expect(card2.equals(card3)).equal(false);
225
+ });
226
+
227
+ test("should handle shoe exhaustion and reshuffle", () => {
228
+ const shoe = ShoeConfig.sixDeck();
229
+ const shuffleId = "test-shuffle-exhaust";
230
+ const shuffleSalt = "test-salt";
231
+ const shoeSize = shoe.getShoeSize(); // 312
232
+
233
+ // Deal last card of first shoe
234
+ const lastCard = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
235
+ expect(lastCard.suit.length).greaterThan(0);
236
+
237
+ // Deal first card of second shoe (reshuffled)
238
+ const firstCardNewShoe = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
239
+ expect(firstCardNewShoe.suit.length).greaterThan(0);
240
+
241
+ // The new shoe should be deterministically shuffled
242
+ // (same shuffleId/salt but different iteration)
243
+ const firstCardNewShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
244
+ expect(firstCardNewShoe.equals(firstCardNewShoe2)).equal(true);
245
+ });
246
+
247
+ test("should deal deterministically from same position", () => {
248
+ const shoe = ShoeConfig.sixDeck();
249
+ const shuffleId = "test-deterministic";
250
+ const shuffleSalt = "test-salt";
251
+
252
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 50, shoe);
253
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 50, shoe);
254
+
255
+ // Same position should yield same card
256
+ expect(card1.equals(card2)).equal(true);
257
+ });
258
+
259
+ test("should handle large shoe positions", () => {
260
+ const shoe = ShoeConfig.eightDeck();
261
+ const shuffleId = "test-large-position";
262
+ const shuffleSalt = "test-salt";
263
+
264
+ // Deal from middle of shoe
265
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 200, shoe);
266
+ expect(card1.suit.length).greaterThan(0);
267
+
268
+ // Deal from near end of shoe
269
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 410, shoe);
270
+ expect(card2.suit.length).greaterThan(0);
271
+
272
+ // Deal from second shoe iteration
273
+ const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 500, shoe);
274
+ expect(card3.suit.length).greaterThan(0);
275
+ });
276
+
277
+ test("should work with Spanish 21 6-deck shoe", () => {
278
+ const shoe = ShoeConfig.spanish21SixDeck();
279
+ const shuffleId = "test-spanish-6deck";
280
+ const shuffleSalt = "test-salt";
281
+
282
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
283
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 100, shoe);
284
+ const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 200, shoe);
285
+
286
+ expect(card1.suit.length).greaterThan(0);
287
+ expect(card2.suit.length).greaterThan(0);
288
+ expect(card3.suit.length).greaterThan(0);
289
+
290
+ // Verify no 10s in Spanish 21
291
+ expect(card1.rank == Rank.TEN).equal(false);
292
+ expect(card2.rank == Rank.TEN).equal(false);
293
+ expect(card3.rank == Rank.TEN).equal(false);
294
+ });
295
+
296
+ test("should handle multiple shoe iterations", () => {
297
+ const shoe = ShoeConfig.sixDeck();
298
+ const shuffleId = "test-iterations";
299
+ const shuffleSalt = "test-salt";
300
+ const shoeSize = shoe.getShoeSize(); // 312
301
+
302
+ // First shoe iteration
303
+ const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
304
+ const card2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
305
+
306
+ // Second shoe iteration (reshuffled)
307
+ const card3 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
308
+ const card4 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize * 2 - 1, shoe);
309
+
310
+ // Third shoe iteration
311
+ const card5 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize * 2, shoe);
312
+
313
+ expect(card1.suit.length).greaterThan(0);
314
+ expect(card2.suit.length).greaterThan(0);
315
+ expect(card3.suit.length).greaterThan(0);
316
+ expect(card4.suit.length).greaterThan(0);
317
+ expect(card5.suit.length).greaterThan(0);
318
+
319
+ // Each iteration should be deterministically shuffled
320
+ const card1Again = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
321
+ expect(card1.equals(card1Again)).equal(true);
322
+
323
+ const card3Again = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
324
+ expect(card3.equals(card3Again)).equal(true);
325
+ });
326
+
327
+ test("should track position correctly across multiple deals", () => {
328
+ const shoe = ShoeConfig.eightDeck();
329
+ const shuffleId = "test-position-tracking";
330
+ const shuffleSalt = "test-salt";
331
+
332
+ // Simulate dealing multiple cards in sequence
333
+ const cards = new Array<Card>(10);
334
+ for (let i = 0; i < 10; i++) {
335
+ cards[i] = dealCardFromShoe(shuffleId, shuffleSalt, i, shoe);
336
+ }
337
+
338
+ // All cards should be different (shuffled shoe)
339
+ for (let i = 0; i < 10; i++) {
340
+ for (let j = i + 1; j < 10; j++) {
341
+ // Cards might be the same by chance, but very unlikely in shuffled shoe
342
+ // We just verify they're valid cards
343
+ expect(cards[i].suit.length).greaterThan(0);
344
+ expect(cards[j].suit.length).greaterThan(0);
345
+ }
346
+ }
347
+ });
348
+ });
349
+
350
+ // ============================================================================
351
+ // Shoe Position Persistence Tests
352
+ // ============================================================================
353
+
354
+ describe("Shoe Position Persistence", () => {
355
+ test("should resume from saved position", () => {
356
+ const shoe = ShoeConfig.sixDeck();
357
+ const shuffleId = "test-resume";
358
+ const shuffleSalt = "test-salt";
359
+
360
+ // Deal 100 cards
361
+ const cardsBefore = new Array<Card>(100);
362
+ for (let i = 0; i < 100; i++) {
363
+ cardsBefore[i] = dealCardFromShoe(shuffleId, shuffleSalt, i, shoe);
364
+ }
365
+
366
+ // "Save" position at 100
367
+ const savedPosition = 100;
368
+
369
+ // "Resume" from saved position
370
+ const cardsAfter = new Array<Card>(10);
371
+ for (let i = 0; i < 10; i++) {
372
+ cardsAfter[i] = dealCardFromShoe(shuffleId, shuffleSalt, savedPosition + i, shoe);
373
+ }
374
+
375
+ // Cards after resume should match what we would have gotten
376
+ const expectedCards = new Array<Card>(10);
377
+ for (let i = 0; i < 10; i++) {
378
+ expectedCards[i] = dealCardFromShoe(shuffleId, shuffleSalt, 100 + i, shoe);
379
+ }
380
+
381
+ for (let i = 0; i < 10; i++) {
382
+ expect(cardsAfter[i].equals(expectedCards[i])).equal(true);
383
+ }
384
+ });
385
+
386
+ test("should handle position across shoe boundaries", () => {
387
+ const shoe = ShoeConfig.sixDeck();
388
+ const shuffleId = "test-boundary";
389
+ const shuffleSalt = "test-salt";
390
+ const shoeSize = shoe.getShoeSize(); // 312
391
+
392
+ // Deal last few cards of first shoe
393
+ const lastCardShoe1 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
394
+
395
+ // Deal first card of second shoe
396
+ const firstCardShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
397
+
398
+ // Deal second card of second shoe
399
+ const secondCardShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize + 1, shoe);
400
+
401
+ expect(lastCardShoe1.suit.length).greaterThan(0);
402
+ expect(firstCardShoe2.suit.length).greaterThan(0);
403
+ expect(secondCardShoe2.suit.length).greaterThan(0);
404
+
405
+ // All should be different
406
+ expect(lastCardShoe1.equals(firstCardShoe2)).equal(false);
407
+ expect(firstCardShoe2.equals(secondCardShoe2)).equal(false);
408
+ });
409
+ });
410
+
@@ -0,0 +1,103 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for BettingRoundState class
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import { BettingRoundState } from "../../poker/poker_game_types";
8
+
9
+ // ============================================================================
10
+ // BettingRoundState Tests
11
+ // ============================================================================
12
+
13
+ describe("BettingRoundState", () => {
14
+ test("should initialize with empty contributions", () => {
15
+ const state = new BettingRoundState();
16
+
17
+ expect(state.getContributionThisRound(0)).equal(0);
18
+ expect(state.getTotalContribution(0)).equal(0);
19
+ expect(state.currentBetToMatch).equal(0);
20
+ expect(state.actingSeatId).equal(-1);
21
+ });
22
+
23
+ test("should track contributions this round", () => {
24
+ const state = new BettingRoundState();
25
+
26
+ state.contribThisRound.set(0, 50);
27
+ state.contribThisRound.set(1, 100);
28
+
29
+ expect(state.getContributionThisRound(0)).equal(50);
30
+ expect(state.getContributionThisRound(1)).equal(100);
31
+ expect(state.getContributionThisRound(2)).equal(0); // Not set
32
+ });
33
+
34
+ test("should track total contributions", () => {
35
+ const state = new BettingRoundState();
36
+
37
+ state.contribTotal.set(0, 150);
38
+ state.contribTotal.set(1, 200);
39
+
40
+ expect(state.getTotalContribution(0)).equal(150);
41
+ expect(state.getTotalContribution(1)).equal(200);
42
+ });
43
+
44
+ test("should calculate amount to call", () => {
45
+ const state = new BettingRoundState();
46
+ state.currentBetToMatch = 100;
47
+ state.contribThisRound.set(0, 50);
48
+ state.contribThisRound.set(1, 100);
49
+
50
+ expect(state.calculateToCall(0)).equal(50); // 100 - 50
51
+ expect(state.calculateToCall(1)).equal(0); // Already matched
52
+ expect(state.calculateToCall(2)).equal(100); // Hasn't contributed
53
+ });
54
+
55
+ test("should reset round state", () => {
56
+ const state = new BettingRoundState();
57
+ state.currentBetToMatch = 100;
58
+ state.lastFullRaiseSize = 50;
59
+ state.lastAggressorSeatId = 2;
60
+ state.actingSeatId = 1;
61
+ state.contribThisRound.set(0, 50);
62
+ state.contribThisRound.set(1, 100);
63
+ // Set total contributions to verify they remain after reset
64
+ state.contribTotal.set(0, 50);
65
+ state.contribTotal.set(1, 100);
66
+
67
+ state.resetRound();
68
+
69
+ expect(state.currentBetToMatch).equal(0);
70
+ expect(state.lastFullRaiseSize).equal(0);
71
+ expect(state.lastAggressorSeatId).equal(-1);
72
+ expect(state.actingSeatId).equal(-1);
73
+ expect(state.getContributionThisRound(0)).equal(0);
74
+ expect(state.getContributionThisRound(1)).equal(0);
75
+ // Total contributions should remain (not cleared by resetRound)
76
+ expect(state.getTotalContribution(0)).equal(50);
77
+ expect(state.getTotalContribution(1)).equal(100);
78
+ });
79
+
80
+ test("should clone correctly", () => {
81
+ const state = new BettingRoundState();
82
+ state.currentBetToMatch = 100;
83
+ state.lastFullRaiseSize = 50;
84
+ state.lastAggressorSeatId = 2;
85
+ state.actingSeatId = 1;
86
+ state.contribThisRound.set(0, 50);
87
+ state.contribTotal.set(0, 150);
88
+
89
+ const cloned = state.clone();
90
+
91
+ expect(cloned.currentBetToMatch).equal(100);
92
+ expect(cloned.lastFullRaiseSize).equal(50);
93
+ expect(cloned.lastAggressorSeatId).equal(2);
94
+ expect(cloned.actingSeatId).equal(1);
95
+ expect(cloned.getContributionThisRound(0)).equal(50);
96
+ expect(cloned.getTotalContribution(0)).equal(150);
97
+
98
+ // Modifying clone shouldn't affect original
99
+ cloned.currentBetToMatch = 200;
100
+ expect(state.currentBetToMatch).equal(100);
101
+ });
102
+ });
103
+
@@ -0,0 +1,171 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for Omaha Poker showdown evaluator
4
+ *
5
+ * In Omaha, players receive 4 hole cards and must use exactly 2 of them
6
+ * combined with exactly 3 community cards to make their best 5-card hand.
7
+ */
8
+
9
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
10
+ import { Card, Suit, Rank, HandType } from "../../cards";
11
+ import { OmahaShowdownEvaluator } from "../../poker/omaha_evaluator";
12
+
13
+ // Helper function to create cards
14
+ function createCard(rank: string, suit: string): Card {
15
+ return new Card(suit, rank);
16
+ }
17
+
18
+ describe("OmahaShowdownEvaluator", () => {
19
+ test("should require exactly 2 hole cards and 3 community cards", () => {
20
+ const evaluator = new OmahaShowdownEvaluator();
21
+
22
+ // 4 hole cards
23
+ const holeCards = new Array<Card>(4);
24
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
25
+ holeCards[1] = createCard(Rank.ACE, Suit.HEARTS);
26
+ holeCards[2] = createCard(Rank.SEVEN, Suit.SPADES);
27
+ holeCards[3] = createCard(Rank.SIX, Suit.HEARTS);
28
+
29
+ // 5 community cards (mixed suits and non-consecutive ranks to avoid flush/straight)
30
+ const community = new Array<Card>(5);
31
+ community[0] = createCard(Rank.KING, Suit.DIAMONDS);
32
+ community[1] = createCard(Rank.QUEEN, Suit.CLUBS);
33
+ community[2] = createCard(Rank.JACK, Suit.HEARTS);
34
+ community[3] = createCard(Rank.NINE, Suit.DIAMONDS);
35
+ community[4] = createCard(Rank.EIGHT, Suit.CLUBS);
36
+
37
+ const rank = evaluator.evaluateHand(holeCards, community);
38
+
39
+ // Should evaluate using best combination of 2 hole + 3 community
40
+ // Best would be A♠ A♥ + K♦ Q♣ J♥ = Pair of Aces with K, Q, J kickers
41
+ expect(rank.handType).equal(HandType.PAIR);
42
+ });
43
+
44
+ test("should find best combination of 2 hole + 3 community", () => {
45
+ const evaluator = new OmahaShowdownEvaluator();
46
+
47
+ // Hole cards: A♠ K♠ Q♥ J♥
48
+ const holeCards = new Array<Card>(4);
49
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
50
+ holeCards[1] = createCard(Rank.KING, Suit.SPADES);
51
+ holeCards[2] = createCard(Rank.QUEEN, Suit.HEARTS);
52
+ holeCards[3] = createCard(Rank.JACK, Suit.HEARTS);
53
+
54
+ // Community: 10♠ 9♠ 8♠ 7♠ 6♠ (all spades)
55
+ const community = new Array<Card>(5);
56
+ community[0] = createCard(Rank.TEN, Suit.SPADES);
57
+ community[1] = createCard(Rank.NINE, Suit.SPADES);
58
+ community[2] = createCard(Rank.EIGHT, Suit.SPADES);
59
+ community[3] = createCard(Rank.SEVEN, Suit.SPADES);
60
+ community[4] = createCard(Rank.SIX, Suit.SPADES);
61
+
62
+ // Best combination: A♠ K♠ + 10♠ 9♠ 8♠ = Flush (A♠ K♠ 10♠ 9♠ 8♠)
63
+ const rank = evaluator.evaluateHand(holeCards, community);
64
+
65
+ expect(rank.handType).equal(HandType.FLUSH);
66
+ });
67
+
68
+ test("should return best 5 cards from 2 hole + 3 community", () => {
69
+ const evaluator = new OmahaShowdownEvaluator();
70
+
71
+ const holeCards = new Array<Card>(4);
72
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
73
+ holeCards[1] = createCard(Rank.KING, Suit.SPADES);
74
+ holeCards[2] = createCard(Rank.QUEEN, Suit.HEARTS);
75
+ holeCards[3] = createCard(Rank.JACK, Suit.HEARTS);
76
+
77
+ const community = new Array<Card>(5);
78
+ community[0] = createCard(Rank.TEN, Suit.SPADES);
79
+ community[1] = createCard(Rank.NINE, Suit.SPADES);
80
+ community[2] = createCard(Rank.EIGHT, Suit.SPADES);
81
+ community[3] = createCard(Rank.SEVEN, Suit.HEARTS);
82
+ community[4] = createCard(Rank.SIX, Suit.HEARTS);
83
+
84
+ const bestHand = evaluator.getBestFiveCards(holeCards, community);
85
+
86
+ expect(bestHand.length).equal(5);
87
+ // Should be A♠ K♠ 10♠ 9♠ 8♠ (flush) or A♠ K♠ Q♥ J♥ 10♠ (straight)
88
+ });
89
+
90
+ test("should compare multiple Omaha hands", () => {
91
+ const evaluator = new OmahaShowdownEvaluator();
92
+
93
+ // Player 0: 4 hole cards that can make a flush
94
+ const holeCards0 = new Array<Card>(4);
95
+ holeCards0[0] = createCard(Rank.ACE, Suit.SPADES);
96
+ holeCards0[1] = createCard(Rank.KING, Suit.SPADES);
97
+ holeCards0[2] = createCard(Rank.QUEEN, Suit.HEARTS);
98
+ holeCards0[3] = createCard(Rank.JACK, Suit.HEARTS);
99
+
100
+ // Player 1: 4 hole cards that can make a straight
101
+ const holeCards1 = new Array<Card>(4);
102
+ holeCards1[0] = createCard(Rank.ACE, Suit.HEARTS);
103
+ holeCards1[1] = createCard(Rank.KING, Suit.HEARTS);
104
+ holeCards1[2] = createCard(Rank.QUEEN, Suit.DIAMONDS);
105
+ holeCards1[3] = createCard(Rank.JACK, Suit.DIAMONDS);
106
+
107
+ // Community: 10♠ 9♠ 8♠ 7♠ 6♠
108
+ const community = new Array<Card>(5);
109
+ community[0] = createCard(Rank.TEN, Suit.SPADES);
110
+ community[1] = createCard(Rank.NINE, Suit.SPADES);
111
+ community[2] = createCard(Rank.EIGHT, Suit.SPADES);
112
+ community[3] = createCard(Rank.SEVEN, Suit.SPADES);
113
+ community[4] = createCard(Rank.SIX, Suit.SPADES);
114
+
115
+ const holeCardsMap = new Map<i32, Card[]>();
116
+ holeCardsMap.set(0, holeCards0);
117
+ holeCardsMap.set(1, holeCards1);
118
+
119
+ const result = evaluator.compareHandsShowdown(holeCardsMap, community);
120
+
121
+ // Player 0 should win with Flush (A♠ K♠ + 10♠ 9♠ 8♠)
122
+ expect(result.winners.length).equal(1);
123
+ expect(result.winners[0]).equal(0);
124
+ });
125
+
126
+ test("should handle full house in Omaha", () => {
127
+ const evaluator = new OmahaShowdownEvaluator();
128
+
129
+ // Hole cards: A♠ A♥ K♠ Q♠ (two Aces, one King, one Queen)
130
+ const holeCards = new Array<Card>(4);
131
+ holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
132
+ holeCards[1] = createCard(Rank.ACE, Suit.HEARTS);
133
+ holeCards[2] = createCard(Rank.KING, Suit.SPADES);
134
+ holeCards[3] = createCard(Rank.QUEEN, Suit.SPADES);
135
+
136
+ // Community: A♦ K♦ K♣ J♠ 7♠ (one Ace, two Kings, mixed suits to avoid flush/straight)
137
+ const community = new Array<Card>(5);
138
+ community[0] = createCard(Rank.ACE, Suit.DIAMONDS);
139
+ community[1] = createCard(Rank.KING, Suit.DIAMONDS);
140
+ community[2] = createCard(Rank.KING, Suit.CLUBS); // Second King for full house
141
+ community[3] = createCard(Rank.JACK, Suit.CLUBS);
142
+ community[4] = createCard(Rank.SEVEN, Suit.HEARTS); // Non-consecutive to break straight
143
+
144
+ // Best: A♠ A♥ (hole) + A♦ K♦ K♣ (community) = Full House (Aces over Kings)
145
+ const rank = evaluator.evaluateHand(holeCards, community);
146
+
147
+ expect(rank.handType).equal(HandType.FULL_HOUSE);
148
+ });
149
+
150
+ test("should handle invalid hand structure gracefully", () => {
151
+ const evaluator = new OmahaShowdownEvaluator();
152
+
153
+ // Wrong number of hole cards
154
+ const wrongHole = new Array<Card>(2);
155
+ wrongHole[0] = createCard(Rank.ACE, Suit.SPADES);
156
+ wrongHole[1] = createCard(Rank.KING, Suit.SPADES);
157
+
158
+ const community = new Array<Card>(5);
159
+ community[0] = createCard(Rank.QUEEN, Suit.SPADES);
160
+ community[1] = createCard(Rank.JACK, Suit.SPADES);
161
+ community[2] = createCard(Rank.TEN, Suit.SPADES);
162
+ community[3] = createCard(Rank.NINE, Suit.SPADES);
163
+ community[4] = createCard(Rank.EIGHT, Suit.SPADES);
164
+
165
+ const rank = evaluator.evaluateHand(wrongHole, community);
166
+
167
+ // Should return high card as fallback
168
+ expect(rank.handType).equal(HandType.HIGH_CARD);
169
+ });
170
+ });
171
+