Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae591e4212 |
@@ -1480,6 +1480,12 @@ body.hasActionBar #holdemSidebar {
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actionRunout {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #d8b46a;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
}
|
||||||
|
|
||||||
.actionWho {
|
.actionWho {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
color: #9c9c9c;
|
color: #9c9c9c;
|
||||||
|
|||||||
@@ -74,6 +74,16 @@ const ActionBar = {
|
|||||||
players.textContent = `${remaining} ${remaining === 1 ? "player" : "players"}`;
|
players.textContent = `${remaining} ${remaining === 1 ? "player" : "players"}`;
|
||||||
row.appendChild(players);
|
row.appendChild(players);
|
||||||
|
|
||||||
|
// Nobody is being asked to act because nobody can — say so, or the bar just
|
||||||
|
// looks like it's waiting for something.
|
||||||
|
if (Betting.isRunningOut(this.state) && !Betting.isHandOver(this.state)) {
|
||||||
|
const runout = document.createElement("span");
|
||||||
|
runout.className = "actionRunout";
|
||||||
|
const stillIn = Betting.liveSeats(this.state).length;
|
||||||
|
runout.textContent = `All-in — running out the board (${stillIn} players)`;
|
||||||
|
row.appendChild(runout);
|
||||||
|
}
|
||||||
|
|
||||||
const index = this.state.actingSeatIndex;
|
const index = this.state.actingSeatIndex;
|
||||||
if (index !== null && index !== undefined && !Betting.isHandOver(this.state)) {
|
if (index !== null && index !== undefined && !Betting.isHandOver(this.state)) {
|
||||||
const player = this.state.seats[index];
|
const player = this.state.seats[index];
|
||||||
|
|||||||
@@ -70,7 +70,19 @@ const Betting = {
|
|||||||
for (let step = 1; step <= count; step++) {
|
for (let step = 1; step <= count; step++) {
|
||||||
const index = this.seatAt(state, fromIndex, step);
|
const index = this.seatAt(state, fromIndex, step);
|
||||||
const player = state.seats[index];
|
const player = state.seats[index];
|
||||||
if (player.inHand && !player.folded && this.stackOf(state, player) >= 0) return index;
|
if (player.inHand && !player.folded) return index;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Who the dealer asks next. A player who is all-in is still in the hand and still
|
||||||
|
// wins pots, but has no decision left, so the action passes over them.
|
||||||
|
nextToAct(state, fromIndex) {
|
||||||
|
const count = state.seatCount;
|
||||||
|
for (let step = 1; step <= count; step++) {
|
||||||
|
const index = this.seatAt(state, fromIndex, step);
|
||||||
|
const player = state.seats[index];
|
||||||
|
if (player.inHand && !player.folded && this.stackOf(state, player) > this.EPSILON_CHIPS) return index;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -156,7 +168,7 @@ const Betting = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
passAction(state, index) {
|
passAction(state, index) {
|
||||||
state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextLiveIndex(state, index);
|
state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextToAct(state, index);
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---- Street and hand boundaries ----
|
// ---- Street and hand boundaries ----
|
||||||
@@ -184,8 +196,13 @@ const Betting = {
|
|||||||
|
|
||||||
const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1;
|
const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1;
|
||||||
state.street = this.STREETS[nextStreetIndex] || "river";
|
state.street = this.STREETS[nextStreetIndex] || "river";
|
||||||
state.actingSeatIndex = this.liveSeats(state).length > 1
|
|
||||||
? this.nextLiveIndex(state, state.dealerSeatIndex)
|
// It takes two players with chips to have a bet. Once everyone left is all-in
|
||||||
|
// — or only one player still has anything behind — the rest of the board is
|
||||||
|
// just dealt out, and asking someone to check when they have nothing to check
|
||||||
|
// with only slows the dealer down.
|
||||||
|
state.actingSeatIndex = this.canStillBet(state)
|
||||||
|
? this.nextToAct(state, state.dealerSeatIndex)
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -193,6 +210,24 @@ const Betting = {
|
|||||||
return this.liveSeats(state).length <= 1;
|
return this.liveSeats(state).length <= 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Players still in the hand who have chips left to put in. Fewer than two of them
|
||||||
|
// means the betting is finished even though the hand isn't.
|
||||||
|
seatsWithChips(state) {
|
||||||
|
return this.liveSeats(state).filter(s => this.stackOf(state, s.player) > this.EPSILON_CHIPS);
|
||||||
|
},
|
||||||
|
|
||||||
|
EPSILON_CHIPS: 0.004,
|
||||||
|
|
||||||
|
canStillBet(state) {
|
||||||
|
return this.liveSeats(state).length > 1 && this.seatsWithChips(state).length > 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
// True when the hand is still live but nobody can bet again — the dealer is just
|
||||||
|
// running the board out.
|
||||||
|
isRunningOut(state) {
|
||||||
|
return this.liveSeats(state).length > 1 && !this.canStillBet(state);
|
||||||
|
},
|
||||||
|
|
||||||
// Deals a fresh betting round: blinds posted, action to the seat after the big
|
// Deals a fresh betting round: blinds posted, action to the seat after the big
|
||||||
// blind, which is where a dealer expects to be looking when the cards go out.
|
// blind, which is where a dealer expects to be looking when the cards go out.
|
||||||
beginHand(state) {
|
beginHand(state) {
|
||||||
@@ -215,7 +250,7 @@ const Betting = {
|
|||||||
|
|
||||||
state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex])));
|
state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex])));
|
||||||
state.lastRaiseSize = bb || 0;
|
state.lastRaiseSize = bb || 0;
|
||||||
state.actingSeatIndex = this.nextLiveIndex(state, bbIndex);
|
state.actingSeatIndex = this.nextToAct(state, bbIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Winners get their share put back in front of them.
|
// Winners get their share put back in front of them.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const PotWizard = {
|
|||||||
this.playerLabelById = new Map(
|
this.playerLabelById = new Map(
|
||||||
players.map(p => [p.id, this.labelFor(p, state.seats.indexOf(p))])
|
players.map(p => [p.id, this.labelFor(p, state.seats.indexOf(p))])
|
||||||
);
|
);
|
||||||
this.seatIndexById = new Map(players.map(p => [p.id, state.seats.indexOf(p)]));
|
|
||||||
const contributions = players.map(p => ({
|
const contributions = players.map(p => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
contribution: HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount),
|
contribution: HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount),
|
||||||
@@ -50,20 +49,6 @@ const PotWizard = {
|
|||||||
this.totals.set(id, (this.totals.get(id) || 0) + amount);
|
this.totals.set(id, (this.totals.get(id) || 0) + amount);
|
||||||
},
|
},
|
||||||
|
|
||||||
// A chopped pot rarely divides exactly, and the odd chip goes to the first player
|
|
||||||
// left of the button — not to whoever the dealer happened to tick first. The
|
|
||||||
// calculator hands leftovers out in the order it's given the winners, so ordering
|
|
||||||
// them here is what makes the payout match the rule.
|
|
||||||
orderFromButton(ids) {
|
|
||||||
const distance = new Map();
|
|
||||||
for (let step = 1; step <= this.state.seatCount; step++) {
|
|
||||||
distance.set(HoldemState.nextSeatIndex(this.state, this.state.dealerSeatIndex, step), step);
|
|
||||||
}
|
|
||||||
return [...ids].sort((a, b) =>
|
|
||||||
(distance.get(this.seatIndexById.get(a)) ?? 0) - (distance.get(this.seatIndexById.get(b)) ?? 0)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderStep() {
|
renderStep() {
|
||||||
// Auto-resolve any round where only one player is left standing — nothing to ask.
|
// Auto-resolve any round where only one player is left standing — nothing to ask.
|
||||||
let contenders = PotCalculator.getContenders(this.entries);
|
let contenders = PotCalculator.getContenders(this.entries);
|
||||||
@@ -142,7 +127,7 @@ const PotWizard = {
|
|||||||
confirmBtn.addEventListener("click", () => {
|
confirmBtn.addEventListener("click", () => {
|
||||||
const selected = Array.from(choices.querySelectorAll("input:checked")).map(i => i.value);
|
const selected = Array.from(choices.querySelectorAll("input:checked")).map(i => i.value);
|
||||||
if (selected.length === 0) return;
|
if (selected.length === 0) return;
|
||||||
const result = PotCalculator.resolveRound(this.entries, this.orderFromButton(selected));
|
const result = PotCalculator.resolveRound(this.entries, selected);
|
||||||
this.recordRound(result);
|
this.recordRound(result);
|
||||||
this.entries = result.updatedEntries;
|
this.entries = result.updatedEntries;
|
||||||
this.renderStep();
|
this.renderStep();
|
||||||
|
|||||||
Reference in New Issue
Block a user