games: the table that couldn't end, and the lock that let go too early
A code review of the uno table found the stuck guard had never once fired. It counted how many bots had passed in a row and wanted more of them than there are seats — but the bot loop hands the turn back the moment it comes round to you, so the count could never get there, and your own empty-handed pass was never in it. A dead table just passed the turn round forever. That is not an ugly ending, it's a game you cannot finish, and a game you cannot finish is chips you cannot cash out. So it asks the real question now: is there anything to draw, and is anyone holding a card that goes. And the table let go of itself too early. busy came off when the request landed, not when the script it came back with had finished playing — so for the seconds a bot lap takes, you could click a card at a board the server had already moved past. It comes off at the end now, like the other tables. Also: left: 0 was being dropped on its way out the door, which is the one number that matters (the seat that just went out), the deck counter didn't come back after a reshuffle, and hoisting fly() into flyNode() had quietly flattened the chip arc on every other table in the room. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -286,23 +286,34 @@
|
||||
if (v.outcome === "won") FX.burst(verdictEl, { count: 34 });
|
||||
}
|
||||
|
||||
// controls is the one place that decides what you can click: whose turn the
|
||||
// server says it is, and whether a move of yours is still in flight. Both
|
||||
// halves matter, and they used to be spread across three functions that each
|
||||
// knew half of it — which is how the table came to unlock itself in the middle
|
||||
// of a bot's turn.
|
||||
function controls() {
|
||||
var live = !!game && game.phase !== "done";
|
||||
var yours = live && game.turn === 0;
|
||||
var drawn = live && game.phase === "drawn";
|
||||
handEl.querySelectorAll(".pete-uno-card").forEach(function (b) {
|
||||
b.disabled = busy || !yours || b.dataset.on !== "1";
|
||||
});
|
||||
if (drawBtn) drawBtn.disabled = busy || !yours || drawn;
|
||||
if (passBtn) passBtn.disabled = busy || !yours;
|
||||
if (deckEl) deckEl.disabled = busy || !yours || drawn;
|
||||
}
|
||||
|
||||
function setPhase(v) {
|
||||
game = v;
|
||||
var live = !!v && v.phase !== "done";
|
||||
var drawn = live && v.phase === "drawn";
|
||||
betting.classList.toggle("hidden", live);
|
||||
playing.classList.toggle("hidden", !live);
|
||||
hideWild();
|
||||
|
||||
var yours = live && v.turn === 0;
|
||||
if (drawBtn) {
|
||||
drawBtn.disabled = !yours || v.phase === "drawn" || busy;
|
||||
drawBtn.classList.toggle("hidden", !!(live && v.phase === "drawn"));
|
||||
}
|
||||
if (passBtn) {
|
||||
passBtn.classList.toggle("hidden", !(live && v.phase === "drawn"));
|
||||
passBtn.disabled = !yours || busy;
|
||||
}
|
||||
if (deckEl) deckEl.disabled = !yours || v.phase === "drawn" || busy;
|
||||
if (drawBtn) drawBtn.classList.toggle("hidden", drawn);
|
||||
if (passBtn) passBtn.classList.toggle("hidden", !drawn);
|
||||
controls();
|
||||
if (!v || !v.outcome) verdictEl.classList.add("hidden");
|
||||
}
|
||||
|
||||
@@ -453,6 +464,10 @@
|
||||
return badge(e.seat, "UNO!", "uno");
|
||||
|
||||
case "reshuffle":
|
||||
// The discard has just gone back under, so the counter has to as
|
||||
// well: the draws that follow in this same script count down from it,
|
||||
// and counting down from a deck that still reads empty gets nowhere.
|
||||
deckCntEl.textContent = String(e.n);
|
||||
deckEl.classList.add("pete-uno-shuffle");
|
||||
return wait(420).then(function () {
|
||||
deckEl.classList.remove("pete-uno-shuffle");
|
||||
@@ -498,10 +513,12 @@
|
||||
var payout = final.payout || 0;
|
||||
if (payout <= 0) return spot.sweep(houseEl, final.bet, { gap: 45, lift: 0.6, fade: true });
|
||||
|
||||
var back = payout - final.bet;
|
||||
// Not `back`: that is the card back up there, and a number wearing its name
|
||||
// is a landmine for whoever next wants a face-down card in here.
|
||||
var profit = payout - final.bet;
|
||||
return spot
|
||||
.pour(houseEl, back, { gap: 60 })
|
||||
.then(function () { return wait(back > 0 ? 380 : 200); })
|
||||
.pour(houseEl, profit, { gap: 60 })
|
||||
.then(function () { return wait(profit > 0 ? 380 : 200); })
|
||||
.then(function () { return spot.sweep(purseEl, payout, { gap: 40, lift: 0.8 }); });
|
||||
}
|
||||
|
||||
@@ -578,35 +595,35 @@
|
||||
|
||||
// ---- talking to the table ----------------------------------------------------
|
||||
|
||||
// send takes the table away for the whole move — not just the request, but the
|
||||
// script that comes back with it. A lap of this table is seconds of animation,
|
||||
// and for every one of them the game on screen is a game the server has already
|
||||
// moved past. Clicking a card in there would send a move against a board that
|
||||
// no longer exists. So busy comes off at the end of the script, not the end of
|
||||
// the request, which is what the other tables do too.
|
||||
function send(path, body, where) {
|
||||
if (busy) return;
|
||||
busy = true;
|
||||
say("", null, where);
|
||||
lock();
|
||||
controls();
|
||||
return window.PeteGames.post(path, body)
|
||||
.then(function (view) {
|
||||
busy = false;
|
||||
return play(view, function () { window.PeteGames.apply(view); });
|
||||
})
|
||||
.catch(function (err) {
|
||||
busy = false;
|
||||
say(err.message, "bad", where);
|
||||
return window.PeteGames.refresh().then(function (v) {
|
||||
if (v && v.uno) paint(v.uno);
|
||||
else { paint(null); spot.render(0); }
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
busy = false;
|
||||
controls();
|
||||
showBet();
|
||||
});
|
||||
}
|
||||
|
||||
// lock takes the table away while a move is in flight, so a second click can't
|
||||
// send a second move against a game the first one is about to change.
|
||||
function lock() {
|
||||
handEl.querySelectorAll(".pete-uno-card").forEach(function (b) { b.disabled = true; });
|
||||
if (drawBtn) drawBtn.disabled = true;
|
||||
if (passBtn) passBtn.disabled = true;
|
||||
if (deckEl) deckEl.disabled = true;
|
||||
}
|
||||
|
||||
// ---- betting ------------------------------------------------------------------
|
||||
|
||||
function showBet() {
|
||||
|
||||
Reference in New Issue
Block a user