Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b9e83e8f | ||
|
|
470fbc0d26 | ||
|
|
1d9353c94e | ||
|
|
6174981cdc | ||
|
|
0ad5592687 | ||
|
|
236c52567e | ||
|
|
b8655be258 | ||
|
|
7e420ce747 | ||
|
|
c36d7f60bd | ||
|
|
15cec18256 | ||
|
|
9c8d981e2d | ||
|
|
907e0165db | ||
|
|
86e1570618 | ||
|
|
6df400322e | ||
|
|
ca9ac7b6af | ||
|
|
80dae56f0d | ||
|
|
6ca99fde6b | ||
|
|
b2262f8604 | ||
|
|
6ad9923aae | ||
|
|
168e8a380c | ||
|
|
edc08d7213 |
@@ -459,6 +459,25 @@ const Betting = {
|
||||
return (state.street || "preflop") === "preflop" && state.seats.every(p => !p.hasActed);
|
||||
},
|
||||
|
||||
// Are the blinds actually down? Moving the button or naming a blind means two
|
||||
// completely different things depending on the answer, and `onlyBlindsIn` alone
|
||||
// cannot tell them apart: a table sitting in setup, with no cards out and nothing
|
||||
// committed, satisfies it just as well as a hand that has only just been dealt.
|
||||
//
|
||||
// With a hand open, the blinds are chips on the felt. Re-aiming them has to take
|
||||
// those chips back and post them again from the new positions, or the badges and
|
||||
// the money disagree.
|
||||
//
|
||||
// Before the deal it is a marker being slid round a table with nothing in the
|
||||
// middle. Re-posting there would *deal the hand* — betting opened, blinds taken off
|
||||
// two stacks, action on the seat after the big blind — which is not what a dealer
|
||||
// means by "she's the big blind this one". Setting the positions is how a dealer
|
||||
// gets ready to deal; the cards come out when they press the button, and not a
|
||||
// moment before.
|
||||
blindsArePosted(state) {
|
||||
return !!state.bettingOpen && this.onlyBlindsIn(state);
|
||||
},
|
||||
|
||||
// Chips the pot has been priced with but nobody has actually paid.
|
||||
//
|
||||
// Two ways a contribution gets into this tool. Tapping the action moves chips: the
|
||||
|
||||
@@ -176,8 +176,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// After it, the blinds have to follow the button or the badges and the
|
||||
// money disagree — but that's a real thing happening to real chips, and
|
||||
// the dealer gets told rather than finding out later.
|
||||
const willRepost = state.bettingOpen && Betting.onlyBlindsIn(state) &&
|
||||
HoldemState.unresolvedContributions(state) > Betting.EPSILON_CHIPS;
|
||||
const willRepost = Betting.blindsArePosted(state);
|
||||
|
||||
seatAction(index, willRepost
|
||||
? `Button on ${nameOf(index)} — blinds taken back and re-posted.`
|
||||
@@ -204,18 +203,29 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (partner === index || partner === null) partner = Betting.nextLiveIndex(state, index);
|
||||
|
||||
const label = which === "sb" ? "small blind" : "big blind";
|
||||
seatAction(index, `${nameOf(index)} posts the ${label}.`, () => {
|
||||
// Only a hand that is already open has blinds to move. Before the deal this
|
||||
// is aiming the next hand, and re-posting would have dealt it — the dealer
|
||||
// says who the blind is well before they say "cards".
|
||||
const posted = Betting.blindsArePosted(state);
|
||||
seatAction(index, posted
|
||||
? `${nameOf(index)} posts the ${label}.`
|
||||
: `${nameOf(index)} has the ${label} next hand.`, () => {
|
||||
state.blindOverride = which === "sb"
|
||||
? { sbIndex: index, bbIndex: partner }
|
||||
: { sbIndex: partner, bbIndex: index };
|
||||
if (Betting.onlyBlindsIn(state)) Betting.repostBlinds(state);
|
||||
if (posted) Betting.repostBlinds(state);
|
||||
});
|
||||
},
|
||||
|
||||
clearBlindOverride: () => seatAction(state.dealerSeatIndex, "Blinds follow the button again.", () => {
|
||||
state.blindOverride = null;
|
||||
if (Betting.onlyBlindsIn(state)) Betting.repostBlinds(state);
|
||||
}),
|
||||
clearBlindOverride: () => {
|
||||
const posted = Betting.blindsArePosted(state);
|
||||
seatAction(state.dealerSeatIndex, posted
|
||||
? "Blinds follow the button again — taken back and re-posted."
|
||||
: "Blinds follow the button again.", () => {
|
||||
state.blindOverride = null;
|
||||
if (posted) Betting.repostBlinds(state);
|
||||
});
|
||||
},
|
||||
|
||||
editSeat: index => Render.toggleSeatEditor(index),
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Saying who the blind is, before saying "cards".
|
||||
//
|
||||
// A dealer sets the table up out loud — button here, she's the small blind, he's the
|
||||
// big blind — and only then deals. The seat menu offers "Make small blind" for exactly
|
||||
// that, and it was answering by dealing the hand: betting opened, two stacks docked,
|
||||
// the action bar up and waiting on the seat after the big blind. The dealer had said
|
||||
// where the blinds go, and the tool heard "deal".
|
||||
//
|
||||
// The condition it was asking is `onlyBlindsIn`, which means "nothing has happened in
|
||||
// this hand except the blinds" — true of a hand one second old, and equally true of a
|
||||
// table in setup with no cards out and nothing committed. Those two need telling apart,
|
||||
// which is what `blindsArePosted` is for.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Betting, HoldemState, makeTable, chipsOnTable } = require("./load");
|
||||
|
||||
describe("before the deal", () => {
|
||||
test("the blinds are not down, however much the table looks ready", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] });
|
||||
assert.equal(state.bettingOpen, false);
|
||||
// The old condition, on its own, says yes here — this is the whole bug.
|
||||
assert.equal(Betting.onlyBlindsIn(state), true);
|
||||
assert.equal(Betting.blindsArePosted(state), false);
|
||||
});
|
||||
|
||||
test("a table straight out of setup, with nobody seated, is the same answer", () => {
|
||||
assert.equal(Betting.blindsArePosted(HoldemState.createDefault()), false);
|
||||
});
|
||||
|
||||
test("re-posting would have dealt the hand, which is why it is guarded", () => {
|
||||
// Not a rule, a demonstration: repostBlinds ends in beginHand. Calling it in
|
||||
// setup is the deal, and nothing about naming a blind should reach it.
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] });
|
||||
Betting.repostBlinds(state);
|
||||
assert.equal(state.bettingOpen, true, "chips off two stacks and the action bar up");
|
||||
assert.notEqual(state.actingSeatIndex, null);
|
||||
});
|
||||
|
||||
test("naming a blind moves a badge and no chips", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] });
|
||||
const before = chipsOnTable(state);
|
||||
|
||||
// What the seat menu does: pin the override, repost only if the blinds are down.
|
||||
state.blindOverride = { sbIndex: 3, bbIndex: 0 };
|
||||
if (Betting.blindsArePosted(state)) Betting.repostBlinds(state);
|
||||
|
||||
assert.equal(state.bettingOpen, false, "still setup — the dealer hasn't dealt");
|
||||
assert.equal(state.actingSeatIndex, null, "nobody is being asked to act");
|
||||
assert.deepEqual(state.seats.map(p => Betting.committed(p)), [0, 0, 0, 0, 0, 0]);
|
||||
assert.equal(chipsOnTable(state), before);
|
||||
|
||||
// And the badges say what the dealer just said.
|
||||
const blinds = HoldemState.getBlindSeats(state);
|
||||
assert.equal(blinds.sbIndex, 3);
|
||||
assert.equal(blinds.bbIndex, 0);
|
||||
});
|
||||
|
||||
test("and the hand that follows posts from where they were aimed", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], smallBlind: 1, bigBlind: 2 });
|
||||
state.blindOverride = { sbIndex: 3, bbIndex: 0 };
|
||||
if (Betting.blindsArePosted(state)) Betting.repostBlinds(state);
|
||||
|
||||
Betting.beginHand(state);
|
||||
assert.equal(Betting.committed(state.seats[3]), 1);
|
||||
assert.equal(Betting.committed(state.seats[0]), 2);
|
||||
});
|
||||
|
||||
test("giving the button away is a marker moving too", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] });
|
||||
const before = chipsOnTable(state);
|
||||
|
||||
const willRepost = Betting.blindsArePosted(state);
|
||||
state.dealerSeatIndex = 2;
|
||||
if (willRepost) Betting.repostBlinds(state);
|
||||
|
||||
assert.equal(willRepost, false);
|
||||
assert.equal(state.bettingOpen, false);
|
||||
assert.equal(chipsOnTable(state), before);
|
||||
});
|
||||
});
|
||||
|
||||
describe("once the hand is dealt", () => {
|
||||
test("the blinds are down, and re-aiming them moves real chips", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||
const before = chipsOnTable(state);
|
||||
Betting.beginHand(state);
|
||||
assert.equal(Betting.blindsArePosted(state), true);
|
||||
assert.equal(Betting.committed(state.seats[1]), 1);
|
||||
|
||||
state.blindOverride = { sbIndex: 3, bbIndex: 0 };
|
||||
if (Betting.blindsArePosted(state)) Betting.repostBlinds(state);
|
||||
|
||||
assert.equal(Betting.committed(state.seats[1]), 0, "the old small blind got their chip back");
|
||||
assert.equal(Betting.committed(state.seats[3]), 1);
|
||||
assert.equal(Betting.committed(state.seats[0]), 2);
|
||||
assert.equal(chipsOnTable(state), before, "no chips created or lost in the shuffle");
|
||||
});
|
||||
|
||||
test("but not once somebody has answered them", () => {
|
||||
const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
Betting.call(state, state.actingSeatIndex);
|
||||
assert.equal(Betting.blindsArePosted(state), false, "there is a call to unpick now");
|
||||
});
|
||||
|
||||
test("nor on a later street", () => {
|
||||
const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
state.street = "flop";
|
||||
assert.equal(Betting.blindsArePosted(state), false);
|
||||
});
|
||||
|
||||
test("and a misdealt hand goes back to being a table in setup", () => {
|
||||
const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
Betting.takeHandBack(state);
|
||||
assert.equal(Betting.blindsArePosted(state), false, "nothing is posted until the dealer deals again");
|
||||
});
|
||||
|
||||
test("as does a hand that has been paid out", () => {
|
||||
const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
Betting.sweepToHandTotals(state);
|
||||
HoldemState.startNextHand(state);
|
||||
assert.equal(Betting.blindsArePosted(state), false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user