diff --git a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml index ba75b70..24fac11 100644 --- a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml +++ b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml @@ -89,39 +89,20 @@ half-way through a night. *@

Table Setup

+ @* The numbers first, and only the numbers. + + This panel is the one thing that is open when the tool is first + opened, and it was 886px of a 667px phone — the two figures its own + summary glances at, the blinds, sat below 218px of prose explaining + three switches a dealer may never touch. The three have gone into + "While you deal" below, with the paragraphs that explain them, which + is where House take and Without live betting already were: the panel + had the vocabulary for this and was only using it for money. *@
- - -

Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.

- - @* The label used to lead with "Track the board", which is the feature's name - rather than what pressing it does to your evening. What it does is put a - card sheet in front of you at four moments: each street, and the showdown. *@ -

Every time you deal a street, a sheet asks which cards came off the deck, and puts them on the felt. At the showdown it asks for each hand that turns over — then Resolve Pot reads the winner and the amount instead of asking you. Close the sheet at any point to go back to picking the winner yourself.

- - @* On by default, and the only setting here that is. A dealer has to - opt in to the tool doing more than they asked; they should not have - to opt in to it staying readable. *@ -

Stops the phone dimming and locking between the flop and the turn. It only holds while betting is open, so the screen sleeps as usual during breaks — and the tool says so the first time it takes hold.

Blinds post themselves each hand. Seats without their own stack start here.

+ + + @* All three change what the tool does rather than what the table is, + and all three are set once and then left alone. The glance says so + when one of them is not on its default, because a switch you cannot + see is a switch you cannot remember turning on. *@ +
+ While you deal +
+ +

Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.

+ + @* The label used to lead with "Track the board", which is the feature's name + rather than what pressing it does to your evening. What it does is put a + card sheet in front of you at four moments: each street, and the showdown. *@ +

Every time you deal a street, a sheet asks which cards came off the deck, and puts them on the felt. At the showdown it asks for each hand that turns over — then Resolve Pot reads the winner and the amount instead of asking you. Close the sheet at any point to go back to picking the winner yourself.

+ + @* On by default, and the only setting here that is. A dealer has to + opt in to the tool doing more than they asked; they should not have + to opt in to it staying readable. *@ +

Stops the phone dimming and locking between the flop and the turn. It only holds while betting is open, so the screen sleeps as usual during breaks — and the tool says so the first time it takes hold.

