@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,36 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { createAsTestImports } from "../core/tests/as-test-imports.js";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const FALLBACK_HEAP_BASE = 64 * 1024;
8
+ const HEAP_REGEX = /\(global \$~lib\/memory\/__heap_base i32 \(i32\.const (\d+)\)\)/;
9
+
10
+ function resolveHeapBase() {
11
+ const watPath = join(__dirname, ".as-test/output/test.wat");
12
+ if (existsSync(watPath)) {
13
+ try {
14
+ const contents = readFileSync(watPath, "utf8");
15
+ const match = contents.match(HEAP_REGEX);
16
+ if (match) {
17
+ return Number(match[1]);
18
+ }
19
+ } catch {
20
+ // fall through to fallback
21
+ }
22
+ }
23
+ return FALLBACK_HEAP_BASE;
24
+ }
25
+
26
+ export default {
27
+ include: ["assembly"],
28
+ flags: "--exportRuntime --bindings raw",
29
+ collectCoverage: false,
30
+ output: ".as-test/output",
31
+ temp: ".as-test/tmp",
32
+ imports() {
33
+ return createAsTestImports(__dirname, { outputDir: ".as-test/output" });
34
+ },
35
+ };
36
+
package/asconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "targets": {
3
+ "release": {
4
+ "binaryFile": "build/cardgames.wasm",
5
+ "textFile": "build/cardgames.wat",
6
+ "sourceMap": true,
7
+ "optimizeLevel": 3,
8
+ "shrinkLevel": 2,
9
+ "converge": false,
10
+ "noAssert": false,
11
+ "runtime": "stub",
12
+ "exportRuntime": true,
13
+ "exportStart": false,
14
+ "bindings": "esm",
15
+ "initialMemory": 10,
16
+ "maximumMemory": 256
17
+ }
18
+ },
19
+ "options": {
20
+ "bindings": "esm"
21
+ }
22
+ }
@@ -0,0 +1,180 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Common tests for blackjack action processing utilities
4
+ */
5
+
6
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
7
+ import {
8
+ AvailableActions,
9
+ calculateAvailableActions,
10
+ shouldDealerHit,
11
+ validateActionPhase,
12
+ validateActiveHand,
13
+ validateCanHit
14
+ } from "../../../blackjack/actions";
15
+ import { BlackjackRules } from "../../../blackjack/rules";
16
+
17
+ // ============================================================================
18
+ // AvailableActions Class Tests
19
+ // ============================================================================
20
+
21
+ describe("AvailableActions", () => {
22
+ test("should initialize with all actions as false", () => {
23
+ const actions = new AvailableActions();
24
+ expect(actions.canStand).equal(false);
25
+ expect(actions.canDouble).equal(false);
26
+ expect(actions.canSplit).equal(false);
27
+ expect(actions.canSurrender).equal(false);
28
+ });
29
+ });
30
+
31
+ // ============================================================================
32
+ // calculateAvailableActions - General Tests
33
+ // ============================================================================
34
+
35
+ describe("calculateAvailableActions - General", () => {
36
+ test("should not allow any actions when hand is standing", () => {
37
+ const standardRules = BlackjackRules.standard();
38
+ const actions = calculateAvailableActions(
39
+ 2,
40
+ false,
41
+ false,
42
+ true, // handIsStanding
43
+ false,
44
+ "PLAYING",
45
+ 1,
46
+ true,
47
+ standardRules
48
+ );
49
+ expect(actions.canStand).equal(false);
50
+ expect(actions.canDouble).equal(false);
51
+ expect(actions.canSplit).equal(false);
52
+ expect(actions.canSurrender).equal(false);
53
+ });
54
+
55
+ test("should not allow any actions when hand is busted", () => {
56
+ const standardRules = BlackjackRules.standard();
57
+ const actions = calculateAvailableActions(
58
+ 3,
59
+ false,
60
+ false,
61
+ false,
62
+ true, // handIsBusted
63
+ "PLAYING",
64
+ 1,
65
+ true,
66
+ standardRules
67
+ );
68
+ expect(actions.canStand).equal(false);
69
+ expect(actions.canDouble).equal(false);
70
+ expect(actions.canSplit).equal(false);
71
+ expect(actions.canSurrender).equal(false);
72
+ });
73
+ });
74
+
75
+ // ============================================================================
76
+ // shouldDealerHit Tests
77
+ // ============================================================================
78
+
79
+ describe("shouldDealerHit", () => {
80
+ test("should hit when hand value is below stand value", () => {
81
+ expect(shouldDealerHit(16, 17, false, false)).equal(true);
82
+ expect(shouldDealerHit(10, 17, false, false)).equal(true);
83
+ expect(shouldDealerHit(1, 17, false, false)).equal(true);
84
+ });
85
+
86
+ test("should not hit when hand value is above stand value", () => {
87
+ expect(shouldDealerHit(18, 17, false, false)).equal(false);
88
+ expect(shouldDealerHit(20, 17, false, false)).equal(false);
89
+ expect(shouldDealerHit(21, 17, false, false)).equal(false);
90
+ });
91
+
92
+ test("should not hit on hard 17 when hitOnSoft17 is false", () => {
93
+ expect(shouldDealerHit(17, 17, false, false)).equal(false);
94
+ });
95
+
96
+ test("should hit on soft 17 when hitOnSoft17 is true", () => {
97
+ expect(shouldDealerHit(17, 17, true, true)).equal(true);
98
+ });
99
+
100
+ test("should not hit on soft 17 when hitOnSoft17 is false", () => {
101
+ expect(shouldDealerHit(17, 17, false, true)).equal(false);
102
+ });
103
+
104
+ test("should not hit on hard 17 even when hitOnSoft17 is true", () => {
105
+ expect(shouldDealerHit(17, 17, true, false)).equal(false);
106
+ });
107
+
108
+ test("should handle custom stand value", () => {
109
+ expect(shouldDealerHit(15, 16, false, false)).equal(true);
110
+ expect(shouldDealerHit(16, 16, false, false)).equal(false);
111
+ expect(shouldDealerHit(17, 16, false, false)).equal(false);
112
+ });
113
+ });
114
+
115
+ // ============================================================================
116
+ // validateActionPhase Tests
117
+ // ============================================================================
118
+
119
+ describe("validateActionPhase", () => {
120
+ test("should not throw when phase matches", () => {
121
+ validateActionPhase("PLAYING", "HIT", "PLAYING");
122
+ // If we get here, no error was thrown
123
+ expect(true).equal(true);
124
+ });
125
+
126
+ // Note: AssemblyScript doesn't support try-catch, so we test that
127
+ // the function aborts on invalid input by checking the contract behavior
128
+ // In a real scenario, invalid phase would cause the contract to abort
129
+ test("should validate phase correctly", () => {
130
+ // Valid phase - should not abort
131
+ validateActionPhase("PLAYING", "HIT", "PLAYING");
132
+ expect(true).equal(true);
133
+ });
134
+ });
135
+
136
+ // ============================================================================
137
+ // validateActiveHand Tests
138
+ // ============================================================================
139
+
140
+ describe("validateActiveHand", () => {
141
+ test("should not throw when hand index is valid", () => {
142
+ validateActiveHand(0, 1);
143
+ validateActiveHand(1, 2);
144
+ validateActiveHand(0, 5);
145
+ // If we get here, no error was thrown
146
+ expect(true).equal(true);
147
+ });
148
+
149
+ // Note: AssemblyScript doesn't support try-catch, so we test that
150
+ // the function aborts on invalid input by checking the contract behavior
151
+ // In a real scenario, invalid hand index would cause the contract to abort
152
+ test("should validate hand index correctly", () => {
153
+ // Valid indices - should not abort
154
+ validateActiveHand(0, 1);
155
+ validateActiveHand(1, 2);
156
+ expect(true).equal(true);
157
+ });
158
+ });
159
+
160
+ // ============================================================================
161
+ // validateCanHit Tests
162
+ // ============================================================================
163
+
164
+ describe("validateCanHit", () => {
165
+ test("should not throw when hand can be hit", () => {
166
+ validateCanHit(false, false, false);
167
+ // If we get here, no error was thrown
168
+ expect(true).equal(true);
169
+ });
170
+
171
+ // Note: AssemblyScript doesn't support try-catch, so we test that
172
+ // the function aborts on invalid input by checking the contract behavior
173
+ // In a real scenario, invalid hit would cause the contract to abort
174
+ test("should validate hit conditions correctly", () => {
175
+ // Valid conditions - should not abort
176
+ validateCanHit(false, false, false);
177
+ expect(true).equal(true);
178
+ });
179
+ });
180
+
@@ -0,0 +1,452 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tests for dealer hole card scenarios
4
+ *
5
+ * Tests various dealer showing/hole card combinations and their interactions
6
+ * with insurance, player blackjack, and game outcomes
7
+ */
8
+
9
+ import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
10
+ import {
11
+ shouldOfferInsurance,
12
+ calculateAvailableActions
13
+ } from "../../../blackjack/actions";
14
+ import { BlackjackRules } from "../../../blackjack/rules";
15
+ import {
16
+ isBlackjack,
17
+ calculateBlackjackHandValue
18
+ } from "../../../blackjack/blackjack";
19
+ import { Card, Rank, Suit } from "../../../cards";
20
+
21
+ // ============================================================================
22
+ // Helper Functions
23
+ // ============================================================================
24
+
25
+ function createCard(rank: string, suit: string = Suit.SPADES): Card {
26
+ return new Card(suit, rank);
27
+ }
28
+
29
+ function createDealerHand(upCard: Card, holeCard: Card): Card[] {
30
+ return [upCard, holeCard];
31
+ }
32
+
33
+ function createPlayerHand(card1: Card, card2: Card): Card[] {
34
+ return [card1, card2];
35
+ }
36
+
37
+ // ============================================================================
38
+ // Dealer Has Ace Showing, 10-K Down (Blackjack)
39
+ // ============================================================================
40
+
41
+ describe("Dealer Scenarios - Ace Showing, 10-K Down (Blackjack)", () => {
42
+ test("should offer insurance when dealer shows ace", () => {
43
+ const rules = BlackjackRules.standard();
44
+ const dealerUpCard = createCard(Rank.ACE);
45
+ expect(shouldOfferInsurance(dealerUpCard.rank, rules)).equal(true);
46
+ });
47
+
48
+ test("dealer has blackjack with A showing and K down - player accepts insurance, player has blackjack", () => {
49
+ const dealerHand = createDealerHand(
50
+ createCard(Rank.ACE),
51
+ createCard(Rank.KING)
52
+ );
53
+ const playerHand = createPlayerHand(
54
+ createCard(Rank.ACE),
55
+ createCard(Rank.KING)
56
+ );
57
+
58
+ // Verify dealer has blackjack
59
+ expect(isBlackjack(dealerHand)).equal(true);
60
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
61
+
62
+ // Verify player has blackjack
63
+ expect(isBlackjack(playerHand)).equal(true);
64
+ expect(calculateBlackjackHandValue(playerHand)).equal(21);
65
+
66
+ // Insurance should be offered
67
+ const rules = BlackjackRules.standard();
68
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
69
+
70
+ // If player accepts insurance and dealer has blackjack:
71
+ // - Insurance pays 2:1 (player wins insurance bet)
72
+ // - Main bet pushes (both have blackjack)
73
+ // Player should be able to continue or the hand should be resolved
74
+ });
75
+
76
+ test("dealer has blackjack with A showing and K down - player accepts insurance, player does not have blackjack", () => {
77
+ const dealerHand = createDealerHand(
78
+ createCard(Rank.ACE),
79
+ createCard(Rank.KING)
80
+ );
81
+ const playerHand = createPlayerHand(
82
+ createCard(Rank.TEN),
83
+ createCard(Rank.SEVEN)
84
+ );
85
+
86
+ // Verify dealer has blackjack
87
+ expect(isBlackjack(dealerHand)).equal(true);
88
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
89
+
90
+ // Verify player does not have blackjack
91
+ expect(isBlackjack(playerHand)).equal(false);
92
+ expect(calculateBlackjackHandValue(playerHand)).equal(17);
93
+
94
+ // Insurance should be offered
95
+ const rules = BlackjackRules.standard();
96
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
97
+
98
+ // If player accepts insurance and dealer has blackjack:
99
+ // - Insurance pays 2:1 (player wins insurance bet)
100
+ // - Main bet loses (dealer blackjack beats player)
101
+ });
102
+
103
+ test("dealer has blackjack with A showing and K down - player denies insurance, player has blackjack", () => {
104
+ const dealerHand = createDealerHand(
105
+ createCard(Rank.ACE),
106
+ createCard(Rank.KING)
107
+ );
108
+ const playerHand = createPlayerHand(
109
+ createCard(Rank.ACE),
110
+ createCard(Rank.KING)
111
+ );
112
+
113
+ // Verify dealer has blackjack
114
+ expect(isBlackjack(dealerHand)).equal(true);
115
+
116
+ // Verify player has blackjack
117
+ expect(isBlackjack(playerHand)).equal(true);
118
+
119
+ // Insurance should be offered
120
+ const rules = BlackjackRules.standard();
121
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
122
+
123
+ // If player denies insurance and dealer has blackjack:
124
+ // - No insurance payout
125
+ // - Main bet pushes (both have blackjack)
126
+ });
127
+
128
+ test("dealer has blackjack with A showing and K down - player denies insurance, player does not have blackjack", () => {
129
+ const dealerHand = createDealerHand(
130
+ createCard(Rank.ACE),
131
+ createCard(Rank.KING)
132
+ );
133
+ const playerHand = createPlayerHand(
134
+ createCard(Rank.TEN),
135
+ createCard(Rank.SEVEN)
136
+ );
137
+
138
+ // Verify dealer has blackjack
139
+ expect(isBlackjack(dealerHand)).equal(true);
140
+
141
+ // Verify player does not have blackjack
142
+ expect(isBlackjack(playerHand)).equal(false);
143
+
144
+ // Insurance should be offered
145
+ const rules = BlackjackRules.standard();
146
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
147
+
148
+ // If player denies insurance and dealer has blackjack:
149
+ // - No insurance payout
150
+ // - Main bet loses (dealer blackjack beats player)
151
+ });
152
+
153
+ test("dealer has blackjack with A showing and Q down", () => {
154
+ const dealerHand = createDealerHand(
155
+ createCard(Rank.ACE),
156
+ createCard(Rank.QUEEN)
157
+ );
158
+
159
+ expect(isBlackjack(dealerHand)).equal(true);
160
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
161
+ });
162
+
163
+ test("dealer has blackjack with A showing and J down", () => {
164
+ const dealerHand = createDealerHand(
165
+ createCard(Rank.ACE),
166
+ createCard(Rank.JACK)
167
+ );
168
+
169
+ expect(isBlackjack(dealerHand)).equal(true);
170
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
171
+ });
172
+
173
+ test("dealer has blackjack with A showing and TEN down", () => {
174
+ const dealerHand = createDealerHand(
175
+ createCard(Rank.ACE),
176
+ createCard(Rank.TEN)
177
+ );
178
+
179
+ expect(isBlackjack(dealerHand)).equal(true);
180
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
181
+ });
182
+ });
183
+
184
+ // ============================================================================
185
+ // Dealer Has K Showing, A Down (Blackjack)
186
+ // ============================================================================
187
+
188
+ describe("Dealer Scenarios - K Showing, A Down (Blackjack)", () => {
189
+ test("dealer has blackjack with K showing and A down - player has blackjack", () => {
190
+ const dealerHand = createDealerHand(
191
+ createCard(Rank.KING),
192
+ createCard(Rank.ACE)
193
+ );
194
+ const playerHand = createPlayerHand(
195
+ createCard(Rank.ACE),
196
+ createCard(Rank.KING)
197
+ );
198
+
199
+ // Verify dealer has blackjack
200
+ expect(isBlackjack(dealerHand)).equal(true);
201
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
202
+
203
+ // Verify player has blackjack
204
+ expect(isBlackjack(playerHand)).equal(true);
205
+ expect(calculateBlackjackHandValue(playerHand)).equal(21);
206
+
207
+ // Insurance should NOT be offered (dealer doesn't show ace)
208
+ const rules = BlackjackRules.standard();
209
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(false);
210
+
211
+ // When dealer has blackjack but doesn't show ace:
212
+ // - No insurance offered
213
+ // - If player has blackjack: push
214
+ // - If player doesn't have blackjack: player loses
215
+ });
216
+
217
+ test("dealer has blackjack with K showing and A down - player does not have blackjack", () => {
218
+ const dealerHand = createDealerHand(
219
+ createCard(Rank.KING),
220
+ createCard(Rank.ACE)
221
+ );
222
+ const playerHand = createPlayerHand(
223
+ createCard(Rank.TEN),
224
+ createCard(Rank.SEVEN)
225
+ );
226
+
227
+ // Verify dealer has blackjack
228
+ expect(isBlackjack(dealerHand)).equal(true);
229
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
230
+
231
+ // Verify player does not have blackjack
232
+ expect(isBlackjack(playerHand)).equal(false);
233
+ expect(calculateBlackjackHandValue(playerHand)).equal(17);
234
+
235
+ // Insurance should NOT be offered (dealer doesn't show ace)
236
+ const rules = BlackjackRules.standard();
237
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(false);
238
+
239
+ // When dealer has blackjack but doesn't show ace:
240
+ // - No insurance offered
241
+ // - Player loses main bet
242
+ });
243
+
244
+ test("dealer has blackjack with Q showing and A down", () => {
245
+ const dealerHand = createDealerHand(
246
+ createCard(Rank.QUEEN),
247
+ createCard(Rank.ACE)
248
+ );
249
+
250
+ expect(isBlackjack(dealerHand)).equal(true);
251
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
252
+
253
+ const rules = BlackjackRules.standard();
254
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(false);
255
+ });
256
+
257
+ test("dealer has blackjack with J showing and A down", () => {
258
+ const dealerHand = createDealerHand(
259
+ createCard(Rank.JACK),
260
+ createCard(Rank.ACE)
261
+ );
262
+
263
+ expect(isBlackjack(dealerHand)).equal(true);
264
+ expect(calculateBlackjackHandValue(dealerHand)).equal(21);
265
+
266
+ const rules = BlackjackRules.standard();
267
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(false);
268
+ });
269
+ });
270
+
271
+ // ============================================================================
272
+ // Dealer Has A Showing, Not 10-K Down (No Blackjack)
273
+ // ============================================================================
274
+
275
+ describe("Dealer Scenarios - Ace Showing, Not 10-K Down (No Blackjack)", () => {
276
+ test("dealer has A showing and 9 down (no blackjack) - player accepts insurance, player has blackjack", () => {
277
+ const dealerHand = createDealerHand(
278
+ createCard(Rank.ACE),
279
+ createCard(Rank.NINE)
280
+ );
281
+ const playerHand = createPlayerHand(
282
+ createCard(Rank.ACE),
283
+ createCard(Rank.KING)
284
+ );
285
+
286
+ // Verify dealer does NOT have blackjack
287
+ expect(isBlackjack(dealerHand)).equal(false);
288
+ expect(calculateBlackjackHandValue(dealerHand)).equal(20);
289
+
290
+ // Verify player has blackjack
291
+ expect(isBlackjack(playerHand)).equal(true);
292
+ expect(calculateBlackjackHandValue(playerHand)).equal(21);
293
+
294
+ // Insurance should be offered
295
+ const rules = BlackjackRules.standard();
296
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
297
+
298
+ // If player accepts insurance and dealer does NOT have blackjack:
299
+ // - Insurance loses (player loses insurance bet)
300
+ // - Main bet: player blackjack beats dealer (player wins 3:2)
301
+ });
302
+
303
+ test("dealer has A showing and 9 down (no blackjack) - player accepts insurance, player does not have blackjack", () => {
304
+ const dealerHand = createDealerHand(
305
+ createCard(Rank.ACE),
306
+ createCard(Rank.NINE)
307
+ );
308
+ const playerHand = createPlayerHand(
309
+ createCard(Rank.TEN),
310
+ createCard(Rank.SEVEN)
311
+ );
312
+
313
+ // Verify dealer does NOT have blackjack
314
+ expect(isBlackjack(dealerHand)).equal(false);
315
+ expect(calculateBlackjackHandValue(dealerHand)).equal(20);
316
+
317
+ // Verify player does not have blackjack
318
+ expect(isBlackjack(playerHand)).equal(false);
319
+ expect(calculateBlackjackHandValue(playerHand)).equal(17);
320
+
321
+ // Insurance should be offered
322
+ const rules = BlackjackRules.standard();
323
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
324
+
325
+ // If player accepts insurance and dealer does NOT have blackjack:
326
+ // - Insurance loses (player loses insurance bet)
327
+ // - Main bet: game continues (dealer has 20, player has 17)
328
+ });
329
+
330
+ test("dealer has A showing and 9 down (no blackjack) - player denies insurance, player has blackjack", () => {
331
+ const dealerHand = createDealerHand(
332
+ createCard(Rank.ACE),
333
+ createCard(Rank.NINE)
334
+ );
335
+ const playerHand = createPlayerHand(
336
+ createCard(Rank.ACE),
337
+ createCard(Rank.KING)
338
+ );
339
+
340
+ // Verify dealer does NOT have blackjack
341
+ expect(isBlackjack(dealerHand)).equal(false);
342
+ expect(calculateBlackjackHandValue(dealerHand)).equal(20);
343
+
344
+ // Verify player has blackjack
345
+ expect(isBlackjack(playerHand)).equal(true);
346
+ expect(calculateBlackjackHandValue(playerHand)).equal(21);
347
+
348
+ // Insurance should be offered
349
+ const rules = BlackjackRules.standard();
350
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
351
+
352
+ // If player denies insurance and dealer does NOT have blackjack:
353
+ // - No insurance bet
354
+ // - Main bet: player blackjack beats dealer (player wins 3:2)
355
+ });
356
+
357
+ test("dealer has A showing and 9 down (no blackjack) - player denies insurance, player does not have blackjack", () => {
358
+ const dealerHand = createDealerHand(
359
+ createCard(Rank.ACE),
360
+ createCard(Rank.NINE)
361
+ );
362
+ const playerHand = createPlayerHand(
363
+ createCard(Rank.TEN),
364
+ createCard(Rank.SEVEN)
365
+ );
366
+
367
+ // Verify dealer does NOT have blackjack
368
+ expect(isBlackjack(dealerHand)).equal(false);
369
+ expect(calculateBlackjackHandValue(dealerHand)).equal(20);
370
+
371
+ // Verify player does not have blackjack
372
+ expect(isBlackjack(playerHand)).equal(false);
373
+ expect(calculateBlackjackHandValue(playerHand)).equal(17);
374
+
375
+ // Insurance should be offered
376
+ const rules = BlackjackRules.standard();
377
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
378
+
379
+ // If player denies insurance and dealer does NOT have blackjack:
380
+ // - No insurance bet
381
+ // - Main bet: game continues (dealer has 20, player has 17)
382
+ });
383
+
384
+ test("dealer has A showing and 8 down (no blackjack)", () => {
385
+ const dealerHand = createDealerHand(
386
+ createCard(Rank.ACE),
387
+ createCard(Rank.EIGHT)
388
+ );
389
+
390
+ expect(isBlackjack(dealerHand)).equal(false);
391
+ expect(calculateBlackjackHandValue(dealerHand)).equal(19);
392
+
393
+ const rules = BlackjackRules.standard();
394
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
395
+ });
396
+
397
+ test("dealer has A showing and 7 down (no blackjack)", () => {
398
+ const dealerHand = createDealerHand(
399
+ createCard(Rank.ACE),
400
+ createCard(Rank.SEVEN)
401
+ );
402
+
403
+ expect(isBlackjack(dealerHand)).equal(false);
404
+ expect(calculateBlackjackHandValue(dealerHand)).equal(18);
405
+
406
+ const rules = BlackjackRules.standard();
407
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
408
+ });
409
+
410
+ test("dealer has A showing and 2 down (no blackjack)", () => {
411
+ const dealerHand = createDealerHand(
412
+ createCard(Rank.ACE),
413
+ createCard(Rank.TWO)
414
+ );
415
+
416
+ expect(isBlackjack(dealerHand)).equal(false);
417
+ expect(calculateBlackjackHandValue(dealerHand)).equal(13);
418
+
419
+ const rules = BlackjackRules.standard();
420
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
421
+ });
422
+ });
423
+
424
+ // ============================================================================
425
+ // Additional Edge Cases
426
+ // ============================================================================
427
+
428
+ describe("Dealer Scenarios - Edge Cases", () => {
429
+ test("dealer has A showing and A down (soft 12, no blackjack)", () => {
430
+ const dealerHand = createDealerHand(
431
+ createCard(Rank.ACE),
432
+ createCard(Rank.ACE)
433
+ );
434
+
435
+ expect(isBlackjack(dealerHand)).equal(false);
436
+ expect(calculateBlackjackHandValue(dealerHand)).equal(12);
437
+
438
+ const rules = BlackjackRules.standard();
439
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(true);
440
+ });
441
+
442
+ test("insurance not offered when insurance is disabled in rules", () => {
443
+ const dealerHand = createDealerHand(
444
+ createCard(Rank.ACE),
445
+ createCard(Rank.KING)
446
+ );
447
+
448
+ const rules = new BlackjackRules(17, false, 4, false, true, false, false); // insuranceOffered = false
449
+ expect(shouldOfferInsurance(dealerHand[0].rank, rules)).equal(false);
450
+ });
451
+ });
452
+