@arcanahq/cardgames 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +722 -0
- package/as-test.config.js +36 -0
- package/asconfig.json +22 -0
- package/assembly/__tests__/blackjack/actions/common.spec.ts +180 -0
- package/assembly/__tests__/blackjack/actions/dealer_scenarios.spec.ts +452 -0
- package/assembly/__tests__/blackjack/actions/double.spec.ts +128 -0
- package/assembly/__tests__/blackjack/actions/edge_cases.spec.ts +1041 -0
- package/assembly/__tests__/blackjack/actions/insurance.spec.ts +39 -0
- package/assembly/__tests__/blackjack/actions/split.spec.ts +96 -0
- package/assembly/__tests__/blackjack/actions/stand.spec.ts +103 -0
- package/assembly/__tests__/blackjack/actions/surrender.spec.ts +89 -0
- package/assembly/__tests__/blackjack/actions/test.ts +18 -0
- package/assembly/__tests__/blackjack/rules.spec.ts +231 -0
- package/assembly/__tests__/deck/deck.spec.ts +551 -0
- package/assembly/__tests__/deck/shoe.spec.ts +410 -0
- package/assembly/__tests__/poker/betting_round.spec.ts +103 -0
- package/assembly/__tests__/poker/omaha.spec.ts +171 -0
- package/assembly/__tests__/poker/pots.spec.ts +255 -0
- package/assembly/__tests__/poker/showdown.spec.ts +324 -0
- package/assembly/__tests__/poker/six_plus.spec.ts +152 -0
- package/assembly/__tests__/poker/stakes.spec.ts +384 -0
- package/assembly/__tests__/poker/stud.spec.ts +190 -0
- package/assembly/__tests__/poker/test.ts +13 -0
- package/assembly/__tests__/test.ts +11 -0
- package/assembly/blackjack/actions.ts +191 -0
- package/assembly/blackjack/blackjack.ts +571 -0
- package/assembly/blackjack/rules.ts +11 -0
- package/assembly/cardgames.ts +314 -0
- package/assembly/cards.ts +314 -0
- package/assembly/cashgames/cash_game_types.ts +142 -0
- package/assembly/cashgames/cash_game_utils.ts +223 -0
- package/assembly/cashgames/index.ts +10 -0
- package/assembly/deck/deck.ts +744 -0
- package/assembly/deck/index.ts +9 -0
- package/assembly/index.ts +28 -0
- package/assembly/poker/index.ts +17 -0
- package/assembly/poker/omaha_evaluator.ts +121 -0
- package/assembly/poker/poker_game_types.ts +233 -0
- package/assembly/poker/poker_game_utils.ts +671 -0
- package/assembly/poker/showdown.ts +106 -0
- package/assembly/poker/showdown_evaluator.ts +225 -0
- package/assembly/poker/six_plus_showdown.ts +96 -0
- package/assembly/poker/stud_evaluator.ts +60 -0
- package/assembly/poker/variant_utils.ts +51 -0
- package/assembly/poker/variants.ts +182 -0
- package/assembly/poker.ts +307 -0
- package/package.json +51 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,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,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
|
+
}
|