"Ann - $135" is the answer to the arithmetic. The dealer's actual job at that moment
is working out that $135 is a black, a green and two reds, while four people watch
them do it.
The chip legend has known the denominations all along. It was only being used to read
chips *into* an amount -- counting a seat's stack -- and never to turn an amount back
into chips.
Final Payouts
Ann - $135
1x$100, 1x$25, 2x$5
Largest first, which is greedy and so not optimal for an arbitrary set of values. Poker
chips are not arbitrary: every real set is canonical -- each denomination divides the
next, 1/5/25/100/500 -- and greedy is exactly optimal on those. A dealer who invents a
3-and-7 chip set still gets a valid answer, possibly one chip longer than it needed to
be, which is a fair trade for not solving a knapsack in a modal.
Two cases where saying nothing is the right answer, and one where it is not.
A table with no chip legend gets the amount on its own -- it has not told us its
denominations, so there is no advice to give. But a legend that exists and cannot reach
the amount does say so: "$3 no chip covers" when the smallest chip is $5. Half a dollar
in a dollar-chip game is the dealer's problem to solve and they can only solve it if
they are told, so it is never rounded away.
12 tests (244 total): the breakdown adds up to the amount across eight amounts, cent
chips make cent amounts exactly, a jumbled legend still pays largest first, the colour
comes along for a future caller, and the two no-advice cases are told apart from the
cannot-reach one.
Driven through a real all-in hand at four sizes: $135 reads "1x$100, 1x$25, 2x$5" and
a tournament-sized $1,350 reads "2x$500, 3x$100, 2x$25", fitting without spill down to
360px. With the legend emptied the line disappears and the payout stands alone.
Contrast clean, no console errors.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
83 lines
3.2 KiB
JavaScript
83 lines
3.2 KiB
JavaScript
// The chips to actually push, for an amount.
|
||
const test = require("node:test");
|
||
const assert = require("node:assert");
|
||
const { Money } = require("./load");
|
||
|
||
// A standard house set.
|
||
const LEGEND = [
|
||
{ value: 1, color: "#fff" },
|
||
{ value: 5, color: "#c0392b" },
|
||
{ value: 25, color: "#27ae60" },
|
||
{ value: 100, color: "#2c3e50" },
|
||
{ value: 500, color: "#8e44ad" },
|
||
];
|
||
|
||
const say = (amount, legend = LEGEND) => Money.formatChipBreakdown(amount, legend);
|
||
|
||
test("a payout comes back as chips, largest first", () => {
|
||
assert.equal(say(132), "1×$100, 1×$25, 1×$5, 2×$1");
|
||
});
|
||
|
||
test("it uses the fewest chips on a normal set", () => {
|
||
const { chips } = Money.chipBreakdown(500, LEGEND);
|
||
assert.deepEqual(chips.map(c => `${c.count}×${c.value}`), ["1×500"]);
|
||
});
|
||
|
||
test("a denomination that doesn't fit is skipped rather than listed as none", () => {
|
||
assert.equal(say(7), "1×$5, 2×$1");
|
||
});
|
||
|
||
test("nothing to pay is nothing to say", () => {
|
||
assert.equal(say(0), "");
|
||
assert.equal(say(-5), "");
|
||
});
|
||
|
||
test("no legend means no advice, not a wrong answer", () => {
|
||
// A table that never set chip colours up gets the amount on its own.
|
||
assert.equal(say(132, []), "");
|
||
assert.equal(say(132, null), "");
|
||
});
|
||
|
||
test("a legend with no values in it yet is the same as none", () => {
|
||
assert.equal(say(132, [{ value: "", color: "#fff" }, { value: 0, color: "#000" }]), "");
|
||
});
|
||
|
||
test("what the smallest chip can't make is reported, not rounded away", () => {
|
||
// 50c in a game whose smallest chip is a dollar is the dealer's problem to solve,
|
||
// and they can only solve it if they're told.
|
||
const { chips, remainder } = Money.chipBreakdown(10.5, LEGEND);
|
||
assert.deepEqual(chips.map(c => `${c.count}×${c.value}`), ["2×5"]);
|
||
assert.equal(remainder, 0.5);
|
||
assert.equal(say(10.5), "2×$5, $0.50 no chip covers");
|
||
});
|
||
|
||
test("cent chips make cent amounts exactly", () => {
|
||
const cents = [{ value: 0.25 }, { value: 0.05 }, { value: 0.01 }];
|
||
assert.equal(say(0.41, cents), "1×$0.25, 3×$0.05, 1×$0.01");
|
||
assert.equal(Money.chipBreakdown(0.41, cents).remainder, 0);
|
||
});
|
||
|
||
test("the chips add up to the amount", () => {
|
||
for (const amount of [1, 7, 33, 132, 499, 500, 1234, 9999]) {
|
||
const { chips, remainder } = Money.chipBreakdown(amount, LEGEND);
|
||
const total = chips.reduce((sum, c) => sum + c.count * c.value, 0) + remainder;
|
||
assert.equal(Money.round(total), amount, `${amount} should be accounted for`);
|
||
}
|
||
});
|
||
|
||
test("a legend in any order still pays largest first", () => {
|
||
const jumbled = [{ value: 25 }, { value: 1 }, { value: 500 }, { value: 5 }, { value: 100 }];
|
||
assert.equal(say(631, jumbled), "1×$500, 1×$100, 1×$25, 1×$5, 1×$1");
|
||
});
|
||
|
||
test("the colour comes along, so the felt can say it in chips", () => {
|
||
const { chips } = Money.chipBreakdown(25, LEGEND);
|
||
assert.equal(chips[0].color, "#27ae60");
|
||
});
|
||
|
||
test("a legend that exists but can't reach the amount does say so", () => {
|
||
// Different from having no legend at all: this table has told us its chips, and
|
||
// the answer is that they don't go that low.
|
||
assert.equal(say(3, [{ value: 5 }, { value: 25 }]), "$3 no chip covers");
|
||
});
|