@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,190 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for Stud Poker showdown evaluator
4
+ *
5
+ * Supports both 5-card and 7-card stud variants
6
+ * In stud poker, there are no community cards - each player has their own complete hand
7
+ */
8
+
9
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
10
+ import { Card, Suit, Rank, HandType } from "../../cards";
11
+ import { StudShowdownEvaluator } from "../../poker/stud_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("StudShowdownEvaluator", () => {
19
+ describe("5-Card Stud", () => {
20
+ test("should evaluate 5-card hands directly", () => {
21
+ const evaluator = new StudShowdownEvaluator();
22
+
23
+ // Player has 5 cards: A♠ K♠ Q♠ J♠ 10♠ (Royal Flush)
24
+ const fiveCards = new Array<Card>(5);
25
+ fiveCards[0] = createCard(Rank.ACE, Suit.SPADES);
26
+ fiveCards[1] = createCard(Rank.KING, Suit.SPADES);
27
+ fiveCards[2] = createCard(Rank.QUEEN, Suit.SPADES);
28
+ fiveCards[3] = createCard(Rank.JACK, Suit.SPADES);
29
+ fiveCards[4] = createCard(Rank.TEN, Suit.SPADES);
30
+
31
+ const rank = evaluator.evaluateHand(fiveCards, new Array<Card>(0));
32
+
33
+ expect(rank.handType).equal(HandType.ROYAL_FLUSH);
34
+ });
35
+
36
+ test("should use all 5 cards for best hand", () => {
37
+ const evaluator = new StudShowdownEvaluator();
38
+
39
+ const fiveCards = new Array<Card>(5);
40
+ fiveCards[0] = createCard(Rank.ACE, Suit.SPADES);
41
+ fiveCards[1] = createCard(Rank.ACE, Suit.HEARTS);
42
+ fiveCards[2] = createCard(Rank.KING, Suit.SPADES);
43
+ fiveCards[3] = createCard(Rank.QUEEN, Suit.SPADES);
44
+ fiveCards[4] = createCard(Rank.JACK, Suit.SPADES);
45
+
46
+ const bestHand = evaluator.getBestFiveCards(fiveCards, new Array<Card>(0));
47
+
48
+ expect(bestHand.length).equal(5);
49
+ // Should return all 5 cards
50
+ });
51
+
52
+ test("should compare multiple 5-card stud hands", () => {
53
+ const evaluator = new StudShowdownEvaluator();
54
+
55
+ // Player 0: Pair of Aces
56
+ const hand0 = new Array<Card>(5);
57
+ hand0[0] = createCard(Rank.ACE, Suit.SPADES);
58
+ hand0[1] = createCard(Rank.ACE, Suit.HEARTS);
59
+ hand0[2] = createCard(Rank.KING, Suit.SPADES);
60
+ hand0[3] = createCard(Rank.QUEEN, Suit.SPADES);
61
+ hand0[4] = createCard(Rank.JACK, Suit.SPADES);
62
+
63
+ // Player 1: Two Pair
64
+ const hand1 = new Array<Card>(5);
65
+ hand1[0] = createCard(Rank.ACE, Suit.DIAMONDS);
66
+ hand1[1] = createCard(Rank.ACE, Suit.CLUBS);
67
+ hand1[2] = createCard(Rank.KING, Suit.HEARTS);
68
+ hand1[3] = createCard(Rank.KING, Suit.DIAMONDS);
69
+ hand1[4] = createCard(Rank.QUEEN, Suit.HEARTS);
70
+
71
+ const holeCardsMap = new Map<i32, Card[]>();
72
+ holeCardsMap.set(0, hand0);
73
+ holeCardsMap.set(1, hand1);
74
+
75
+ const result = evaluator.compareHandsShowdown(holeCardsMap, new Array<Card>(0));
76
+
77
+ // Player 1 should win with Two Pair
78
+ expect(result.winners.length).equal(1);
79
+ expect(result.winners[0]).equal(1);
80
+ });
81
+ });
82
+
83
+ describe("7-Card Stud", () => {
84
+ test("should choose best 5 from 7 cards", () => {
85
+ const evaluator = new StudShowdownEvaluator();
86
+
87
+ // 7 cards: A♠ K♠ Q♠ J♠ 10♠ 9♠ 8♠
88
+ // Best 5: A♠ K♠ Q♠ J♠ 10♠ (Royal Flush)
89
+ const sevenCards = new Array<Card>(7);
90
+ sevenCards[0] = createCard(Rank.ACE, Suit.SPADES);
91
+ sevenCards[1] = createCard(Rank.KING, Suit.SPADES);
92
+ sevenCards[2] = createCard(Rank.QUEEN, Suit.SPADES);
93
+ sevenCards[3] = createCard(Rank.JACK, Suit.SPADES);
94
+ sevenCards[4] = createCard(Rank.TEN, Suit.SPADES);
95
+ sevenCards[5] = createCard(Rank.NINE, Suit.SPADES);
96
+ sevenCards[6] = createCard(Rank.EIGHT, Suit.SPADES);
97
+
98
+ const rank = evaluator.evaluateHand(sevenCards, new Array<Card>(0));
99
+
100
+ expect(rank.handType).equal(HandType.ROYAL_FLUSH);
101
+ });
102
+
103
+ test("should return best 5 cards from 7", () => {
104
+ const evaluator = new StudShowdownEvaluator();
105
+
106
+ // 7 cards with best 5 being a flush
107
+ const sevenCards = new Array<Card>(7);
108
+ sevenCards[0] = createCard(Rank.ACE, Suit.SPADES);
109
+ sevenCards[1] = createCard(Rank.KING, Suit.SPADES);
110
+ sevenCards[2] = createCard(Rank.QUEEN, Suit.SPADES);
111
+ sevenCards[3] = createCard(Rank.JACK, Suit.SPADES);
112
+ sevenCards[4] = createCard(Rank.TEN, Suit.SPADES);
113
+ sevenCards[5] = createCard(Rank.NINE, Suit.HEARTS); // Different suit
114
+ sevenCards[6] = createCard(Rank.EIGHT, Suit.HEARTS); // Different suit
115
+
116
+ const bestHand = evaluator.getBestFiveCards(sevenCards, new Array<Card>(0));
117
+
118
+ expect(bestHand.length).equal(5);
119
+ // Should be the 5 spades (flush)
120
+ });
121
+
122
+ test("should compare multiple 7-card stud hands", () => {
123
+ const evaluator = new StudShowdownEvaluator();
124
+
125
+ // Player 0: 7 cards, best 5 is a flush (A♠ K♠ Q♠ J♠ 10♠)
126
+ const hand0 = new Array<Card>(7);
127
+ hand0[0] = createCard(Rank.ACE, Suit.SPADES);
128
+ hand0[1] = createCard(Rank.KING, Suit.SPADES);
129
+ hand0[2] = createCard(Rank.QUEEN, Suit.SPADES);
130
+ hand0[3] = createCard(Rank.JACK, Suit.SPADES);
131
+ hand0[4] = createCard(Rank.TEN, Suit.SPADES);
132
+ hand0[5] = createCard(Rank.NINE, Suit.HEARTS);
133
+ hand0[6] = createCard(Rank.EIGHT, Suit.HEARTS);
134
+
135
+ // Player 1: 7 cards, best 5 is a straight (A♥ K♥ Q♥ J♥ 10♥ - but this is also a flush!)
136
+ // Use a non-flush straight instead
137
+ const hand1 = new Array<Card>(7);
138
+ hand1[0] = createCard(Rank.ACE, Suit.HEARTS);
139
+ hand1[1] = createCard(Rank.KING, Suit.DIAMONDS);
140
+ hand1[2] = createCard(Rank.QUEEN, Suit.CLUBS);
141
+ hand1[3] = createCard(Rank.JACK, Suit.HEARTS);
142
+ hand1[4] = createCard(Rank.TEN, Suit.DIAMONDS);
143
+ hand1[5] = createCard(Rank.NINE, Suit.CLUBS);
144
+ hand1[6] = createCard(Rank.EIGHT, Suit.HEARTS);
145
+
146
+ const holeCardsMap = new Map<i32, Card[]>();
147
+ holeCardsMap.set(0, hand0);
148
+ holeCardsMap.set(1, hand1);
149
+
150
+ const result = evaluator.compareHandsShowdown(holeCardsMap, new Array<Card>(0));
151
+
152
+ // Player 0 should win with Flush (beats Straight)
153
+ expect(result.winners.length).equal(1);
154
+ expect(result.winners[0]).equal(0);
155
+ });
156
+
157
+ test("should handle ties in 7-card stud", () => {
158
+ const evaluator = new StudShowdownEvaluator();
159
+
160
+ // Both players have same best 5-card hand
161
+ const hand0 = new Array<Card>(7);
162
+ hand0[0] = createCard(Rank.ACE, Suit.SPADES);
163
+ hand0[1] = createCard(Rank.ACE, Suit.HEARTS);
164
+ hand0[2] = createCard(Rank.KING, Suit.SPADES);
165
+ hand0[3] = createCard(Rank.QUEEN, Suit.SPADES);
166
+ hand0[4] = createCard(Rank.JACK, Suit.SPADES);
167
+ hand0[5] = createCard(Rank.TEN, Suit.HEARTS);
168
+ hand0[6] = createCard(Rank.NINE, Suit.HEARTS);
169
+
170
+ const hand1 = new Array<Card>(7);
171
+ hand1[0] = createCard(Rank.ACE, Suit.DIAMONDS);
172
+ hand1[1] = createCard(Rank.ACE, Suit.CLUBS);
173
+ hand1[2] = createCard(Rank.KING, Suit.HEARTS);
174
+ hand1[3] = createCard(Rank.QUEEN, Suit.HEARTS);
175
+ hand1[4] = createCard(Rank.JACK, Suit.HEARTS);
176
+ hand1[5] = createCard(Rank.TEN, Suit.DIAMONDS);
177
+ hand1[6] = createCard(Rank.NINE, Suit.DIAMONDS);
178
+
179
+ const holeCardsMap = new Map<i32, Card[]>();
180
+ holeCardsMap.set(0, hand0);
181
+ holeCardsMap.set(1, hand1);
182
+
183
+ const result = evaluator.compareHandsShowdown(holeCardsMap, new Array<Card>(0));
184
+
185
+ // Both have pair of Aces with same kickers
186
+ expect(result.winners.length).equal(2);
187
+ });
188
+ });
189
+ });
190
+
@@ -0,0 +1,13 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Test entry point for poker utilities
4
+ */
5
+
6
+ import "./stakes.spec";
7
+ import "./pots.spec";
8
+ import "./betting_round.spec";
9
+ import "./showdown.spec";
10
+ import "./six_plus.spec";
11
+ import "./stud.spec";
12
+ import "./omaha.spec";
13
+
@@ -0,0 +1,11 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Test entry point for @cardgames library
4
+ */
5
+
6
+ import "./blackjack/actions/test";
7
+ import "./blackjack/rules.spec";
8
+ import "./deck/deck.spec";
9
+ import "./deck/shoe.spec";
10
+ import "./poker/test";
11
+
@@ -0,0 +1,191 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Blackjack Action Processing Utilities
4
+ *
5
+ * Provides reusable blackjack action processing logic that can be used
6
+ * by any blackjack implementation. This library handles the game logic
7
+ * while allowing implementations to manage their own state structure.
8
+ */
9
+
10
+ import { BlackjackRules } from "./rules";
11
+
12
+ /**
13
+ * Available actions result
14
+ */
15
+ export class AvailableActions {
16
+ canStand: bool = false;
17
+ canDouble: bool = false;
18
+ canSplit: bool = false;
19
+ canSurrender: bool = false;
20
+ }
21
+
22
+ /**
23
+ * Calculate available actions for a hand based on rules
24
+ */
25
+ export function calculateAvailableActions(
26
+ handCardsLength: i32,
27
+ handIsFromSplit: bool,
28
+ handIsSplitAces: bool,
29
+ handIsStanding: bool,
30
+ handIsBusted: bool,
31
+ gamePhase: string,
32
+ playerHandsCount: i32,
33
+ canSplit: bool, // Pre-calculated: whether cards can be split
34
+ rules: BlackjackRules
35
+ ): AvailableActions {
36
+ const result = new AvailableActions();
37
+
38
+ // Stand is always available if hand is not busted and not already standing
39
+ // (and we're in the playing phase)
40
+ if (gamePhase == "PLAYING" && !handIsStanding && !handIsBusted) {
41
+ result.canStand = true;
42
+
43
+ // Other actions are only available on first two cards
44
+ if (handCardsLength == 2 && !handIsSplitAces) {
45
+ result.canDouble = true;
46
+ if (handIsFromSplit && !rules.doubleAfterSplit) {
47
+ result.canDouble = false;
48
+ }
49
+ result.canSurrender = rules.surrenderAllowed && !handIsFromSplit;
50
+ if (canSplit && playerHandsCount < rules.maxSplitHands) {
51
+ result.canSplit = true;
52
+ }
53
+ }
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ /**
60
+ * Check if dealer should hit based on rules
61
+ */
62
+ export function shouldDealerHit(
63
+ handValue: i32,
64
+ dealerStandValue: i32,
65
+ hitOnSoft17: bool,
66
+ isSoftHand: bool
67
+ ): bool {
68
+ // Below stand value - must hit
69
+ if (handValue < dealerStandValue) {
70
+ return true;
71
+ }
72
+
73
+ // Above stand value - stand
74
+ if (handValue > dealerStandValue) {
75
+ return false;
76
+ }
77
+
78
+ // At stand value - check if soft 17
79
+ if (handValue == dealerStandValue && hitOnSoft17) {
80
+ return isSoftHand;
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * Validate action can be performed in current phase
88
+ */
89
+ export function validateActionPhase(gamePhase: string, action: string, requiredPhase: string): void {
90
+ if (gamePhase != requiredPhase) {
91
+ throw new Error(`Can only perform ${action} during ${requiredPhase} phase`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Validate hand exists and is active
97
+ */
98
+ export function validateActiveHand(currentHandIndex: i32, playerHandsLength: i32): void {
99
+ if (currentHandIndex < 0 || currentHandIndex >= playerHandsLength) {
100
+ throw new Error("No active hand found");
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Validate hand can be hit
106
+ */
107
+ export function validateCanHit(handIsStanding: bool, handIsBusted: bool, handIsSplitAces: bool): void {
108
+ if (handIsStanding || handIsBusted || handIsSplitAces) {
109
+ throw new Error("Cannot hit on this hand");
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Validate hand can be stood
115
+ */
116
+ export function validateCanStand(handIsStanding: bool, handIsBusted: bool): void {
117
+ if (handIsStanding) {
118
+ throw new Error("Hand is already standing");
119
+ }
120
+ if (handIsBusted) {
121
+ throw new Error("Cannot stand on busted hand");
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Validate hand can be doubled
127
+ */
128
+ export function validateCanDouble(
129
+ handCardsLength: i32,
130
+ handIsSplitAces: bool,
131
+ handIsFromSplit: bool,
132
+ rules: BlackjackRules
133
+ ): void {
134
+ if (handCardsLength != 2) {
135
+ throw new Error("Can only double on first two cards");
136
+ }
137
+
138
+ if (handIsSplitAces) {
139
+ throw new Error("Cannot double on split aces");
140
+ }
141
+
142
+ if (handIsFromSplit && !rules.doubleAfterSplit) {
143
+ throw new Error("Cannot double after split");
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Validate hand can be split
149
+ */
150
+ export function validateCanSplit(
151
+ handCardsLength: i32,
152
+ playerHandsCount: i32,
153
+ canSplit: bool, // Pre-calculated: whether cards can be split
154
+ rules: BlackjackRules
155
+ ): void {
156
+ if (handCardsLength != 2) {
157
+ throw new Error("Can only split on first two cards");
158
+ }
159
+
160
+ if (playerHandsCount >= rules.maxSplitHands) {
161
+ throw new Error("Maximum number of split hands reached");
162
+ }
163
+
164
+ if (!canSplit) {
165
+ throw new Error("Cards cannot be split");
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Validate hand can be surrendered
171
+ */
172
+ export function validateCanSurrender(
173
+ handCardsLength: i32,
174
+ handIsFromSplit: bool,
175
+ rules: BlackjackRules
176
+ ): void {
177
+ if (!rules.surrenderAllowed) {
178
+ throw new Error("Surrender is not allowed");
179
+ }
180
+
181
+ if (handCardsLength != 2 || handIsFromSplit) {
182
+ throw new Error("Can only surrender on first two cards of original hand");
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Check if insurance should be offered
188
+ */
189
+ export function shouldOfferInsurance(dealerUpCardRank: string, rules: BlackjackRules): bool {
190
+ return dealerUpCardRank == "A" && rules.insuranceOffered;
191
+ }