games: the short stack that could win money it never matched
A review pass, and it found the one that would have cost somebody real chips. Side pots were only ever cut in runout() — the path taken when the betting stops because nobody is left able to bet. But a hand reaches a showdown with an all-in player in it and the betting having finished perfectly normally: a short stack shoves, two players who still have chips behind call, and then keep betting past them street after street to the river. Nothing was cut. One pot, everybody eligible, and the short stack takes the lot — every chip the deep players put in after they were already all-in, money that could never have been lost to them. All-in for 100 against two players who each put in 500, and the best hand collects 1,100 instead of the 300 it was playing for. Chip conservation never saw it. The chips balance perfectly; they just land in the wrong seat. And every browser session went through runout(), because a player shoving is what ends the betting. It took reading the code. Also from the review: play() dereferenced a table it had just been handed as null, the top-up button offered chips the wallet could not cover, and the trainer's ETA was sixty thousand hands optimistic on the first line it printed. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -154,7 +154,7 @@ type holdemEventView struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func viewHoldemEvents(evs []holdem.Event, g holdem.State) []holdemEventView {
|
||||
func viewHoldemEvents(evs []holdem.Event) []holdemEventView {
|
||||
out := make([]holdemEventView, 0, len(evs))
|
||||
for _, e := range evs {
|
||||
v := holdemEventView{Kind: e.Kind, Seat: e.Seat, Amount: e.Amount, Total: e.Total, Text: e.Text}
|
||||
@@ -344,6 +344,6 @@ func (s *Server) persistHoldem(w http.ResponseWriter, user string, g holdem.Stat
|
||||
hv := viewHoldem(g)
|
||||
v.Holdem = &hv
|
||||
}
|
||||
v.HoldemEvents = viewHoldemEvents(evs, g)
|
||||
v.HoldemEvents = viewHoldemEvents(evs)
|
||||
writeJSON(w, v)
|
||||
}
|
||||
|
||||
@@ -245,8 +245,13 @@
|
||||
}
|
||||
}
|
||||
if (view.phase === "handover") {
|
||||
topupBtn.disabled = !view.max_topup;
|
||||
topupBtn.textContent = view.max_topup ? "Top up " + money(view.max_topup) : "Top up";
|
||||
// The table has room for max_topup, but the button must not promise chips the
|
||||
// wallet cannot cover — clicking it would only earn a refusal.
|
||||
var wallet = window.PeteGames.view();
|
||||
var can = Math.min(view.max_topup, wallet ? wallet.chips : 0);
|
||||
topupBtn.disabled = can <= 0;
|
||||
topupBtn.dataset.amount = can;
|
||||
topupBtn.textContent = can > 0 ? "Top up " + money(can) : "Top up";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +274,10 @@
|
||||
// happening happens here; render() is the state it settles into.
|
||||
function play(events, final) {
|
||||
var chain = Promise.resolve();
|
||||
// No table to settle into — the session closed and storage has already cleared
|
||||
// it. There is nothing to animate onto, and render() would walk seats that
|
||||
// aren't there.
|
||||
if (!final) { render0(); return Promise.resolve(); }
|
||||
if (!events || !events.length) { render(final); return chain; }
|
||||
|
||||
events.forEach(function (e) {
|
||||
@@ -562,8 +571,10 @@
|
||||
b.addEventListener("click", function () {
|
||||
var which = b.dataset.raisePreset;
|
||||
var to;
|
||||
// A pot-sized raise is: call what's owed, then bet what the pot would then be.
|
||||
// So the total is twice what you owe, plus the pot as it stands.
|
||||
if (which === "max") to = view.max_raise_to;
|
||||
else to = view.owed + view.pot * Number(which) + (view.pot > 0 ? view.owed : 0);
|
||||
else to = 2 * view.owed + view.pot * Number(which);
|
||||
to = Math.max(view.min_raise_to, Math.min(view.max_raise_to, Math.round(to)));
|
||||
slider.value = to;
|
||||
showRaise();
|
||||
@@ -573,7 +584,7 @@
|
||||
if (dealBtn) dealBtn.addEventListener("click", function () { send({ move: "deal" }, betweenMsg); });
|
||||
if (leaveBtn) leaveBtn.addEventListener("click", function () { send({ move: "leave" }, betweenMsg); });
|
||||
if (topupBtn) topupBtn.addEventListener("click", function () {
|
||||
send({ move: "topup", amount: view.max_topup }, betweenMsg);
|
||||
send({ move: "topup", amount: Number(topupBtn.dataset.amount || 0) }, betweenMsg);
|
||||
});
|
||||
|
||||
// ---- sitting down ----------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user