// Turning the standings into "who hands cash to whom". const test = require("node:test"); const assert = require("node:assert"); const { Money, HoldemState, SettleUp, makeTable } = require("./load"); const standings = pairs => pairs.map(([name, cashPosition]) => ({ name, cashPosition })); const lines = plan => plan.payments.map(p => `${p.from} -> ${p.to} ${Money.format(p.amount)}`); const paidTo = (plan, name) => plan.payments.filter(p => p.to === name).reduce((sum, p) => sum + p.amount, 0); const paidBy = (plan, name) => plan.payments.filter(p => p.from === name).reduce((sum, p) => sum + p.amount, 0); test("one loser, one winner", () => { const plan = SettleUp.plan(standings([["Ann", 40], ["Ben", -40]])); assert.deepEqual(lines(plan), ["Ben -> Ann $40"]); assert.equal(plan.balanced, true); }); test("everybody flat means nothing to do", () => { const plan = SettleUp.plan(standings([["Ann", 0], ["Ben", 0]])); assert.deepEqual(plan.payments, []); assert.equal(plan.balanced, true); }); test("nobody at all means nothing to do", () => { const plan = SettleUp.plan([]); assert.deepEqual(plan.payments, []); assert.equal(plan.balanced, true); }); test("each player ends up square", () => { const table = standings([ ["Ann", 120], ["Ben", -75], ["Cal", -60], ["Dee", 30], ["Eve", -15], ]); const plan = SettleUp.plan(table); table.forEach(row => { const settled = paidTo(plan, row.name) - paidBy(plan, row.name); assert.equal(settled, row.cashPosition, `${row.name} should end up square`); }); assert.equal(plan.balanced, true); }); test("at most one payment fewer than there are people", () => { const table = standings([ ["Ann", 120], ["Ben", -75], ["Cal", -60], ["Dee", 30], ["Eve", -15], ]); const plan = SettleUp.plan(table); assert.ok(plan.payments.length <= table.length - 1, `${plan.payments.length} payments for ${table.length} players`); }); test("a player who owes two people has both lines together", () => { // The list gets read out one player at a time, so a payer's obligations must not // be split up by somebody else's. const plan = SettleUp.plan(standings([["Ann", 50], ["Cal", 30], ["Ben", -80]])); assert.deepEqual(lines(plan), ["Ben -> Ann $50", "Ben -> Cal $30"]); }); test("cents survive", () => { const plan = SettleUp.plan(standings([["Ann", 0.1], ["Ben", 0.2], ["Cal", -0.3]])); assert.deepEqual(lines(plan), ["Cal -> Ben $0.20", "Cal -> Ann $0.10"]); assert.equal(plan.balanced, true); }); test("more chips than cash leaves somebody unpaid, and says so", () => { // The table is $25 up on the bank — an unrecorded rebuy or a miscount. The winners // cannot all be paid, and pretending otherwise is how a real person ends up short. const plan = SettleUp.plan(standings([["Ann", 100], ["Ben", -75]])); assert.deepEqual(lines(plan), ["Ben -> Ann $75"]); assert.equal(plan.shortBy, 25); assert.equal(plan.overBy, 0); assert.equal(plan.balanced, false); }); test("less chips than cash leaves a payer with nowhere to send it", () => { const plan = SettleUp.plan(standings([["Ann", 50], ["Ben", -75]])); assert.deepEqual(lines(plan), ["Ben -> Ann $50"]); assert.equal(plan.overBy, 25); assert.equal(plan.shortBy, 0); assert.equal(plan.balanced, false); }); test("no payment is ever for nothing", () => { const plan = SettleUp.plan(standings([ ["Ann", 60], ["Ben", -60], ["Cal", 0], ["Dee", 0], ])); assert.ok(plan.payments.every(p => p.amount > 0)); assert.equal(plan.payments.length, 1); }); test("nobody pays themselves", () => { const plan = SettleUp.plan(standings([ ["Ann", 90], ["Ben", -30], ["Cal", -30], ["Dee", -30], ])); assert.ok(plan.payments.every(p => p.from !== p.to)); }); test("the payments total what the winners are owed", () => { const table = standings([["Ann", 42.5], ["Ben", 17.5], ["Cal", -60]]); const plan = SettleUp.plan(table); const moved = plan.payments.reduce((sum, p) => sum + p.amount, 0); assert.equal(Money.round(moved), 60); }); test("it settles a real table's standings, not just hand-made numbers", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2], stack: 100 }); state.seats[0].buyIns = 100; state.seats[1].buyIns = 100; state.seats[2].buyIns = 100; state.seats[0].stack = 175; state.seats[1].stack = 90; state.seats[2].stack = 35; const rows = HoldemState.sessionStandings(state); const plan = SettleUp.plan(rows); // Bank balances — 300 in, 300 on the table — so everyone can be paid. assert.equal(HoldemState.bankReconciliation(state).balanced, true); assert.equal(plan.balanced, true); rows.forEach(row => { assert.equal(paidTo(plan, row.name) - paidBy(plan, row.name), row.cashPosition); }); });