Let the dealer find one hand in the log instead of scrolling the night
The hand log exists for one moment: somebody at the rail says they won a pot an hour ago and wants it looked up. A night runs to sixty hands or more and the panel showed five of them at a time, in a box fixed at 260px whether the dealer was on a phone or a 27" screen with room for twenty. There was no way to ask for a hand — only to scroll and read every row. A filter box over the list now takes a name, an amount or a hand number. Matching is loose on purpose: "fay" whatever case it's typed in, "190" for a $190 pot or a $190 payout, "$1,90" for the same typed badly, "#12" when the dealer knows the hand and wants that exact one. Beside it, how much of the night is hidden — "8 of 60" — since a filter matching one hand otherwise looks like a table that only played one. The list also takes the room it has: clamp(200px, 42vh, 560px) rather than a flat 260, which is seven or eight rows instead of five on a desktop and no worse on a phone. Export stays the whole night, always. A CSV quietly missing the hands scrolled past would be a record with holes in it and nothing on the file to say so — if a filter is up when it's exported, the toast says how many went in. Matching deliberately covers only what the row displays. Searching the full ledger as well was the first attempt, so the hand that broke somebody could be found under the loser's name — but the ledger holds everyone who put a chip in, and measured on a seven-handed night where nobody folded, "Fay" returned 60 of 60. A filter that searches text the panel doesn't show looks broken even when it's right. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
// Finding one hand in a night of them.
|
||||
//
|
||||
// What the log is actually opened for is a single hand — the one somebody at the rail
|
||||
// is arguing about. A night runs to sixty or more, five at a time in the panel, and
|
||||
// there was no way to ask for one: the dealer scrolled and read every row.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Render } = require("./load");
|
||||
|
||||
const hand = (handNumber, pot, payouts, ledger = []) => ({
|
||||
handNumber, pot, timestamp: Date.now(), payouts, ledger,
|
||||
});
|
||||
|
||||
// A night as the tool records it: labels carry the cards the wizard was shown.
|
||||
const NIGHT = [
|
||||
hand(1, 40, [{ label: "Ann (A♠ K♠)", amount: 40 }], [{ name: "Ann", in: 20 }, { name: "Ben", in: 20 }]),
|
||||
hand(2, 190, [{ label: "Fay", amount: 190 }], [{ name: "Fay", in: 95 }, { name: "Gus", in: 95 }]),
|
||||
hand(12, 19, [{ label: "Ben", amount: 19 }], [{ name: "Ben", in: 10 }, { name: "Ann", in: 9 }]),
|
||||
hand(120, 76, [{ label: "Cal", amount: 38 }, { label: "Dee", amount: 38 }], [{ name: "Cal" }, { name: "Dee" }]),
|
||||
];
|
||||
|
||||
const found = query => NIGHT.filter(h => Render.handMatchesFilter(h, query)).map(h => h.handNumber);
|
||||
|
||||
describe("no filter", () => {
|
||||
test("shows the whole night", () => {
|
||||
assert.deepEqual(found(""), [1, 2, 12, 120]);
|
||||
assert.deepEqual(found(" "), [1, 2, 12, 120]);
|
||||
assert.deepEqual(found(null), [1, 2, 12, 120]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("by name", () => {
|
||||
test("finds the hand somebody won", () => {
|
||||
assert.deepEqual(found("Fay"), [2]);
|
||||
});
|
||||
|
||||
test("case doesn't matter — nobody types a capital at 2am", () => {
|
||||
assert.deepEqual(found("fay"), [2]);
|
||||
assert.deepEqual(found("FAY"), [2]);
|
||||
});
|
||||
|
||||
test("a name inside a label with cards on it still matches", () => {
|
||||
// The wizard writes the label as "Ann (A♠ K♠)", so the name is a substring
|
||||
// rather than the whole cell.
|
||||
assert.deepEqual(found("ann"), [1]);
|
||||
});
|
||||
|
||||
test("matches what the row shows, not everyone who was in the hand", () => {
|
||||
// Gus is in hand 2's ledger — he paid for it — but the row names the winner,
|
||||
// and searching text the panel doesn't display is a filter that looks broken
|
||||
// even when it's right. Measured on a seven-handed night where nobody folded,
|
||||
// matching the ledger returned 60 of 60 for every name at the table.
|
||||
assert.deepEqual(found("gus"), []);
|
||||
});
|
||||
|
||||
test("a split pot is found by either winner", () => {
|
||||
assert.deepEqual(found("cal"), [120]);
|
||||
assert.deepEqual(found("dee"), [120]);
|
||||
});
|
||||
|
||||
test("a name nobody has finds nothing rather than everything", () => {
|
||||
assert.deepEqual(found("zed"), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("by amount", () => {
|
||||
test("finds a pot", () => {
|
||||
assert.deepEqual(found("190"), [2]);
|
||||
});
|
||||
|
||||
test("a dollar sign and commas are ignored", () => {
|
||||
assert.deepEqual(found("$190"), [2]);
|
||||
assert.deepEqual(found("$1,90"), [2], "typed badly, still found");
|
||||
});
|
||||
|
||||
test("finds a payout that isn't the whole pot", () => {
|
||||
assert.deepEqual(found("38"), [120], "half of a chopped $76");
|
||||
});
|
||||
|
||||
test("a partial amount narrows rather than missing", () => {
|
||||
// Half remembering "something around forty" should still get there.
|
||||
assert.deepEqual(found("4"), [1], "$40");
|
||||
assert.deepEqual(found("19"), [2, 12], "the $190 pot and the $19 one");
|
||||
});
|
||||
|
||||
test("a bare number is an exact hand number, not a prefix of one", () => {
|
||||
// Amounts match on their leading digits, because half a remembered figure is
|
||||
// still a lead. Hand numbers don't: typing 12 to mean the hand is a dealer who
|
||||
// knows which hand they want, and dragging 120 and 128 along with it is noise.
|
||||
// "#12" is there for when they do want the prefix.
|
||||
assert.ok(!found("12").includes(120));
|
||||
});
|
||||
});
|
||||
|
||||
describe("by hand number", () => {
|
||||
test("a bare number matches the hand number as well as amounts", () => {
|
||||
assert.ok(found("12").includes(12));
|
||||
});
|
||||
|
||||
test("a hash pins it to the hand number and nothing else", () => {
|
||||
assert.deepEqual(found("#12"), [12, 120], "12 and 120 both start with 12");
|
||||
assert.deepEqual(found("#2"), [2], "and not the $190 pot of hand 2's neighbours");
|
||||
});
|
||||
|
||||
test("a hash with a number nobody played finds nothing", () => {
|
||||
assert.deepEqual(found("#7"), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("odd input can't break it", () => {
|
||||
test("punctuation on its own doesn't match everything", () => {
|
||||
assert.deepEqual(found("$"), [], "no digits and no letters left to match on");
|
||||
assert.deepEqual(found("#"), [1, 2, 12, 120], "an empty hand number is a prefix of them all");
|
||||
});
|
||||
|
||||
test("an entry with pieces missing doesn't throw", () => {
|
||||
const broken = { handNumber: 5, pot: 10 };
|
||||
assert.doesNotThrow(() => Render.handMatchesFilter(broken, "ann"));
|
||||
assert.equal(Render.handMatchesFilter(broken, "ann"), false);
|
||||
assert.equal(Render.handMatchesFilter(broken, "10"), true, "the pot is still findable");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user