@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,410 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Tests for multi-deck shoe functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
|
|
7
|
+
import {
|
|
8
|
+
ShoeConfig,
|
|
9
|
+
DeckConfig,
|
|
10
|
+
dealCardFromShoe,
|
|
11
|
+
CardIndexMapper
|
|
12
|
+
} from "../../deck/deck";
|
|
13
|
+
import { Card, Suit, Rank } from "../../cards";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// ShoeConfig Tests
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
describe("ShoeConfig", () => {
|
|
20
|
+
test("should create single deck shoe", () => {
|
|
21
|
+
const shoe = ShoeConfig.standard(1);
|
|
22
|
+
expect(shoe.numDecks).equal(1);
|
|
23
|
+
expect(shoe.deckConfig.totalCards).equal(52);
|
|
24
|
+
expect(shoe.getShoeSize()).equal(52);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should create 2-deck shoe", () => {
|
|
28
|
+
const shoe = ShoeConfig.standard(2);
|
|
29
|
+
expect(shoe.numDecks).equal(2);
|
|
30
|
+
expect(shoe.deckConfig.totalCards).equal(52);
|
|
31
|
+
expect(shoe.getShoeSize()).equal(104); // 2 * 52
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should create 4-deck shoe", () => {
|
|
35
|
+
const shoe = ShoeConfig.standard(4);
|
|
36
|
+
expect(shoe.numDecks).equal(4);
|
|
37
|
+
expect(shoe.deckConfig.totalCards).equal(52);
|
|
38
|
+
expect(shoe.getShoeSize()).equal(208); // 4 * 52
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should create 6-deck shoe", () => {
|
|
42
|
+
const shoe = ShoeConfig.standard(6);
|
|
43
|
+
expect(shoe.numDecks).equal(6);
|
|
44
|
+
expect(shoe.deckConfig.totalCards).equal(52);
|
|
45
|
+
expect(shoe.getShoeSize()).equal(312); // 6 * 52
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should create 8-deck shoe", () => {
|
|
49
|
+
const shoe = ShoeConfig.standard(8);
|
|
50
|
+
expect(shoe.numDecks).equal(8);
|
|
51
|
+
expect(shoe.deckConfig.totalCards).equal(52);
|
|
52
|
+
expect(shoe.getShoeSize()).equal(416); // 8 * 52
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should create custom number of decks", () => {
|
|
56
|
+
const shoe = ShoeConfig.standard(10);
|
|
57
|
+
expect(shoe.numDecks).equal(10);
|
|
58
|
+
expect(shoe.getShoeSize()).equal(520); // 10 * 52
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("should create Spanish 21 with any number of decks", () => {
|
|
62
|
+
const shoe1 = ShoeConfig.spanish21(1);
|
|
63
|
+
expect(shoe1.numDecks).equal(1);
|
|
64
|
+
expect(shoe1.getShoeSize()).equal(48); // 1 * 48
|
|
65
|
+
|
|
66
|
+
const shoe6 = ShoeConfig.spanish21(6);
|
|
67
|
+
expect(shoe6.numDecks).equal(6);
|
|
68
|
+
expect(shoe6.getShoeSize()).equal(288); // 6 * 48
|
|
69
|
+
|
|
70
|
+
const shoe12 = ShoeConfig.spanish21(12);
|
|
71
|
+
expect(shoe12.numDecks).equal(12);
|
|
72
|
+
expect(shoe12.getShoeSize()).equal(576); // 12 * 48
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("should create shoe with custom deck config using withDeck", () => {
|
|
76
|
+
const deckConfig = DeckConfig.standard();
|
|
77
|
+
const shoe = ShoeConfig.withDeck(deckConfig, 4);
|
|
78
|
+
expect(shoe.numDecks).equal(4);
|
|
79
|
+
expect(shoe.getShoeSize()).equal(208); // 4 * 52
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("should create Spanish 21 shoe with withDeck", () => {
|
|
83
|
+
const spanishDeck = DeckConfig.spanish21();
|
|
84
|
+
const shoe = ShoeConfig.withDeck(spanishDeck, 6);
|
|
85
|
+
expect(shoe.numDecks).equal(6);
|
|
86
|
+
expect(shoe.deckConfig.totalCards).equal(48);
|
|
87
|
+
expect(shoe.getShoeSize()).equal(288); // 6 * 48
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("should support backward compatibility with custom method", () => {
|
|
91
|
+
const deckConfig = DeckConfig.standard();
|
|
92
|
+
const shoe = ShoeConfig.custom(deckConfig, 4);
|
|
93
|
+
expect(shoe.numDecks).equal(4);
|
|
94
|
+
expect(shoe.getShoeSize()).equal(208); // 4 * 52
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should create shoe using constructor directly", () => {
|
|
98
|
+
const deckConfig = DeckConfig.standard();
|
|
99
|
+
const shoe = new ShoeConfig(deckConfig, 3);
|
|
100
|
+
expect(shoe.numDecks).equal(3);
|
|
101
|
+
expect(shoe.getShoeSize()).equal(156); // 3 * 52
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Backward compatibility tests
|
|
105
|
+
test("should support deprecated convenience methods", () => {
|
|
106
|
+
const single = ShoeConfig.singleDeck();
|
|
107
|
+
expect(single.numDecks).equal(1);
|
|
108
|
+
|
|
109
|
+
const six = ShoeConfig.sixDeck();
|
|
110
|
+
expect(six.numDecks).equal(6);
|
|
111
|
+
|
|
112
|
+
const eight = ShoeConfig.eightDeck();
|
|
113
|
+
expect(eight.numDecks).equal(8);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// createUnshuffledShoeIndices Tests
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
describe("createUnshuffledShoeIndices", () => {
|
|
122
|
+
test("should create indices for single deck shoe", () => {
|
|
123
|
+
const shoe = ShoeConfig.singleDeck();
|
|
124
|
+
const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
|
|
125
|
+
|
|
126
|
+
expect(indices.length).equal(52);
|
|
127
|
+
// First card should be 2 of Spades (index 0)
|
|
128
|
+
expect(indices[0]).equal(0);
|
|
129
|
+
// Last card should be Ace of Clubs (index 51)
|
|
130
|
+
expect(indices[51]).equal(51);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("should create indices for 6-deck shoe", () => {
|
|
134
|
+
const shoe = ShoeConfig.sixDeck();
|
|
135
|
+
const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
|
|
136
|
+
|
|
137
|
+
expect(indices.length).equal(312); // 6 * 52
|
|
138
|
+
|
|
139
|
+
// Each deck should have the same card order
|
|
140
|
+
// First deck: indices 0-51
|
|
141
|
+
expect(indices[0]).equal(0); // 2♠
|
|
142
|
+
expect(indices[51]).equal(51); // A♣
|
|
143
|
+
|
|
144
|
+
// Second deck: indices 52-103 (same order)
|
|
145
|
+
expect(indices[52]).equal(0); // 2♠
|
|
146
|
+
expect(indices[103]).equal(51); // A♣
|
|
147
|
+
|
|
148
|
+
// Third deck: indices 104-155
|
|
149
|
+
expect(indices[104]).equal(0); // 2♠
|
|
150
|
+
expect(indices[155]).equal(51); // A♣
|
|
151
|
+
|
|
152
|
+
// Sixth deck: indices 260-311
|
|
153
|
+
expect(indices[260]).equal(0); // 2♠
|
|
154
|
+
expect(indices[311]).equal(51); // A♣
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("should create indices for 8-deck shoe", () => {
|
|
158
|
+
const shoe = ShoeConfig.eightDeck();
|
|
159
|
+
const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
|
|
160
|
+
|
|
161
|
+
expect(indices.length).equal(416); // 8 * 52
|
|
162
|
+
|
|
163
|
+
// Verify each deck has same order
|
|
164
|
+
for (let deck = 0; deck < 8; deck++) {
|
|
165
|
+
const deckStart = deck * 52;
|
|
166
|
+
expect(indices[deckStart]).equal(0); // First card of each deck
|
|
167
|
+
expect(indices[deckStart + 51]).equal(51); // Last card of each deck
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("should create indices for Spanish 21 6-deck shoe", () => {
|
|
172
|
+
const shoe = ShoeConfig.spanish21SixDeck();
|
|
173
|
+
const indices = CardIndexMapper.createUnshuffledShoeIndices(shoe);
|
|
174
|
+
|
|
175
|
+
expect(indices.length).equal(288); // 6 * 48
|
|
176
|
+
|
|
177
|
+
// Verify structure
|
|
178
|
+
for (let deck = 0; deck < 6; deck++) {
|
|
179
|
+
const deckStart = deck * 48;
|
|
180
|
+
expect(indices[deckStart]).equal(0); // First card of each deck
|
|
181
|
+
expect(indices[deckStart + 47]).equal(47); // Last card of each deck
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// dealCardFromShoe Tests
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
describe("dealCardFromShoe", () => {
|
|
191
|
+
test("should deal cards from 6-deck shoe", () => {
|
|
192
|
+
const shoe = ShoeConfig.sixDeck();
|
|
193
|
+
const shuffleId = "test-shuffle-6deck";
|
|
194
|
+
const shuffleSalt = "test-salt";
|
|
195
|
+
|
|
196
|
+
// Deal first card
|
|
197
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
|
|
198
|
+
expect(card1.suit.length).greaterThan(0); // Verify card is valid
|
|
199
|
+
|
|
200
|
+
// Deal second card
|
|
201
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 1, shoe);
|
|
202
|
+
expect(card2.suit.length).greaterThan(0); // Verify card is valid
|
|
203
|
+
|
|
204
|
+
// Cards should be different (shuffled)
|
|
205
|
+
expect(card1.equals(card2)).equal(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("should deal cards from 8-deck shoe", () => {
|
|
209
|
+
const shoe = ShoeConfig.eightDeck();
|
|
210
|
+
const shuffleId = "test-shuffle-8deck";
|
|
211
|
+
const shuffleSalt = "test-salt";
|
|
212
|
+
|
|
213
|
+
// Deal multiple cards
|
|
214
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
|
|
215
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 1, shoe);
|
|
216
|
+
const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 2, shoe);
|
|
217
|
+
|
|
218
|
+
expect(card1.suit.length).greaterThan(0);
|
|
219
|
+
expect(card2.suit.length).greaterThan(0);
|
|
220
|
+
expect(card3.suit.length).greaterThan(0);
|
|
221
|
+
|
|
222
|
+
// All should be different
|
|
223
|
+
expect(card1.equals(card2)).equal(false);
|
|
224
|
+
expect(card2.equals(card3)).equal(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("should handle shoe exhaustion and reshuffle", () => {
|
|
228
|
+
const shoe = ShoeConfig.sixDeck();
|
|
229
|
+
const shuffleId = "test-shuffle-exhaust";
|
|
230
|
+
const shuffleSalt = "test-salt";
|
|
231
|
+
const shoeSize = shoe.getShoeSize(); // 312
|
|
232
|
+
|
|
233
|
+
// Deal last card of first shoe
|
|
234
|
+
const lastCard = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
|
|
235
|
+
expect(lastCard.suit.length).greaterThan(0);
|
|
236
|
+
|
|
237
|
+
// Deal first card of second shoe (reshuffled)
|
|
238
|
+
const firstCardNewShoe = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
|
|
239
|
+
expect(firstCardNewShoe.suit.length).greaterThan(0);
|
|
240
|
+
|
|
241
|
+
// The new shoe should be deterministically shuffled
|
|
242
|
+
// (same shuffleId/salt but different iteration)
|
|
243
|
+
const firstCardNewShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
|
|
244
|
+
expect(firstCardNewShoe.equals(firstCardNewShoe2)).equal(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("should deal deterministically from same position", () => {
|
|
248
|
+
const shoe = ShoeConfig.sixDeck();
|
|
249
|
+
const shuffleId = "test-deterministic";
|
|
250
|
+
const shuffleSalt = "test-salt";
|
|
251
|
+
|
|
252
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 50, shoe);
|
|
253
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 50, shoe);
|
|
254
|
+
|
|
255
|
+
// Same position should yield same card
|
|
256
|
+
expect(card1.equals(card2)).equal(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("should handle large shoe positions", () => {
|
|
260
|
+
const shoe = ShoeConfig.eightDeck();
|
|
261
|
+
const shuffleId = "test-large-position";
|
|
262
|
+
const shuffleSalt = "test-salt";
|
|
263
|
+
|
|
264
|
+
// Deal from middle of shoe
|
|
265
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 200, shoe);
|
|
266
|
+
expect(card1.suit.length).greaterThan(0);
|
|
267
|
+
|
|
268
|
+
// Deal from near end of shoe
|
|
269
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 410, shoe);
|
|
270
|
+
expect(card2.suit.length).greaterThan(0);
|
|
271
|
+
|
|
272
|
+
// Deal from second shoe iteration
|
|
273
|
+
const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 500, shoe);
|
|
274
|
+
expect(card3.suit.length).greaterThan(0);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("should work with Spanish 21 6-deck shoe", () => {
|
|
278
|
+
const shoe = ShoeConfig.spanish21SixDeck();
|
|
279
|
+
const shuffleId = "test-spanish-6deck";
|
|
280
|
+
const shuffleSalt = "test-salt";
|
|
281
|
+
|
|
282
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
|
|
283
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, 100, shoe);
|
|
284
|
+
const card3 = dealCardFromShoe(shuffleId, shuffleSalt, 200, shoe);
|
|
285
|
+
|
|
286
|
+
expect(card1.suit.length).greaterThan(0);
|
|
287
|
+
expect(card2.suit.length).greaterThan(0);
|
|
288
|
+
expect(card3.suit.length).greaterThan(0);
|
|
289
|
+
|
|
290
|
+
// Verify no 10s in Spanish 21
|
|
291
|
+
expect(card1.rank == Rank.TEN).equal(false);
|
|
292
|
+
expect(card2.rank == Rank.TEN).equal(false);
|
|
293
|
+
expect(card3.rank == Rank.TEN).equal(false);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("should handle multiple shoe iterations", () => {
|
|
297
|
+
const shoe = ShoeConfig.sixDeck();
|
|
298
|
+
const shuffleId = "test-iterations";
|
|
299
|
+
const shuffleSalt = "test-salt";
|
|
300
|
+
const shoeSize = shoe.getShoeSize(); // 312
|
|
301
|
+
|
|
302
|
+
// First shoe iteration
|
|
303
|
+
const card1 = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
|
|
304
|
+
const card2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
|
|
305
|
+
|
|
306
|
+
// Second shoe iteration (reshuffled)
|
|
307
|
+
const card3 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
|
|
308
|
+
const card4 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize * 2 - 1, shoe);
|
|
309
|
+
|
|
310
|
+
// Third shoe iteration
|
|
311
|
+
const card5 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize * 2, shoe);
|
|
312
|
+
|
|
313
|
+
expect(card1.suit.length).greaterThan(0);
|
|
314
|
+
expect(card2.suit.length).greaterThan(0);
|
|
315
|
+
expect(card3.suit.length).greaterThan(0);
|
|
316
|
+
expect(card4.suit.length).greaterThan(0);
|
|
317
|
+
expect(card5.suit.length).greaterThan(0);
|
|
318
|
+
|
|
319
|
+
// Each iteration should be deterministically shuffled
|
|
320
|
+
const card1Again = dealCardFromShoe(shuffleId, shuffleSalt, 0, shoe);
|
|
321
|
+
expect(card1.equals(card1Again)).equal(true);
|
|
322
|
+
|
|
323
|
+
const card3Again = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
|
|
324
|
+
expect(card3.equals(card3Again)).equal(true);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("should track position correctly across multiple deals", () => {
|
|
328
|
+
const shoe = ShoeConfig.eightDeck();
|
|
329
|
+
const shuffleId = "test-position-tracking";
|
|
330
|
+
const shuffleSalt = "test-salt";
|
|
331
|
+
|
|
332
|
+
// Simulate dealing multiple cards in sequence
|
|
333
|
+
const cards = new Array<Card>(10);
|
|
334
|
+
for (let i = 0; i < 10; i++) {
|
|
335
|
+
cards[i] = dealCardFromShoe(shuffleId, shuffleSalt, i, shoe);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// All cards should be different (shuffled shoe)
|
|
339
|
+
for (let i = 0; i < 10; i++) {
|
|
340
|
+
for (let j = i + 1; j < 10; j++) {
|
|
341
|
+
// Cards might be the same by chance, but very unlikely in shuffled shoe
|
|
342
|
+
// We just verify they're valid cards
|
|
343
|
+
expect(cards[i].suit.length).greaterThan(0);
|
|
344
|
+
expect(cards[j].suit.length).greaterThan(0);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ============================================================================
|
|
351
|
+
// Shoe Position Persistence Tests
|
|
352
|
+
// ============================================================================
|
|
353
|
+
|
|
354
|
+
describe("Shoe Position Persistence", () => {
|
|
355
|
+
test("should resume from saved position", () => {
|
|
356
|
+
const shoe = ShoeConfig.sixDeck();
|
|
357
|
+
const shuffleId = "test-resume";
|
|
358
|
+
const shuffleSalt = "test-salt";
|
|
359
|
+
|
|
360
|
+
// Deal 100 cards
|
|
361
|
+
const cardsBefore = new Array<Card>(100);
|
|
362
|
+
for (let i = 0; i < 100; i++) {
|
|
363
|
+
cardsBefore[i] = dealCardFromShoe(shuffleId, shuffleSalt, i, shoe);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// "Save" position at 100
|
|
367
|
+
const savedPosition = 100;
|
|
368
|
+
|
|
369
|
+
// "Resume" from saved position
|
|
370
|
+
const cardsAfter = new Array<Card>(10);
|
|
371
|
+
for (let i = 0; i < 10; i++) {
|
|
372
|
+
cardsAfter[i] = dealCardFromShoe(shuffleId, shuffleSalt, savedPosition + i, shoe);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Cards after resume should match what we would have gotten
|
|
376
|
+
const expectedCards = new Array<Card>(10);
|
|
377
|
+
for (let i = 0; i < 10; i++) {
|
|
378
|
+
expectedCards[i] = dealCardFromShoe(shuffleId, shuffleSalt, 100 + i, shoe);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < 10; i++) {
|
|
382
|
+
expect(cardsAfter[i].equals(expectedCards[i])).equal(true);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("should handle position across shoe boundaries", () => {
|
|
387
|
+
const shoe = ShoeConfig.sixDeck();
|
|
388
|
+
const shuffleId = "test-boundary";
|
|
389
|
+
const shuffleSalt = "test-salt";
|
|
390
|
+
const shoeSize = shoe.getShoeSize(); // 312
|
|
391
|
+
|
|
392
|
+
// Deal last few cards of first shoe
|
|
393
|
+
const lastCardShoe1 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize - 1, shoe);
|
|
394
|
+
|
|
395
|
+
// Deal first card of second shoe
|
|
396
|
+
const firstCardShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize, shoe);
|
|
397
|
+
|
|
398
|
+
// Deal second card of second shoe
|
|
399
|
+
const secondCardShoe2 = dealCardFromShoe(shuffleId, shuffleSalt, shoeSize + 1, shoe);
|
|
400
|
+
|
|
401
|
+
expect(lastCardShoe1.suit.length).greaterThan(0);
|
|
402
|
+
expect(firstCardShoe2.suit.length).greaterThan(0);
|
|
403
|
+
expect(secondCardShoe2.suit.length).greaterThan(0);
|
|
404
|
+
|
|
405
|
+
// All should be different
|
|
406
|
+
expect(lastCardShoe1.equals(firstCardShoe2)).equal(false);
|
|
407
|
+
expect(firstCardShoe2.equals(secondCardShoe2)).equal(false);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Tests for BettingRoundState class
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
|
|
7
|
+
import { BettingRoundState } from "../../poker/poker_game_types";
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// BettingRoundState Tests
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
describe("BettingRoundState", () => {
|
|
14
|
+
test("should initialize with empty contributions", () => {
|
|
15
|
+
const state = new BettingRoundState();
|
|
16
|
+
|
|
17
|
+
expect(state.getContributionThisRound(0)).equal(0);
|
|
18
|
+
expect(state.getTotalContribution(0)).equal(0);
|
|
19
|
+
expect(state.currentBetToMatch).equal(0);
|
|
20
|
+
expect(state.actingSeatId).equal(-1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should track contributions this round", () => {
|
|
24
|
+
const state = new BettingRoundState();
|
|
25
|
+
|
|
26
|
+
state.contribThisRound.set(0, 50);
|
|
27
|
+
state.contribThisRound.set(1, 100);
|
|
28
|
+
|
|
29
|
+
expect(state.getContributionThisRound(0)).equal(50);
|
|
30
|
+
expect(state.getContributionThisRound(1)).equal(100);
|
|
31
|
+
expect(state.getContributionThisRound(2)).equal(0); // Not set
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should track total contributions", () => {
|
|
35
|
+
const state = new BettingRoundState();
|
|
36
|
+
|
|
37
|
+
state.contribTotal.set(0, 150);
|
|
38
|
+
state.contribTotal.set(1, 200);
|
|
39
|
+
|
|
40
|
+
expect(state.getTotalContribution(0)).equal(150);
|
|
41
|
+
expect(state.getTotalContribution(1)).equal(200);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should calculate amount to call", () => {
|
|
45
|
+
const state = new BettingRoundState();
|
|
46
|
+
state.currentBetToMatch = 100;
|
|
47
|
+
state.contribThisRound.set(0, 50);
|
|
48
|
+
state.contribThisRound.set(1, 100);
|
|
49
|
+
|
|
50
|
+
expect(state.calculateToCall(0)).equal(50); // 100 - 50
|
|
51
|
+
expect(state.calculateToCall(1)).equal(0); // Already matched
|
|
52
|
+
expect(state.calculateToCall(2)).equal(100); // Hasn't contributed
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should reset round state", () => {
|
|
56
|
+
const state = new BettingRoundState();
|
|
57
|
+
state.currentBetToMatch = 100;
|
|
58
|
+
state.lastFullRaiseSize = 50;
|
|
59
|
+
state.lastAggressorSeatId = 2;
|
|
60
|
+
state.actingSeatId = 1;
|
|
61
|
+
state.contribThisRound.set(0, 50);
|
|
62
|
+
state.contribThisRound.set(1, 100);
|
|
63
|
+
// Set total contributions to verify they remain after reset
|
|
64
|
+
state.contribTotal.set(0, 50);
|
|
65
|
+
state.contribTotal.set(1, 100);
|
|
66
|
+
|
|
67
|
+
state.resetRound();
|
|
68
|
+
|
|
69
|
+
expect(state.currentBetToMatch).equal(0);
|
|
70
|
+
expect(state.lastFullRaiseSize).equal(0);
|
|
71
|
+
expect(state.lastAggressorSeatId).equal(-1);
|
|
72
|
+
expect(state.actingSeatId).equal(-1);
|
|
73
|
+
expect(state.getContributionThisRound(0)).equal(0);
|
|
74
|
+
expect(state.getContributionThisRound(1)).equal(0);
|
|
75
|
+
// Total contributions should remain (not cleared by resetRound)
|
|
76
|
+
expect(state.getTotalContribution(0)).equal(50);
|
|
77
|
+
expect(state.getTotalContribution(1)).equal(100);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("should clone correctly", () => {
|
|
81
|
+
const state = new BettingRoundState();
|
|
82
|
+
state.currentBetToMatch = 100;
|
|
83
|
+
state.lastFullRaiseSize = 50;
|
|
84
|
+
state.lastAggressorSeatId = 2;
|
|
85
|
+
state.actingSeatId = 1;
|
|
86
|
+
state.contribThisRound.set(0, 50);
|
|
87
|
+
state.contribTotal.set(0, 150);
|
|
88
|
+
|
|
89
|
+
const cloned = state.clone();
|
|
90
|
+
|
|
91
|
+
expect(cloned.currentBetToMatch).equal(100);
|
|
92
|
+
expect(cloned.lastFullRaiseSize).equal(50);
|
|
93
|
+
expect(cloned.lastAggressorSeatId).equal(2);
|
|
94
|
+
expect(cloned.actingSeatId).equal(1);
|
|
95
|
+
expect(cloned.getContributionThisRound(0)).equal(50);
|
|
96
|
+
expect(cloned.getTotalContribution(0)).equal(150);
|
|
97
|
+
|
|
98
|
+
// Modifying clone shouldn't affect original
|
|
99
|
+
cloned.currentBetToMatch = 200;
|
|
100
|
+
expect(state.currentBetToMatch).equal(100);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Tests for Omaha Poker showdown evaluator
|
|
4
|
+
*
|
|
5
|
+
* In Omaha, players receive 4 hole cards and must use exactly 2 of them
|
|
6
|
+
* combined with exactly 3 community cards to make their best 5-card hand.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
|
|
10
|
+
import { Card, Suit, Rank, HandType } from "../../cards";
|
|
11
|
+
import { OmahaShowdownEvaluator } from "../../poker/omaha_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("OmahaShowdownEvaluator", () => {
|
|
19
|
+
test("should require exactly 2 hole cards and 3 community cards", () => {
|
|
20
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
21
|
+
|
|
22
|
+
// 4 hole cards
|
|
23
|
+
const holeCards = new Array<Card>(4);
|
|
24
|
+
holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
25
|
+
holeCards[1] = createCard(Rank.ACE, Suit.HEARTS);
|
|
26
|
+
holeCards[2] = createCard(Rank.SEVEN, Suit.SPADES);
|
|
27
|
+
holeCards[3] = createCard(Rank.SIX, Suit.HEARTS);
|
|
28
|
+
|
|
29
|
+
// 5 community cards (mixed suits and non-consecutive ranks to avoid flush/straight)
|
|
30
|
+
const community = new Array<Card>(5);
|
|
31
|
+
community[0] = createCard(Rank.KING, Suit.DIAMONDS);
|
|
32
|
+
community[1] = createCard(Rank.QUEEN, Suit.CLUBS);
|
|
33
|
+
community[2] = createCard(Rank.JACK, Suit.HEARTS);
|
|
34
|
+
community[3] = createCard(Rank.NINE, Suit.DIAMONDS);
|
|
35
|
+
community[4] = createCard(Rank.EIGHT, Suit.CLUBS);
|
|
36
|
+
|
|
37
|
+
const rank = evaluator.evaluateHand(holeCards, community);
|
|
38
|
+
|
|
39
|
+
// Should evaluate using best combination of 2 hole + 3 community
|
|
40
|
+
// Best would be A♠ A♥ + K♦ Q♣ J♥ = Pair of Aces with K, Q, J kickers
|
|
41
|
+
expect(rank.handType).equal(HandType.PAIR);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should find best combination of 2 hole + 3 community", () => {
|
|
45
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
46
|
+
|
|
47
|
+
// Hole cards: A♠ K♠ Q♥ J♥
|
|
48
|
+
const holeCards = new Array<Card>(4);
|
|
49
|
+
holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
50
|
+
holeCards[1] = createCard(Rank.KING, Suit.SPADES);
|
|
51
|
+
holeCards[2] = createCard(Rank.QUEEN, Suit.HEARTS);
|
|
52
|
+
holeCards[3] = createCard(Rank.JACK, Suit.HEARTS);
|
|
53
|
+
|
|
54
|
+
// Community: 10♠ 9♠ 8♠ 7♠ 6♠ (all spades)
|
|
55
|
+
const community = new Array<Card>(5);
|
|
56
|
+
community[0] = createCard(Rank.TEN, Suit.SPADES);
|
|
57
|
+
community[1] = createCard(Rank.NINE, Suit.SPADES);
|
|
58
|
+
community[2] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
59
|
+
community[3] = createCard(Rank.SEVEN, Suit.SPADES);
|
|
60
|
+
community[4] = createCard(Rank.SIX, Suit.SPADES);
|
|
61
|
+
|
|
62
|
+
// Best combination: A♠ K♠ + 10♠ 9♠ 8♠ = Flush (A♠ K♠ 10♠ 9♠ 8♠)
|
|
63
|
+
const rank = evaluator.evaluateHand(holeCards, community);
|
|
64
|
+
|
|
65
|
+
expect(rank.handType).equal(HandType.FLUSH);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("should return best 5 cards from 2 hole + 3 community", () => {
|
|
69
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
70
|
+
|
|
71
|
+
const holeCards = new Array<Card>(4);
|
|
72
|
+
holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
73
|
+
holeCards[1] = createCard(Rank.KING, Suit.SPADES);
|
|
74
|
+
holeCards[2] = createCard(Rank.QUEEN, Suit.HEARTS);
|
|
75
|
+
holeCards[3] = createCard(Rank.JACK, Suit.HEARTS);
|
|
76
|
+
|
|
77
|
+
const community = new Array<Card>(5);
|
|
78
|
+
community[0] = createCard(Rank.TEN, Suit.SPADES);
|
|
79
|
+
community[1] = createCard(Rank.NINE, Suit.SPADES);
|
|
80
|
+
community[2] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
81
|
+
community[3] = createCard(Rank.SEVEN, Suit.HEARTS);
|
|
82
|
+
community[4] = createCard(Rank.SIX, Suit.HEARTS);
|
|
83
|
+
|
|
84
|
+
const bestHand = evaluator.getBestFiveCards(holeCards, community);
|
|
85
|
+
|
|
86
|
+
expect(bestHand.length).equal(5);
|
|
87
|
+
// Should be A♠ K♠ 10♠ 9♠ 8♠ (flush) or A♠ K♠ Q♥ J♥ 10♠ (straight)
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("should compare multiple Omaha hands", () => {
|
|
91
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
92
|
+
|
|
93
|
+
// Player 0: 4 hole cards that can make a flush
|
|
94
|
+
const holeCards0 = new Array<Card>(4);
|
|
95
|
+
holeCards0[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
96
|
+
holeCards0[1] = createCard(Rank.KING, Suit.SPADES);
|
|
97
|
+
holeCards0[2] = createCard(Rank.QUEEN, Suit.HEARTS);
|
|
98
|
+
holeCards0[3] = createCard(Rank.JACK, Suit.HEARTS);
|
|
99
|
+
|
|
100
|
+
// Player 1: 4 hole cards that can make a straight
|
|
101
|
+
const holeCards1 = new Array<Card>(4);
|
|
102
|
+
holeCards1[0] = createCard(Rank.ACE, Suit.HEARTS);
|
|
103
|
+
holeCards1[1] = createCard(Rank.KING, Suit.HEARTS);
|
|
104
|
+
holeCards1[2] = createCard(Rank.QUEEN, Suit.DIAMONDS);
|
|
105
|
+
holeCards1[3] = createCard(Rank.JACK, Suit.DIAMONDS);
|
|
106
|
+
|
|
107
|
+
// Community: 10♠ 9♠ 8♠ 7♠ 6♠
|
|
108
|
+
const community = new Array<Card>(5);
|
|
109
|
+
community[0] = createCard(Rank.TEN, Suit.SPADES);
|
|
110
|
+
community[1] = createCard(Rank.NINE, Suit.SPADES);
|
|
111
|
+
community[2] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
112
|
+
community[3] = createCard(Rank.SEVEN, Suit.SPADES);
|
|
113
|
+
community[4] = createCard(Rank.SIX, Suit.SPADES);
|
|
114
|
+
|
|
115
|
+
const holeCardsMap = new Map<i32, Card[]>();
|
|
116
|
+
holeCardsMap.set(0, holeCards0);
|
|
117
|
+
holeCardsMap.set(1, holeCards1);
|
|
118
|
+
|
|
119
|
+
const result = evaluator.compareHandsShowdown(holeCardsMap, community);
|
|
120
|
+
|
|
121
|
+
// Player 0 should win with Flush (A♠ K♠ + 10♠ 9♠ 8♠)
|
|
122
|
+
expect(result.winners.length).equal(1);
|
|
123
|
+
expect(result.winners[0]).equal(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should handle full house in Omaha", () => {
|
|
127
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
128
|
+
|
|
129
|
+
// Hole cards: A♠ A♥ K♠ Q♠ (two Aces, one King, one Queen)
|
|
130
|
+
const holeCards = new Array<Card>(4);
|
|
131
|
+
holeCards[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
132
|
+
holeCards[1] = createCard(Rank.ACE, Suit.HEARTS);
|
|
133
|
+
holeCards[2] = createCard(Rank.KING, Suit.SPADES);
|
|
134
|
+
holeCards[3] = createCard(Rank.QUEEN, Suit.SPADES);
|
|
135
|
+
|
|
136
|
+
// Community: A♦ K♦ K♣ J♠ 7♠ (one Ace, two Kings, mixed suits to avoid flush/straight)
|
|
137
|
+
const community = new Array<Card>(5);
|
|
138
|
+
community[0] = createCard(Rank.ACE, Suit.DIAMONDS);
|
|
139
|
+
community[1] = createCard(Rank.KING, Suit.DIAMONDS);
|
|
140
|
+
community[2] = createCard(Rank.KING, Suit.CLUBS); // Second King for full house
|
|
141
|
+
community[3] = createCard(Rank.JACK, Suit.CLUBS);
|
|
142
|
+
community[4] = createCard(Rank.SEVEN, Suit.HEARTS); // Non-consecutive to break straight
|
|
143
|
+
|
|
144
|
+
// Best: A♠ A♥ (hole) + A♦ K♦ K♣ (community) = Full House (Aces over Kings)
|
|
145
|
+
const rank = evaluator.evaluateHand(holeCards, community);
|
|
146
|
+
|
|
147
|
+
expect(rank.handType).equal(HandType.FULL_HOUSE);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("should handle invalid hand structure gracefully", () => {
|
|
151
|
+
const evaluator = new OmahaShowdownEvaluator();
|
|
152
|
+
|
|
153
|
+
// Wrong number of hole cards
|
|
154
|
+
const wrongHole = new Array<Card>(2);
|
|
155
|
+
wrongHole[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
156
|
+
wrongHole[1] = createCard(Rank.KING, Suit.SPADES);
|
|
157
|
+
|
|
158
|
+
const community = new Array<Card>(5);
|
|
159
|
+
community[0] = createCard(Rank.QUEEN, Suit.SPADES);
|
|
160
|
+
community[1] = createCard(Rank.JACK, Suit.SPADES);
|
|
161
|
+
community[2] = createCard(Rank.TEN, Suit.SPADES);
|
|
162
|
+
community[3] = createCard(Rank.NINE, Suit.SPADES);
|
|
163
|
+
community[4] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
164
|
+
|
|
165
|
+
const rank = evaluator.evaluateHand(wrongHole, community);
|
|
166
|
+
|
|
167
|
+
// Should return high card as fallback
|
|
168
|
+
expect(rank.handType).equal(HandType.HIGH_CARD);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|