balatro-cli 0.1.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 +56 -0
- package/bin/start.js +8 -0
- package/package.json +29 -0
- package/src/blinds.js +38 -0
- package/src/cli.js +117 -0
- package/src/deck.js +133 -0
- package/src/handRank.js +135 -0
- package/src/index.js +217 -0
- package/src/jokers.js +98 -0
- package/src/logStyle.js +92 -0
- package/src/round.js +70 -0
- package/src/scoring.js +64 -0
- package/src/shop.js +105 -0
- package/src/tests/deck.test.js +82 -0
- package/src/tests/handRank.test.js +73 -0
- package/src/tests/scoring_jokers.test.js +85 -0
- package/src/tests/shop_cli_round.test.js +139 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createDeck,
|
|
6
|
+
createGameDeck,
|
|
7
|
+
draw,
|
|
8
|
+
dealHand,
|
|
9
|
+
cardChipValue,
|
|
10
|
+
sortHand,
|
|
11
|
+
formatCard,
|
|
12
|
+
formatCardPadded,
|
|
13
|
+
} from '../deck.js';
|
|
14
|
+
|
|
15
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
16
|
+
|
|
17
|
+
test('deck: createDeck has 52 unique cards', () => {
|
|
18
|
+
const deck = createDeck();
|
|
19
|
+
assert.equal(deck.length, 52);
|
|
20
|
+
const set = new Set(deck.map((c) => `${c.rank}-${c.suit}`));
|
|
21
|
+
assert.equal(set.size, 52);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('deck: cardChipValue matches Balatro ranks', () => {
|
|
25
|
+
assert.equal(cardChipValue('A'), 11);
|
|
26
|
+
assert.equal(cardChipValue('K'), 10);
|
|
27
|
+
assert.equal(cardChipValue('Q'), 10);
|
|
28
|
+
assert.equal(cardChipValue('J'), 10);
|
|
29
|
+
assert.equal(cardChipValue('2'), 2);
|
|
30
|
+
assert.equal(cardChipValue('9'), 9);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('deck: draw and reshuffle from discard when empty', () => {
|
|
34
|
+
const state = { drawPile: [{ rank: 'A', suit: 'spades' }], discardPile: [{ rank: 'K', suit: 'hearts' }] };
|
|
35
|
+
const first = draw(state, 1);
|
|
36
|
+
assert.equal(first.length, 1);
|
|
37
|
+
assert.equal(first[0].rank, 'A');
|
|
38
|
+
const second = draw(state, 1);
|
|
39
|
+
assert.equal(second.length, 1);
|
|
40
|
+
assert.equal(second[0].rank, 'K');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('deck: dealHand draws 8 cards from game deck', () => {
|
|
44
|
+
const state = createGameDeck();
|
|
45
|
+
const hand = dealHand(state);
|
|
46
|
+
assert.equal(hand.length, 8);
|
|
47
|
+
assert.equal(state.drawPile.length, 52 - 8);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('deck: sortHand orders by rank then suit', () => {
|
|
51
|
+
const hand = [
|
|
52
|
+
{ rank: 'A', suit: 'hearts' },
|
|
53
|
+
{ rank: '2', suit: 'spades' },
|
|
54
|
+
{ rank: '10', suit: 'clubs' },
|
|
55
|
+
{ rank: '10', suit: 'diamonds' },
|
|
56
|
+
{ rank: 'K', suit: 'clubs' },
|
|
57
|
+
];
|
|
58
|
+
sortHand(hand);
|
|
59
|
+
const ranks = hand.map((c) => c.rank + c.suit[0]);
|
|
60
|
+
// 2 最小,其次 10c, 10d, Kc, Ah
|
|
61
|
+
assert.deepEqual(ranks, ['2s', '10c', '10d', 'Kc', 'Ah']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('deck: formatCard adds suit symbol and optional color', () => {
|
|
65
|
+
const c = { rank: 'A', suit: 'hearts' };
|
|
66
|
+
const colored = formatCard(c, true);
|
|
67
|
+
const plain = formatCard(c, false);
|
|
68
|
+
assert.equal(stripAnsi(colored), 'A♥');
|
|
69
|
+
assert.equal(plain, 'A♥');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('deck: formatCardPadded enforces visible width', () => {
|
|
73
|
+
const hand = [
|
|
74
|
+
{ rank: '2', suit: 'spades' },
|
|
75
|
+
{ rank: '10', suit: 'hearts' },
|
|
76
|
+
{ rank: 'Q', suit: 'diamonds' },
|
|
77
|
+
];
|
|
78
|
+
for (const c of hand) {
|
|
79
|
+
const s = formatCardPadded(c, 5);
|
|
80
|
+
assert.equal(stripAnsi(s).length, 5);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { evaluateHand, HAND_IDS } from '../handRank.js';
|
|
5
|
+
|
|
6
|
+
const c = (rank, suit) => ({ rank, suit });
|
|
7
|
+
|
|
8
|
+
test('handRank: HighCard', () => {
|
|
9
|
+
const hand = [c('A', 'spades'), c('9', 'hearts'), c('5', 'clubs'), c('3', 'diamonds'), c('2', 'clubs')];
|
|
10
|
+
const r = evaluateHand(hand);
|
|
11
|
+
assert.equal(r.handId, HAND_IDS.HIGH_CARD);
|
|
12
|
+
assert.equal(r.scoringCards.length, 1);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('handRank: Pair', () => {
|
|
16
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('5', 'clubs'), c('3', 'diamonds'), c('2', 'clubs')];
|
|
17
|
+
const r = evaluateHand(hand);
|
|
18
|
+
assert.equal(r.handId, HAND_IDS.PAIR);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('handRank: TwoPair', () => {
|
|
22
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('K', 'clubs'), c('K', 'diamonds'), c('2', 'clubs')];
|
|
23
|
+
const r = evaluateHand(hand);
|
|
24
|
+
assert.equal(r.handId, HAND_IDS.TWO_PAIR);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('handRank: ThreeOfAKind', () => {
|
|
28
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('A', 'clubs'), c('5', 'diamonds'), c('2', 'clubs')];
|
|
29
|
+
const r = evaluateHand(hand);
|
|
30
|
+
assert.equal(r.handId, HAND_IDS.THREE_OF_A_KIND);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('handRank: Straight (A2345 wheel)', () => {
|
|
34
|
+
const hand = [c('A', 'spades'), c('2', 'hearts'), c('3', 'clubs'), c('4', 'diamonds'), c('5', 'clubs')];
|
|
35
|
+
const r = evaluateHand(hand);
|
|
36
|
+
assert.equal(r.handId, HAND_IDS.STRAIGHT);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('handRank: Straight (TJQKA)', () => {
|
|
40
|
+
const hand = [c('10', 'spades'), c('J', 'hearts'), c('Q', 'clubs'), c('K', 'diamonds'), c('A', 'clubs')];
|
|
41
|
+
const r = evaluateHand(hand);
|
|
42
|
+
assert.equal(r.handId, HAND_IDS.STRAIGHT);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('handRank: Flush', () => {
|
|
46
|
+
const hand = [c('2', 'spades'), c('5', 'spades'), c('8', 'spades'), c('J', 'spades'), c('K', 'spades')];
|
|
47
|
+
const r = evaluateHand(hand);
|
|
48
|
+
assert.equal(r.handId, HAND_IDS.FLUSH);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('handRank: FullHouse', () => {
|
|
52
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('A', 'clubs'), c('K', 'diamonds'), c('K', 'clubs')];
|
|
53
|
+
const r = evaluateHand(hand);
|
|
54
|
+
assert.equal(r.handId, HAND_IDS.FULL_HOUSE);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('handRank: FourOfAKind', () => {
|
|
58
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('A', 'clubs'), c('A', 'diamonds'), c('K', 'clubs')];
|
|
59
|
+
const r = evaluateHand(hand);
|
|
60
|
+
assert.equal(r.handId, HAND_IDS.FOUR_OF_A_KIND);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('handRank: StraightFlush', () => {
|
|
64
|
+
const hand = [c('6', 'hearts'), c('7', 'hearts'), c('8', 'hearts'), c('9', 'hearts'), c('10', 'hearts')];
|
|
65
|
+
const r = evaluateHand(hand);
|
|
66
|
+
assert.equal(r.handId, HAND_IDS.STRAIGHT_FLUSH);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('handRank: RoyalFlush', () => {
|
|
70
|
+
const hand = [c('10', 'spades'), c('J', 'spades'), c('Q', 'spades'), c('K', 'spades'), c('A', 'spades')];
|
|
71
|
+
const r = evaluateHand(hand);
|
|
72
|
+
assert.equal(r.handId, HAND_IDS.ROYAL_FLUSH);
|
|
73
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { computeScore, getHandBaseChipsMult } from '../scoring.js';
|
|
5
|
+
import { getJokerById } from '../jokers.js';
|
|
6
|
+
|
|
7
|
+
const c = (rank, suit) => ({ rank, suit });
|
|
8
|
+
|
|
9
|
+
test('scoring: base chips and mult for FourOfAKind', () => {
|
|
10
|
+
const base = getHandBaseChipsMult('FourOfAKind', 1);
|
|
11
|
+
assert.equal(base.chips, 60);
|
|
12
|
+
assert.equal(base.mult, 7);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('scoring: computeScore without jokers (FourOfAKind)', () => {
|
|
16
|
+
const hand = [
|
|
17
|
+
c('A', 'spades'),
|
|
18
|
+
c('A', 'hearts'),
|
|
19
|
+
c('A', 'clubs'),
|
|
20
|
+
c('A', 'diamonds'),
|
|
21
|
+
c('K', 'clubs'),
|
|
22
|
+
];
|
|
23
|
+
const r = computeScore(hand, {}, []);
|
|
24
|
+
// chips = base(60) + cards(4*A*11 + K*10 = 54) = 114, mult=7
|
|
25
|
+
assert.equal(r.chips, 114);
|
|
26
|
+
assert.equal(r.mult, 7);
|
|
27
|
+
assert.equal(r.score, 114 * 7);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('scoring: PlusMult then TimesMult jokers', () => {
|
|
31
|
+
const hand = [
|
|
32
|
+
c('A', 'spades'),
|
|
33
|
+
c('A', 'hearts'),
|
|
34
|
+
c('A', 'clubs'),
|
|
35
|
+
c('A', 'diamonds'),
|
|
36
|
+
c('K', 'clubs'),
|
|
37
|
+
];
|
|
38
|
+
const plusMult = getJokerById('Joker_PlusMult'); // +3 mult
|
|
39
|
+
const timesMult = getJokerById('Joker_TimesMult'); // x2 mult
|
|
40
|
+
const r = computeScore(hand, {}, [plusMult, timesMult]);
|
|
41
|
+
// base chips=114, base mult=7 -> +3 =>10, *2 =>20
|
|
42
|
+
assert.equal(r.mult, 20);
|
|
43
|
+
assert.equal(r.score, 114 * 20);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('scoring: Pair bonus only for Pair hand', () => {
|
|
47
|
+
const pairHand = [c('A', 'spades'), c('A', 'hearts'), c('2', 'clubs'), c('3', 'diamonds'), c('4', 'clubs')];
|
|
48
|
+
const highHand = [c('A', 'spades'), c('K', 'hearts'), c('2', 'clubs'), c('3', 'diamonds'), c('4', 'clubs')];
|
|
49
|
+
const pairJoker = getJokerById('Joker_Pair'); // +mult when handId=Pair
|
|
50
|
+
|
|
51
|
+
const rPair = computeScore(pairHand, {}, [pairJoker]);
|
|
52
|
+
const rHigh = computeScore(highHand, {}, [pairJoker]);
|
|
53
|
+
|
|
54
|
+
assert.ok(rPair.mult > rHigh.mult, 'Pair joker should increase mult only for Pair');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('scoring: NoDiscard joker depends on discardsLeft', () => {
|
|
58
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('2', 'clubs'), c('3', 'diamonds'), c('4', 'clubs')];
|
|
59
|
+
const noDiscard = getJokerById('Joker_NoDiscard');
|
|
60
|
+
|
|
61
|
+
const withDiscard = computeScore(hand, {}, [noDiscard], { discardsLeft: 1 });
|
|
62
|
+
const noBonus = computeScore(hand, {}, [noDiscard], { discardsLeft: 0 });
|
|
63
|
+
|
|
64
|
+
assert.ok(withDiscard.mult > noBonus.mult);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('scoring: PerJokerMult scales with joker count', () => {
|
|
68
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('2', 'clubs'), c('3', 'diamonds'), c('4', 'clubs')];
|
|
69
|
+
const perJoker = getJokerById('Joker_EveryJoker');
|
|
70
|
+
const baseJoker = getJokerById('Joker_Base');
|
|
71
|
+
|
|
72
|
+
const rOne = computeScore(hand, {}, [perJoker]);
|
|
73
|
+
const rThree = computeScore(hand, {}, [perJoker, baseJoker, baseJoker]);
|
|
74
|
+
|
|
75
|
+
assert.ok(rThree.mult > rOne.mult, 'more jokers should give higher mult for PerJoker joker');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('scoring: planet level increases base chips/mult', () => {
|
|
79
|
+
const hand = [c('A', 'spades'), c('A', 'hearts'), c('2', 'clubs'), c('3', 'diamonds'), c('4', 'clubs')];
|
|
80
|
+
const r1 = computeScore(hand, { Pair: 1 }, []);
|
|
81
|
+
const r3 = computeScore(hand, { Pair: 3 }, []); // +2 levels
|
|
82
|
+
|
|
83
|
+
assert.ok(r3.chips > r1.chips);
|
|
84
|
+
assert.ok(r3.mult > r1.mult);
|
|
85
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { createShopState, canBuyJoker, buyJoker, sellJoker, buyPlanet, buyVoucher, refreshShop } from '../shop.js';
|
|
5
|
+
import { parsePlayIndices, parseDiscardIndices } from '../cli.js';
|
|
6
|
+
import { playHand, doDiscard } from '../round.js';
|
|
7
|
+
import { getBlindConfig, BLIND_ORDER, ANTES_COUNT } from '../blinds.js';
|
|
8
|
+
|
|
9
|
+
const c = (rank, suit) => ({ rank, suit });
|
|
10
|
+
|
|
11
|
+
test('cli: parsePlayIndices supports spaces, commas and no spaces', () => {
|
|
12
|
+
assert.deepEqual(parsePlayIndices('1 3 5 6 8'), [0, 2, 4, 5, 7]);
|
|
13
|
+
assert.deepEqual(parsePlayIndices('1,3,5,6,8'), [0, 2, 4, 5, 7]);
|
|
14
|
+
assert.deepEqual(parsePlayIndices('13568'), [0, 2, 4, 5, 7]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('cli: parseDiscardIndices supports q 2 5 and q25', () => {
|
|
18
|
+
assert.deepEqual(parseDiscardIndices('q 2 5'), [1, 4]);
|
|
19
|
+
assert.deepEqual(parseDiscardIndices('q25'), [1, 4]);
|
|
20
|
+
assert.deepEqual(parseDiscardIndices('discard 1 3'), [0, 2]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('shop: createShopState generates jokers, optional planet and voucher', () => {
|
|
24
|
+
const runState = { money: 10, jokers: [], voucherBoughtThisAnte: false };
|
|
25
|
+
const s = createShopState(runState);
|
|
26
|
+
assert.ok(Array.isArray(s.jokers));
|
|
27
|
+
assert.ok(s.jokers.length <= 2);
|
|
28
|
+
assert.ok(Array.isArray(s.planet));
|
|
29
|
+
assert.ok(s.voucher === null || typeof s.voucher === 'object');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('shop: canBuyJoker, buyJoker, sellJoker', () => {
|
|
33
|
+
const runState = { money: 10, jokers: [], jokerSlots: 2 };
|
|
34
|
+
const s = createShopState(runState);
|
|
35
|
+
const j = s.jokers[0];
|
|
36
|
+
assert.ok(canBuyJoker(runState, j));
|
|
37
|
+
const beforeMoney = runState.money;
|
|
38
|
+
const ok = buyJoker(runState, j).ok;
|
|
39
|
+
assert.ok(ok);
|
|
40
|
+
assert.equal(runState.jokers.length, 1);
|
|
41
|
+
assert.equal(runState.money, beforeMoney - j.price);
|
|
42
|
+
|
|
43
|
+
const sell = sellJoker(runState, 0);
|
|
44
|
+
assert.ok(sell.ok);
|
|
45
|
+
assert.equal(runState.jokers.length, 0);
|
|
46
|
+
assert.ok(sell.money >= 1 && sell.money <= j.price);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('shop: buyPlanet upgrades hand level', () => {
|
|
50
|
+
const runState = { money: 10, handLevels: {} };
|
|
51
|
+
const planet = { handId: 'Pair', price: 4 };
|
|
52
|
+
const res = buyPlanet(runState, planet);
|
|
53
|
+
assert.ok(res.ok);
|
|
54
|
+
assert.equal(runState.handLevels.Pair, 2); // from default 1 -> 2
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('shop: buyVoucher updates jokerSlots and flag', () => {
|
|
58
|
+
const runState = { money: 20, jokerSlots: 5, voucherBoughtThisAnte: false };
|
|
59
|
+
const voucher = { id: 'Blank', price: 10 };
|
|
60
|
+
const res = buyVoucher(runState, voucher);
|
|
61
|
+
assert.ok(res.ok);
|
|
62
|
+
assert.equal(runState.voucherBoughtThisAnte, true);
|
|
63
|
+
assert.equal(runState.jokerSlots, 6);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('shop: refreshShop consumes money and rerolls', () => {
|
|
67
|
+
const runState = { money: 20, jokers: [] };
|
|
68
|
+
const state = createShopState(runState);
|
|
69
|
+
const oldCost = state.refreshCost;
|
|
70
|
+
const oldJokerNames = state.jokers.map((j) => j.id).sort();
|
|
71
|
+
const res = refreshShop(state, runState);
|
|
72
|
+
assert.ok(res.ok);
|
|
73
|
+
assert.equal(state.refreshCost, oldCost + 1);
|
|
74
|
+
const newNames = state.jokers.map((j) => j.id).sort();
|
|
75
|
+
// 不强制不同,但刷新后至少结构合理
|
|
76
|
+
assert.ok(Array.isArray(state.jokers));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('round: playHand enforces must_play_5 and handsLeft', () => {
|
|
80
|
+
const fiveExtra = [
|
|
81
|
+
{ rank: '2', suit: 'hearts' },
|
|
82
|
+
{ rank: '3', suit: 'hearts' },
|
|
83
|
+
{ rank: '4', suit: 'hearts' },
|
|
84
|
+
{ rank: '5', suit: 'hearts' },
|
|
85
|
+
{ rank: '6', suit: 'hearts' },
|
|
86
|
+
];
|
|
87
|
+
const roundState = {
|
|
88
|
+
deckState: { drawPile: [...fiveExtra], discardPile: [] },
|
|
89
|
+
hand: [c('A', 'spades'), c('K', 'spades'), c('Q', 'spades'), c('J', 'spades'), c('10', 'spades')],
|
|
90
|
+
handsLeft: 1,
|
|
91
|
+
discardsLeft: 1,
|
|
92
|
+
target: 0,
|
|
93
|
+
debuff: 'must_play_5',
|
|
94
|
+
playedHandTypes: new Set(),
|
|
95
|
+
mustPlay5: true,
|
|
96
|
+
};
|
|
97
|
+
const state = { handLevels: {}, jokers: [] };
|
|
98
|
+
let r = playHand(roundState, state, [0, 1, 2, 3]);
|
|
99
|
+
assert.equal(r.error, 'must_play_5');
|
|
100
|
+
r = playHand(roundState, state, [0, 1, 2, 3, 4]);
|
|
101
|
+
assert.equal(r.error, undefined);
|
|
102
|
+
assert.equal(roundState.handsLeft, 0);
|
|
103
|
+
assert.ok(r.cleared);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('blinds: getBlindConfig returns target, hands, discards, boss debuff', () => {
|
|
107
|
+
const small = getBlindConfig(0, 'small');
|
|
108
|
+
assert.equal(small.target, 300);
|
|
109
|
+
assert.equal(small.hands, 4);
|
|
110
|
+
assert.equal(small.discards, 4);
|
|
111
|
+
assert.equal(small.debuff, null);
|
|
112
|
+
|
|
113
|
+
const big = getBlindConfig(0, 'big');
|
|
114
|
+
assert.equal(big.target, 450);
|
|
115
|
+
assert.ok(big.target > small.target);
|
|
116
|
+
|
|
117
|
+
const boss = getBlindConfig(0, 'boss');
|
|
118
|
+
assert.ok(boss.target >= 600);
|
|
119
|
+
assert.ok(['none', 'no_discard', 'target_2x', 'must_play_5'].includes(boss.debuff));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('blinds: ANTES_COUNT and BLIND_ORDER', () => {
|
|
123
|
+
assert.equal(ANTES_COUNT, 8);
|
|
124
|
+
assert.deepEqual(BLIND_ORDER, ['small', 'big', 'boss']);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('round: doDiscard decrements discardsLeft and errors when none left', () => {
|
|
128
|
+
const roundState = {
|
|
129
|
+
deckState: { drawPile: [c('2', 'hearts')], discardPile: [] },
|
|
130
|
+
hand: [c('A', 'spades'), c('K', 'spades')],
|
|
131
|
+
handsLeft: 1,
|
|
132
|
+
discardsLeft: 1,
|
|
133
|
+
};
|
|
134
|
+
let res = doDiscard(roundState, [0]);
|
|
135
|
+
assert.ok(res.success);
|
|
136
|
+
assert.equal(roundState.discardsLeft, 0);
|
|
137
|
+
res = doDiscard(roundState, [0]);
|
|
138
|
+
assert.equal(res.error, 'no_discards_left');
|
|
139
|
+
});
|