Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8616636212 | ||
|
|
4db6f6e4d3 |
@@ -318,6 +318,20 @@ button,
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* `hidden` means hidden.
|
||||
|
||||
The attribute only carries `display: none` from the browser's own stylesheet, which
|
||||
is the weakest rule there is: any class that sets a display — and every button here
|
||||
sets `inline-flex` — beats it. That is not a hypothetical. Seven overlays in this
|
||||
file already carry their own `[hidden] { display: none }` because each one was found
|
||||
the hard way, and the eighth was a button that shipped as a 21x44 empty box under the
|
||||
bank check on every table that didn't need it.
|
||||
|
||||
One rule, for everything, so there is no ninth. */
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ---------- Poker table ---------- */
|
||||
|
||||
#pokerTableWrap {
|
||||
@@ -2338,6 +2352,9 @@ body.hasActionBar #holdemSidebar {
|
||||
.waitingAdd .holdemBtn {
|
||||
flex: none;
|
||||
min-height: 44px;
|
||||
/* "Add" is three letters, which came out 43px wide — one pixel under the rule, and
|
||||
the audit called it. A thumb doesn't know the word is short. */
|
||||
min-width: 56px;
|
||||
}
|
||||
|
||||
.waitingList {
|
||||
|
||||
@@ -459,6 +459,47 @@ const Betting = {
|
||||
return (state.street || "preflop") === "preflop" && state.seats.every(p => !p.hasActed);
|
||||
},
|
||||
|
||||
// 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
|
||||
// bet leaves the stack, sits in front of the player, and is swept into their hand
|
||||
// total at the end of the street. The other way is the "without live betting" panel
|
||||
// — a base pot amount, or chips counted out on a seat — where the dealer is
|
||||
// recording a hand they are watching rather than driving, and nothing ever leaves
|
||||
// anybody's stack.
|
||||
//
|
||||
// The pot wizard prices both the same way, and awarding it credits the winner. So
|
||||
// in that second mode the table simply grew: measured at a three-handed table with
|
||||
// a $50 base pot, paying the pot took the chips on the table from $600 to $750 and
|
||||
// left the bank check blaming a rebuy that never happened.
|
||||
//
|
||||
// So before the pot is pushed, whatever was assumed rather than taken comes off the
|
||||
// stacks that assumed it. Chips already committed or swept are left alone; they
|
||||
// have been paid once already, and taking them twice is the same bug the other way
|
||||
// round.
|
||||
collectAssumedContributions(state) {
|
||||
const taken = [];
|
||||
state.seats.forEach((player, index) => {
|
||||
const priced = HoldemState.computeContribution(player, state.chipLegend, state.basePotAmount);
|
||||
const alreadyPaid = this.round(this.committed(player) + (Number(player.amountOverride) || 0));
|
||||
const owed = this.round(priced - alreadyPaid);
|
||||
if (owed <= this.EPSILON_CHIPS) return;
|
||||
|
||||
// Never more than they are holding. A seat priced above its own stack is a
|
||||
// dealer's typo or a player who is short, and inventing the difference is
|
||||
// what this whole function exists to stop.
|
||||
const moved = this.round(Math.min(owed, this.stackOf(state, player)));
|
||||
if (moved <= this.EPSILON_CHIPS) return;
|
||||
|
||||
player.stack = this.round(this.stackOf(state, player) - moved);
|
||||
// Recorded where a swept bet lives, so the money is in exactly the place
|
||||
// the rest of the tool looks for chips that are in the middle this hand.
|
||||
player.amountOverride = this.round((Number(player.amountOverride) || 0) + moved);
|
||||
taken.push({ index, name: HoldemState.displayName(player, index), amount: moved });
|
||||
});
|
||||
return { taken, total: this.round(taken.reduce((sum, row) => sum + row.amount, 0)) };
|
||||
},
|
||||
|
||||
// A misdeal: the hand never happened, so every chip goes back where it came from.
|
||||
//
|
||||
// The rulings sheet in this tool has told dealers to redeal since the day it was
|
||||
|
||||
@@ -261,6 +261,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (rake > 0) {
|
||||
state.rakeTaken = Money.round((Number(state.rakeTaken) || 0) + rake);
|
||||
}
|
||||
// Anything the pot was priced with but nobody actually paid — the
|
||||
// base pot and counted chips of the no-live-betting flow — comes off
|
||||
// the stacks that owe it before the winner is paid. Otherwise the
|
||||
// table gains the whole pot out of nowhere.
|
||||
Betting.collectAssumedContributions(state);
|
||||
if (payouts.length > 0) {
|
||||
HoldemState.recordHand(state, payouts, rake);
|
||||
// Winnings go straight back in front of the player, so the next
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// Chips the pot was priced with that nobody actually paid.
|
||||
//
|
||||
// There are two ways a contribution gets into this tool. Tapping the action moves
|
||||
// chips: the bet leaves the stack, sits in front of the player, and is swept into their
|
||||
// hand total at the end of the street. The other is the "without live betting" panel —
|
||||
// a base pot amount, or chips counted out on a seat — where the dealer is recording a
|
||||
// hand they are watching rather than driving, and nothing ever leaves a stack.
|
||||
//
|
||||
// The pot wizard prices both the same way and awarding it credits the winner, so in
|
||||
// that second mode the table simply grew.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Money, Betting, HoldemState, chipsOnTable } = require("./load");
|
||||
|
||||
const basePotTable = (base = 50, stack = 200) => {
|
||||
const state = HoldemState.createDefault();
|
||||
HoldemState.resizeSeats(state, 4);
|
||||
state.basePotAmount = base;
|
||||
["Ann", "Ben", "Cal"].forEach((name, i) => {
|
||||
state.seats[i].name = name;
|
||||
state.seats[i].inHand = true;
|
||||
state.seats[i].stack = stack;
|
||||
state.seats[i].buyIns = stack;
|
||||
});
|
||||
return state;
|
||||
};
|
||||
|
||||
const pricedPot = (state) => state.seats.reduce(
|
||||
(sum, p) => sum + HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount), 0);
|
||||
|
||||
describe("what nobody paid", () => {
|
||||
test("comes off the stacks that owe it", () => {
|
||||
const state = basePotTable();
|
||||
const collected = Betting.collectAssumedContributions(state);
|
||||
assert.equal(collected.total, 150);
|
||||
assert.deepEqual(collected.taken.map(row => row.name), ["Ann", "Ben", "Cal"]);
|
||||
assert.deepEqual(state.seats.slice(0, 3).map(p => p.stack), [150, 150, 150]);
|
||||
});
|
||||
|
||||
test("and lands where the rest of the tool looks for chips in the middle", () => {
|
||||
const state = basePotTable();
|
||||
Betting.collectAssumedContributions(state);
|
||||
assert.deepEqual(state.seats.slice(0, 3).map(p => p.amountOverride), [50, 50, 50]);
|
||||
assert.equal(HoldemState.unresolvedContributions(state), 150);
|
||||
});
|
||||
|
||||
test("chips counted out on a seat are money too", () => {
|
||||
const state = basePotTable(0);
|
||||
state.seats[0].chipCounts = { red: 4 }; // four $5 chips
|
||||
const collected = Betting.collectAssumedContributions(state);
|
||||
assert.equal(collected.total, 20);
|
||||
assert.equal(state.seats[0].stack, 180);
|
||||
});
|
||||
});
|
||||
|
||||
describe("what has already been paid", () => {
|
||||
test("a live bet is left exactly alone", () => {
|
||||
const state = basePotTable(0);
|
||||
state.smallBlind = 5;
|
||||
state.bigBlind = 10;
|
||||
Betting.beginHand(state);
|
||||
const before = state.seats.map(p => Betting.stackOf(state, p));
|
||||
|
||||
const collected = Betting.collectAssumedContributions(state);
|
||||
assert.equal(collected.total, 0, "the blinds are already off their stacks");
|
||||
assert.deepEqual(state.seats.map(p => Betting.stackOf(state, p)), before);
|
||||
});
|
||||
|
||||
test("nor is a swept street taken a second time", () => {
|
||||
const state = basePotTable(0);
|
||||
state.smallBlind = 5;
|
||||
state.bigBlind = 10;
|
||||
Betting.beginHand(state);
|
||||
Betting.sweepToHandTotals(state);
|
||||
const before = state.seats.map(p => Betting.stackOf(state, p));
|
||||
|
||||
assert.equal(Betting.collectAssumedContributions(state).total, 0);
|
||||
assert.deepEqual(state.seats.map(p => Betting.stackOf(state, p)), before);
|
||||
});
|
||||
|
||||
test("a base pot on top of a live bet only collects the difference", () => {
|
||||
// The base pot says "assume everyone is in for fifty". Somebody who has already
|
||||
// put twenty in owes thirty of it, not fifty.
|
||||
const state = basePotTable(50);
|
||||
state.seats[0].committed = 20;
|
||||
state.seats[0].stack = 180;
|
||||
const collected = Betting.collectAssumedContributions(state);
|
||||
const ann = collected.taken.find(row => row.name === "Ann");
|
||||
assert.equal(ann.amount, 30);
|
||||
assert.equal(state.seats[0].stack, 150, "fifty in the middle, either way");
|
||||
});
|
||||
});
|
||||
|
||||
describe("what it will not do", () => {
|
||||
test("take more than a player is holding", () => {
|
||||
const state = basePotTable(50);
|
||||
state.seats[1].stack = 20;
|
||||
Betting.collectAssumedContributions(state);
|
||||
assert.equal(state.seats[1].stack, 0, "all of it, and no further");
|
||||
assert.equal(state.seats[1].amountOverride, 20);
|
||||
});
|
||||
|
||||
test("touch an empty chair", () => {
|
||||
const state = basePotTable();
|
||||
const before = { ...state.seats[3] };
|
||||
Betting.collectAssumedContributions(state);
|
||||
assert.equal(state.seats[3].stack, before.stack);
|
||||
assert.equal(state.seats[3].amountOverride, before.amountOverride);
|
||||
});
|
||||
|
||||
test("or a seat that isn't in the hand", () => {
|
||||
// The base pot stands in for "everyone put the same in", and somebody sitting
|
||||
// out did not.
|
||||
const state = basePotTable();
|
||||
state.seats[2].inHand = false;
|
||||
const collected = Betting.collectAssumedContributions(state);
|
||||
assert.deepEqual(collected.taken.map(row => row.name), ["Ann", "Ben"]);
|
||||
assert.equal(state.seats[2].stack, 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the whole hand", () => {
|
||||
test("conserves the chips on the table, which it did not before", () => {
|
||||
const state = basePotTable();
|
||||
const total = chipsOnTable(state);
|
||||
const pot = pricedPot(state);
|
||||
|
||||
Betting.collectAssumedContributions(state);
|
||||
Betting.creditWinnings(state, [{ id: state.seats[0].id, label: "Ann", amount: pot }]);
|
||||
HoldemState.startNextHand(state);
|
||||
|
||||
assert.equal(chipsOnTable(state), total, "six hundred before, six hundred after");
|
||||
assert.deepEqual(state.seats.slice(0, 3).map(p => p.stack), [300, 150, 150]);
|
||||
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||
});
|
||||
|
||||
test("and the bank check stays balanced all the way through", () => {
|
||||
const state = basePotTable();
|
||||
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||
Betting.collectAssumedContributions(state);
|
||||
assert.equal(HoldemState.bankReconciliation(state).balanced, true, "chips are in the middle, not gone");
|
||||
Betting.creditWinnings(state, [{ id: state.seats[0].id, label: "Ann", amount: 150 }]);
|
||||
HoldemState.startNextHand(state);
|
||||
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||
});
|
||||
|
||||
test("a live-betting hand is unchanged by any of this", () => {
|
||||
const state = basePotTable(0);
|
||||
state.smallBlind = 5;
|
||||
state.bigBlind = 10;
|
||||
const total = chipsOnTable(state);
|
||||
Betting.beginHand(state);
|
||||
[3, 0, 1, 2].forEach(() => { if (state.actingSeatIndex !== null) Betting.call(state, state.actingSeatIndex); });
|
||||
Betting.sweepToHandTotals(state);
|
||||
const pot = HoldemState.unresolvedContributions(state);
|
||||
|
||||
Betting.collectAssumedContributions(state);
|
||||
Betting.creditWinnings(state, [{ id: state.seats[0].id, label: "Ann", amount: pot }]);
|
||||
HoldemState.startNextHand(state);
|
||||
|
||||
assert.equal(chipsOnTable(state), total);
|
||||
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||
});
|
||||
|
||||
test("collecting twice takes nothing the second time", () => {
|
||||
// The pot wizard can be opened, cancelled and opened again.
|
||||
const state = basePotTable();
|
||||
Betting.collectAssumedContributions(state);
|
||||
const stacks = state.seats.map(p => p.stack);
|
||||
const again = Betting.collectAssumedContributions(state);
|
||||
assert.equal(again.total, 0);
|
||||
assert.deepEqual(state.seats.map(p => p.stack), stacks);
|
||||
});
|
||||
});
|
||||
@@ -719,6 +719,26 @@ const LayoutAudit = {
|
||||
return { name: "the felt shows one dealer button", offenders };
|
||||
},
|
||||
|
||||
// Anything that says it is hidden and is drawn anyway.
|
||||
//
|
||||
// `hidden` gets its `display: none` from the browser's own stylesheet, the weakest
|
||||
// rule in the cascade, so any class that sets a display beats it — and in this tool
|
||||
// every button class does. The result is an element the code believes is gone,
|
||||
// sitting on the screen: found as a 21x44 empty box under the bank check, and this
|
||||
// file's own touch check had been calling it "could not be probed" rather than
|
||||
// saying the plainer thing, which is that it should not be there at all.
|
||||
hiddenButPainted() {
|
||||
const offenders = [...document.querySelectorAll("[hidden]")]
|
||||
.filter(el => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) return false;
|
||||
const style = getComputedStyle(el);
|
||||
return style.display !== "none" && style.visibility !== "hidden";
|
||||
})
|
||||
.map(el => `${this.describe(el)} :: hidden, but drawn ${Math.round(el.getBoundingClientRect().width)}x${Math.round(el.getBoundingClientRect().height)}`);
|
||||
return { name: "hidden means hidden", offenders };
|
||||
},
|
||||
|
||||
// ---- Runner ----
|
||||
|
||||
// Every check this file knows how to make, every time.
|
||||
@@ -747,6 +767,7 @@ const LayoutAudit = {
|
||||
this.fieldFontSize(),
|
||||
this.seatOwnership(),
|
||||
this.oneDealerButton(),
|
||||
this.hiddenButPainted(),
|
||||
this.contrast(options.contrast),
|
||||
this.touchTargets(options.minimumTarget, options.touchTargets),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user