Make the pot button offer a pot-sized raise #94
@@ -430,7 +430,6 @@ const ActionBar = {
|
||||
const player = this.state.seats[index];
|
||||
const stack = Betting.stackOf(this.state, player);
|
||||
const maxTo = Betting.round(Betting.committed(player) + stack);
|
||||
const pot = Betting.potSoFar(this.state);
|
||||
// Two different minimums, and conflating them was a bug. `minTo` is clamped to
|
||||
// what this player can actually put in, which is what the Min button should
|
||||
// offer. `legalMin` is what the rules require — for a short stack the two are
|
||||
@@ -446,10 +445,16 @@ const ActionBar = {
|
||||
const quick = document.createElement("div");
|
||||
quick.className = "raiseQuick";
|
||||
|
||||
// The pot amounts come from Betting, which knows that a pot raise is measured
|
||||
// off the pot as it stands *after* the caller's own call goes in. The pad used
|
||||
// to work it out here as "the bet plus the pot", which left the call out and
|
||||
// made both amounts short: pre-flop at 1/2 it offered "Pot $5" where a pot
|
||||
// raise is $7, and a half-pot that clamped up to the minimum and sat next to
|
||||
// the Min button showing the same $4 twice.
|
||||
const options = [
|
||||
{ label: "Min", value: minTo },
|
||||
{ label: "½ pot", value: Betting.round(this.state.currentBet + pot / 2) },
|
||||
{ label: "Pot", value: Betting.round(this.state.currentBet + pot) },
|
||||
{ label: "½ pot", value: Betting.potRaiseTo(this.state, index, 0.5) },
|
||||
{ label: "Pot", value: Betting.potRaiseTo(this.state, index, 1) },
|
||||
{ label: "All-in", value: maxTo },
|
||||
];
|
||||
options.forEach(option => {
|
||||
|
||||
@@ -518,6 +518,27 @@ const Betting = {
|
||||
: this.nextToAct(state, bbIndex);
|
||||
},
|
||||
|
||||
// What a pot-sized raise actually is, as a "raise to" total.
|
||||
//
|
||||
// Not "the bet plus the pot as it stands". The raiser calls first, and their call
|
||||
// is in the pot before the raise is measured off it, so the pot being matched is
|
||||
// the one that exists after the call:
|
||||
//
|
||||
// raise to = the current bet + (pot + what they owe) × fraction
|
||||
//
|
||||
// Pre-flop at 1/2 that gives $7, not $5: call the $2, which makes the pot $5, then
|
||||
// raise $5. Leaving the call out understates every one of these, and players say
|
||||
// "pot" out loud meaning a definite number.
|
||||
//
|
||||
// Facing no bet this is a plain pot-sized bet — toCall is zero and currentBet is
|
||||
// zero, so it comes out as the pot, which is the right answer there too.
|
||||
potRaiseTo(state, index, fraction = 1) {
|
||||
const player = state.seats[index];
|
||||
if (!player) return 0;
|
||||
const potAfterCall = this.round(this.potSoFar(state) + this.toCall(state, player));
|
||||
return this.round((state.currentBet || 0) + potAfterCall * fraction);
|
||||
},
|
||||
|
||||
// Who turns their hand over first.
|
||||
//
|
||||
// A call the dealer makes at every showdown that isn't a fold, and one nobody at
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// What a pot-sized raise actually is.
|
||||
//
|
||||
// The raise pad worked it out as "the current bet plus the pot as it stands", which
|
||||
// leaves out the fact that the raiser calls first and their call is in the pot before
|
||||
// the raise is measured off it. Every quick amount came out short — pre-flop at 1/2 it
|
||||
// offered "Pot $5" where a pot raise is $7. Players say "pot" out loud meaning a
|
||||
// definite number, and a dealer tapping the button that claims to be it should record
|
||||
// what was actually said.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Betting, makeTable } = require("./load");
|
||||
|
||||
describe("pre-flop at 1/2", () => {
|
||||
const dealt = () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], stack: 200, dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
return state;
|
||||
};
|
||||
|
||||
test("a pot raise from the first player to act is $7", () => {
|
||||
// Call the $2, which makes the pot $5, then raise $5.
|
||||
const state = dealt();
|
||||
assert.equal(Betting.potSoFar(state), 3, "the blinds");
|
||||
assert.equal(Betting.potRaiseTo(state, state.actingSeatIndex), 7);
|
||||
});
|
||||
|
||||
test("half pot is $4.50, which is above the $4 minimum rather than equal to it", () => {
|
||||
// The old arithmetic gave $3.50, which clamped up to the minimum and put the
|
||||
// same $4 on two buttons side by side.
|
||||
const state = dealt();
|
||||
assert.equal(Betting.potRaiseTo(state, state.actingSeatIndex, 0.5), 4.5);
|
||||
assert.notEqual(Betting.potRaiseTo(state, state.actingSeatIndex, 0.5),
|
||||
Betting.minRaiseTo(state), "the two quick buttons say different things");
|
||||
});
|
||||
|
||||
test("the small blind owes less, so their pot raise is smaller", () => {
|
||||
// They have $1 in already: call $1, pot becomes $4, raise $4, total $6.
|
||||
const state = dealt();
|
||||
const { sbIndex } = require("./load").HoldemState.getBlindSeats(state);
|
||||
assert.equal(Betting.toCall(state, state.seats[sbIndex]), 1);
|
||||
assert.equal(Betting.potRaiseTo(state, sbIndex), 6);
|
||||
});
|
||||
|
||||
test("the big blind owes nothing, so it is just the pot", () => {
|
||||
const state = dealt();
|
||||
const { bbIndex } = require("./load").HoldemState.getBlindSeats(state);
|
||||
assert.equal(Betting.toCall(state, state.seats[bbIndex]), 0);
|
||||
assert.equal(Betting.potRaiseTo(state, bbIndex), 5, "the $2 bet plus the $3 pot");
|
||||
});
|
||||
});
|
||||
|
||||
describe("after the flop", () => {
|
||||
const flopWithBet = () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2], stack: 500, dealerSeatIndex: 0 });
|
||||
Betting.beginHand(state);
|
||||
while (state.actingSeatIndex !== null) Betting.call(state, state.actingSeatIndex);
|
||||
Betting.closeStreet(state);
|
||||
return state;
|
||||
};
|
||||
|
||||
test("a pot raise facing a bet is three times the bet plus the dead money", () => {
|
||||
const state = flopWithBet();
|
||||
const potBefore = Betting.potSoFar(state); // $6 carried over
|
||||
const bettor = state.actingSeatIndex;
|
||||
Betting.raiseTo(state, bettor, 10);
|
||||
|
||||
const raiser = state.actingSeatIndex;
|
||||
// Call $10, pot becomes $26, raise $26 → $36. Which is 3×10 + 6.
|
||||
assert.equal(Betting.potRaiseTo(state, raiser), 36);
|
||||
assert.equal(Betting.potRaiseTo(state, raiser), 3 * 10 + potBefore);
|
||||
});
|
||||
|
||||
test("half pot facing that bet is $23", () => {
|
||||
const state = flopWithBet();
|
||||
Betting.raiseTo(state, state.actingSeatIndex, 10);
|
||||
// Call $10, pot becomes $26, raise $13 → $23.
|
||||
assert.equal(Betting.potRaiseTo(state, state.actingSeatIndex, 0.5), 23);
|
||||
});
|
||||
|
||||
test("facing no bet at all it is a plain pot-sized bet", () => {
|
||||
const state = flopWithBet();
|
||||
assert.equal(state.currentBet, 0, "nobody has bet the flop");
|
||||
assert.equal(Betting.toCall(state, state.seats[state.actingSeatIndex]), 0);
|
||||
assert.equal(Betting.potRaiseTo(state, state.actingSeatIndex), Betting.potSoFar(state));
|
||||
});
|
||||
});
|
||||
|
||||
describe("it stays sane at the edges", () => {
|
||||
test("a seat that doesn't exist gets zero rather than throwing", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1], stack: 100 });
|
||||
assert.equal(Betting.potRaiseTo(state, 99), 0);
|
||||
});
|
||||
|
||||
test("the amount is always at least a call", () => {
|
||||
// A raise-to below the current bet would be nonsense to offer. Every fraction
|
||||
// from a tenth up lands at or above what the player owes.
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], stack: 200 });
|
||||
Betting.beginHand(state);
|
||||
const index = state.actingSeatIndex;
|
||||
[0.1, 0.25, 0.5, 0.75, 1].forEach(fraction => {
|
||||
assert.ok(Betting.potRaiseTo(state, index, fraction) >= state.currentBet,
|
||||
`${fraction} of the pot should still cover the bet`);
|
||||
});
|
||||
});
|
||||
|
||||
test("cent-sized blinds don't drift", () => {
|
||||
const state = makeTable({ seatCount: 6, seated: [0, 1, 2], stack: 20, smallBlind: 0.05, bigBlind: 0.1 });
|
||||
Betting.beginHand(state);
|
||||
// Pot $0.15, owes $0.10: call to $0.25, raise $0.25 → $0.35.
|
||||
assert.equal(Betting.potRaiseTo(state, state.actingSeatIndex), 0.35);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user