Hold'em: every seat on the ring is the same seat, and the ring is measured before it is believed #122
@@ -89,39 +89,20 @@
|
||||
half-way through a night. *@
|
||||
<details class="sidebarPanel" id="tableSetupPanel">
|
||||
<summary><h2>Table Setup</h2> <span class="panelGlance" id="glanceTableSetup" hidden></span></summary>
|
||||
@* 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. *@
|
||||
<div class="panelBody">
|
||||
<label class="fieldRow">
|
||||
<span>Seats at table</span>
|
||||
<input type="number" id="seatCountInput" min="2" max="24" value="10" />
|
||||
</label>
|
||||
<label class="fieldRow">
|
||||
<span>Blinds move</span>
|
||||
<select id="directionSelect">
|
||||
<option value="clockwise" selected>Clockwise</option>
|
||||
<option value="counterclockwise">Counterclockwise</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="fieldRow">
|
||||
<span>Dedicated dealer (not a seat)</span>
|
||||
<input type="checkbox" id="dedicatedDealerToggle" />
|
||||
</label>
|
||||
<p class="fieldHint">Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Ask me for the cards as they come out</span>
|
||||
<input type="checkbox" id="trackBoardToggle" />
|
||||
</label>
|
||||
@* 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. *@
|
||||
<p class="fieldHint">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.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Keep the screen on during a hand</span>
|
||||
<input type="checkbox" id="keepScreenAwakeToggle" />
|
||||
</label>
|
||||
@* 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. *@
|
||||
<p class="fieldHint">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.</p>
|
||||
<div class="blindsRow">
|
||||
<label class="fieldRow">
|
||||
<span>Small blind</span>
|
||||
@@ -137,6 +118,44 @@
|
||||
<input type="number" id="defaultStackInput" min="0" step="1" placeholder="e.g. 200" />
|
||||
</label>
|
||||
<p class="fieldHint">Blinds post themselves each hand. Seats without their own stack start here.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Blinds move</span>
|
||||
<select id="directionSelect">
|
||||
<option value="clockwise" selected>Clockwise</option>
|
||||
<option value="counterclockwise">Counterclockwise</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@* 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. *@
|
||||
<details class="subPanel">
|
||||
<summary>While you deal <span class="panelGlance subPanelGlance" id="glanceWhileYouDeal" hidden></span></summary>
|
||||
<div class="subPanelBody">
|
||||
<label class="fieldRow">
|
||||
<span>Dedicated dealer (not a seat)</span>
|
||||
<input type="checkbox" id="dedicatedDealerToggle" />
|
||||
</label>
|
||||
<p class="fieldHint">Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Ask me for the cards as they come out</span>
|
||||
<input type="checkbox" id="trackBoardToggle" />
|
||||
</label>
|
||||
@* 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. *@
|
||||
<p class="fieldHint">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.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Keep the screen on during a hand</span>
|
||||
<input type="checkbox" id="keepScreenAwakeToggle" />
|
||||
</label>
|
||||
@* 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. *@
|
||||
<p class="fieldHint">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.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="subPanel">
|
||||
<summary>House take</summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
(() => {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('<details class="sidebarPanel" id="tableSetupPanel">');
|
||||
assert.ok(at >= 0, "there is no Table Setup panel");
|
||||
const end = PAGE.indexOf('<details class="sidebarPanel"', at + 1);
|
||||
assert.ok(end > 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('<details class="subPanel">');
|
||||
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(/<p class="fieldHint">/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("<summary>While you deal");
|
||||
assert.ok(at >= 0, 'there is no "While you deal" disclosure');
|
||||
return panel.slice(panel.lastIndexOf("<details", at), panel.indexOf("</details>", 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(!/<details class="subPanel"[^>]*\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("<summary>House take"));
|
||||
assert.ok(panel.indexOf("<summary>House take") < panel.indexOf("<summary>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("<span", at), at + 40), /class="panelGlance subPanelGlance"/);
|
||||
assert.match(panel.slice(at, panel.indexOf(">", 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/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user