Hold'em: every seat on the ring is the same seat, and the ring is measured before it is believed #122
@@ -1086,6 +1086,30 @@ button,
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* And on a chair with somebody in it, it isn't drawn at all.
|
||||
|
||||
Not a taste call — it had nowhere left to stand. The corner it was using is also where
|
||||
the blind badge drops off the rim and where a long name reaches, and measured across
|
||||
348 seats on 36 tables, 117 of them had this digit printed under something else: all
|
||||
66 seats posting a blind had it fully covered, and 51 more had it sitting on the first
|
||||
letter of the player's own name — worst case the whole glyph, on "Jonah" at 54px.
|
||||
|
||||
Every other corner is spoken for too, and each by something that has to be there. The
|
||||
button's chip has 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 of name, bet and stack, all
|
||||
of them about this hand. The chair number is the only mark on a puck that isn't, and
|
||||
it is the one that gave way.
|
||||
|
||||
Nothing is lost that a tap doesn't return: the seat editor's heading and the seat
|
||||
menu's first line both name the chair now — see HoldemState.seatHeading — a seat with
|
||||
no name typed in still says "Seat 5" as its name, and an empty chair still wears its
|
||||
number large and centred, which is the one time the number is the thing being looked
|
||||
for. */
|
||||
.seat:not(.seatEmpty) .seatChairNumber {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ---- The puck is a stack of rows, and every row is the height of a row ----
|
||||
|
||||
Nine chairs on a ring, and until this block no two of them were the same size. A puck
|
||||
@@ -1644,7 +1668,7 @@ button,
|
||||
phone, on a table where the seats had dropped to 8% of the felt.
|
||||
|
||||
Two steps up the scale for the two numbers a dealer reads across the table, one for
|
||||
the chair number in the corner, which is a reference rather than a reading. Not
|
||||
the number on an open chair, which is a reference rather than a reading. Not
|
||||
proportional to the width — 96 to 144 is 1.5x and this is 1.25x — because a name is
|
||||
a label on a puck, not a headline, and the puck also has to hold it: 15px is the
|
||||
largest size at which "Mike from work", the longest name the sweep uses, still comes
|
||||
|
||||
@@ -2302,7 +2302,9 @@ const Render = {
|
||||
|
||||
const index = this.selectedSeatIndex;
|
||||
const player = this.state.seats[index];
|
||||
title.textContent = this.puckLabel(player, index);
|
||||
// Named and numbered. The chair number came off the puck's face this round —
|
||||
// see HoldemState.seatHeading — and this is one of the two places it went.
|
||||
title.textContent = HoldemState.seatHeading(player, index);
|
||||
|
||||
body.innerHTML = "";
|
||||
const template = document.getElementById("seatEditorTemplate");
|
||||
|
||||
@@ -243,7 +243,12 @@ const SeatMenu = {
|
||||
|
||||
const stack = Betting.stackOf(this.state, player);
|
||||
const owed = Betting.toCall(this.state, player);
|
||||
const bits = [];
|
||||
// Which chair this is leads the line. It came off the puck's face this round —
|
||||
// see HoldemState.seatHeading — and this is the other place it went. It goes in
|
||||
// the subtitle rather than the heading because the heading is a name and names
|
||||
// are long: this header is stuck to the top of the sheet that scrolls, and a
|
||||
// second line of h2 is height every action below it pays for.
|
||||
const bits = [`Seat ${this.seatIndex + 1}`];
|
||||
if (stack > 0) bits.push(`${Money.format(stack)} behind`);
|
||||
if (owed > 0) bits.push(`${Money.format(owed)} to call`);
|
||||
subtitle.textContent = bits.join(" · ");
|
||||
|
||||
@@ -476,6 +476,23 @@ const HoldemState = {
|
||||
return player.name && player.name.trim() ? player.name.trim() : `Seat ${seatIndex + 1}`;
|
||||
},
|
||||
|
||||
// The same seat, named and numbered, for the two sheets that are about one seat.
|
||||
//
|
||||
// The chair number used to be a grey digit in the corner of the puck, and the corner
|
||||
// of the puck is where the blind badge lands and where a long name reaches: measured
|
||||
// across 348 seats on 36 tables, 117 of them had it printed under something else —
|
||||
// every seat posting a blind, and one in seven with it sitting on the first letter of
|
||||
// the player's own name. The felt has no free corner to move it to, so it stopped
|
||||
// being on the felt. These two headings are where a dealer goes when the question is
|
||||
// about one seat, and they answer it now.
|
||||
//
|
||||
// An unnamed seat is already called "Seat 5" by displayName, so it doesn't say it
|
||||
// twice.
|
||||
seatHeading(player, seatIndex) {
|
||||
const name = player && player.name ? player.name.trim() : "";
|
||||
return name ? `${name} · Seat ${seatIndex + 1}` : `Seat ${seatIndex + 1}`;
|
||||
},
|
||||
|
||||
// Contribution the table calculator will treat this player as having put in.
|
||||
//
|
||||
// Chips already pushed into the middle count whether or not the player is still
|
||||
|
||||
@@ -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