@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,152 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Tests for Six-Plus Hold'em showdown evaluator
|
|
4
|
+
*
|
|
5
|
+
* Six-Plus Hold'em uses a 36-card deck and different hand rankings:
|
|
6
|
+
* - Flush beats Full House
|
|
7
|
+
* - Three of a Kind beats Straight
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
|
|
11
|
+
import { Card, Suit, Rank, HandType } from "../../cards";
|
|
12
|
+
import { SixPlusShowdownEvaluator } from "../../poker/six_plus_showdown";
|
|
13
|
+
|
|
14
|
+
// Helper function to create cards
|
|
15
|
+
function createCard(rank: string, suit: string): Card {
|
|
16
|
+
return new Card(suit, rank);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("SixPlusShowdownEvaluator", () => {
|
|
20
|
+
test("should rank Flush higher than Full House", () => {
|
|
21
|
+
const evaluator = new SixPlusShowdownEvaluator();
|
|
22
|
+
|
|
23
|
+
// Flush: A♠ K♠ Q♠ J♠ 10♠
|
|
24
|
+
const flushHole = new Array<Card>(2);
|
|
25
|
+
flushHole[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
26
|
+
flushHole[1] = createCard(Rank.KING, Suit.SPADES);
|
|
27
|
+
const flushCommunity = new Array<Card>(5);
|
|
28
|
+
flushCommunity[0] = createCard(Rank.QUEEN, Suit.SPADES);
|
|
29
|
+
flushCommunity[1] = createCard(Rank.JACK, Suit.SPADES);
|
|
30
|
+
flushCommunity[2] = createCard(Rank.TEN, Suit.SPADES);
|
|
31
|
+
flushCommunity[3] = createCard(Rank.NINE, Suit.SPADES);
|
|
32
|
+
flushCommunity[4] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
33
|
+
|
|
34
|
+
// Full House: A♠ A♥ A♦ K♠ K♥
|
|
35
|
+
const fullHouseHole = new Array<Card>(2);
|
|
36
|
+
fullHouseHole[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
37
|
+
fullHouseHole[1] = createCard(Rank.ACE, Suit.HEARTS);
|
|
38
|
+
const fullHouseCommunity = new Array<Card>(5);
|
|
39
|
+
fullHouseCommunity[0] = createCard(Rank.ACE, Suit.DIAMONDS);
|
|
40
|
+
fullHouseCommunity[1] = createCard(Rank.KING, Suit.SPADES);
|
|
41
|
+
fullHouseCommunity[2] = createCard(Rank.KING, Suit.HEARTS);
|
|
42
|
+
fullHouseCommunity[3] = createCard(Rank.QUEEN, Suit.HEARTS);
|
|
43
|
+
fullHouseCommunity[4] = createCard(Rank.JACK, Suit.HEARTS);
|
|
44
|
+
|
|
45
|
+
const flushRank = evaluator.evaluateHand(flushHole, flushCommunity);
|
|
46
|
+
const fullHouseRank = evaluator.evaluateHand(fullHouseHole, fullHouseCommunity);
|
|
47
|
+
|
|
48
|
+
// In Six-Plus, Flush should beat Full House
|
|
49
|
+
const comparison = evaluator.compareHands(flushRank, fullHouseRank);
|
|
50
|
+
expect(comparison).equal(1); // Flush wins
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("should rank Three of a Kind higher than Straight", () => {
|
|
54
|
+
const evaluator = new SixPlusShowdownEvaluator();
|
|
55
|
+
|
|
56
|
+
// Three of a Kind: A♠ A♥ A♦ K♠ Q♠ (no flush/straight possible)
|
|
57
|
+
const tripsHole = new Array<Card>(2);
|
|
58
|
+
tripsHole[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
59
|
+
tripsHole[1] = createCard(Rank.ACE, Suit.HEARTS);
|
|
60
|
+
const tripsCommunity = new Array<Card>(5);
|
|
61
|
+
tripsCommunity[0] = createCard(Rank.ACE, Suit.DIAMONDS);
|
|
62
|
+
tripsCommunity[1] = createCard(Rank.KING, Suit.HEARTS);
|
|
63
|
+
tripsCommunity[2] = createCard(Rank.QUEEN, Suit.CLUBS);
|
|
64
|
+
tripsCommunity[3] = createCard(Rank.JACK, Suit.DIAMONDS);
|
|
65
|
+
tripsCommunity[4] = createCard(Rank.TEN, Suit.HEARTS);
|
|
66
|
+
|
|
67
|
+
// Straight: A♠ K♠ Q♠ J♠ 10♠ (but this is actually a flush, so use different setup)
|
|
68
|
+
// Straight: 9♠ 8♠ 7♠ 6♠ 5♠
|
|
69
|
+
const straightHole = new Array<Card>(2);
|
|
70
|
+
straightHole[0] = createCard(Rank.NINE, Suit.SPADES);
|
|
71
|
+
straightHole[1] = createCard(Rank.EIGHT, Suit.HEARTS);
|
|
72
|
+
const straightCommunity = new Array<Card>(5);
|
|
73
|
+
straightCommunity[0] = createCard(Rank.SEVEN, Suit.DIAMONDS);
|
|
74
|
+
straightCommunity[1] = createCard(Rank.SIX, Suit.CLUBS);
|
|
75
|
+
straightCommunity[2] = createCard(Rank.FIVE, Suit.SPADES);
|
|
76
|
+
straightCommunity[3] = createCard(Rank.FOUR, Suit.HEARTS);
|
|
77
|
+
straightCommunity[4] = createCard(Rank.TWO, Suit.HEARTS);
|
|
78
|
+
|
|
79
|
+
const tripsRank = evaluator.evaluateHand(tripsHole, tripsCommunity);
|
|
80
|
+
const straightRank = evaluator.evaluateHand(straightHole, straightCommunity);
|
|
81
|
+
|
|
82
|
+
// In Six-Plus, Three of a Kind should beat Straight
|
|
83
|
+
const comparison = evaluator.compareHands(tripsRank, straightRank);
|
|
84
|
+
expect(comparison).equal(1); // Three of a Kind wins
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("should handle showdown with multiple players", () => {
|
|
88
|
+
const evaluator = new SixPlusShowdownEvaluator();
|
|
89
|
+
|
|
90
|
+
// Player 0: Flush (should win in Six-Plus)
|
|
91
|
+
const holeCards0 = new Array<Card>(2);
|
|
92
|
+
holeCards0[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
93
|
+
holeCards0[1] = createCard(Rank.KING, Suit.SPADES);
|
|
94
|
+
|
|
95
|
+
// Player 1: Full House (should lose to Flush in Six-Plus)
|
|
96
|
+
const holeCards1 = new Array<Card>(2);
|
|
97
|
+
holeCards1[0] = createCard(Rank.ACE, Suit.HEARTS);
|
|
98
|
+
holeCards1[1] = createCard(Rank.ACE, Suit.DIAMONDS);
|
|
99
|
+
|
|
100
|
+
const community = new Array<Card>(5);
|
|
101
|
+
community[0] = createCard(Rank.QUEEN, Suit.SPADES);
|
|
102
|
+
community[1] = createCard(Rank.JACK, Suit.SPADES);
|
|
103
|
+
community[2] = createCard(Rank.TEN, Suit.SPADES);
|
|
104
|
+
community[3] = createCard(Rank.ACE, Suit.CLUBS);
|
|
105
|
+
community[4] = createCard(Rank.KING, Suit.HEARTS);
|
|
106
|
+
|
|
107
|
+
const holeCardsMap = new Map<i32, Card[]>();
|
|
108
|
+
holeCardsMap.set(0, holeCards0);
|
|
109
|
+
holeCardsMap.set(1, holeCards1);
|
|
110
|
+
|
|
111
|
+
const result = evaluator.compareHandsShowdown(holeCardsMap, community);
|
|
112
|
+
|
|
113
|
+
// Player 0 should win with Flush (beats Full House in Six-Plus)
|
|
114
|
+
expect(result.winners.length).equal(1);
|
|
115
|
+
expect(result.winners[0]).equal(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("should preserve standard rankings for other hands", () => {
|
|
119
|
+
const evaluator = new SixPlusShowdownEvaluator();
|
|
120
|
+
|
|
121
|
+
// Royal Flush should still beat everything
|
|
122
|
+
const royalFlushHole = new Array<Card>(2);
|
|
123
|
+
royalFlushHole[0] = createCard(Rank.ACE, Suit.SPADES);
|
|
124
|
+
royalFlushHole[1] = createCard(Rank.KING, Suit.SPADES);
|
|
125
|
+
const royalFlushCommunity = new Array<Card>(5);
|
|
126
|
+
royalFlushCommunity[0] = createCard(Rank.QUEEN, Suit.SPADES);
|
|
127
|
+
royalFlushCommunity[1] = createCard(Rank.JACK, Suit.SPADES);
|
|
128
|
+
royalFlushCommunity[2] = createCard(Rank.TEN, Suit.SPADES);
|
|
129
|
+
royalFlushCommunity[3] = createCard(Rank.NINE, Suit.SPADES);
|
|
130
|
+
royalFlushCommunity[4] = createCard(Rank.EIGHT, Suit.SPADES);
|
|
131
|
+
|
|
132
|
+
// Flush (high in Six-Plus, but not Royal Flush) - use lower non-consecutive cards
|
|
133
|
+
const flushHole = new Array<Card>(2);
|
|
134
|
+
flushHole[0] = createCard(Rank.ACE, Suit.HEARTS);
|
|
135
|
+
flushHole[1] = createCard(Rank.KING, Suit.HEARTS);
|
|
136
|
+
const flushCommunity = new Array<Card>(5);
|
|
137
|
+
flushCommunity[0] = createCard(Rank.QUEEN, Suit.HEARTS);
|
|
138
|
+
flushCommunity[1] = createCard(Rank.JACK, Suit.HEARTS);
|
|
139
|
+
flushCommunity[2] = createCard(Rank.SEVEN, Suit.HEARTS); // Changed from TEN to break straight
|
|
140
|
+
flushCommunity[3] = createCard(Rank.FIVE, Suit.HEARTS); // Changed from NINE to break straight
|
|
141
|
+
flushCommunity[4] = createCard(Rank.THREE, Suit.HEARTS); // Changed from SIX to break straight
|
|
142
|
+
|
|
143
|
+
const royalRank = evaluator.evaluateHand(royalFlushHole, royalFlushCommunity);
|
|
144
|
+
const flushRank = evaluator.evaluateHand(flushHole, flushCommunity);
|
|
145
|
+
|
|
146
|
+
// Royal Flush should still beat Flush
|
|
147
|
+
// Royal Flush (type 10, maps to 10 in Six-Plus) > Flush (type 6, maps to 7 in Six-Plus)
|
|
148
|
+
const comparison = evaluator.compareHands(royalRank, flushRank);
|
|
149
|
+
expect(comparison).equal(1); // Royal Flush wins
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Tests for stakes and betting utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from "assemblyscript-unittest-framework/assembly";
|
|
7
|
+
import { Stakes, AnteType, BettingRoundState, PokerSeatBase } from "../../poker/poker_game_types";
|
|
8
|
+
import {
|
|
9
|
+
calculateAnteAmount,
|
|
10
|
+
postAntes,
|
|
11
|
+
postBlinds,
|
|
12
|
+
getNextActingSeat,
|
|
13
|
+
validateBuyIn,
|
|
14
|
+
processBuyIn,
|
|
15
|
+
isBettingRoundComplete
|
|
16
|
+
} from "../../poker/poker_game_utils";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// calculateAnteAmount Tests
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
describe("calculateAnteAmount", () => {
|
|
23
|
+
test("should return 0 for NONE ante type", () => {
|
|
24
|
+
const stakes = new Stakes(10, 20, 5);
|
|
25
|
+
const ante = calculateAnteAmount(stakes, AnteType.NONE, 0.0);
|
|
26
|
+
expect(ante).equal(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should return fixed ante amount for FIXED type", () => {
|
|
30
|
+
const stakes = new Stakes(10, 20, 5);
|
|
31
|
+
const ante = calculateAnteAmount(stakes, AnteType.FIXED, 0.0);
|
|
32
|
+
expect(ante).equal(5);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("should calculate percentage ante for PERCENTAGE type", () => {
|
|
36
|
+
const stakes = new Stakes(10, 20, 0);
|
|
37
|
+
const ante = calculateAnteAmount(stakes, AnteType.PERCENTAGE, 10.0); // 10% of BB
|
|
38
|
+
expect(ante).equal(2); // 10% of 20 = 2
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should handle zero big blind for percentage ante", () => {
|
|
42
|
+
const stakes = new Stakes(10, 0, 0);
|
|
43
|
+
const ante = calculateAnteAmount(stakes, AnteType.PERCENTAGE, 10.0);
|
|
44
|
+
expect(ante).equal(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// postAntes Tests
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
describe("postAntes", () => {
|
|
53
|
+
test("should post antes for all players in hand", () => {
|
|
54
|
+
const stakes = new Stakes(10, 20, 5);
|
|
55
|
+
const bettingState = new BettingRoundState();
|
|
56
|
+
|
|
57
|
+
const seats = new Array<PokerSeatBase>(3);
|
|
58
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
59
|
+
seats[0].inHand = true;
|
|
60
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
61
|
+
seats[1].inHand = true;
|
|
62
|
+
seats[2] = new PokerSeatBase(2, null, 0); // Empty seat
|
|
63
|
+
seats[2].inHand = false;
|
|
64
|
+
|
|
65
|
+
const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
|
|
66
|
+
|
|
67
|
+
expect(result.totalAnteCollected).equal(10); // 2 players * 5 ante
|
|
68
|
+
expect(result.seats[0].stack).equal(95); // 100 - 5
|
|
69
|
+
expect(result.seats[1].stack).equal(95); // 100 - 5
|
|
70
|
+
expect(bettingState.getContributionThisRound(0)).equal(5);
|
|
71
|
+
expect(bettingState.getContributionThisRound(1)).equal(5);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("should handle all-in antes", () => {
|
|
75
|
+
const stakes = new Stakes(10, 20, 5);
|
|
76
|
+
const bettingState = new BettingRoundState();
|
|
77
|
+
|
|
78
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
79
|
+
seats[0] = new PokerSeatBase(0, "player1", 3); // Less than ante
|
|
80
|
+
seats[0].inHand = true;
|
|
81
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
82
|
+
seats[1].inHand = true;
|
|
83
|
+
|
|
84
|
+
const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
|
|
85
|
+
|
|
86
|
+
expect(result.totalAnteCollected).equal(8); // 3 + 5
|
|
87
|
+
expect(result.seats[0].stack).equal(0);
|
|
88
|
+
expect(result.seats[0].allIn).equal(true);
|
|
89
|
+
expect(result.seats[1].stack).equal(95);
|
|
90
|
+
expect(bettingState.getContributionThisRound(0)).equal(3);
|
|
91
|
+
expect(bettingState.getContributionThisRound(1)).equal(5);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should skip players not in hand", () => {
|
|
95
|
+
const stakes = new Stakes(10, 20, 5);
|
|
96
|
+
const bettingState = new BettingRoundState();
|
|
97
|
+
|
|
98
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
99
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
100
|
+
seats[0].inHand = false; // Not in hand
|
|
101
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
102
|
+
seats[1].inHand = true;
|
|
103
|
+
|
|
104
|
+
const result = postAntes(seats, stakes, AnteType.FIXED, 0.0, bettingState);
|
|
105
|
+
|
|
106
|
+
expect(result.totalAnteCollected).equal(5); // Only player2
|
|
107
|
+
expect(result.seats[0].stack).equal(100); // Unchanged
|
|
108
|
+
expect(result.seats[1].stack).equal(95);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// postBlinds Tests
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
describe("postBlinds", () => {
|
|
117
|
+
test("should post small blind and big blind", () => {
|
|
118
|
+
const stakes = new Stakes(10, 20, 0);
|
|
119
|
+
const bettingState = new BettingRoundState();
|
|
120
|
+
|
|
121
|
+
const seats = new Array<PokerSeatBase>(3);
|
|
122
|
+
seats[0] = new PokerSeatBase(0, "sb", 100);
|
|
123
|
+
seats[0].inHand = true;
|
|
124
|
+
seats[1] = new PokerSeatBase(1, "bb", 100);
|
|
125
|
+
seats[1].inHand = true;
|
|
126
|
+
seats[2] = new PokerSeatBase(2, "other", 100);
|
|
127
|
+
seats[2].inHand = true;
|
|
128
|
+
|
|
129
|
+
const result = postBlinds(seats, stakes, 0, 1, bettingState);
|
|
130
|
+
|
|
131
|
+
expect(result.seats[0].stack).equal(90); // 100 - 10 SB
|
|
132
|
+
expect(result.seats[1].stack).equal(80); // 100 - 20 BB
|
|
133
|
+
expect(result.seats[2].stack).equal(100); // Unchanged
|
|
134
|
+
expect(result.currentBetToMatch).equal(20); // BB amount
|
|
135
|
+
expect(bettingState.getContributionThisRound(0)).equal(10);
|
|
136
|
+
expect(bettingState.getContributionThisRound(1)).equal(20);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should handle all-in small blind", () => {
|
|
140
|
+
const stakes = new Stakes(10, 20, 0);
|
|
141
|
+
const bettingState = new BettingRoundState();
|
|
142
|
+
|
|
143
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
144
|
+
seats[0] = new PokerSeatBase(0, "sb", 5); // Less than SB
|
|
145
|
+
seats[0].inHand = true;
|
|
146
|
+
seats[1] = new PokerSeatBase(1, "bb", 100);
|
|
147
|
+
seats[1].inHand = true;
|
|
148
|
+
|
|
149
|
+
const result = postBlinds(seats, stakes, 0, 1, bettingState);
|
|
150
|
+
|
|
151
|
+
expect(result.seats[0].stack).equal(0);
|
|
152
|
+
expect(result.seats[0].allIn).equal(true);
|
|
153
|
+
expect(bettingState.getContributionThisRound(0)).equal(5);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("should handle all-in big blind", () => {
|
|
157
|
+
const stakes = new Stakes(10, 20, 0);
|
|
158
|
+
const bettingState = new BettingRoundState();
|
|
159
|
+
|
|
160
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
161
|
+
seats[0] = new PokerSeatBase(0, "sb", 100);
|
|
162
|
+
seats[0].inHand = true;
|
|
163
|
+
seats[1] = new PokerSeatBase(1, "bb", 15); // Less than BB
|
|
164
|
+
seats[1].inHand = true;
|
|
165
|
+
|
|
166
|
+
const result = postBlinds(seats, stakes, 0, 1, bettingState);
|
|
167
|
+
|
|
168
|
+
expect(result.seats[1].stack).equal(0);
|
|
169
|
+
expect(result.seats[1].allIn).equal(true);
|
|
170
|
+
expect(result.currentBetToMatch).equal(15); // Actual BB posted
|
|
171
|
+
expect(bettingState.getContributionThisRound(1)).equal(15);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// getNextActingSeat Tests
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
describe("getNextActingSeat", () => {
|
|
180
|
+
test("should return first to act preflop (after BB)", () => {
|
|
181
|
+
const bettingState = new BettingRoundState();
|
|
182
|
+
bettingState.currentBetToMatch = 20;
|
|
183
|
+
|
|
184
|
+
const seats = new Array<PokerSeatBase>(4);
|
|
185
|
+
seats[0] = new PokerSeatBase(0, "button", 100);
|
|
186
|
+
seats[0].inHand = true;
|
|
187
|
+
seats[1] = new PokerSeatBase(1, "sb", 100);
|
|
188
|
+
seats[1].inHand = true;
|
|
189
|
+
seats[2] = new PokerSeatBase(2, "bb", 100);
|
|
190
|
+
seats[2].inHand = true;
|
|
191
|
+
seats[3] = new PokerSeatBase(3, "utg", 100);
|
|
192
|
+
seats[3].inHand = true;
|
|
193
|
+
|
|
194
|
+
// Preflop: action starts after BB (button + 3)
|
|
195
|
+
const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
|
|
196
|
+
expect(nextSeat).equal(3); // UTG (button + 3)
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("should return first to act postflop (after button)", () => {
|
|
200
|
+
const bettingState = new BettingRoundState();
|
|
201
|
+
bettingState.currentBetToMatch = 0;
|
|
202
|
+
|
|
203
|
+
const seats = new Array<PokerSeatBase>(3);
|
|
204
|
+
seats[0] = new PokerSeatBase(0, "button", 100);
|
|
205
|
+
seats[0].inHand = true;
|
|
206
|
+
seats[1] = new PokerSeatBase(1, "sb", 100);
|
|
207
|
+
seats[1].inHand = true;
|
|
208
|
+
seats[2] = new PokerSeatBase(2, "bb", 100);
|
|
209
|
+
seats[2].inHand = true;
|
|
210
|
+
|
|
211
|
+
// Postflop: action starts after button (button + 1)
|
|
212
|
+
const nextSeat = getNextActingSeat(seats, 0, false, bettingState);
|
|
213
|
+
expect(nextSeat).equal(1); // SB (button + 1)
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("should skip folded players", () => {
|
|
217
|
+
const bettingState = new BettingRoundState();
|
|
218
|
+
bettingState.currentBetToMatch = 20;
|
|
219
|
+
|
|
220
|
+
const seats = new Array<PokerSeatBase>(3);
|
|
221
|
+
seats[0] = new PokerSeatBase(0, "button", 100);
|
|
222
|
+
seats[0].inHand = true;
|
|
223
|
+
seats[1] = new PokerSeatBase(1, "sb", 100);
|
|
224
|
+
seats[1].inHand = true;
|
|
225
|
+
seats[2] = new PokerSeatBase(2, "bb", 100);
|
|
226
|
+
seats[2].inHand = false; // Folded
|
|
227
|
+
|
|
228
|
+
const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
|
|
229
|
+
// Should wrap around to button since BB is folded
|
|
230
|
+
expect(nextSeat >= 0).equal(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("should skip all-in players", () => {
|
|
234
|
+
const bettingState = new BettingRoundState();
|
|
235
|
+
bettingState.currentBetToMatch = 20;
|
|
236
|
+
|
|
237
|
+
const seats = new Array<PokerSeatBase>(3);
|
|
238
|
+
seats[0] = new PokerSeatBase(0, "button", 100);
|
|
239
|
+
seats[0].inHand = true;
|
|
240
|
+
seats[1] = new PokerSeatBase(1, "sb", 100);
|
|
241
|
+
seats[1].inHand = true;
|
|
242
|
+
seats[1].allIn = true; // All-in
|
|
243
|
+
seats[2] = new PokerSeatBase(2, "bb", 100);
|
|
244
|
+
seats[2].inHand = true;
|
|
245
|
+
|
|
246
|
+
const nextSeat = getNextActingSeat(seats, 0, true, bettingState);
|
|
247
|
+
// Should skip all-in SB (seat 1)
|
|
248
|
+
// Preflop: button (0) + 3 = 3, but only 3 seats (0,1,2), so wraps to 0
|
|
249
|
+
// Then skips 0 (button), 1 (all-in), finds 2 (BB)
|
|
250
|
+
expect(nextSeat >= 0).equal(true);
|
|
251
|
+
expect(nextSeat !== 1).equal(true); // Should not be all-in SB
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// validateBuyIn Tests
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
describe("validateBuyIn", () => {
|
|
260
|
+
test("should validate buy-in within range", () => {
|
|
261
|
+
expect(validateBuyIn(100, 50, 200)).equal(true);
|
|
262
|
+
expect(validateBuyIn(50, 50, 200)).equal(true); // Min
|
|
263
|
+
expect(validateBuyIn(200, 50, 200)).equal(true); // Max
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("should reject buy-in below minimum", () => {
|
|
267
|
+
expect(validateBuyIn(49, 50, 200)).equal(false);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("should reject buy-in above maximum", () => {
|
|
271
|
+
expect(validateBuyIn(201, 50, 200)).equal(false);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// processBuyIn Tests
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
279
|
+
describe("processBuyIn", () => {
|
|
280
|
+
test("should process valid buy-in", () => {
|
|
281
|
+
const seat = new PokerSeatBase(0, "player1", 0);
|
|
282
|
+
const updatedSeat = processBuyIn(seat, 100, 50, 200);
|
|
283
|
+
|
|
284
|
+
expect(updatedSeat !== null).equal(true);
|
|
285
|
+
if (updatedSeat !== null) {
|
|
286
|
+
expect(updatedSeat.stack).equal(100);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("should return null for invalid buy-in", () => {
|
|
291
|
+
const seat = new PokerSeatBase(0, "player1", 0);
|
|
292
|
+
const updatedSeat = processBuyIn(seat, 30, 50, 200);
|
|
293
|
+
|
|
294
|
+
expect(updatedSeat === null).equal(true);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("should add to existing stack", () => {
|
|
298
|
+
const seat = new PokerSeatBase(0, "player1", 50);
|
|
299
|
+
const updatedSeat = processBuyIn(seat, 100, 50, 200);
|
|
300
|
+
|
|
301
|
+
expect(updatedSeat !== null).equal(true);
|
|
302
|
+
if (updatedSeat !== null) {
|
|
303
|
+
expect(updatedSeat.stack).equal(150); // 50 + 100
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// isBettingRoundComplete Tests
|
|
310
|
+
// ============================================================================
|
|
311
|
+
|
|
312
|
+
describe("isBettingRoundComplete", () => {
|
|
313
|
+
test("should return true when all players have matched bet", () => {
|
|
314
|
+
const bettingState = new BettingRoundState();
|
|
315
|
+
bettingState.currentBetToMatch = 20;
|
|
316
|
+
|
|
317
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
318
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
319
|
+
seats[0].inHand = true;
|
|
320
|
+
seats[0].hasActedThisRound = true;
|
|
321
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
322
|
+
seats[1].inHand = true;
|
|
323
|
+
seats[1].hasActedThisRound = true;
|
|
324
|
+
|
|
325
|
+
bettingState.contribThisRound.set(0, 20);
|
|
326
|
+
bettingState.contribThisRound.set(1, 20);
|
|
327
|
+
|
|
328
|
+
expect(isBettingRoundComplete(seats, bettingState)).equal(true);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("should return false when player hasn't matched bet", () => {
|
|
332
|
+
const bettingState = new BettingRoundState();
|
|
333
|
+
bettingState.currentBetToMatch = 20;
|
|
334
|
+
|
|
335
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
336
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
337
|
+
seats[0].inHand = true;
|
|
338
|
+
seats[0].hasActedThisRound = true;
|
|
339
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
340
|
+
seats[1].inHand = true;
|
|
341
|
+
seats[1].hasActedThisRound = true;
|
|
342
|
+
|
|
343
|
+
bettingState.contribThisRound.set(0, 20);
|
|
344
|
+
bettingState.contribThisRound.set(1, 10); // Hasn't matched
|
|
345
|
+
|
|
346
|
+
expect(isBettingRoundComplete(seats, bettingState)).equal(false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("should return false when player hasn't acted", () => {
|
|
350
|
+
const bettingState = new BettingRoundState();
|
|
351
|
+
bettingState.currentBetToMatch = 20;
|
|
352
|
+
|
|
353
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
354
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
355
|
+
seats[0].inHand = true;
|
|
356
|
+
seats[0].hasActedThisRound = true;
|
|
357
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
358
|
+
seats[1].inHand = true;
|
|
359
|
+
seats[1].hasActedThisRound = false; // Hasn't acted
|
|
360
|
+
|
|
361
|
+
bettingState.contribThisRound.set(0, 20);
|
|
362
|
+
bettingState.contribThisRound.set(1, 20);
|
|
363
|
+
|
|
364
|
+
expect(isBettingRoundComplete(seats, bettingState)).equal(false);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("should return true with only one active player", () => {
|
|
368
|
+
const bettingState = new BettingRoundState();
|
|
369
|
+
bettingState.currentBetToMatch = 20;
|
|
370
|
+
|
|
371
|
+
const seats = new Array<PokerSeatBase>(2);
|
|
372
|
+
seats[0] = new PokerSeatBase(0, "player1", 100);
|
|
373
|
+
seats[0].inHand = true;
|
|
374
|
+
seats[0].hasActedThisRound = true; // Has acted
|
|
375
|
+
seats[1] = new PokerSeatBase(1, "player2", 100);
|
|
376
|
+
seats[1].inHand = false; // Folded
|
|
377
|
+
|
|
378
|
+
// Player 1 has matched (or doesn't need to match if no bet)
|
|
379
|
+
bettingState.contribThisRound.set(0, 20);
|
|
380
|
+
|
|
381
|
+
expect(isBettingRoundComplete(seats, bettingState)).equal(true);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|