@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
package/README.md ADDED
@@ -0,0 +1,722 @@
1
+ # @arcanahq/cardgames
2
+
3
+ Card game utilities and blackjack action processing library for Arcana contracts.
4
+
5
+ ## Overview
6
+
7
+ This library provides reusable card game logic that can be shared across different card game implementations. It builds on top of `@arcanahq/core` and provides:
8
+
9
+ - **Deck Management**: Deterministic shuffling and dealing for both standard (52-card) and Spanish 21 (48-card) decks
10
+ - **Card Index Mapping**: Efficient index-based card representation (0-51 for standard, 0-47 for Spanish 21)
11
+ - **Blackjack Rules Configuration**: Configurable rules for different blackjack variants (standard, Spanish 21, etc.)
12
+ - **Blackjack Action Processing**: Utility functions for validating and processing blackjack actions
13
+ - **Poker Game Utilities**: Hand evaluation, pot management, blinds/antes posting, betting rounds, rake calculation
14
+ - **Cash Game Utilities**: Buy-in validation, rake calculation, rebuy management
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @arcanahq/cardgames
20
+ ```
21
+
22
+ Or use as a local dependency:
23
+ ```json
24
+ {
25
+ "dependencies": {
26
+ "@arcanahq/cardgames": "file:../packages/cardgames"
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Structure
32
+
33
+ ```
34
+ @cardgames/
35
+ ├── assembly/
36
+ │ ├── blackjack/
37
+ │ │ ├── rules.ts # Blackjack rules configuration
38
+ │ │ └── actions.ts # Blackjack action processing utilities
39
+ │ ├── deck/
40
+ │ │ ├── deck.ts # Deck management (shuffling, dealing, index mapping)
41
+ │ │ └── index.ts # Deck module exports
42
+ │ ├── cards.ts # Card, Suit, Rank classes
43
+ │ ├── cardgames.ts # Card game utilities (deck creation, shuffling, dealing)
44
+ │ └── index.ts # Main entry point
45
+ └── package.json
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Deck Management
51
+
52
+ #### Single Deck (52 cards)
53
+
54
+ ```typescript
55
+ import { DeckConfig, dealCardByIndex, createShuffledDeck } from "@arcanahq/cardgames/assembly/deck";
56
+
57
+ const config = DeckConfig.standard();
58
+
59
+ // Deal a card deterministically
60
+ const card = dealCardByIndex("shuffle-id", "shuffle-salt", 0, config);
61
+
62
+ // Create full shuffled deck
63
+ const deck = createShuffledDeck("shuffle-id", "shuffle-salt", 0, config);
64
+ ```
65
+
66
+ #### Spanish 21 Deck (48 cards, no 10s)
67
+
68
+ ```typescript
69
+ const config = DeckConfig.spanish21();
70
+
71
+ // Deal a card from Spanish 21 deck
72
+ const card = dealCardByIndex("shuffle-id", "shuffle-salt", 0, config);
73
+ // card will never be a 10
74
+
75
+ // Create full shuffled Spanish 21 deck
76
+ const deck = createShuffledDeck("shuffle-id", "shuffle-salt", 0, config);
77
+ ```
78
+
79
+ #### Multi-Deck Shoes
80
+
81
+ ```typescript
82
+ import { ShoeConfig, dealCardFromShoe, DeckConfig } from "@arcanahq/cardgames/assembly/deck";
83
+
84
+ // Standard shoe with any number of decks
85
+ const shoe6 = ShoeConfig.standard(6); // 6-deck shoe (312 cards)
86
+ const shoe8 = ShoeConfig.standard(8); // 8-deck shoe (416 cards)
87
+ const shoeCustom = ShoeConfig.standard(10); // 10-deck shoe (520 cards)
88
+
89
+ // Spanish 21 shoe with any number of decks
90
+ const spanishShoe6 = ShoeConfig.spanish21(6); // 6-deck Spanish 21 (288 cards)
91
+ const spanishShoe8 = ShoeConfig.spanish21(8); // 8-deck Spanish 21 (384 cards)
92
+
93
+ // Create shoe with any custom deck configuration
94
+ const spanishDeck = DeckConfig.spanish21();
95
+ const customShoe = ShoeConfig.withDeck(spanishDeck, 6); // 6-deck Spanish 21
96
+
97
+ // Or with a custom deck config
98
+ // Use standard or Spanish 21, or create your own DeckConfig subclass
99
+ const customDeck = DeckConfig.standard(); // or DeckConfig.spanish21()
100
+ const shoe = ShoeConfig.withDeck(customDeck, 4); // 4-deck shoe
101
+
102
+ // Deal cards from shoe (tracks position, auto-reshuffles when exhausted)
103
+ let shoePosition = 0;
104
+ const card1 = dealCardFromShoe("shuffle-id", "shuffle-salt", shoePosition++, shoe6);
105
+ const card2 = dealCardFromShoe("shuffle-id", "shuffle-salt", shoePosition++, shoe6);
106
+ // Position persists across game sessions
107
+ ```
108
+
109
+ #### Card Index Mapping
110
+
111
+ ```typescript
112
+ import { CardIndexMapper } from "@arcanahq/cardgames/assembly/deck";
113
+
114
+ const config = DeckConfig.standard();
115
+
116
+ // Convert index to card
117
+ const card = CardIndexMapper.indexToCard(0, config); // 2 of Spades
118
+ const card2 = CardIndexMapper.indexToCard(51, config); // Ace of Clubs
119
+
120
+ // Convert card to index
121
+ const index = CardIndexMapper.cardToIndex(card, config); // 0
122
+
123
+ // Create unshuffled deck indices
124
+ const indices = CardIndexMapper.createUnshuffledDeckIndices(config);
125
+ // [0, 1, 2, ..., 51]
126
+ ```
127
+
128
+ #### Deterministic Shuffling
129
+
130
+ ```typescript
131
+ import { deterministicShuffleIndices, CardIndexMapper } from "@arcanahq/cardgames/assembly/deck";
132
+
133
+ const config = DeckConfig.standard();
134
+ const unshuffledIndices = CardIndexMapper.createUnshuffledDeckIndices(config);
135
+
136
+ // Shuffle deterministically
137
+ const shuffledIndices = deterministicShuffleIndices(
138
+ unshuffledIndices,
139
+ "shuffle-id",
140
+ "shuffle-salt",
141
+ 0 // seedIndex
142
+ );
143
+
144
+ // Same parameters = same shuffle
145
+ const shuffled2 = deterministicShuffleIndices(
146
+ unshuffledIndices,
147
+ "shuffle-id",
148
+ "shuffle-salt",
149
+ 0
150
+ );
151
+ // shuffledIndices === shuffled2
152
+ ```
153
+
154
+ ### Blackjack Rules
155
+
156
+ #### Standard Rules
157
+
158
+ ```typescript
159
+ import { BlackjackRules } from "@arcanahq/cardgames/assembly/blackjack/rules";
160
+
161
+ // Use standard rules (most common casino rules)
162
+ const rules = BlackjackRules.standard();
163
+ // - Dealer stands on 17
164
+ // - No hit on soft 17
165
+ // - No double after split
166
+ // - Surrender allowed
167
+ // - Insurance offered
168
+ ```
169
+
170
+ #### Spanish 21 Rules
171
+
172
+ ```typescript
173
+ const rules = BlackjackRules.spanish21();
174
+ // - 48-card deck (no 10s)
175
+ // - Dealer hits on soft 17
176
+ // - Double after split allowed
177
+ // - No insurance
178
+ ```
179
+
180
+ #### Custom Rules
181
+
182
+ ```typescript
183
+ const rules = new BlackjackRules(
184
+ 17, // dealerStandValue
185
+ false, // hitOnSoft17
186
+ 4, // maxSplitHands
187
+ false, // doubleAfterSplit
188
+ true, // surrenderAllowed
189
+ false, // lateSurrender
190
+ true, // insuranceOffered
191
+ false // isSpanish21
192
+ );
193
+
194
+ // Or use factory methods
195
+ const rules = BlackjackRules.dealerHitsSoft17();
196
+ const rules = BlackjackRules.allowDoubleAfterSplit();
197
+ ```
198
+
199
+ #### Custom Payouts
200
+
201
+ ```typescript
202
+ const rules = new BlackjackRules(
203
+ 17, false, 4, false, true, false, true, false,
204
+ 2.0, // payoutBlackjack (6:5 instead of 3:2)
205
+ 1.0, // payoutWin
206
+ 1.0, // payoutPush
207
+ 0.0, // payoutLose
208
+ 0.5, // payoutSurrender
209
+ 2.0 // payoutInsurance
210
+ );
211
+ ```
212
+
213
+ ### Blackjack Action Validation
214
+
215
+ #### Calculate Available Actions
216
+
217
+ ```typescript
218
+ import { calculateAvailableActions } from "@arcanahq/cardgames/assembly/blackjack/actions";
219
+
220
+ const actions = calculateAvailableActions(
221
+ hand.cards.length, // handCardsLength
222
+ hand.isFromSplit, // handIsFromSplit
223
+ hand.isSplitAces, // handIsSplitAces
224
+ hand.isStanding, // handIsStanding
225
+ hand.isBusted, // handIsBusted
226
+ state.gamePhase, // gamePhase
227
+ state.playerHands.length, // playerHandsCount
228
+ canSplitCards(hand.cards), // canSplit (pre-calculated)
229
+ rules
230
+ );
231
+
232
+ // Use actions
233
+ if (actions.canStand) { /* ... */ }
234
+ if (actions.canDouble) { /* ... */ }
235
+ if (actions.canSplit) { /* ... */ }
236
+ if (actions.canSurrender) { /* ... */ }
237
+ ```
238
+
239
+ #### Validate Actions
240
+
241
+ ```typescript
242
+ import {
243
+ validateCanDouble,
244
+ validateCanSplit,
245
+ validateCanSurrender,
246
+ validateCanHit,
247
+ validateCanStand,
248
+ validateActionPhase,
249
+ validateActiveHand
250
+ } from "@arcanahq/cardgames/assembly/blackjack/actions";
251
+
252
+ // Validate double action
253
+ validateCanDouble(
254
+ hand.cards.length,
255
+ hand.isSplitAces,
256
+ hand.isFromSplit,
257
+ rules
258
+ );
259
+
260
+ // Validate split action
261
+ validateCanSplit(
262
+ hand.cards.length,
263
+ state.playerHands.length,
264
+ canSplitCards(hand.cards),
265
+ rules
266
+ );
267
+
268
+ // Validate surrender action
269
+ validateCanSurrender(
270
+ hand.cards.length,
271
+ hand.isFromSplit,
272
+ rules
273
+ );
274
+
275
+ // Validate hit action
276
+ validateCanHit(
277
+ hand.isStanding,
278
+ hand.isBusted,
279
+ hand.isSplitAces
280
+ );
281
+
282
+ // Validate stand action
283
+ validateCanStand(
284
+ hand.isStanding,
285
+ hand.isBusted
286
+ );
287
+
288
+ // Validate game phase
289
+ validateActionPhase(state.gamePhase, "HIT", "PLAYING");
290
+
291
+ // Validate active hand
292
+ validateActiveHand(currentHandIndex, state.playerHands.length);
293
+ ```
294
+
295
+ ### Dealer Logic
296
+
297
+ ```typescript
298
+ import { shouldDealerHit, shouldOfferInsurance } from "@arcanahq/cardgames/assembly/blackjack/actions";
299
+
300
+ // Check if dealer should hit
301
+ const dealerShouldHit = shouldDealerHit(
302
+ dealerHandValue,
303
+ rules.dealerStandValue,
304
+ rules.hitOnSoft17,
305
+ isSoftHand(dealerCards)
306
+ );
307
+
308
+ // Check if insurance should be offered
309
+ const offerInsurance = shouldOfferInsurance(
310
+ dealerUpCard.rank,
311
+ rules
312
+ );
313
+ ```
314
+
315
+ ### Card Types
316
+
317
+ ```typescript
318
+ import { Card, Suit, Rank } from "@arcanahq/cardgames/assembly/cards";
319
+
320
+ // Create a card
321
+ const card = new Card(Suit.HEARTS, Rank.ACE);
322
+
323
+ // Use suit and rank constants
324
+ const suits = Suit.ALL; // [♠, ♥, ♦, ♣]
325
+ const ranks = Rank.ALL; // [2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A]
326
+ ```
327
+
328
+ ### Card Game Utilities
329
+
330
+ ```typescript
331
+ import {
332
+ createStandardDeck,
333
+ shuffleDeck,
334
+ dealCard,
335
+ dealCards,
336
+ cardToInt,
337
+ intToCard
338
+ } from "@arcanahq/cardgames/assembly/cardgames";
339
+
340
+ // Create a standard 52-card deck
341
+ const deck = createStandardDeck();
342
+
343
+ // Shuffle with deterministic randomness
344
+ const shuffled = shuffleDeck(deck, seed);
345
+
346
+ // Deal cards
347
+ const card = dealCard(deck);
348
+ const cards = dealCards(deck, 5);
349
+
350
+ // Convert between Card and integer (0-51)
351
+ const index = cardToInt(card);
352
+ const card2 = intToCard(index);
353
+ ```
354
+
355
+ ### Core Blackjack Functions
356
+
357
+ ```typescript
358
+ import {
359
+ dealerShouldHit,
360
+ isSoftHand,
361
+ calculateBlackjackHandValue,
362
+ isBlackjack,
363
+ isBusted,
364
+ canSplitCards
365
+ } from "@arcanahq/cardgames/assembly/blackjack/rules";
366
+
367
+ // Calculate hand value
368
+ const value = calculateBlackjackHandValue(cards);
369
+
370
+ // Check for blackjack
371
+ const hasBlackjack = isBlackjack(cards);
372
+
373
+ // Check for bust
374
+ const isBusted = isBusted(cards);
375
+
376
+ // Check if cards can be split
377
+ const canSplit = canSplitCards(cards);
378
+
379
+ // Check if hand is soft
380
+ const isSoft = isSoftHand(cards);
381
+
382
+ // Check if dealer should hit
383
+ const shouldHit = dealerShouldHit(cards, rules);
384
+ ```
385
+
386
+ ## Extensibility
387
+
388
+ ### Creating Custom Rule Variants
389
+
390
+ ```typescript
391
+ // Example: European Blackjack (no hole card)
392
+ class EuropeanBlackjackRules extends BlackjackRules {
393
+ static create(): BlackjackRules {
394
+ const rules = BlackjackRules.standard();
395
+ // Customize for European rules
396
+ return rules;
397
+ }
398
+ }
399
+ ```
400
+
401
+ ### Adding Custom Actions
402
+
403
+ ```typescript
404
+ // Extend AvailableActions for custom game variants
405
+ class CustomAvailableActions extends AvailableActions {
406
+ canInsurance: bool = false;
407
+ canEvenMoney: bool = false;
408
+ }
409
+ ```
410
+
411
+ ## Testing
412
+
413
+ The library includes comprehensive test coverage:
414
+
415
+ ```bash
416
+ npm test
417
+ ```
418
+
419
+ All core functions are tested with:
420
+ - Standard blackjack rules
421
+ - Spanish 21 rules
422
+ - Custom rule configurations
423
+ - Edge cases and error conditions
424
+
425
+ ## API Reference
426
+
427
+ ### BlackjackRules
428
+
429
+ Configurable blackjack rules class with factory methods:
430
+ - `BlackjackRules.standard()` - Standard casino rules
431
+ - `BlackjackRules.spanish21()` - Spanish 21 variant
432
+ - `BlackjackRules.dealerHitsSoft17()` - Dealer hits on soft 17
433
+ - `BlackjackRules.allowDoubleAfterSplit()` - Allow double after split
434
+
435
+ ### AvailableActions
436
+
437
+ Result class from `calculateAvailableActions()`:
438
+ - `canStand: bool` - Can player stand
439
+ - `canDouble: bool` - Can player double
440
+ - `canSplit: bool` - Can player split
441
+ - `canSurrender: bool` - Can player surrender
442
+
443
+ ### Validation Functions
444
+
445
+ All validation functions throw errors on invalid conditions:
446
+ - `validateActionPhase()` - Validates game phase
447
+ - `validateActiveHand()` - Validates hand exists
448
+ - `validateCanHit()` - Validates hit action
449
+ - `validateCanStand()` - Validates stand action
450
+ - `validateCanDouble()` - Validates double action
451
+ - `validateCanSplit()` - Validates split action
452
+ - `validateCanSurrender()` - Validates surrender action
453
+
454
+ ### Utility Functions
455
+
456
+ - `calculateAvailableActions()` - Calculate all available actions
457
+ - `shouldDealerHit()` - Determine if dealer should hit
458
+ - `shouldOfferInsurance()` - Determine if insurance should be offered
459
+
460
+ ## Dependencies
461
+
462
+ - `@arcanahq/core`: Core framework for Arcana contracts
463
+
464
+ ## Poker Game Utilities
465
+
466
+ The library includes comprehensive utilities for poker-style card games:
467
+
468
+ ### Stakes and Betting
469
+
470
+ ```typescript
471
+ import { Stakes, AnteType, BettingRoundState, PokerSeatBase } from "@arcanahq/cardgames/assembly/poker/poker_game_types";
472
+ import {
473
+ calculateAnteAmount,
474
+ postAntes,
475
+ postBlinds,
476
+ getNextActingSeat,
477
+ validateBuyIn,
478
+ processBuyIn,
479
+ isBettingRoundComplete
480
+ } from "@arcanahq/cardgames/assembly/poker/poker_game_utils";
481
+
482
+ // Create stakes configuration
483
+ const stakes = new Stakes(10, 20, 5); // SB: 10, BB: 20, Ante: 5
484
+
485
+ // Calculate ante amount
486
+ const anteAmount = calculateAnteAmount(stakes, AnteType.FIXED, 0.0);
487
+
488
+ // Post antes for all players
489
+ const bettingState = new BettingRoundState();
490
+ const antesResult = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
491
+ // antesResult.seats - updated seats with antes deducted
492
+ // antesResult.totalAnteCollected - total ante collected
493
+
494
+ // Post small blind and big blind
495
+ const blindsResult = postBlinds(seats, stakes, sbSeatId, bbSeatId, bettingState);
496
+ // blindsResult.seats - updated seats with blinds deducted
497
+ // blindsResult.currentBetToMatch - current bet to match (BB amount)
498
+
499
+ // Get next player to act
500
+ const nextSeatId = getNextActingSeat(seats, buttonSeatId, true, bettingState); // true = preflop
501
+
502
+ // Validate and process buy-in
503
+ if (validateBuyIn(buyInAmount, minBuyIn, maxBuyIn)) {
504
+ const updatedSeat = processBuyIn(seat, buyInAmount, minBuyIn, maxBuyIn);
505
+ }
506
+
507
+ // Check if betting round is complete
508
+ const isComplete = isBettingRoundComplete(seats, bettingState);
509
+ ```
510
+
511
+ ### Showdown Utilities
512
+
513
+ The showdown system uses an interface pattern, allowing different implementations for various poker variants.
514
+
515
+ #### Standard Hold'em (Default)
516
+
517
+ ```typescript
518
+ import {
519
+ compareHandsShowdown,
520
+ compareFiveCardHands,
521
+ getPlayerHandRank,
522
+ getPlayerBestHand,
523
+ compareTwoHands,
524
+ StandardShowdownEvaluator
525
+ } from "@arcanahq/cardgames/assembly/poker/showdown";
526
+ import { Card } from "@arcanahq/cardgames/assembly/cards";
527
+
528
+ // Compare multiple players' hands in a showdown
529
+ const holeCardsMap = new Map<i32, Card[]>();
530
+ holeCardsMap.set(0, player0HoleCards);
531
+ holeCardsMap.set(1, player1HoleCards);
532
+ holeCardsMap.set(2, player2HoleCards);
533
+
534
+ const communityCards = [flop, turn, river]; // 5 cards total
535
+
536
+ const result = compareHandsShowdown(holeCardsMap, communityCards);
537
+ // result.winners - array of seat IDs that tied for the win
538
+ // result.handRanks - map of seat ID to their HandRank
539
+ // result.bestFiveCards - map of seat ID to their best 5-card hand
540
+
541
+ // Compare exactly 5-card hands (for games like 5-card draw)
542
+ const fiveCardHands = new Map<i32, Card[]>();
543
+ fiveCardHands.set(0, player0FiveCards);
544
+ fiveCardHands.set(1, player1FiveCards);
545
+ const result = compareFiveCardHands(fiveCardHands);
546
+
547
+ // Get a single player's hand rank
548
+ const handRank = getPlayerHandRank(holeCards, communityCards);
549
+
550
+ // Get a single player's best 5-card hand
551
+ const bestHand = getPlayerBestHand(holeCards, communityCards);
552
+
553
+ // Compare two specific hands
554
+ const winner = compareTwoHands(holeCards1, holeCards2, communityCards);
555
+ // Returns: 1 if hand1 wins, -1 if hand2 wins, 0 if tie
556
+ ```
557
+
558
+ #### Six-Plus Hold'em
559
+
560
+ ```typescript
561
+ import { SixPlusShowdownEvaluator } from "@arcanahq/cardgames/assembly/poker/six_plus_showdown";
562
+ import { Card } from "@arcanahq/cardgames/assembly/cards";
563
+
564
+ // Create Six-Plus evaluator (uses 36-card deck, different hand rankings)
565
+ const evaluator = new SixPlusShowdownEvaluator();
566
+
567
+ // Use the evaluator for showdown
568
+ const result = evaluator.compareHandsShowdown(holeCardsMap, communityCards);
569
+ // result.winners - array of seat IDs that tied for the win
570
+
571
+ // Six-Plus hand rankings:
572
+ // - Flush beats Full House (opposite of standard)
573
+ // - Three of a Kind beats Straight (opposite of standard)
574
+ ```
575
+
576
+ #### Stud Poker (5-Card and 7-Card)
577
+
578
+ ```typescript
579
+ import { StudShowdownEvaluator } from "@arcanahq/cardgames/assembly/poker/stud_evaluator";
580
+ import { Card } from "@arcanahq/cardgames/assembly/cards";
581
+
582
+ const evaluator = new StudShowdownEvaluator();
583
+
584
+ // For 7-card stud: each player has 7 cards, choose best 5
585
+ const sevenCardHands = new Map<i32, Card[]>();
586
+ sevenCardHands.set(0, player0SevenCards); // 7 cards
587
+ sevenCardHands.set(1, player1SevenCards); // 7 cards
588
+
589
+ // No community cards for stud
590
+ const result = evaluator.compareHandsShowdown(sevenCardHands, new Array<Card>(0));
591
+
592
+ // For 5-card stud: each player has 5 cards, use all 5
593
+ const fiveCardHands = new Map<i32, Card[]>();
594
+ fiveCardHands.set(0, player0FiveCards); // 5 cards
595
+ fiveCardHands.set(1, player1FiveCards); // 5 cards
596
+
597
+ const result = evaluator.compareHandsShowdown(fiveCardHands, new Array<Card>(0));
598
+ ```
599
+
600
+ #### Omaha
601
+
602
+ ```typescript
603
+ import { OmahaShowdownEvaluator } from "@arcanahq/cardgames/assembly/poker/omaha_evaluator";
604
+ import { Card } from "@arcanahq/cardgames/assembly/cards";
605
+
606
+ const evaluator = new OmahaShowdownEvaluator();
607
+
608
+ // Omaha: 4 hole cards, must use exactly 2 + 3 community cards
609
+ const holeCardsMap = new Map<i32, Card[]>();
610
+ holeCardsMap.set(0, player0FourCards); // 4 cards
611
+ holeCardsMap.set(1, player1FourCards); // 4 cards
612
+
613
+ const communityCards = [flop, turn, river]; // 5 cards
614
+
615
+ const result = evaluator.compareHandsShowdown(holeCardsMap, communityCards);
616
+ // Automatically tries all combinations of 2 hole + 3 community cards
617
+ ```
618
+
619
+ #### Using Variant Configurations
620
+
621
+ ```typescript
622
+ import {
623
+ VariantConfigs,
624
+ PokerVariant,
625
+ getEvaluatorForVariant,
626
+ createEvaluator
627
+ } from "@arcanahq/cardgames/assembly/poker/variants";
628
+ import { ShowdownEvaluator } from "@arcanahq/cardgames/assembly/poker/showdown_evaluator";
629
+
630
+ // Get evaluator by variant name
631
+ const holdemEvaluator = getEvaluatorForVariant(PokerVariant.TEXAS_HOLDEM);
632
+ const studEvaluator = getEvaluatorForVariant(PokerVariant.SEVEN_CARD_STUD);
633
+ const omahaEvaluator = getEvaluatorForVariant(PokerVariant.OMAHA);
634
+ const sixPlusEvaluator = getEvaluatorForVariant(PokerVariant.SIX_PLUS_HOLDEM);
635
+
636
+ // Or use variant configuration
637
+ const config = VariantConfigs.sevenCardStud();
638
+ const evaluator = createEvaluator(config);
639
+
640
+ // Available configurations:
641
+ // - VariantConfigs.texasHoldem()
642
+ // - VariantConfigs.omaha()
643
+ // - VariantConfigs.omahaHiLo()
644
+ // - VariantConfigs.sevenCardStud()
645
+ // - VariantConfigs.fiveCardStud()
646
+ // - VariantConfigs.razz() // Lowball 7-card stud
647
+ // - VariantConfigs.sixPlusHoldem()
648
+ ```
649
+
650
+ #### Custom Evaluator
651
+
652
+ ```typescript
653
+ import { ShowdownEvaluator, ShowdownResult } from "@arcanahq/cardgames/assembly/poker/showdown_evaluator";
654
+ import { Card, HandRank } from "@arcanahq/cardgames/assembly/cards";
655
+
656
+ // Extend ShowdownEvaluator for custom poker variants
657
+ class CustomShowdownEvaluator extends ShowdownEvaluator {
658
+ evaluateHand(holeCards: Card[], communityCards: Card[]): HandRank {
659
+ // Custom evaluation logic
660
+ }
661
+
662
+ getBestFiveCards(holeCards: Card[], communityCards: Card[]): Card[] {
663
+ // Custom best hand selection
664
+ }
665
+
666
+ compareHands(hand1: HandRank, hand2: HandRank): i32 {
667
+ // Custom comparison logic
668
+ }
669
+ }
670
+
671
+ const customEvaluator = new CustomShowdownEvaluator();
672
+ const result = customEvaluator.compareHandsShowdown(holeCardsMap, communityCards);
673
+ ```
674
+
675
+ ### Pot Management
676
+
677
+ ```typescript
678
+ import {
679
+ constructSidePots,
680
+ calculatePokerRake,
681
+ distributePot,
682
+ splitPotForRuns,
683
+ lockPots
684
+ } from "@arcanahq/cardgames/assembly/poker/poker_game_utils";
685
+
686
+ // Construct side pots from player contributions
687
+ const contributions = new Map<i32, i64>();
688
+ contributions.set(seat1Id, 100);
689
+ contributions.set(seat2Id, 200); // All-in
690
+ const pots = constructSidePots(contributions, 0);
691
+
692
+ // Calculate rake
693
+ const rakeConfig = new PokerRakeConfig(5.0, 10); // 5% rake, cap at 10
694
+ const rake = calculatePokerRake(potAmount, rakeConfig);
695
+
696
+ // Distribute pot to winners
697
+ const distribution = distributePot(pot, winners, runIndex, buttonSeatId, rakeConfig);
698
+ // distribution.payouts - Map of seat ID to payout amount
699
+ // distribution.rake - Rake amount deducted
700
+ ```
701
+
702
+ ### Betting Round State
703
+
704
+ ```typescript
705
+ import { BettingRoundState } from "@arcanahq/cardgames/assembly/poker/poker_game_types";
706
+
707
+ const bettingState = new BettingRoundState();
708
+
709
+ // Track contributions
710
+ bettingState.contribThisRound.set(seatId, amount);
711
+ bettingState.contribTotal.set(seatId, totalAmount);
712
+
713
+ // Calculate amount to call
714
+ const toCall = bettingState.calculateToCall(seatId);
715
+
716
+ // Reset for next street
717
+ bettingState.resetRound();
718
+ ```
719
+
720
+ ## License
721
+
722
+ MIT