@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,571 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Blackjack Card Game Utilities
|
|
4
|
+
*
|
|
5
|
+
* Provides utilities for blackjack-specific card game operations:
|
|
6
|
+
* - Blackjack card value calculations
|
|
7
|
+
* - Hand value calculations with ace handling
|
|
8
|
+
* - Blackjack-specific game rules
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Card, Rank, Suit } from "../cards";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extended Card class for blackjack with hidden card support
|
|
15
|
+
*/
|
|
16
|
+
export class BlackjackCard extends Card {
|
|
17
|
+
isHidden: bool;
|
|
18
|
+
|
|
19
|
+
constructor(suit: string = "", rank: string = "", isHidden: bool = false) {
|
|
20
|
+
super(suit, rank);
|
|
21
|
+
this.isHidden = isHidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static fromCard(card: Card, isHidden: bool = false): BlackjackCard {
|
|
25
|
+
return new BlackjackCard(card.suit, card.rank, isHidden);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
clone(): BlackjackCard {
|
|
29
|
+
return new BlackjackCard(this.suit, this.rank, this.isHidden);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Gets the numeric value of a card rank for blackjack
|
|
35
|
+
* Aces return 11 (will be adjusted in hand calculation)
|
|
36
|
+
* Face cards (J, Q, K) return 10
|
|
37
|
+
* Number cards return their face value
|
|
38
|
+
*/
|
|
39
|
+
export function getBlackjackCardValue(card: Card): i32 {
|
|
40
|
+
if (card.rank === Rank.ACE) {
|
|
41
|
+
return 11; // Default to 11, will be adjusted in hand calculation
|
|
42
|
+
} else if (card.rank === Rank.JACK || card.rank === Rank.QUEEN || card.rank === Rank.KING) {
|
|
43
|
+
return 10;
|
|
44
|
+
} else if (card.rank === "?") {
|
|
45
|
+
return 0; // Unknown card
|
|
46
|
+
} else {
|
|
47
|
+
// Parse number cards (2-10)
|
|
48
|
+
const num = parseInt(card.rank);
|
|
49
|
+
if (isNaN(num) || num < 2 || num > 10) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
return <i32>num;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calculates the optimal value of a hand in blackjack
|
|
58
|
+
* Aces are counted as 11 when beneficial, otherwise as 1
|
|
59
|
+
*/
|
|
60
|
+
export function calculateBlackjackHandValue(cards: Card[]): i32 {
|
|
61
|
+
if (cards.length === 0) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let total: i32 = 0;
|
|
66
|
+
let aces: i32 = 0;
|
|
67
|
+
|
|
68
|
+
// First pass: count all cards, treating aces as 11
|
|
69
|
+
for (let i = 0; i < cards.length; i++) {
|
|
70
|
+
const card = cards[i];
|
|
71
|
+
if (!card) {
|
|
72
|
+
continue; // Skip null cards
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Skip hidden cards (for blackjack, we check isHidden property)
|
|
76
|
+
// Check if card is a BlackjackCard by checking if it has isHidden property
|
|
77
|
+
// We use a type guard: if the card is a BlackjackCard, it will have isHidden
|
|
78
|
+
const bjCard = changetype<BlackjackCard>(card);
|
|
79
|
+
if (bjCard !== null && bjCard.isHidden === true) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Also check for suit === "hidden" for backward compatibility
|
|
83
|
+
if (card.suit === "hidden") {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (card.rank === Rank.ACE) {
|
|
88
|
+
aces++;
|
|
89
|
+
total += 11;
|
|
90
|
+
} else {
|
|
91
|
+
total += getBlackjackCardValue(card);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Adjust aces down from 11 to 1 if needed to avoid busting
|
|
96
|
+
while (total > 21 && aces > 0) {
|
|
97
|
+
total -= 10; // Convert one ace from 11 to 1
|
|
98
|
+
aces--;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return total;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Checks if a hand is a blackjack (21 with exactly 2 cards)
|
|
106
|
+
*/
|
|
107
|
+
export function isBlackjack(cards: Card[]): bool {
|
|
108
|
+
return cards.length === 2 && calculateBlackjackHandValue(cards) === 21;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Checks if a hand is busted (over 21)
|
|
113
|
+
*/
|
|
114
|
+
export function isBusted(cards: Card[]): bool {
|
|
115
|
+
return calculateBlackjackHandValue(cards) > 21;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if two cards can be split (same rank)
|
|
120
|
+
*/
|
|
121
|
+
export function canSplitCards(cards: Card[]): bool {
|
|
122
|
+
if (cards.length !== 2) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rank1 = cards[0].rank;
|
|
127
|
+
const rank2 = cards[1].rank;
|
|
128
|
+
|
|
129
|
+
// Special case: face cards (J, Q, K) can split with each other
|
|
130
|
+
if ((rank1 === Rank.JACK || rank1 === Rank.QUEEN || rank1 === Rank.KING) &&
|
|
131
|
+
(rank2 === Rank.JACK || rank2 === Rank.QUEEN || rank2 === Rank.KING)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Otherwise, ranks must match exactly
|
|
136
|
+
return rank1 === rank2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Configurable Blackjack Rules
|
|
142
|
+
* Supports different blackjack variants (standard, Spanish 21, etc.)
|
|
143
|
+
*/
|
|
144
|
+
export class BlackjackRules {
|
|
145
|
+
// Dealer rules
|
|
146
|
+
dealerStandValue: i32 = 17; // Dealer stands on this value or higher
|
|
147
|
+
hitOnSoft17: bool = false; // Dealer hits on soft 17 (A-6) if true, stands if false
|
|
148
|
+
|
|
149
|
+
// Game mechanics
|
|
150
|
+
maxSplitHands: i32 = 4; // Maximum number of hands from splitting
|
|
151
|
+
doubleAfterSplit: bool = false; // Allow double after split (standard is false)
|
|
152
|
+
surrenderAllowed: bool = true; // Allow surrender
|
|
153
|
+
lateSurrender: bool = false; // Late surrender (after dealer checks for blackjack)
|
|
154
|
+
insuranceOffered: bool = true; // Offer insurance when dealer shows ace
|
|
155
|
+
|
|
156
|
+
// Deck configuration
|
|
157
|
+
isSpanish21: bool = false; // Spanish 21 uses 48-card deck (no 10s)
|
|
158
|
+
deckSize: i32 = 52; // Standard deck size (48 for Spanish 21)
|
|
159
|
+
|
|
160
|
+
// Hand values
|
|
161
|
+
blackjackValue: i32 = 21;
|
|
162
|
+
bustValue: i32 = 22;
|
|
163
|
+
|
|
164
|
+
// Payout rates (as multipliers)
|
|
165
|
+
payoutBlackjack: f64 = 1.5; // 3:2
|
|
166
|
+
payoutWin: f64 = 1.0; // 1:1
|
|
167
|
+
payoutPush: f64 = 1.0; // 1:1 (return bet)
|
|
168
|
+
payoutLose: f64 = 0.0; // 0:1
|
|
169
|
+
payoutSurrender: f64 = 0.5; // 0.5:1 (half bet returned)
|
|
170
|
+
payoutInsurance: f64 = 2.0; // 2:1
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
dealerStandValue: i32 = 17,
|
|
174
|
+
hitOnSoft17: bool = false,
|
|
175
|
+
maxSplitHands: i32 = 4,
|
|
176
|
+
doubleAfterSplit: bool = false,
|
|
177
|
+
surrenderAllowed: bool = true,
|
|
178
|
+
lateSurrender: bool = false,
|
|
179
|
+
insuranceOffered: bool = true,
|
|
180
|
+
isSpanish21: bool = false,
|
|
181
|
+
payoutBlackjack: f64 = 1.5,
|
|
182
|
+
payoutWin: f64 = 1.0,
|
|
183
|
+
payoutPush: f64 = 1.0,
|
|
184
|
+
payoutLose: f64 = 0.0,
|
|
185
|
+
payoutSurrender: f64 = 0.5,
|
|
186
|
+
payoutInsurance: f64 = 2.0
|
|
187
|
+
) {
|
|
188
|
+
this.dealerStandValue = dealerStandValue;
|
|
189
|
+
this.hitOnSoft17 = hitOnSoft17;
|
|
190
|
+
this.maxSplitHands = maxSplitHands;
|
|
191
|
+
this.doubleAfterSplit = doubleAfterSplit;
|
|
192
|
+
this.surrenderAllowed = surrenderAllowed;
|
|
193
|
+
this.lateSurrender = lateSurrender;
|
|
194
|
+
this.insuranceOffered = insuranceOffered;
|
|
195
|
+
this.isSpanish21 = isSpanish21;
|
|
196
|
+
this.deckSize = isSpanish21 ? 48 : 52;
|
|
197
|
+
this.payoutBlackjack = payoutBlackjack;
|
|
198
|
+
this.payoutWin = payoutWin;
|
|
199
|
+
this.payoutPush = payoutPush;
|
|
200
|
+
this.payoutLose = payoutLose;
|
|
201
|
+
this.payoutSurrender = payoutSurrender;
|
|
202
|
+
this.payoutInsurance = payoutInsurance;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create standard blackjack rules (most common casino rules)
|
|
207
|
+
*/
|
|
208
|
+
static standard(): BlackjackRules {
|
|
209
|
+
return new BlackjackRules(
|
|
210
|
+
17, // dealerStandValue
|
|
211
|
+
false, // hitOnSoft17 (most casinos stand on soft 17)
|
|
212
|
+
4, // maxSplitHands
|
|
213
|
+
false, // doubleAfterSplit (standard rule: no double after split)
|
|
214
|
+
true, // surrenderAllowed
|
|
215
|
+
false, // lateSurrender
|
|
216
|
+
true, // insuranceOffered
|
|
217
|
+
false // isSpanish21
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create Spanish 21 rules (48-card deck, no 10s)
|
|
223
|
+
*/
|
|
224
|
+
static spanish21(): BlackjackRules {
|
|
225
|
+
return new BlackjackRules(
|
|
226
|
+
17, // dealerStandValue
|
|
227
|
+
true, // hitOnSoft17 (Spanish 21 typically hits on soft 17)
|
|
228
|
+
4, // maxSplitHands
|
|
229
|
+
true, // doubleAfterSplit (Spanish 21 allows double after split)
|
|
230
|
+
true, // surrenderAllowed
|
|
231
|
+
false, // lateSurrender
|
|
232
|
+
false, // insuranceOffered (Spanish 21 doesn't offer insurance)
|
|
233
|
+
true // isSpanish21
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Create rules with dealer hitting on soft 17
|
|
239
|
+
*/
|
|
240
|
+
static dealerHitsSoft17(): BlackjackRules {
|
|
241
|
+
const rules = BlackjackRules.standard();
|
|
242
|
+
rules.hitOnSoft17 = true;
|
|
243
|
+
return rules;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Create rules allowing double after split
|
|
248
|
+
*/
|
|
249
|
+
static allowDoubleAfterSplit(): BlackjackRules {
|
|
250
|
+
const rules = BlackjackRules.standard();
|
|
251
|
+
rules.doubleAfterSplit = true;
|
|
252
|
+
return rules;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Legacy static constants for backward compatibility
|
|
258
|
+
*/
|
|
259
|
+
export class BlackjackPayouts {
|
|
260
|
+
static readonly BLACKJACK: f64 = 1.5; // 3:2
|
|
261
|
+
static readonly WIN: f64 = 1.0; // 1:1
|
|
262
|
+
static readonly PUSH: f64 = 1.0; // 1:1 (return bet)
|
|
263
|
+
static readonly LOSE: f64 = 0.0; // 0:1
|
|
264
|
+
static readonly SURRENDER: f64 = 0.5; // 0.5:1 (half bet returned)
|
|
265
|
+
static readonly INSURANCE: f64 = 2.0; // 2:1
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Checks if a hand is a soft hand (contains an ace counted as 11)
|
|
270
|
+
* A soft hand is one where an ace is counted as 11 and the hand value could be reduced by 10
|
|
271
|
+
* This is used to determine dealer behavior on soft 17
|
|
272
|
+
*/
|
|
273
|
+
export function isSoftHand(cards: Card[]): bool {
|
|
274
|
+
if (cards.length === 0) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let hasAce = false;
|
|
279
|
+
let nonAceValue: i32 = 0;
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < cards.length; i++) {
|
|
282
|
+
const card = cards[i];
|
|
283
|
+
if (!card) continue;
|
|
284
|
+
|
|
285
|
+
// Check if card is a BlackjackCard with isHidden property
|
|
286
|
+
const bjCard = changetype<BlackjackCard>(card);
|
|
287
|
+
if (bjCard !== null && bjCard.isHidden === true) continue;
|
|
288
|
+
if (card.suit === "hidden") continue;
|
|
289
|
+
|
|
290
|
+
if (card.rank === Rank.ACE) {
|
|
291
|
+
hasAce = true;
|
|
292
|
+
} else {
|
|
293
|
+
nonAceValue += getBlackjackCardValue(card);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!hasAce) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// A soft hand is one where we can count an ace as 11
|
|
302
|
+
// Check if hand value is exactly 17 and has an ace (soft 17)
|
|
303
|
+
const handValue = calculateBlackjackHandValue(cards);
|
|
304
|
+
|
|
305
|
+
// If hand value is 17 and has an ace, check if it's soft
|
|
306
|
+
if (handValue === 17 && hasAce) {
|
|
307
|
+
// Calculate hard value (treating all aces as 1)
|
|
308
|
+
let hardValue: i32 = 0;
|
|
309
|
+
for (let i = 0; i < cards.length; i++) {
|
|
310
|
+
const card = cards[i];
|
|
311
|
+
if (!card) continue;
|
|
312
|
+
const bjCard = changetype<BlackjackCard>(card);
|
|
313
|
+
if (bjCard !== null && bjCard.isHidden === true) continue;
|
|
314
|
+
if (card.suit === "hidden") continue;
|
|
315
|
+
|
|
316
|
+
if (card.rank === Rank.ACE) {
|
|
317
|
+
hardValue += 1; // Ace as 1
|
|
318
|
+
} else {
|
|
319
|
+
hardValue += getBlackjackCardValue(card);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// If hard value is less than 17, it's a soft 17
|
|
323
|
+
return hardValue < 17;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// For other values, check if we can count an ace as 11 without busting
|
|
327
|
+
return hasAce && (nonAceValue + 11 <= 21);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Determines if dealer should hit based on configurable rules
|
|
332
|
+
* @param cards Dealer's cards
|
|
333
|
+
* @param rules Blackjack rules configuration
|
|
334
|
+
* @returns true if dealer should hit, false if dealer should stand
|
|
335
|
+
*/
|
|
336
|
+
export function dealerShouldHit(cards: Card[], rules: BlackjackRules): bool {
|
|
337
|
+
const handValue = calculateBlackjackHandValue(cards);
|
|
338
|
+
|
|
339
|
+
// Dealer must hit if below stand value
|
|
340
|
+
if (handValue < rules.dealerStandValue) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Dealer stands if above stand value
|
|
345
|
+
if (handValue > rules.dealerStandValue) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// At exactly stand value (17), check if it's soft 17
|
|
350
|
+
if (handValue === rules.dealerStandValue) {
|
|
351
|
+
// If dealer hits on soft 17 and this is a soft hand, dealer hits
|
|
352
|
+
if (rules.hitOnSoft17 && isSoftHand(cards)) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
// Otherwise dealer stands
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Result class for Pair Plus bonus bet evaluation
|
|
364
|
+
*/
|
|
365
|
+
export class PairPlusResult {
|
|
366
|
+
hasPair: bool = false;
|
|
367
|
+
pairType: string = ""; // "perfect", "colored", "mixed", or ""
|
|
368
|
+
payoutMultiplier: i32 = 0; // 25, 10, 5, or 0
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Result class for 21+3 bonus bet evaluation
|
|
373
|
+
*/
|
|
374
|
+
export class TwentyOnePlusThreeResult {
|
|
375
|
+
hasMatch: bool = false;
|
|
376
|
+
handType: string = ""; // "suited_three_kind", "straight_flush", "three_kind", "straight", "flush", or ""
|
|
377
|
+
payoutMultiplier: i32 = 0; // 100, 40, 30, 10, 5, or 0
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get card color (red or black) from suit
|
|
382
|
+
* Hearts and Diamonds are red, Spades and Clubs are black
|
|
383
|
+
*/
|
|
384
|
+
function getCardColor(suit: string): string {
|
|
385
|
+
if (suit === Suit.HEARTS || suit === Suit.DIAMONDS) {
|
|
386
|
+
return "red";
|
|
387
|
+
} else if (suit === Suit.SPADES || suit === Suit.CLUBS) {
|
|
388
|
+
return "black";
|
|
389
|
+
}
|
|
390
|
+
return "";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Check if all cards have the same rank
|
|
395
|
+
*/
|
|
396
|
+
function isThreeOfAKind(cards: Card[]): bool {
|
|
397
|
+
if (cards.length !== 3) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
const rank1 = cards[0].rank;
|
|
401
|
+
const rank2 = cards[1].rank;
|
|
402
|
+
const rank3 = cards[2].rank;
|
|
403
|
+
return rank1 === rank2 && rank2 === rank3;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Check if all cards have the same suit
|
|
408
|
+
*/
|
|
409
|
+
function isFlush(cards: Card[]): bool {
|
|
410
|
+
if (cards.length !== 3) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
const suit1 = cards[0].suit;
|
|
414
|
+
const suit2 = cards[1].suit;
|
|
415
|
+
const suit3 = cards[2].suit;
|
|
416
|
+
return suit1 === suit2 && suit2 === suit3;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Check if all cards have the same rank and suit (suited three of a kind)
|
|
421
|
+
*/
|
|
422
|
+
function isSuitedThreeOfAKind(cards: Card[]): bool {
|
|
423
|
+
if (cards.length !== 3) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
return isThreeOfAKind(cards) && isFlush(cards);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Check if 3 cards form a straight (consecutive ranks)
|
|
431
|
+
* Handles A-2-3 and Q-K-A straights
|
|
432
|
+
*/
|
|
433
|
+
function isStraight(cards: Card[]): bool {
|
|
434
|
+
if (cards.length !== 3) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Get rank values
|
|
439
|
+
const rankValues = new Array<i32>(3);
|
|
440
|
+
for (let i = 0; i < 3; i++) {
|
|
441
|
+
rankValues[i] = Rank.getValue(cards[i].rank);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Sort rank values
|
|
445
|
+
rankValues.sort((a, b) => a - b);
|
|
446
|
+
|
|
447
|
+
// Check normal straight (e.g., 2-3-4, 5-6-7)
|
|
448
|
+
if (rankValues[1] === rankValues[0] + 1 && rankValues[2] === rankValues[1] + 1) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Check A-2-3 straight (wheel)
|
|
453
|
+
if (rankValues[0] === 2 && rankValues[1] === 3 && rankValues[2] === 14) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check Q-K-A straight
|
|
458
|
+
if (rankValues[0] === 12 && rankValues[1] === 13 && rankValues[2] === 14) {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Check if cards form a straight flush (straight + flush)
|
|
467
|
+
*/
|
|
468
|
+
function isStraightFlush(cards: Card[]): bool {
|
|
469
|
+
return isStraight(cards) && isFlush(cards);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Evaluate Pair Plus bonus bet
|
|
474
|
+
* Checks if the first two cards form a pair and what type
|
|
475
|
+
* @param card1 First player card
|
|
476
|
+
* @param card2 Second player card
|
|
477
|
+
* @returns PairPlusResult with pair type and payout multiplier
|
|
478
|
+
*/
|
|
479
|
+
export function evaluatePairPlus(card1: Card, card2: Card): PairPlusResult {
|
|
480
|
+
const result = new PairPlusResult();
|
|
481
|
+
|
|
482
|
+
// Check if ranks match
|
|
483
|
+
if (card1.rank !== card2.rank) {
|
|
484
|
+
return result; // No pair
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
result.hasPair = true;
|
|
488
|
+
|
|
489
|
+
// Perfect pair: same rank and suit
|
|
490
|
+
if (card1.suit === card2.suit) {
|
|
491
|
+
result.pairType = "perfect";
|
|
492
|
+
result.payoutMultiplier = 25;
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Check if same color
|
|
497
|
+
const color1 = getCardColor(card1.suit);
|
|
498
|
+
const color2 = getCardColor(card2.suit);
|
|
499
|
+
|
|
500
|
+
if (color1 === color2 && color1.length > 0) {
|
|
501
|
+
result.pairType = "colored";
|
|
502
|
+
result.payoutMultiplier = 10;
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Mixed pair: same rank, different suits (and different colors)
|
|
507
|
+
result.pairType = "mixed";
|
|
508
|
+
result.payoutMultiplier = 5;
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Evaluate 21+3 bonus bet
|
|
514
|
+
* Uses player's two cards plus dealer's upcard to form a three-card poker hand
|
|
515
|
+
* @param card1 First player card
|
|
516
|
+
* @param card2 Second player card
|
|
517
|
+
* @param card3 Dealer's upcard
|
|
518
|
+
* @returns TwentyOnePlusThreeResult with hand type and payout multiplier
|
|
519
|
+
*/
|
|
520
|
+
export function evaluateTwentyOnePlusThree(card1: Card, card2: Card, card3: Card): TwentyOnePlusThreeResult {
|
|
521
|
+
const result = new TwentyOnePlusThreeResult();
|
|
522
|
+
const cards = new Array<Card>(3);
|
|
523
|
+
cards[0] = card1;
|
|
524
|
+
cards[1] = card2;
|
|
525
|
+
cards[2] = card3;
|
|
526
|
+
|
|
527
|
+
// Check in order of highest payout first
|
|
528
|
+
|
|
529
|
+
// Suited three of a kind: all same rank and suit (100:1)
|
|
530
|
+
if (isSuitedThreeOfAKind(cards)) {
|
|
531
|
+
result.hasMatch = true;
|
|
532
|
+
result.handType = "suited_three_kind";
|
|
533
|
+
result.payoutMultiplier = 100;
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Straight flush: straight + flush (40:1)
|
|
538
|
+
if (isStraightFlush(cards)) {
|
|
539
|
+
result.hasMatch = true;
|
|
540
|
+
result.handType = "straight_flush";
|
|
541
|
+
result.payoutMultiplier = 40;
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Three of a kind: all same rank (30:1)
|
|
546
|
+
if (isThreeOfAKind(cards)) {
|
|
547
|
+
result.hasMatch = true;
|
|
548
|
+
result.handType = "three_kind";
|
|
549
|
+
result.payoutMultiplier = 30;
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Straight: consecutive ranks (10:1)
|
|
554
|
+
if (isStraight(cards)) {
|
|
555
|
+
result.hasMatch = true;
|
|
556
|
+
result.handType = "straight";
|
|
557
|
+
result.payoutMultiplier = 10;
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Flush: all same suit (5:1)
|
|
562
|
+
if (isFlush(cards)) {
|
|
563
|
+
result.hasMatch = true;
|
|
564
|
+
result.handType = "flush";
|
|
565
|
+
result.payoutMultiplier = 5;
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// No match
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Blackjack Rules Configuration
|
|
4
|
+
*
|
|
5
|
+
* Provides configurable blackjack rules for different game variants
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Card and Rank are used in blackjack.ts, not here
|
|
9
|
+
|
|
10
|
+
// Re-export from local blackjack module
|
|
11
|
+
export { BlackjackRules, BlackjackPayouts, dealerShouldHit, isSoftHand, calculateBlackjackHandValue, isBlackjack as coreIsBlackjack, isBusted as coreIsBusted, canSplitCards as coreCanSplitCards, PairPlusResult, evaluatePairPlus, TwentyOnePlusThreeResult, evaluateTwentyOnePlusThree } from "./blackjack";
|