+
+
House take diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index 7e7fdf7..7930334 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -3083,6 +3083,21 @@ input[type="checkbox"] { font-size: var(--fs-sm); } +/* So a glance can hang off the right of it, the way the panel headings above already + do. Without this the span anchors to whichever ancestor is positioned, which is the + sidebar. */ +.subPanel > summary { + position: relative; +} + +/* A step quieter than the panel headings' glance. This one is a footnote on a line that + is itself a footnote — if it read at the same weight as "Off by $40" the eye would + stop at a row saying nothing has gone wrong. */ +.subPanelGlance { + font-size: var(--fs-xs); + color: var(--text-dim); +} + @media (hover: hover) { .subPanel summary:hover { color: #c5c5c5; diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js index c650632..f600b52 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js @@ -349,6 +349,11 @@ document.addEventListener("DOMContentLoaded", () => { // the right panel to have open on a table nobody has touched, and the wrong one to // have open on the fourth hand of a night. // + // That was half the answer. The other half was what the panel held while it was + // open — 886px of it, on a 667px phone — and it has since given three switches and + // the paragraphs explaining them to "While you deal". The panel this opens is 529px + // now, and the two figures its own summary glances at are the first two rows of it. + // // Decided once, here, rather than on every render — a panel that reopened itself // whenever the table emptied would fight the dealer who had just shut it. (() => { diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js index 0a6cbd7..dacc119 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js @@ -161,8 +161,8 @@ const Render = { // on and stays away otherwise — nobody waiting, no notes, no hands yet — because a // row reading "Nobody waiting" all night is the clutter this is here to remove, and // eight rows that always say something are eight rows that say nothing again. - PANEL_GLANCES: ["glanceTableSetup", "glanceStandings", "glanceWaiting", - "glanceHands", "glanceChips", "glanceNotes"], + PANEL_GLANCES: ["glanceTableSetup", "glanceWhileYouDeal", "glanceStandings", + "glanceWaiting", "glanceHands", "glanceChips", "glanceNotes"], // Pure, so the wording can be tested without a page to hang it on. Every value is // read off the state the panel below it renders from, never off the panel's own @@ -204,8 +204,24 @@ const Render = { // over an empty table would be answering for a count that was never taken. const nothingBanked = bank.cashIn === 0 && bank.chipsInPlay === 0; + // The three switches behind "While you deal", which is shut unless somebody + // opens it: a switch nobody can see is a switch nobody remembers turning on, and + // a dealer wondering why a card sheet keeps appearing should be told where it + // lives without having to go looking. + // + // One of them, not all three. This row is 44px of a 320px column with a label + // already on it, and the three joined run to 49 characters — which ellipsizes + // into "House dealer · Asking for c…", a row that has told nobody anything. So + // it names the one most likely to be the surprise, and opening the panel shows + // the rest. Keeping the screen on is the only one that ships on, so it is its + // absence that gets said. + const dealing = state.trackBoard ? "Asking for cards" + : state.dedicatedDealer ? "House dealer" + : state.keepScreenAwake === false ? "Screen may sleep" : ""; + return { glanceTableSetup: glance(`${state.seatCount} seats${blinds}`), + glanceWhileYouDeal: glance(dealing), glanceStandings: nothingBanked ? glance("") : bank.balanced ? glance("Balanced", "ok") : glance(`Off by ${Money.format(Math.abs(bank.difference))}`, "warn"), diff --git a/tests/holdem/panelGlance.test.js b/tests/holdem/panelGlance.test.js index 1f0f1e1..939f297 100644 --- a/tests/holdem/panelGlance.test.js +++ b/tests/holdem/panelGlance.test.js @@ -175,8 +175,8 @@ describe("the rows that stay quiet until they have news", () => { }); describe("what the row is allowed to be", () => { - const ROWS = ["glanceTableSetup", "glanceStandings", "glanceWaiting", - "glanceHands", "glanceChips", "glanceNotes"]; + const ROWS = ["glanceTableSetup", "glanceWhileYouDeal", "glanceStandings", + "glanceWaiting", "glanceHands", "glanceChips", "glanceNotes"]; test("every glance the sidebar renders is one this knows about", () => { // The list drives the DOM writes, so a glance computed and never listed is a diff --git a/tests/holdem/theFirstScreen.test.js b/tests/holdem/theFirstScreen.test.js new file mode 100644 index 0000000..726baae --- /dev/null +++ b/tests/holdem/theFirstScreen.test.js @@ -0,0 +1,210 @@ +// What Table Setup holds while it is open. +// +// It is the one panel the tool opens by itself, on a table nobody has set up yet — so +// it is most of what a dealer sees the first time they arrive. Measured on a fresh +// table, before this: the panel stood 886px on a 667px phone, and the whole page 2076. +// +// The order was the problem more than the length. The two figures the panel's own +// summary glances at — "10 seats · $1/$2" — were the seat count at the top and the +// blinds 218px further down, under three switches and three paragraphs explaining +// them: a dedicated dealer, a card sheet at every street, and whether the phone is +// allowed to sleep. A dealer setting up a $1/$2 game read all of that on the way to +// the two boxes they came for. +// +// The three have gone into "While you deal", with the paragraphs that explain them. +// That is not a new idea in this panel — House take and Without live betting were +// already nested disclosures — it is the same idea applied to the things that were not +// about money. Afterwards: the panel is 529px, the page 1719 on the same phone and +// 1275 on a 1280x900 desktop, and the first two rows are the seats and the blinds. +// +// A switch nobody can see is a switch nobody remembers turning on, so the disclosure +// carries a glance of its own. +const test = require("node:test"); +const { describe } = require("node:test"); +const assert = require("node:assert"); +const fs = require("fs"); +const path = require("path"); +const { Render, makeTable } = require("./load"); + +const ROOT = path.join(__dirname, "..", "..", "JoshHeaps.Net"); +const PAGE = fs.readFileSync(path.join(ROOT, "Pages", "Casino", "TexasHoldem.cshtml"), "utf8"); +const CSS = fs.readFileSync(path.join(ROOT, "wwwroot", "css", "casino", "holdem.css"), "utf8"); +const MAIN = fs.readFileSync(path.join(ROOT, "wwwroot", "js", "casino", "holdem", "main.js"), "utf8"); + +// The Table Setup panel, from its opening tag to the panel after it. +const setupPanel = () => { + const at = PAGE.indexOf('
'); + assert.ok(at >= 0, "there is no Table Setup panel"); + const end = PAGE.indexOf('
at, "the panel after Table Setup has gone"); + return PAGE.slice(at, end); +}; + +// What the panel shows without anything being opened: everything before the first +// nested disclosure. +const alwaysShowing = () => { + const panel = setupPanel(); + const firstSub = panel.indexOf('
'); + assert.ok(firstSub >= 0, "the panel has no nested disclosures at all"); + return panel.slice(0, firstSub); +}; + +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)); +}; + +const glance = state => (Render.panelGlances(state).glanceWhileYouDeal || {}).text; + +const table = () => makeTable({ seatCount: 9, seated: [], stack: 0 }); + +// --------------------------------------------------------------------------- + +describe("what the open panel leads with", () => { + // The two the summary already glances at. They were first and seventh. + test("the seats and the blinds, in that order, above everything else", () => { + const body = alwaysShowing(); + const seats = body.indexOf('id="seatCountInput"'); + const small = body.indexOf('id="smallBlindInput"'); + const big = body.indexOf('id="bigBlindInput"'); + const stack = body.indexOf('id="defaultStackInput"'); + assert.ok(seats >= 0 && small >= 0 && big >= 0 && stack >= 0, + "one of the four numbers is no longer in the part of the panel that shows"); + assert.ok(seats < small, "the seat count is below the blinds"); + assert.ok(small < big && big < stack, "the blinds and the stack have swapped"); + }); + + test("and nothing in it is a switch about how the tool behaves", () => { + const body = alwaysShowing(); + ["dedicatedDealerToggle", "trackBoardToggle", "keepScreenAwakeToggle"].forEach(id => { + assert.ok(!body.includes(id), `${id} is still on the panel's first screen`); + }); + }); + + // One line, and it is about the boxes directly above it. + test("one paragraph survives, the short one", () => { + const hints = alwaysShowing().match(/

/g) || []; + assert.equal(hints.length, 1, "the panel's first screen is back to being prose"); + assert.match(alwaysShowing(), /Blinds post themselves each hand/); + }); +}); + +describe("where the three switches went", () => { + const dealing = () => { + const panel = setupPanel(); + const at = panel.indexOf("

While you deal"); + assert.ok(at >= 0, 'there is no "While you deal" disclosure'); + return panel.slice(panel.lastIndexOf("", at)); + }; + + test("all three of them, with the paragraphs that explain them", () => { + const body = dealing(); + ["dedicatedDealerToggle", "trackBoardToggle", "keepScreenAwakeToggle"].forEach(id => { + assert.ok(body.includes(id), `${id} did not come with the rest`); + }); + assert.match(body, /no seat shows the dealer button/); + assert.match(body, /a sheet asks which cards came off the deck/); + assert.match(body, /Stops the phone dimming and locking/); + }); + + // The same shape House take and Without live betting already had. A disclosure that + // ships open is a disclosure that has saved nobody anything. + test("it is shut, like the two money ones beside it", () => { + const panel = setupPanel(); + assert.ok(!/
]*\bopen\b/.test(panel), + "a nested disclosure in this panel ships open"); + }); + + test("and it sits above them, because it is not about money", () => { + const panel = setupPanel(); + assert.ok(panel.indexOf("While you deal") < panel.indexOf("House take")); + assert.ok(panel.indexOf("House take") < panel.indexOf("Without live betting")); + }); +}); + +describe("the glance on a shut disclosure", () => { + test("silence when all three are where they ship", () => { + const state = table(); + assert.equal(glance(state), ""); + }); + + test("and a name when one of them is not", () => { + const state = table(); + state.trackBoard = true; + assert.equal(glance(state), "Asking for cards"); + + const other = table(); + other.dedicatedDealer = true; + assert.equal(glance(other), "House dealer"); + }); + + // The only one that ships on, so it is its absence that is worth saying. + test("the screen setting speaks by going quiet, not by being on", () => { + const on = table(); + on.keepScreenAwake = true; + assert.equal(glance(on), ""); + + const off = table(); + off.keepScreenAwake = false; + assert.equal(glance(off), "Screen may sleep"); + }); + + // Joined, the three run to 49 characters, and this row is 44px of a 320px column + // with a label already on it. "House dealer · Asking for c…" has told nobody + // anything; the one most likely to be the surprise has. + test("one of them, never a list of three", () => { + const state = table(); + state.trackBoard = true; + state.dedicatedDealer = true; + state.keepScreenAwake = false; + assert.equal(glance(state), "Asking for cards"); + assert.ok(glance(state).length <= 24); + }); + + test("it is a row this sidebar knows how to write", () => { + assert.ok(Render.PANEL_GLANCES.includes("glanceWhileYouDeal"), + "the glance is computed and never written, so the row keeps its old text all night"); + const panel = setupPanel(); + const at = panel.indexOf('id="glanceWhileYouDeal"'); + assert.ok(at >= 0, "there is nowhere to write it"); + assert.match(panel.slice(panel.lastIndexOf("", at)), /hidden/); + }); +}); + +describe("what had to be true for it to hang there", () => { + // Without this the span anchors to whichever ancestor is positioned, which is the + // sidebar — the glance would appear at the top right of the whole column. + test("the summary it hangs off is a positioned box", () => { + assert.match(ruleFor(".subPanel > summary"), /position:\s*relative/); + }); + + // A footnote on a line that is itself a footnote. At the weight of "Off by $40" the + // eye stops on a row saying nothing has gone wrong. + test("and it is drawn a step quieter than the panel headings' own", () => { + const sub = ruleFor(".subPanelGlance"); + assert.match(sub, /font-size:\s*var\(--fs-xs\)/); + assert.match(sub, /color:\s*var\(--text-dim\)/); + assert.match(ruleFor(".panelGlance"), /font-size:\s*var\(--fs-sm\)/); + }); +}); + +describe("the panel still opens itself only when it should", () => { + test("on a table nobody has touched, and not on the fourth hand", () => { + const at = MAIN.indexOf('document.getElementById("tableSetupPanel")'); + assert.ok(at >= 0, "nothing decides whether the panel starts open"); + const block = MAIN.slice(at, at + 400); + assert.match(block, /Number\(state\.handNumber\) \|\| 1\) === 1/); + assert.match(block, /!state\.seats\.some\(seat => HoldemState\.seatInPlay\(seat\)\)/); + assert.match(block, /panel\.open = fresh/); + }); + + test("what was measured is written beside the decision", () => { + const at = MAIN.indexOf("Table Setup used to ship open"); + assert.ok(at >= 0); + const note = MAIN.slice(at, at + 1200); + assert.match(note, /886px/); + assert.match(note, /529px/); + }); +});