Take the chair number off the corner it was never alone in
A seat's number was a grey digit in the top-left corner of the puck, and the top-left corner is also where the blind badge drops off the rim and where a long centred name reaches. Measured across 348 seats on 36 tables: 117 of them had it printed under something else. All 66 seats posting a blind had it covered outright — a 22px badge at z-index 4 over a 6px digit — and 51 more had it sitting on the first letter of the player's own name, the whole glyph in the worst case, on a 54px puck. There is nowhere on the felt to move it to. The button's chip has the other end of the top rim. Bets land on the rim between a seat and the pot, which is the bottom edge for the seats above centre and the top edge for the ones below. The middle of the puck is name, bet and stack, and all three are about this hand. The chair number is the only mark on a puck that isn't, so it is the one that gave way. Nothing is lost that a tap doesn't return. The seat editor's heading reads "Mike from work · Seat 2" now and the seat menu's first line leads with the chair; a seat nobody has named still calls itself "Seat 5"; and an open chair still wears its number large and centred, which is the one moment the number is the thing being looked for rather than a footnote on somebody else's name. The sweep had never mentioned any of this. The check that asks whether a puck covers itself up knew about two of the three things a puck wears, and only ever held them against writing — so the mark sitting on the names was invisible to it, and so was a badge painted straight over another mark. Both gaps are closed: put the number back and it reports five offenders on a Galaxy S8 ten-handed, naming Christopher, Alexandra, Jonah and the SB. 1148 tests, 418 audits clean.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// The one mark on a puck that isn't about this hand.
|
||||
//
|
||||
// A seat's chair number was a grey digit in the top-left corner, and the top-left corner
|
||||
// is where the blind badge drops off the rim and where a long centred name reaches.
|
||||
// Measured across 348 seats on 36 tables: 117 of them had the number printed under
|
||||
// something else. All 66 seats posting a blind had it covered outright — the badge is
|
||||
// 22px wide over a 6px digit, at z-index 4 — and 51 more had it sitting on the first
|
||||
// letter of the player's own name, the whole glyph in the worst case, on a 54px puck.
|
||||
//
|
||||
// There is nowhere on the felt to move it to. The button's chip owns the right-hand end
|
||||
// of the top rim. Bets land on the rim between a seat and the pot, which is the bottom
|
||||
// edge for the seats above centre and the top edge for the ones below. The middle of the
|
||||
// puck is three rows — name, bet, stack — and all three are about this hand. So the mark
|
||||
// that isn't gave way, and the two sheets that are about one seat say which seat.
|
||||
//
|
||||
// The sweep had never mentioned any of it, because the check that asks whether a puck
|
||||
// covers itself up only knew about two of the three things a puck wears, and only ever
|
||||
// tested them against writing.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { HoldemState, makeTable } = require("./load");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..", "JoshHeaps.Net");
|
||||
const CSS = fs.readFileSync(path.join(ROOT, "wwwroot", "css", "casino", "holdem.css"), "utf8");
|
||||
const AUDIT = fs.readFileSync(path.join(__dirname, "uiAudit.js"), "utf8");
|
||||
const src = name => fs.readFileSync(
|
||||
path.join(ROOT, "wwwroot", "js", "casino", "holdem", `${name}.js`), "utf8");
|
||||
|
||||
const ruleFor = selector => {
|
||||
const at = CSS.indexOf(`\n${selector} {`);
|
||||
assert.ok(at >= 0, `there is no \`${selector}\` rule`);
|
||||
return CSS.slice(at, CSS.indexOf("}", at));
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("a chair with somebody in it", () => {
|
||||
test("does not draw its number on the felt at all", () => {
|
||||
assert.match(ruleFor(".seat:not(.seatEmpty) .seatChairNumber"), /display:\s*none/);
|
||||
});
|
||||
|
||||
// Where the number is the thing being looked for rather than a footnote on somebody
|
||||
// else's name — which chair is open — it is still large and centred and washed.
|
||||
test("and an open one still wears its number, large and in the middle", () => {
|
||||
const rule = ruleFor(".seatEmpty .seatChairNumber");
|
||||
assert.match(rule, /top:\s*50%/);
|
||||
assert.match(rule, /left:\s*50%/);
|
||||
assert.match(rule, /background:/);
|
||||
});
|
||||
|
||||
// Removing it from the felt is only defensible because the felt is not the only
|
||||
// place it appears. A seat nobody has named says "Seat 5" as its name already.
|
||||
test("a seat with no name typed in still says which seat it is", () => {
|
||||
const state = makeTable({ seatCount: 6 });
|
||||
state.seats[4].name = "";
|
||||
assert.equal(HoldemState.displayName(state.seats[4], 4), "Seat 5");
|
||||
});
|
||||
});
|
||||
|
||||
describe("the two sheets that are about one seat", () => {
|
||||
test("name the chair as well as the player", () => {
|
||||
assert.equal(HoldemState.seatHeading({ name: "Alexandra" }, 2), "Alexandra · Seat 3");
|
||||
});
|
||||
|
||||
test("and don't say it twice when the name is already the number", () => {
|
||||
assert.equal(HoldemState.seatHeading({ name: "" }, 4), "Seat 5");
|
||||
assert.equal(HoldemState.seatHeading({ name: " " }, 4), "Seat 5");
|
||||
assert.equal(HoldemState.seatHeading({}, 0), "Seat 1");
|
||||
assert.equal(HoldemState.seatHeading(null, 0), "Seat 1");
|
||||
});
|
||||
|
||||
test("a typed name keeps its own spacing off the ends", () => {
|
||||
assert.equal(HoldemState.seatHeading({ name: " Mike from work " }, 1), "Mike from work · Seat 2");
|
||||
});
|
||||
|
||||
test("the seat editor's heading is the one that says it", () => {
|
||||
const render = src("render");
|
||||
const at = render.indexOf("renderSeatEditor() {");
|
||||
assert.ok(at >= 0);
|
||||
const body = render.slice(at, render.indexOf("\n },", at));
|
||||
assert.match(body, /title\.textContent = HoldemState\.seatHeading\(player, index\)/);
|
||||
});
|
||||
|
||||
// Not the heading here: this header is stuck to the top of the sheet that scrolls,
|
||||
// and a name long enough to wrap would cost every action below it a line.
|
||||
test("the seat menu says it on its first line instead, and says it first", () => {
|
||||
const menu = src("seatMenu");
|
||||
const at = menu.indexOf("const stack = Betting.stackOf(this.state, player);");
|
||||
assert.ok(at >= 0);
|
||||
const body = menu.slice(at, menu.indexOf("subtitle.textContent", at));
|
||||
assert.match(body, /const bits = \[`Seat \$\{this\.seatIndex \+ 1\}`\]/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the sweep can see it now", () => {
|
||||
const check = () => {
|
||||
const at = AUDIT.indexOf(" seatLabelsClear() {");
|
||||
assert.ok(at >= 0, "the check that asks whether a puck covers itself up is gone");
|
||||
return AUDIT.slice(at, AUDIT.indexOf("\n },", at));
|
||||
};
|
||||
|
||||
// The mark that was sitting on names was neither of the two this check knew about.
|
||||
test("the chair number counts as something a puck wears", () => {
|
||||
assert.match(check(), /const MARKS = "\.dealerChip, \.blindBadge, \.seatChairNumber"/);
|
||||
});
|
||||
|
||||
// And a badge painted over the number is a puck covering itself up, which is the
|
||||
// whole subject of this check, and was invisible to it.
|
||||
test("and the marks are held against each other, not only against the writing", () => {
|
||||
const body = check();
|
||||
assert.match(body, /marks\.forEach\(\(a, i\) => marks\.slice\(i \+ 1\)/);
|
||||
assert.match(body, /px across/);
|
||||
});
|
||||
|
||||
test("a mark that isn't drawn isn't an offender", () => {
|
||||
const body = check();
|
||||
assert.match(body, /style\.display === "none"/);
|
||||
assert.match(body, /parseFloat\(style\.opacity\) < 0\.05/);
|
||||
// An empty badge slot has a box and no text; three of them sit on every puck.
|
||||
assert.match(body, /if \(!\(mark\.textContent \|\| ""\)\.trim\(\)\) return null;/);
|
||||
});
|
||||
|
||||
test("and the check says what it now covers", () => {
|
||||
assert.match(AUDIT, /name: "nothing a seat wears covers its name or another mark"/);
|
||||
});
|
||||
});
|
||||
+53
-22
@@ -1258,36 +1258,67 @@ const LayoutAudit = {
|
||||
// right without any of them being modelled. Line boxes rather than one bounding box,
|
||||
// too: a two-line name that clears the chip on the line beside it is not covered
|
||||
// because its *other* line happens to sit under where the chip is.
|
||||
//
|
||||
// Two things a puck wears were missing from this for eleven rounds, and between them
|
||||
// they are why the chair number could sit on the felt covered up without the sweep
|
||||
// ever saying so.
|
||||
//
|
||||
// The first is the chair number itself. This check has always asked whether the
|
||||
// button or a blind badge sits on a name, and the mark that was sitting on names was
|
||||
// neither of those: measured across 348 seats on 36 tables, 51 of them had the number
|
||||
// printed on the first letter of the player's own name.
|
||||
//
|
||||
// The second is that marks were only ever tested against the *writing*, never against
|
||||
// each other — so a badge painted straight over the chair number, which is what all
|
||||
// 66 seats posting a blind were doing, was invisible to a check whose whole subject
|
||||
// is a puck covering itself up.
|
||||
seatLabelsClear() {
|
||||
const offenders = [];
|
||||
// What a puck wears, as opposed to what it writes.
|
||||
const MARKS = ".dealerChip, .blindBadge, .seatChairNumber";
|
||||
const painted = mark => {
|
||||
const box = mark.getBoundingClientRect();
|
||||
if (!box.width || !box.height) return null;
|
||||
if (!(mark.textContent || "").trim()) return null;
|
||||
const style = getComputedStyle(mark);
|
||||
if (style.display === "none" || style.visibility === "hidden" ||
|
||||
parseFloat(style.opacity) < 0.05) return null;
|
||||
return box;
|
||||
};
|
||||
const across = (a, b) => {
|
||||
const wide = Math.min(a.right, b.right) - Math.max(a.left, b.left);
|
||||
const deep = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top);
|
||||
return wide > 1 && deep > 1 ? wide : 0;
|
||||
};
|
||||
|
||||
document.querySelectorAll(".seat").forEach(seat => {
|
||||
const marks = [...seat.querySelectorAll(MARKS)]
|
||||
.map(mark => ({ mark, box: painted(mark) }))
|
||||
.filter(entry => entry.box);
|
||||
|
||||
const name = seat.querySelector(".seatPuckName");
|
||||
if (!name || !name.textContent.trim()) return;
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(name);
|
||||
const lines = [...range.getClientRects()].filter(line => line.width > 1 && line.height > 1);
|
||||
if (!lines.length) return;
|
||||
|
||||
seat.querySelectorAll(".dealerChip, .blindBadge").forEach(mark => {
|
||||
const over = mark.getBoundingClientRect();
|
||||
if (!over.width || !over.height) return;
|
||||
const marked = getComputedStyle(mark);
|
||||
if (marked.display === "none" || marked.visibility === "hidden" ||
|
||||
parseFloat(marked.opacity) < 0.05) return;
|
||||
|
||||
let worst = 0;
|
||||
lines.forEach(line => {
|
||||
const across = Math.min(line.right, over.right) - Math.max(line.left, over.left);
|
||||
const down = Math.min(line.bottom, over.bottom) - Math.max(line.top, over.top);
|
||||
if (across > 1 && down > 1) worst = Math.max(worst, across);
|
||||
if (name && name.textContent.trim()) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(name);
|
||||
const lines = [...range.getClientRects()].filter(line => line.width > 1 && line.height > 1);
|
||||
marks.forEach(({ mark, box }) => {
|
||||
const worst = lines.reduce((most, line) => Math.max(most, across(line, box)), 0);
|
||||
if (worst > 1) {
|
||||
offenders.push(`${this.describe(name)} :: ${Math.round(worst)}px under ${this.describe(mark)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// And the marks against each other. The rim holds three of them — the blinds
|
||||
// at one end, the button at the other — and there is not room for a third.
|
||||
marks.forEach((a, i) => marks.slice(i + 1).forEach(b => {
|
||||
const worst = across(a.box, b.box);
|
||||
if (worst > 1) {
|
||||
offenders.push(`${this.describe(name)} :: ${Math.round(worst)}px under ${this.describe(mark)}`);
|
||||
offenders.push(`${this.describe(a.mark)} :: ${Math.round(worst)}px across ${this.describe(b.mark)}`);
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
return { name: "seat names aren't painted under their own badges", offenders };
|
||||
return { name: "nothing a seat wears covers its name or another mark", offenders };
|
||||
},
|
||||
|
||||
// A seat's writing, covered by something belonging to a different seat.
|
||||
|
||||
Reference in New Issue
Block a user