Add the straddle round to the release
This commit is contained in:
@@ -208,6 +208,7 @@
|
||||
<button class="dealerChip" type="button" title="Make this seat the dealer button">D</button>
|
||||
<span class="blindBadge sbBadge">SB</span>
|
||||
<span class="blindBadge bbBadge">BB</span>
|
||||
<span class="blindBadge strBadge" title="Straddled — the action comes back to this seat">STR</span>
|
||||
<div class="seatPuckName"></div>
|
||||
<div class="seatPuckCards"></div>
|
||||
<div class="seatPuckTotal"></div>
|
||||
@@ -360,6 +361,23 @@
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="ruling" data-short="Straddle">
|
||||
<h3>A straddle is a blind raise</h3>
|
||||
<p>
|
||||
The player left of the big blind may put out twice it before the cards,
|
||||
without looking. It is a blind bet, not a raise made in turn, so it does
|
||||
three things at once: everyone else acts first, the minimum raise becomes
|
||||
twice the straddle, and the straddler still has the option when the
|
||||
action comes back — they may raise even if nobody raised into them.
|
||||
</p>
|
||||
<p>
|
||||
Re-straddles carry on to the left, each doubling again, and stop before
|
||||
the small blind. Tap <em>Straddle</em> on the deal prompt and the tool
|
||||
posts it, puts the right player on the clock and prices the raise;
|
||||
the seat wears an <em>STR</em> badge for the hand.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="ruling" data-short="Short all-in">
|
||||
<h3>A short all-in doesn't reopen the betting</h3>
|
||||
<p>
|
||||
|
||||
@@ -796,6 +796,19 @@ button,
|
||||
color: #241a05;
|
||||
}
|
||||
|
||||
/* The straddle. It takes the big blind's slot on the puck, which is free by
|
||||
construction: the chain starts left of the big blind and stops short of the small
|
||||
one, so no seat ever wears two of these.
|
||||
|
||||
Violet rather than a third shade of gold or blue, because the badge a dealer is
|
||||
looking for mid-hand is the one telling them where the action comes back to.
|
||||
Dark text on the light end of the ramp, measured 5.76:1 against the darker stop. */
|
||||
.strBadge {
|
||||
left: 22px;
|
||||
background: linear-gradient(180deg, #cbaaf0, #a97fd6);
|
||||
color: #1b1030;
|
||||
}
|
||||
|
||||
/* A blind the dealer named by hand rather than one the button handed out. Without
|
||||
the ring the table just looks like it's put the blinds in the wrong place. */
|
||||
.blindBadge.blindManual {
|
||||
@@ -3520,6 +3533,38 @@ textarea:focus-visible {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* The straddle rides on the deal row, so it must not take half of it: .actionBtn is
|
||||
flex: 1 and would have split the row evenly with the button the dealer presses every
|
||||
hand. It sizes to its own words instead, and stays quiet next to the green one —
|
||||
choosing a straddle is the exception, dealing is the rule. */
|
||||
.actionBtn.actionStraddle {
|
||||
flex: 0 1 auto;
|
||||
background: linear-gradient(180deg, #33304a, #24223a);
|
||||
border-color: #4b4770;
|
||||
color: #e6e1ff;
|
||||
font-size: 0.82rem;
|
||||
padding-left: 0.7rem;
|
||||
padding-right: 0.7rem;
|
||||
}
|
||||
|
||||
.actionBtn.actionStraddleOff {
|
||||
background: linear-gradient(180deg, #2e2e2e, #232323);
|
||||
border-color: #4a4a4a;
|
||||
color: #dcdcdc;
|
||||
}
|
||||
|
||||
/* Three buttons across a 360px phone is a squeeze, and the two ways out are both
|
||||
worse than this one. Letting the row wrap put the deal button on its own line and
|
||||
made the bar 64px taller — measured at 360x640, that is the bottom seat three fifths
|
||||
behind the bar, and this layout deliberately doesn't shrink the felt to suit (see the
|
||||
max-width: none override, where capping it was tried and rejected). Letting flex
|
||||
squash freely took the deal button to 84px with its label over three lines.
|
||||
A floor of 7rem keeps the label to two lines and the row to one, so arming a straddle
|
||||
costs no felt at all. */
|
||||
.dealPromptButtons .actionDeal {
|
||||
min-width: 7rem;
|
||||
}
|
||||
|
||||
/* The quieter half of the deal prompt: things the deal is about to do that a dealer
|
||||
would otherwise only discover afterwards. */
|
||||
.dealPromptLine.dealPromptAside {
|
||||
|
||||
@@ -592,6 +592,43 @@ const ActionBar = {
|
||||
// by itself the instant the previous hand ended, which meant blinds were committed
|
||||
// while people were still sitting down — and the blind badges would drift away from
|
||||
// the money already posted as seats changed. Now the dealer says when.
|
||||
// ", Dee straddles $4" — read out as part of what goes in before the cards, because
|
||||
// that is what it is. Two of them read as "Dee straddles $4, Eve $8": the second
|
||||
// name doesn't need the verb again, and the bar has no room for it.
|
||||
straddleClause(preview) {
|
||||
const straddles = preview.straddles || [];
|
||||
if (straddles.length === 0) return "";
|
||||
const name = i => HoldemState.displayName(this.state.seats[i], i);
|
||||
const [first, ...rest] = straddles;
|
||||
return `, ${name(first.index)} straddles ${Money.format(first.amount)}` +
|
||||
rest.map(s => `, ${name(s.index)} ${Money.format(s.amount)}`).join("");
|
||||
},
|
||||
|
||||
// Turning it on, doubling it, and calling it off. Heads-up there is no seat left of
|
||||
// the big blind to straddle from, so nothing is offered rather than a button that
|
||||
// would do nothing.
|
||||
straddleButtons(preview) {
|
||||
const on = (preview.straddles || []).length;
|
||||
const max = preview.maxStraddles || 0;
|
||||
if (max <= 0) return [];
|
||||
|
||||
const buttons = [];
|
||||
const nextAmount = Money.format(this.state.bigBlind * Math.pow(2, on + 1));
|
||||
if (on < max) {
|
||||
buttons.push(this.button(
|
||||
on === 0 ? `Straddle ${nextAmount}` : `Re-straddle ${nextAmount}`,
|
||||
"actionStraddle",
|
||||
() => this.commit(on === 0 ? "the straddle" : "the re-straddle",
|
||||
() => { this.state.straddleCount = on + 1; })));
|
||||
}
|
||||
if (on > 0) {
|
||||
buttons.push(this.button("No straddle", "actionStraddle actionStraddleOff",
|
||||
() => this.commit("calling the straddle off",
|
||||
() => { this.state.straddleCount = 0; })));
|
||||
}
|
||||
return buttons;
|
||||
},
|
||||
|
||||
renderDealPrompt() {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "dealPrompt";
|
||||
@@ -689,11 +726,14 @@ const ActionBar = {
|
||||
// A dead small blind is a hand with only one blind in it. Saying "nobody posts
|
||||
// the small blind" out loud is the whole point — a dealer who reads a preview
|
||||
// with a blind missing and isn't told why will assume the tool has lost track.
|
||||
line.textContent = preview.deadSmallBlind
|
||||
const blinds = document.createElement("span");
|
||||
blinds.textContent = (preview.deadSmallBlind
|
||||
? `No small blind this hand, ${name(preview.bbIndex)} posts ${Money.format(preview.bigBlind)}`
|
||||
: `${name(preview.sbIndex)} posts ${Money.format(preview.smallBlind)}, ` +
|
||||
`${name(preview.bbIndex)} posts ${Money.format(preview.bigBlind)}` +
|
||||
`${preview.headsUp ? " · heads-up" : ""}${preview.manual ? " · blinds set by hand" : ""}`;
|
||||
`${name(preview.bbIndex)} posts ${Money.format(preview.bigBlind)}`) +
|
||||
this.straddleClause(preview) +
|
||||
`${preview.headsUp ? " · heads-up" : ""}${preview.manual ? " · blinds set by hand" : ""}`;
|
||||
line.appendChild(blinds);
|
||||
wrap.appendChild(line);
|
||||
|
||||
// Why a position is missing. Both of these are the dead button rule doing its
|
||||
@@ -767,7 +807,12 @@ const ActionBar = {
|
||||
}
|
||||
|
||||
const row = document.createElement("div");
|
||||
row.className = "actionButtons";
|
||||
row.className = "actionButtons dealPromptButtons";
|
||||
// Beside the deal button rather than on a line of its own. Measured at 390x844:
|
||||
// its own line cost 44px of felt on every deal prompt, straddle or no straddle,
|
||||
// and this row is already there and already 44px tall. It reads in the order it
|
||||
// happens, too — say the straddle, then deal.
|
||||
this.straddleButtons(preview).forEach(button => row.appendChild(button));
|
||||
row.appendChild(this.button("Post blinds & deal", "actionDeal", () => {
|
||||
this.commit("dealing the hand", () => Betting.beginHand(this.state));
|
||||
}));
|
||||
|
||||
@@ -93,9 +93,60 @@ const Betting = {
|
||||
satOut,
|
||||
smallBlind: this.round(state.smallBlind || 0),
|
||||
bigBlind: this.round(state.bigBlind || 0),
|
||||
// What the straddles will be if the hand is dealt as it stands, and how
|
||||
// many more this table could take.
|
||||
straddles: this.straddleSeats(state),
|
||||
maxStraddles: this.maxStraddles(state),
|
||||
};
|
||||
},
|
||||
|
||||
// ---- Straddles ----
|
||||
//
|
||||
// A straddle is a blind raise put out before the cards, by the player left of the
|
||||
// big blind, for twice it. It buys the last word before the flop: everyone else
|
||||
// acts first, and the straddler still has the option when it comes back round even
|
||||
// if nobody raised. Re-straddles carry on left, each doubling again.
|
||||
//
|
||||
// Home games run them constantly and a dealer has to get three things right at
|
||||
// once — the amount, who is first to act, and what the minimum raise becomes. The
|
||||
// tool used to do none of them, so a dealer's only way through was to deal, have
|
||||
// the straddler "raise" to $4, and hand the action to the wrong player.
|
||||
|
||||
// How many straddles this table could take. Everyone but the two blinds can put
|
||||
// one out; heads-up that is nobody, which is the right answer — the button is
|
||||
// already the small blind and there is no seat left of the big blind to straddle
|
||||
// from.
|
||||
maxStraddles(state) {
|
||||
return Math.max(0, this.liveSeats(this.tableAsDealt(state)).length - 2);
|
||||
},
|
||||
|
||||
// Who straddles and for how much, walking left from the big blind and doubling.
|
||||
// Asked of the table as it will be dealt, so a seat about to be sat out for having
|
||||
// no chips is never handed a straddle it can't post.
|
||||
straddleSeats(state, count = state.straddleCount) {
|
||||
const wanted = Math.min(Math.max(0, Number(count) || 0), this.maxStraddles(state));
|
||||
if (wanted <= 0) return [];
|
||||
|
||||
const dealt = this.tableAsDealt(state);
|
||||
const buttonIndex = HoldemState.buttonSeatForDeal(dealt);
|
||||
const asDealt = buttonIndex === dealt.dealerSeatIndex
|
||||
? dealt
|
||||
: { ...dealt, dealerSeatIndex: buttonIndex };
|
||||
const { bbIndex } = HoldemState.getBlindSeats(asDealt);
|
||||
if (bbIndex === null) return [];
|
||||
|
||||
const straddles = [];
|
||||
let index = bbIndex;
|
||||
let amount = this.round((Number(state.bigBlind) || 0) * 2);
|
||||
for (let n = 0; n < wanted; n++) {
|
||||
index = this.nextToAct(asDealt, index);
|
||||
if (index === null || index === bbIndex) break;
|
||||
straddles.push({ index, amount });
|
||||
amount = this.round(amount * 2);
|
||||
}
|
||||
return straddles;
|
||||
},
|
||||
|
||||
// Does handing this seat chips also put them back in the game?
|
||||
//
|
||||
// At a real table those are one event, and here they were two: a busted player got
|
||||
@@ -475,6 +526,7 @@ const Betting = {
|
||||
state.currentBet = 0;
|
||||
state.actingSeatIndex = null;
|
||||
state.postedBlinds = null;
|
||||
state.postedStraddles = [];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -505,17 +557,35 @@ const Betting = {
|
||||
// from one who has just walked up and had the blinds go past them.
|
||||
state.dealtLastHand = state.seats.filter(p => p.inHand).map(p => p.id);
|
||||
|
||||
state.currentBet = this.round(Math.max(
|
||||
sbIndex === null ? 0 : this.committed(state.seats[sbIndex]),
|
||||
this.committed(state.seats[bbIndex])));
|
||||
state.lastRaiseSize = bb || 0;
|
||||
// Straddles go out after the blinds and before the cards, each one left of the
|
||||
// last. Posted here rather than left to the dealer to fake as a raise, because
|
||||
// a raise from that seat would hand the action to the wrong player.
|
||||
const straddles = this.straddleSeats(state);
|
||||
straddles.forEach(({ index, amount }) => this.putIn(state, index, amount));
|
||||
state.postedStraddles = straddles.map(({ index }) => index);
|
||||
|
||||
state.currentBet = this.round(state.seats.reduce(
|
||||
(most, player) => Math.max(most, this.committed(player)), 0));
|
||||
// A straddle is a blind raise, so what has to be matched to raise again is the
|
||||
// straddle itself, not the big blind underneath it: $2 blinds with a $4
|
||||
// straddle make the next raise $8, the same way the $2 blind makes it $4.
|
||||
// Left alone, the minimum would come out at $6 and the dealer would be talked
|
||||
// out of it by every player at the table.
|
||||
state.lastRaiseSize = straddles.length > 0 ? state.currentBet : (bb || 0);
|
||||
|
||||
// Heads-up the button is the small blind and acts first before the flop; with
|
||||
// three or more it's the seat after the big blind. Either way, a blind big
|
||||
// enough to put that player all-in leaves them nothing to decide.
|
||||
//
|
||||
// A straddle moves that seat along: first to act is left of the last straddler,
|
||||
// and the straddler keeps the option, which falls out of nobody having acted
|
||||
// yet.
|
||||
const lastPosted = straddles.length > 0
|
||||
? straddles[straddles.length - 1].index
|
||||
: bbIndex;
|
||||
state.actingSeatIndex = headsUp
|
||||
? (this.stackOf(state, state.seats[sbIndex]) > this.EPSILON_CHIPS ? sbIndex : this.nextToAct(state, sbIndex))
|
||||
: this.nextToAct(state, bbIndex);
|
||||
: this.nextToAct(state, lastPosted);
|
||||
},
|
||||
|
||||
// What a pot-sized raise actually is, as a "raise to" total.
|
||||
|
||||
@@ -310,6 +310,14 @@ const Render = {
|
||||
sbBadge.classList.toggle("blindManual", !!manualBlinds && index === sbIndex);
|
||||
bbBadge.classList.toggle("blindManual", !!manualBlinds && index === bbIndex);
|
||||
|
||||
// A straddle is a blind too, and the seat wearing it is the one the action
|
||||
// comes back to. Marked from what the deal actually posted rather than from
|
||||
// the count, so the badge can't outlive the chips it stands for — before
|
||||
// the cards there is nothing out there yet.
|
||||
const straddleBadge = node.querySelector(".strBadge");
|
||||
const straddled = (this.state.postedStraddles || []).includes(index);
|
||||
straddleBadge.classList.toggle("visible", this.state.bettingOpen && straddled);
|
||||
|
||||
this.updatePuckContent(node, player, index);
|
||||
node.classList.toggle("seatSelected", index === this.selectedSeatIndex);
|
||||
|
||||
|
||||
@@ -100,6 +100,14 @@ const HoldemState = {
|
||||
// the big blind rather than waiting for it. Cleared every hand: it's a
|
||||
// decision about one deal, not a standing arrangement.
|
||||
postingIn: [],
|
||||
// How many straddles are on for the hand about to be dealt. A straddle is
|
||||
// announced hand by hand, so this is cleared with every other one-deal
|
||||
// decision rather than standing until somebody turns it off.
|
||||
straddleCount: 0,
|
||||
// Which seats actually put a straddle out when the hand was dealt, so the
|
||||
// felt can badge them. Read off the deal rather than worked out again
|
||||
// later, the same way the posted blinds are.
|
||||
postedStraddles: [],
|
||||
// Set when somebody leaves out of the blinds and the rotation has to hold
|
||||
// to the seats to keep the big blind moving one player at a time. The
|
||||
// button then sits on the chair they vacated, and no small blind is posted.
|
||||
@@ -201,6 +209,11 @@ const HoldemState = {
|
||||
// asking the whole table to post.
|
||||
if (!Array.isArray(parsed.dealtLastHand)) parsed.dealtLastHand = [];
|
||||
if (!Array.isArray(parsed.postingIn)) parsed.postingIn = [];
|
||||
// None on is the right reading of a save from before straddles existed:
|
||||
// whatever is in front of those players is already in their committed
|
||||
// chips, and nothing is owed on top of it.
|
||||
if (!(Number(parsed.straddleCount) > 0)) parsed.straddleCount = 0;
|
||||
if (!Array.isArray(parsed.postedStraddles)) parsed.postedStraddles = [];
|
||||
|
||||
// A table saved before dealing and betting were separate has no flag, and
|
||||
// guessing false would show "deal the hand" over a hand already in
|
||||
@@ -643,6 +656,10 @@ const HoldemState = {
|
||||
// Posting to come in is a decision about one deal. Carrying it forward would
|
||||
// charge somebody an extra big blind every hand for the rest of the night.
|
||||
state.postingIn = [];
|
||||
// So is a straddle. It is announced hand by hand at a table, and a straddle
|
||||
// that stayed on would take chips off somebody who never said the word.
|
||||
state.straddleCount = 0;
|
||||
state.postedStraddles = [];
|
||||
state.seats.forEach(p => {
|
||||
p.folded = false;
|
||||
p.cards = ["", ""];
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// The straddle: a blind raise put out before the cards by the player left of the big
|
||||
// blind, for twice it.
|
||||
//
|
||||
// Home games run them constantly, and it is three rules at once — the amount, who is
|
||||
// first to act, and what the minimum raise becomes. Faking one by dealing and having
|
||||
// the straddler "raise" gets the last of those right and the other two wrong.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Betting, HoldemState, makeTable, chipsOnTable } = require("./load");
|
||||
|
||||
// Six seats, everyone in, button on seat 0: small blind seat 1, big blind seat 2,
|
||||
// straddle seat 3.
|
||||
const table = (straddles = 0, extra = {}) => {
|
||||
const state = makeTable({ seatCount: 6, stack: 200, smallBlind: 1, bigBlind: 2, dealerSeatIndex: 0, ...extra });
|
||||
state.straddleCount = straddles;
|
||||
return state;
|
||||
};
|
||||
|
||||
const dealt = (straddles = 0, extra = {}) => {
|
||||
const state = table(straddles, extra);
|
||||
Betting.beginHand(state);
|
||||
return state;
|
||||
};
|
||||
|
||||
describe("what goes in the middle", () => {
|
||||
test("the seat left of the big blind puts out twice it", () => {
|
||||
const state = dealt(1);
|
||||
assert.deepEqual(state.seats.map(p => p.committed), [0, 1, 2, 4, 0, 0]);
|
||||
});
|
||||
|
||||
test("a re-straddle doubles again, one seat further round", () => {
|
||||
const state = dealt(2);
|
||||
assert.deepEqual(state.seats.map(p => p.committed), [0, 1, 2, 4, 8, 0]);
|
||||
});
|
||||
|
||||
test("none of it comes from anywhere but the straddler's stack", () => {
|
||||
const before = chipsOnTable(table(2));
|
||||
assert.equal(chipsOnTable(dealt(2)), before);
|
||||
});
|
||||
|
||||
test("the felt is told who straddled, not left to work it out", () => {
|
||||
assert.deepEqual(dealt(2).postedStraddles, [3, 4]);
|
||||
assert.deepEqual(dealt(0).postedStraddles, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("who is on the clock", () => {
|
||||
test("action starts left of the straddler, not left of the big blind", () => {
|
||||
assert.equal(dealt(0).actingSeatIndex, 3, "without a straddle it is the seat after the big blind");
|
||||
assert.equal(dealt(1).actingSeatIndex, 4);
|
||||
assert.equal(dealt(2).actingSeatIndex, 5);
|
||||
});
|
||||
|
||||
test("the straddler still has the option when it comes back to them", () => {
|
||||
const state = dealt(1);
|
||||
// Everyone calls the straddle round to the big blind.
|
||||
[4, 5, 0, 1, 2].forEach(seat => {
|
||||
assert.equal(state.actingSeatIndex, seat, `expected seat ${seat} to be up`);
|
||||
Betting.call(state, seat);
|
||||
});
|
||||
assert.equal(state.actingSeatIndex, 3, "back to the straddler");
|
||||
assert.equal(Betting.toCall(state, state.seats[3]), 0, "nothing to call");
|
||||
assert.equal(Betting.isStreetComplete(state), false, "and the street isn't over until they say so");
|
||||
});
|
||||
|
||||
test("the straddler checking closes it", () => {
|
||||
const state = dealt(1);
|
||||
[4, 5, 0, 1, 2].forEach(seat => Betting.call(state, seat));
|
||||
Betting.check(state, 3);
|
||||
assert.equal(Betting.isStreetComplete(state), true);
|
||||
assert.equal(state.actingSeatIndex, null);
|
||||
});
|
||||
|
||||
test("the big blind acts in turn, not last", () => {
|
||||
const state = dealt(1);
|
||||
Betting.call(state, 4);
|
||||
Betting.call(state, 5);
|
||||
Betting.call(state, 0);
|
||||
Betting.call(state, 1);
|
||||
assert.equal(state.actingSeatIndex, 2, "the big blind is up before the straddler");
|
||||
assert.equal(Betting.toCall(state, state.seats[2]), 2, "and owes the difference to the straddle");
|
||||
});
|
||||
});
|
||||
|
||||
describe("what it costs to raise", () => {
|
||||
test("the minimum raise is twice the straddle, not the straddle plus a blind", () => {
|
||||
// $2 blinds, $4 straddle: the next raise is to $8. Measured off the big blind
|
||||
// instead it comes out at $6, and every player at the table would argue.
|
||||
assert.equal(Betting.minRaiseTo(dealt(1)), 8);
|
||||
assert.equal(Betting.minRaiseTo(dealt(2)), 16);
|
||||
assert.equal(Betting.minRaiseTo(dealt(0)), 4);
|
||||
});
|
||||
|
||||
test("what the table owes is measured off the straddle", () => {
|
||||
const state = dealt(1);
|
||||
assert.equal(state.currentBet, 4);
|
||||
assert.equal(Betting.toCall(state, state.seats[4]), 4, "a fresh player calls the straddle");
|
||||
assert.equal(Betting.toCall(state, state.seats[1]), 3, "the small blind owes the rest");
|
||||
assert.equal(Betting.toCall(state, state.seats[2]), 2, "so does the big blind");
|
||||
assert.equal(Betting.toCall(state, state.seats[3]), 0, "the straddler is already good");
|
||||
});
|
||||
|
||||
test("a raise over the straddle is a raise, and the straddler can still re-raise", () => {
|
||||
const state = dealt(1);
|
||||
Betting.raiseTo(state, 4, 12);
|
||||
assert.equal(state.currentBet, 12);
|
||||
assert.equal(Betting.minRaiseTo(state), 20, "twice again over a raise of eight");
|
||||
assert.equal(state.seats[3].hasActed, false, "the straddler has not acted yet");
|
||||
});
|
||||
});
|
||||
|
||||
describe("how many a table can take", () => {
|
||||
test("everyone but the two blinds", () => {
|
||||
assert.equal(Betting.maxStraddles(table(0)), 4);
|
||||
assert.equal(Betting.maxStraddles(table(0, { seatCount: 3, seated: [0, 1, 2] })), 1);
|
||||
});
|
||||
|
||||
test("heads-up, none — there is no seat left of the big blind to straddle from", () => {
|
||||
const heads = table(1, { seatCount: 6, seated: [0, 1] });
|
||||
assert.equal(Betting.maxStraddles(heads), 0);
|
||||
assert.deepEqual(Betting.straddleSeats(heads), []);
|
||||
Betting.beginHand(heads);
|
||||
assert.deepEqual(heads.seats.map(p => p.committed), [1, 2, 0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
test("asking for more than the table has stops at the last seat", () => {
|
||||
const state = table(0, { seatCount: 4, seated: [0, 1, 2, 3] });
|
||||
state.straddleCount = 99;
|
||||
assert.equal(Betting.straddleSeats(state).length, 2);
|
||||
Betting.beginHand(state);
|
||||
assert.deepEqual(state.seats.map(p => p.committed), [8, 1, 2, 4]);
|
||||
assert.equal(state.actingSeatIndex, 1, "round to the small blind, who is first to act");
|
||||
});
|
||||
|
||||
test("a seat with no chips is never handed one", () => {
|
||||
const state = table(1);
|
||||
state.seats[3].stack = 0;
|
||||
// Seat 3 is sat out for the deal, so the straddle passes to seat 4.
|
||||
assert.deepEqual(Betting.straddleSeats(state), [{ index: 4, amount: 4 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("what it doesn't outlive", () => {
|
||||
test("the hand it was called for", () => {
|
||||
const state = dealt(1);
|
||||
HoldemState.startNextHand(state);
|
||||
assert.equal(state.straddleCount, 0, "nobody straddles again by accident");
|
||||
assert.deepEqual(state.postedStraddles, []);
|
||||
});
|
||||
|
||||
test("but it survives the button being pushed around before the deal", () => {
|
||||
// repostBlinds takes every posted chip back and deals again; a straddle that
|
||||
// vanished there would have been paid once and never returned.
|
||||
const state = dealt(1);
|
||||
const before = chipsOnTable(state);
|
||||
Betting.repostBlinds(state);
|
||||
assert.deepEqual(state.seats.map(p => p.committed), [0, 1, 2, 4, 0, 0]);
|
||||
assert.equal(chipsOnTable(state), before);
|
||||
});
|
||||
|
||||
test("a table saved before straddles existed reads as having none", () => {
|
||||
const parsed = JSON.parse(JSON.stringify(makeTable({ seatCount: 6 })));
|
||||
delete parsed.straddleCount;
|
||||
delete parsed.postedStraddles;
|
||||
const migrated = HoldemState.migrate(parsed);
|
||||
assert.equal(migrated.straddleCount, 0);
|
||||
assert.deepEqual(migrated.postedStraddles, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("what the dealer is shown before dealing", () => {
|
||||
test("the straddles that are coming, and how many more it could take", () => {
|
||||
const preview = Betting.blindPreview(table(1));
|
||||
assert.deepEqual(preview.straddles, [{ index: 3, amount: 4 }]);
|
||||
assert.equal(preview.maxStraddles, 4);
|
||||
});
|
||||
|
||||
test("and the preview matches what dealing actually does", () => {
|
||||
const state = table(2);
|
||||
const preview = Betting.blindPreview(state);
|
||||
Betting.beginHand(state);
|
||||
assert.deepEqual(preview.straddles.map(s => s.index), state.postedStraddles);
|
||||
preview.straddles.forEach(({ index, amount }) => {
|
||||
assert.equal(state.seats[index].committed, amount, `seat ${index + 1}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user