@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,314 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Card Game Utilities Library
4
+ *
5
+ * Provides common card game operations that can be shared across different card games:
6
+ * - Standard deck creation (52 cards)
7
+ * - Deterministic shuffling using Fisher-Yates algorithm
8
+ * - Card dealing utilities
9
+ *
10
+ * All operations are deterministic when using seeded randomness from the random module.
11
+ */
12
+
13
+ import { Card, Suit, Rank } from "./cards";
14
+ import { RandomSeed, getRandomIntInRange } from "@arcanahq/core/assembly/primitives/random";
15
+
16
+ /**
17
+ * Creates a standard 52-card deck in unshuffled order
18
+ * Cards are ordered: suits in order (Spades, Hearts, Diamonds, Clubs),
19
+ * ranks in order (2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A)
20
+ *
21
+ * @returns A new array containing all 52 cards in standard order
22
+ */
23
+ export function createStandardDeck(): Card[] {
24
+ const deck = new Array<Card>(52);
25
+ let index = 0;
26
+
27
+ for (let s = 0; s < Suit.ALL.length; s++) {
28
+ for (let r = 0; r < Rank.ALL.length; r++) {
29
+ deck[index] = new Card(Suit.ALL[s], Rank.ALL[r]);
30
+ index++;
31
+ }
32
+ }
33
+
34
+ return deck;
35
+ }
36
+
37
+ /**
38
+ * Shuffles a deck using the Fisher-Yates algorithm with deterministic randomness
39
+ * The shuffle is reproducible given the same seed
40
+ *
41
+ * @param deck The deck to shuffle (will be modified in place)
42
+ * @param seed The random seed to use for shuffling
43
+ * @returns The updated random seed after shuffling
44
+ */
45
+ export function shuffleDeck(deck: Card[], seed: RandomSeed): RandomSeed {
46
+ const n = deck.length;
47
+ let currentSeed = seed;
48
+
49
+ // Fisher-Yates shuffle: iterate from last element to first
50
+ for (let i = n - 1; i > 0; i--) {
51
+ // Get random index from 0 to i (inclusive)
52
+ const result = getRandomIntInRange(currentSeed, 0, i + 1);
53
+ const j = <i32>result.value;
54
+ currentSeed = result.seed;
55
+
56
+ // Swap cards[i] and cards[j]
57
+ const temp = deck[i];
58
+ deck[i] = deck[j];
59
+ deck[j] = temp;
60
+ }
61
+
62
+ return currentSeed;
63
+ }
64
+
65
+ /**
66
+ * Result class for createShuffledDeck
67
+ */
68
+ export class ShuffledDeckResult {
69
+ deck: Card[];
70
+ newSeed: RandomSeed;
71
+
72
+ constructor(deck: Card[], newSeed: RandomSeed) {
73
+ this.deck = deck;
74
+ this.newSeed = newSeed;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Creates a shuffled deck from a standard deck using deterministic randomness
80
+ *
81
+ * @param seed The random seed to use for shuffling
82
+ * @returns A ShuffledDeckResult containing the shuffled deck and the updated random seed
83
+ */
84
+ export function createShuffledDeck(seed: RandomSeed): ShuffledDeckResult {
85
+ const deck = createStandardDeck();
86
+ const newSeed = shuffleDeck(deck, seed);
87
+ return new ShuffledDeckResult(deck, newSeed);
88
+ }
89
+
90
+ /**
91
+ * Deals a single card from the top of the deck
92
+ *
93
+ * @param deck The deck to deal from (will be modified)
94
+ * @returns The dealt card, or null if the deck is empty
95
+ */
96
+ export function dealCard(deck: Card[]): Card | null {
97
+ if (deck.length === 0) {
98
+ return null;
99
+ }
100
+ const card = deck.pop();
101
+ return card !== null ? card : null;
102
+ }
103
+
104
+ /**
105
+ * Deals multiple cards from the top of the deck
106
+ *
107
+ * @param deck The deck to deal from (will be modified)
108
+ * @param count The number of cards to deal
109
+ * @returns An array of dealt cards (may be shorter than count if deck runs out)
110
+ */
111
+ export function dealCards(deck: Card[], count: i32): Card[] {
112
+ const dealt = new Array<Card>(0);
113
+ for (let i = 0; i < count; i++) {
114
+ if (deck.length === 0) {
115
+ break; // Deck is empty
116
+ }
117
+ const card = dealCard(deck);
118
+ if (card !== null) {
119
+ dealt.push(card);
120
+ } else {
121
+ break; // Deck is empty
122
+ }
123
+ }
124
+ return dealt;
125
+ }
126
+
127
+ /**
128
+ * Result class for dealCardFromDeck
129
+ */
130
+ export class DealCardResult {
131
+ card: Card | null;
132
+ newDeck: Card[];
133
+ newSeed: RandomSeed;
134
+
135
+ constructor(card: Card | null, newDeck: Card[], newSeed: RandomSeed) {
136
+ this.card = card;
137
+ this.newDeck = newDeck;
138
+ this.newSeed = newSeed;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Deals a card from a specific position in the deck using deterministic randomness
144
+ * This is useful for games that need to deal cards in a specific order based on a seed
145
+ *
146
+ * @param deck The deck to deal from
147
+ * @param seed The random seed to use for selecting the card
148
+ * @returns A DealCardResult containing the dealt card, the updated deck, and the updated seed
149
+ */
150
+ export function dealCardFromDeck(
151
+ deck: Card[],
152
+ seed: RandomSeed
153
+ ): DealCardResult {
154
+ if (deck.length === 0) {
155
+ return new DealCardResult(null, deck, seed);
156
+ }
157
+
158
+ // Use deterministic randomness to select a card index
159
+ const result = getRandomIntInRange(seed, 0, deck.length);
160
+ const cardIndex = <i32>result.value;
161
+ const newSeed = result.seed;
162
+
163
+ // Remove the selected card from the deck
164
+ const card = deck[cardIndex];
165
+ const newDeck = new Array<Card>(deck.length - 1);
166
+
167
+ let newDeckIndex = 0;
168
+ for (let i = 0; i < deck.length; i++) {
169
+ if (i !== cardIndex) {
170
+ newDeck[newDeckIndex] = deck[i];
171
+ newDeckIndex++;
172
+ }
173
+ }
174
+
175
+ return new DealCardResult(card, newDeck, newSeed);
176
+ }
177
+
178
+ /**
179
+ * Gets the number of cards remaining in the deck
180
+ *
181
+ * @param deck The deck to check
182
+ * @returns The number of cards in the deck
183
+ */
184
+ export function getDeckSize(deck: Card[]): i32 {
185
+ return deck.length;
186
+ }
187
+
188
+ /**
189
+ * Checks if the deck is empty
190
+ *
191
+ * @param deck The deck to check
192
+ * @returns True if the deck is empty, false otherwise
193
+ */
194
+ export function isDeckEmpty(deck: Card[]): bool {
195
+ return deck.length === 0;
196
+ }
197
+
198
+ /**
199
+ * Creates a copy of a deck without modifying the original
200
+ *
201
+ * @param deck The deck to clone
202
+ * @returns A new array containing copies of all cards in the deck
203
+ */
204
+ export function cloneDeck(deck: Card[]): Card[] {
205
+ const cloned = new Array<Card>(deck.length);
206
+ for (let i = 0; i < deck.length; i++) {
207
+ cloned[i] = deck[i];
208
+ }
209
+ return cloned;
210
+ }
211
+
212
+ /**
213
+ * Converts a Card to an integer representation (0-51)
214
+ * Mapping: cardIndex = suitIndex * 13 + rankIndex
215
+ * - Suit order: Spades=0, Hearts=1, Diamonds=2, Clubs=3
216
+ * - Rank order: 2=0, 3=1, 4=2, 5=3, 6=4, 7=5, 8=6, 9=7, 10=8, J=9, Q=10, K=11, A=12
217
+ *
218
+ * @param card The card to convert
219
+ * @returns An integer from 0-51 representing the card
220
+ */
221
+ export function cardToInt(card: Card): i32 {
222
+ // Find suit index
223
+ let suitIndex: i32 = -1;
224
+ for (let i = 0; i < Suit.ALL.length; i++) {
225
+ if (Suit.ALL[i] === card.suit) {
226
+ suitIndex = i;
227
+ break;
228
+ }
229
+ }
230
+
231
+ // Find rank index
232
+ let rankIndex: i32 = -1;
233
+ for (let i = 0; i < Rank.ALL.length; i++) {
234
+ if (Rank.ALL[i] === card.rank) {
235
+ rankIndex = i;
236
+ break;
237
+ }
238
+ }
239
+
240
+ // If card is invalid, return -1
241
+ if (suitIndex < 0 || rankIndex < 0) {
242
+ return -1;
243
+ }
244
+
245
+ // Calculate card index: suitIndex * 13 + rankIndex
246
+ return suitIndex * 13 + rankIndex;
247
+ }
248
+
249
+ /**
250
+ * Converts an integer (0-51) back to a Card
251
+ * Mapping: cardIndex = suitIndex * 13 + rankIndex
252
+ * - Suit order: Spades=0, Hearts=1, Diamonds=2, Clubs=3
253
+ * - Rank order: 2=0, 3=1, 4=2, 5=3, 6=4, 7=5, 8=6, 9=7, 10=8, J=9, Q=10, K=11, A=12
254
+ *
255
+ * @param value The integer value (0-51) representing the card
256
+ * @returns A Card object, or null if the value is invalid
257
+ */
258
+ export function intToCard(value: i32): Card | null {
259
+ // Validate range
260
+ if (value < 0 || value >= 52) {
261
+ return null;
262
+ }
263
+
264
+ // Calculate suit and rank indices
265
+ const suitIndex = value / 13; // Integer division
266
+ const rankIndex = value % 13; // Modulo
267
+
268
+ // Get suit and rank from arrays
269
+ if (suitIndex >= Suit.ALL.length || rankIndex >= Rank.ALL.length) {
270
+ return null;
271
+ }
272
+
273
+ const suit = Suit.ALL[suitIndex];
274
+ const rank = Rank.ALL[rankIndex];
275
+
276
+ return new Card(suit, rank);
277
+ }
278
+
279
+ /**
280
+ * Converts a deck of cards to an array of integers
281
+ * Useful for serialization and storage
282
+ *
283
+ * @param deck The deck to serialize
284
+ * @returns An array of integers (0-51) representing each card
285
+ */
286
+ export function deckToIntArray(deck: Card[]): i32[] {
287
+ const result = new Array<i32>(deck.length);
288
+ for (let i = 0; i < deck.length; i++) {
289
+ result[i] = cardToInt(deck[i]);
290
+ }
291
+ return result;
292
+ }
293
+
294
+ /**
295
+ * Converts an array of integers back to a deck of cards
296
+ * Useful for deserialization
297
+ *
298
+ * @param ints An array of integers (0-51) representing cards
299
+ * @returns An array of Card objects
300
+ */
301
+ export function intArrayToDeck(ints: i32[]): Card[] {
302
+ const deck = new Array<Card>(ints.length);
303
+ for (let i = 0; i < ints.length; i++) {
304
+ const card = intToCard(ints[i]);
305
+ if (card !== null) {
306
+ deck[i] = card;
307
+ } else {
308
+ // Invalid card value - skip or use a default?
309
+ // For now, we'll create a placeholder card
310
+ deck[i] = new Card(Suit.SPADES, Rank.TWO);
311
+ }
312
+ }
313
+ return deck;
314
+ }
@@ -0,0 +1,314 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Deck of Cards Library
4
+ *
5
+ * Provides Card, Suit, and Rank classes for card games.
6
+ * All operations are deterministic when using seeded randomness.
7
+ */
8
+
9
+ import { getRandomIntInRange, RandomSeed, RandomResult } from "@arcanahq/core/assembly/primitives/random";
10
+
11
+ // Card suit constants
12
+ export class Suit {
13
+ static readonly SPADES: string = "♠";
14
+ static readonly HEARTS: string = "♥";
15
+ static readonly DIAMONDS: string = "♦";
16
+ static readonly CLUBS: string = "♣";
17
+
18
+ static readonly ALL: string[] = [Suit.SPADES, Suit.HEARTS, Suit.DIAMONDS, Suit.CLUBS];
19
+
20
+ static isValid(suit: string): bool {
21
+ return suit === Suit.SPADES || suit === Suit.HEARTS || suit === Suit.DIAMONDS || suit === Suit.CLUBS;
22
+ }
23
+ }
24
+
25
+ // Card rank constants
26
+ export class Rank {
27
+ static readonly TWO: string = "2";
28
+ static readonly THREE: string = "3";
29
+ static readonly FOUR: string = "4";
30
+ static readonly FIVE: string = "5";
31
+ static readonly SIX: string = "6";
32
+ static readonly SEVEN: string = "7";
33
+ static readonly EIGHT: string = "8";
34
+ static readonly NINE: string = "9";
35
+ static readonly TEN: string = "10";
36
+ static readonly JACK: string = "J";
37
+ static readonly QUEEN: string = "Q";
38
+ static readonly KING: string = "K";
39
+ static readonly ACE: string = "A";
40
+
41
+ static readonly ALL: string[] = [
42
+ Rank.TWO, Rank.THREE, Rank.FOUR, Rank.FIVE, Rank.SIX, Rank.SEVEN,
43
+ Rank.EIGHT, Rank.NINE, Rank.TEN, Rank.JACK, Rank.QUEEN, Rank.KING, Rank.ACE
44
+ ];
45
+
46
+ static isValid(rank: string): bool {
47
+ for (let i = 0; i < Rank.ALL.length; i++) {
48
+ if (Rank.ALL[i] === rank) {
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Get numeric value for comparison (2=2, A=14)
57
+ */
58
+ static getValue(rank: string): i32 {
59
+ if (rank === Rank.TWO) return 2;
60
+ if (rank === Rank.THREE) return 3;
61
+ if (rank === Rank.FOUR) return 4;
62
+ if (rank === Rank.FIVE) return 5;
63
+ if (rank === Rank.SIX) return 6;
64
+ if (rank === Rank.SEVEN) return 7;
65
+ if (rank === Rank.EIGHT) return 8;
66
+ if (rank === Rank.NINE) return 9;
67
+ if (rank === Rank.TEN) return 10;
68
+ if (rank === Rank.JACK) return 11;
69
+ if (rank === Rank.QUEEN) return 12;
70
+ if (rank === Rank.KING) return 13;
71
+ if (rank === Rank.ACE) return 14;
72
+ return 0;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Card class representing a single playing card
78
+ */
79
+ export class Card {
80
+ suit: string;
81
+ rank: string;
82
+
83
+ constructor(suit: string, rank: string) {
84
+ this.suit = suit;
85
+ this.rank = rank;
86
+ }
87
+
88
+ static fromString(str: string): Card | null {
89
+ // Format: "As" (Ace of spades), "Kh" (King of hearts), "2d" (2 of diamonds), "Tc" (10 of clubs)
90
+ if (str.length < 2) return null;
91
+
92
+ const rankStr = str.substring(0, str.length - 1);
93
+ const suitStr = str.substring(str.length - 1);
94
+
95
+ // Map suit characters
96
+ let suit: string = "";
97
+ if (suitStr === "s" || suitStr === "S") suit = Suit.SPADES;
98
+ else if (suitStr === "h" || suitStr === "H") suit = Suit.HEARTS;
99
+ else if (suitStr === "d" || suitStr === "D") suit = Suit.DIAMONDS;
100
+ else if (suitStr === "c" || suitStr === "C") suit = Suit.CLUBS;
101
+ else return null;
102
+
103
+ // Map rank characters
104
+ let rank: string = "";
105
+ if (rankStr === "2") rank = Rank.TWO;
106
+ else if (rankStr === "3") rank = Rank.THREE;
107
+ else if (rankStr === "4") rank = Rank.FOUR;
108
+ else if (rankStr === "5") rank = Rank.FIVE;
109
+ else if (rankStr === "6") rank = Rank.SIX;
110
+ else if (rankStr === "7") rank = Rank.SEVEN;
111
+ else if (rankStr === "8") rank = Rank.EIGHT;
112
+ else if (rankStr === "9") rank = Rank.NINE;
113
+ else if (rankStr === "T" || rankStr === "t" || rankStr === "10") rank = Rank.TEN;
114
+ else if (rankStr === "J" || rankStr === "j") rank = Rank.JACK;
115
+ else if (rankStr === "Q" || rankStr === "q") rank = Rank.QUEEN;
116
+ else if (rankStr === "K" || rankStr === "k") rank = Rank.KING;
117
+ else if (rankStr === "A" || rankStr === "a") rank = Rank.ACE;
118
+ else return null;
119
+
120
+ return new Card(suit, rank);
121
+ }
122
+
123
+ toString(): string {
124
+ let rankStr: string = "";
125
+ if (this.rank === Rank.TWO) rankStr = "2";
126
+ else if (this.rank === Rank.THREE) rankStr = "3";
127
+ else if (this.rank === Rank.FOUR) rankStr = "4";
128
+ else if (this.rank === Rank.FIVE) rankStr = "5";
129
+ else if (this.rank === Rank.SIX) rankStr = "6";
130
+ else if (this.rank === Rank.SEVEN) rankStr = "7";
131
+ else if (this.rank === Rank.EIGHT) rankStr = "8";
132
+ else if (this.rank === Rank.NINE) rankStr = "9";
133
+ else if (this.rank === Rank.TEN) rankStr = "T";
134
+ else if (this.rank === Rank.JACK) rankStr = "J";
135
+ else if (this.rank === Rank.QUEEN) rankStr = "Q";
136
+ else if (this.rank === Rank.KING) rankStr = "K";
137
+ else if (this.rank === Rank.ACE) rankStr = "A";
138
+ else rankStr = "?";
139
+
140
+ let suitStr: string = "";
141
+ if (this.suit === Suit.SPADES) suitStr = "s";
142
+ else if (this.suit === Suit.HEARTS) suitStr = "h";
143
+ else if (this.suit === Suit.DIAMONDS) suitStr = "d";
144
+ else if (this.suit === Suit.CLUBS) suitStr = "c";
145
+ else suitStr = "?";
146
+
147
+ return rankStr + suitStr;
148
+ }
149
+
150
+ equals(other: Card | null): bool {
151
+ if (other === null) return false;
152
+ return this.suit === other.suit && this.rank === other.rank;
153
+ }
154
+
155
+ getValue(): i32 {
156
+ return Rank.getValue(this.rank);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Hand rank types for poker evaluation
162
+ */
163
+ export class HandType {
164
+ static readonly HIGH_CARD: i32 = 1;
165
+ static readonly PAIR: i32 = 2;
166
+ static readonly TWO_PAIR: i32 = 3;
167
+ static readonly THREE_OF_A_KIND: i32 = 4;
168
+ static readonly STRAIGHT: i32 = 5;
169
+ static readonly FLUSH: i32 = 6;
170
+ static readonly FULL_HOUSE: i32 = 7;
171
+ static readonly FOUR_OF_A_KIND: i32 = 8;
172
+ static readonly STRAIGHT_FLUSH: i32 = 9;
173
+ static readonly ROYAL_FLUSH: i32 = 10;
174
+ }
175
+
176
+ /**
177
+ * HandRank represents the strength of a poker hand
178
+ */
179
+ export class HandRank {
180
+ handType: i32;
181
+ kickers: i32[]; // Sorted descending, used for tie-breaking
182
+
183
+ constructor(handType: i32, kickers: i32[] = []) {
184
+ this.handType = handType;
185
+ this.kickers = kickers;
186
+ }
187
+
188
+ /**
189
+ * Compare two hand ranks
190
+ * Returns: -1 if this < other, 0 if equal, 1 if this > other
191
+ */
192
+ compare(other: HandRank): i32 {
193
+ if (this.handType < other.handType) return -1;
194
+ if (this.handType > other.handType) return 1;
195
+
196
+ // Same hand type, compare kickers
197
+ const maxLen = this.kickers.length > other.kickers.length ? this.kickers.length : other.kickers.length;
198
+ for (let i = 0; i < maxLen; i++) {
199
+ const thisKicker = i < this.kickers.length ? this.kickers[i] : 0;
200
+ const otherKicker = i < other.kickers.length ? other.kickers[i] : 0;
201
+ if (thisKicker < otherKicker) return -1;
202
+ if (thisKicker > otherKicker) return 1;
203
+ }
204
+
205
+ return 0;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Deck class representing a standard 52-card deck
211
+ */
212
+ export class Deck {
213
+ cards: Card[];
214
+
215
+ constructor() {
216
+ this.cards = new Array<Card>(52);
217
+ let idx = 0;
218
+ for (let s = 0; s < Suit.ALL.length; s++) {
219
+ for (let r = 0; r < Rank.ALL.length; r++) {
220
+ this.cards[idx] = new Card(Suit.ALL[s], Rank.ALL[r]);
221
+ idx++;
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Create a new deck from existing cards (for deserialization)
228
+ */
229
+ static fromCards(cards: Card[]): Deck {
230
+ const deck = new Deck();
231
+ deck.cards = cards;
232
+ return deck;
233
+ }
234
+
235
+ /**
236
+ * Shuffle the deck using Fisher-Yates algorithm with deterministic randomness
237
+ */
238
+ shuffle(seed: RandomSeed): RandomSeed {
239
+ const n = this.cards.length;
240
+ let currentSeed = seed;
241
+
242
+ for (let i = n - 1; i > 0; i--) {
243
+ // Get random index from 0 to i (inclusive)
244
+ const result = getRandomIntInRange(currentSeed, 0, i + 1);
245
+ const j = <i32>result.value;
246
+ currentSeed = result.seed;
247
+
248
+ // Swap cards[i] and cards[j]
249
+ const temp = this.cards[i];
250
+ this.cards[i] = this.cards[j];
251
+ this.cards[j] = temp;
252
+ }
253
+
254
+ return currentSeed;
255
+ }
256
+
257
+ /**
258
+ * Deal one card from the top of the deck
259
+ */
260
+ dealCard(): Card | null {
261
+ if (this.cards.length === 0) return null;
262
+ return this.cards.pop();
263
+ }
264
+
265
+ /**
266
+ * Deal multiple cards
267
+ */
268
+ dealCards(count: i32): Card[] {
269
+ const dealt = new Array<Card>(0);
270
+ for (let i = 0; i < count; i++) {
271
+ const card = this.dealCard();
272
+ if (card !== null) {
273
+ dealt.push(card);
274
+ } else {
275
+ break;
276
+ }
277
+ }
278
+ return dealt;
279
+ }
280
+
281
+ /**
282
+ * Burn a card (remove without returning)
283
+ */
284
+ burnCard(): bool {
285
+ return this.dealCard() !== null;
286
+ }
287
+
288
+ /**
289
+ * Get number of cards remaining
290
+ */
291
+ size(): i32 {
292
+ return this.cards.length;
293
+ }
294
+
295
+ /**
296
+ * Check if deck is empty
297
+ */
298
+ isEmpty(): bool {
299
+ return this.cards.length === 0;
300
+ }
301
+
302
+ /**
303
+ * Get a copy of the deck
304
+ */
305
+ clone(): Deck {
306
+ const newDeck = new Deck();
307
+ newDeck.cards = new Array<Card>(this.cards.length);
308
+ for (let i = 0; i < this.cards.length; i++) {
309
+ newDeck.cards[i] = this.cards[i];
310
+ }
311
+ return newDeck;
312
+ }
313
+ }
314
+