// 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"/); }); });