Keep the list of who's next, instead of leaving it on a napkin #110
@@ -146,6 +146,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<p id="standingsEmptyHint" class="fieldHint">Everyone who has bought in shows up here, with what they'd be owed if the night ended now.</p>
|
<p id="standingsEmptyHint" class="fieldHint">Everyone who has bought in shows up here, with what they'd be owed if the night ended now.</p>
|
||||||
|
|
||||||
|
<h3 class="panelSubhead">Waiting list</h3>
|
||||||
|
<form id="waitingListForm" class="waitingAdd">
|
||||||
|
<input type="text" id="waitingNameInput" maxlength="24" placeholder="Who's waiting?" autocomplete="off" />
|
||||||
|
<button type="submit" id="waitingAddBtn" class="holdemBtn holdemBtnSmall">Add</button>
|
||||||
|
</form>
|
||||||
|
<ol id="waitingList" class="waitingList"></ol>
|
||||||
|
<p id="waitingEmptyHint" class="fieldHint">Nobody waiting. Add the people standing behind the chairs, and the tool will offer them the next seat that opens.</p>
|
||||||
|
|
||||||
<div id="bankReconcile" class="bankReconcile" hidden>
|
<div id="bankReconcile" class="bankReconcile" hidden>
|
||||||
<div class="bankReconcileRow">
|
<div class="bankReconcileRow">
|
||||||
<span>Cash taken</span>
|
<span>Cash taken</span>
|
||||||
|
|||||||
@@ -2312,6 +2312,86 @@ body.hasActionBar #holdemSidebar {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The people standing behind the chairs. A short list in a panel that already has a
|
||||||
|
lot in it, so it is built from rows rather than a table: a name, the seat it can go
|
||||||
|
in, and a way off the list. */
|
||||||
|
.waitingAdd {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingAdd input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 44px;
|
||||||
|
background: #141414;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #eee;
|
||||||
|
font-family: inherit;
|
||||||
|
/* 16px, or iOS zooms the whole page in when it takes focus. */
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingAdd .holdemBtn {
|
||||||
|
flex: none;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingList {
|
||||||
|
list-style: none;
|
||||||
|
counter-reset: waiting;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
counter-increment: waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The position in the queue, which is the whole point of the list. */
|
||||||
|
.waitingRow::before {
|
||||||
|
content: counter(waiting) ".";
|
||||||
|
flex: none;
|
||||||
|
min-width: 1.2rem;
|
||||||
|
color: #9a9a9a;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingName {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #e8e8e8;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingNote {
|
||||||
|
flex: none;
|
||||||
|
color: #9a9a9a;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingRow .holdemBtn {
|
||||||
|
flex: none;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingDropBtn {
|
||||||
|
min-width: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Side-pot breakdown ---------- */
|
/* ---------- Side-pot breakdown ---------- */
|
||||||
|
|
||||||
.potBreakdown {
|
.potBreakdown {
|
||||||
|
|||||||
@@ -56,6 +56,35 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onSeatLongPress: index => SeatMenu.open(state, seatMenuHooks, index),
|
onSeatLongPress: index => SeatMenu.open(state, seatMenuHooks, index),
|
||||||
|
|
||||||
|
// The waiting list, from the Session panel and from an empty chair's menu.
|
||||||
|
// The state change goes inside runUndoable, not before it: the snapshot is
|
||||||
|
// taken as the first thing that happens, so anything done ahead of it is
|
||||||
|
// already baked into what Undo would restore.
|
||||||
|
seatWaiting: (id, index) => {
|
||||||
|
const entry = (state.waitingList || []).find(row => row.id === id);
|
||||||
|
const seat = state.seats[index];
|
||||||
|
if (!entry || !seat) {
|
||||||
|
Toast.show("They aren't on the list any more.", { duration: 3000 });
|
||||||
|
hooks.rerender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const occupied = (seat.name || "").trim() || Number(seat.buyIns) > 0 || Number(seat.stack) > 0;
|
||||||
|
if (occupied) {
|
||||||
|
Toast.show(`${HoldemState.displayName(seat, index)} is already in that seat.`, { duration: 3500 });
|
||||||
|
hooks.rerender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runUndoable(`${entry.name} takes seat ${index + 1}. Add their chips when they buy in.`,
|
||||||
|
() => HoldemState.seatFromWaitingList(state, id, index));
|
||||||
|
},
|
||||||
|
|
||||||
|
dropWaiting: id => {
|
||||||
|
const entry = (state.waitingList || []).find(row => row.id === id);
|
||||||
|
if (!entry) return;
|
||||||
|
runUndoable(`${entry.name} taken off the waiting list.`,
|
||||||
|
() => HoldemState.removeFromWaitingList(state, id));
|
||||||
|
},
|
||||||
onGiveButton: index => seatMenuHooks.makeDealer(index),
|
onGiveButton: index => seatMenuHooks.makeDealer(index),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -403,6 +432,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
// A misdeal. Everything in the middle goes back to whoever put it there, and the
|
// A misdeal. Everything in the middle goes back to whoever put it there, and the
|
||||||
// same button deals the same hand again.
|
// same button deals the same hand again.
|
||||||
|
document.getElementById("waitingListForm").addEventListener("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const field = document.getElementById("waitingNameInput");
|
||||||
|
const name = field.value.trim();
|
||||||
|
if (!name) return;
|
||||||
|
field.value = "";
|
||||||
|
runUndoable(`${name} added to the waiting list.`, () => HoldemState.addToWaitingList(state, name));
|
||||||
|
field.focus();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("misdealBtn").addEventListener("click", async () => {
|
document.getElementById("misdealBtn").addEventListener("click", async () => {
|
||||||
const inPlay = HoldemState.unresolvedContributions(state);
|
const inPlay = HoldemState.unresolvedContributions(state);
|
||||||
const acted = state.seats.filter(p => p.hasActed).length;
|
const acted = state.seats.filter(p => p.hasActed).length;
|
||||||
|
|||||||
@@ -1058,9 +1058,63 @@ const Render = {
|
|||||||
|
|
||||||
this.renderBankReconciliation();
|
this.renderBankReconciliation();
|
||||||
this.renderSettleUp(standings);
|
this.renderSettleUp(standings);
|
||||||
|
this.renderWaitingList();
|
||||||
this.renderStorageWarning();
|
this.renderStorageWarning();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Who is standing behind the chairs, in the order they turned up. Each one carries
|
||||||
|
// the two things a dealer does with them: sit them down, or take them off the list
|
||||||
|
// because they've gone to the bar.
|
||||||
|
renderWaitingList() {
|
||||||
|
const list = document.getElementById("waitingList");
|
||||||
|
const hint = document.getElementById("waitingEmptyHint");
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
const waiting = this.state.waitingList || [];
|
||||||
|
const open = HoldemState.openSeats(this.state);
|
||||||
|
list.innerHTML = "";
|
||||||
|
hint.hidden = waiting.length > 0;
|
||||||
|
|
||||||
|
waiting.forEach((entry, position) => {
|
||||||
|
const row = document.createElement("li");
|
||||||
|
row.className = "waitingRow";
|
||||||
|
|
||||||
|
const name = document.createElement("span");
|
||||||
|
name.className = "waitingName";
|
||||||
|
name.textContent = entry.name;
|
||||||
|
row.appendChild(name);
|
||||||
|
|
||||||
|
// Only the person at the front is offered a seat, because a list nobody
|
||||||
|
// keeps in order is just a pile of names — and jumping the queue is the
|
||||||
|
// thing the dealer gets shouted at for.
|
||||||
|
if (position === 0 && open.length > 0) {
|
||||||
|
const seat = document.createElement("button");
|
||||||
|
seat.type = "button";
|
||||||
|
seat.className = "holdemBtn holdemBtnSmall waitingSeatBtn";
|
||||||
|
seat.textContent = `Seat ${open[0] + 1}`;
|
||||||
|
seat.title = `Sit ${entry.name} in seat ${open[0] + 1}`;
|
||||||
|
seat.addEventListener("click", () => this.hooks.seatWaiting(entry.id, open[0]));
|
||||||
|
row.appendChild(seat);
|
||||||
|
} else if (position === 0) {
|
||||||
|
const full = document.createElement("span");
|
||||||
|
full.className = "waitingNote";
|
||||||
|
full.textContent = "table full";
|
||||||
|
row.appendChild(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
const drop = document.createElement("button");
|
||||||
|
drop.type = "button";
|
||||||
|
drop.className = "holdemBtn holdemBtnSmall holdemBtnGhost waitingDropBtn";
|
||||||
|
drop.textContent = "✕";
|
||||||
|
drop.setAttribute("aria-label", `Take ${entry.name} off the waiting list`);
|
||||||
|
drop.title = `Take ${entry.name} off the list`;
|
||||||
|
drop.addEventListener("click", () => this.hooks.dropWaiting(entry.id));
|
||||||
|
row.appendChild(drop);
|
||||||
|
|
||||||
|
list.appendChild(row);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// A toast is a moment; this table not being saved is a fact about the rest of the
|
// A toast is a moment; this table not being saved is a fact about the rest of the
|
||||||
// night. It sits at the top of the panel a dealer opens to check the money, and it
|
// night. It sits at the top of the panel a dealer opens to check the money, and it
|
||||||
// stays there, because the raise pad dismisses toasts and the one time this warning
|
// stays there, because the raise pad dismisses toasts and the one time this warning
|
||||||
|
|||||||
@@ -47,6 +47,21 @@ const SeatMenu = {
|
|||||||
run: () => this.hooks.toggleDealtIn(index),
|
run: () => this.hooks.toggleDealtIn(index),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// An empty chair with people waiting: the dealer taps the chair somebody is
|
||||||
|
// about to sit in, which is what their hand is doing anyway. Only the front of
|
||||||
|
// the queue is offered, for the same reason the panel only offers them — a list
|
||||||
|
// that isn't kept in order is just a pile of names.
|
||||||
|
const waiting = (this.state.waitingList || [])[0];
|
||||||
|
const emptyChair = !(player.name || "").trim() &&
|
||||||
|
!(Number(player.buyIns) > 0) && !(Number(player.stack) > 0);
|
||||||
|
if (waiting && emptyChair) {
|
||||||
|
actions.push({
|
||||||
|
label: `Seat ${waiting.name}`,
|
||||||
|
className: "menuAccent",
|
||||||
|
run: () => this.hooks.seatWaiting(waiting.id, index),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Leaving for the night, which is a different thing from sitting out a hand:
|
// Leaving for the night, which is a different thing from sitting out a hand:
|
||||||
// the chips go back over the rail and the chair opens. Offered only for a seat
|
// the chips go back over the rail and the chair opens. Offered only for a seat
|
||||||
// that has somebody in it, since an empty chair has nobody to pay.
|
// that has somebody in it, since an empty chair has nobody to pay.
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ const HoldemState = {
|
|||||||
// out, and both halves have to stay on the record or the bank check reads
|
// out, and both halves have to stay on the record or the bank check reads
|
||||||
// as a shortfall for the rest of the evening.
|
// as a shortfall for the rest of the evening.
|
||||||
cashedOut: [],
|
cashedOut: [],
|
||||||
|
// People standing behind the chairs. A dealer at a full table keeps this
|
||||||
|
// list on a napkin; keeping it here means the tool can offer the next seat
|
||||||
|
// that opens to whoever has been waiting longest.
|
||||||
|
waitingList: [],
|
||||||
// Which seats actually put a straddle out when the hand was dealt, so the
|
// Which seats actually put a straddle out when the hand was dealt, so the
|
||||||
// felt can badge them. Read off the deal rather than worked out again
|
// felt can badge them. Read off the deal rather than worked out again
|
||||||
// later, the same way the posted blinds are.
|
// later, the same way the posted blinds are.
|
||||||
@@ -233,6 +237,16 @@ const HoldemState = {
|
|||||||
noFlopNoDrop: rake.noFlopNoDrop !== false,
|
noFlopNoDrop: rake.noFlopNoDrop !== false,
|
||||||
};
|
};
|
||||||
parsed.rakeTaken = Math.max(0, Money.round(Number(parsed.rakeTaken) || 0));
|
parsed.rakeTaken = Math.max(0, Money.round(Number(parsed.rakeTaken) || 0));
|
||||||
|
parsed.waitingList = Array.isArray(parsed.waitingList)
|
||||||
|
? parsed.waitingList
|
||||||
|
.map(row => (typeof row === "string" ? { id: null, name: row } : row))
|
||||||
|
.filter(row => row && String(row.name || "").trim())
|
||||||
|
.map(row => ({
|
||||||
|
id: row.id || this.makeId("waiting"),
|
||||||
|
name: String(row.name).trim().slice(0, 24),
|
||||||
|
addedAt: Number(row.addedAt) || 0,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
parsed.cashedOut = Array.isArray(parsed.cashedOut)
|
parsed.cashedOut = Array.isArray(parsed.cashedOut)
|
||||||
? parsed.cashedOut
|
? parsed.cashedOut
|
||||||
.filter(row => row && row.id)
|
.filter(row => row && row.id)
|
||||||
@@ -875,6 +889,59 @@ const HoldemState = {
|
|||||||
//
|
//
|
||||||
// Chips in play counts everything a player could hand back: what's in front of
|
// Chips in play counts everything a player could hand back: what's in front of
|
||||||
// them and what's in the middle waiting to be won.
|
// them and what's in the middle waiting to be won.
|
||||||
|
// ---- The people standing behind the chairs ----
|
||||||
|
//
|
||||||
|
// A full home game always has a couple of them, and the list lives on a napkin or
|
||||||
|
// in the dealer's head — which is where the argument comes from an hour later about
|
||||||
|
// who said they were next. Kept in order of arrival, because that is the only
|
||||||
|
// ordering anybody has ever agreed to.
|
||||||
|
|
||||||
|
addToWaitingList(state, name) {
|
||||||
|
const trimmed = String(name || "").trim().slice(0, 24);
|
||||||
|
if (!trimmed) return null;
|
||||||
|
if (!Array.isArray(state.waitingList)) state.waitingList = [];
|
||||||
|
const entry = { id: this.makeId("waiting"), name: trimmed, addedAt: Date.now() };
|
||||||
|
state.waitingList.push(entry);
|
||||||
|
return entry;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFromWaitingList(state, id) {
|
||||||
|
if (!Array.isArray(state.waitingList)) return null;
|
||||||
|
const index = state.waitingList.findIndex(row => row.id === id);
|
||||||
|
if (index < 0) return null;
|
||||||
|
return state.waitingList.splice(index, 1)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Chairs with nobody in them, in seat order. What "a seat has opened" means, and
|
||||||
|
// the same question the deal prompt answers when it counts the players.
|
||||||
|
openSeats(state) {
|
||||||
|
return state.seats
|
||||||
|
.map((player, index) => ({ player, index }))
|
||||||
|
.filter(({ player }) => !(player.name || "").trim() &&
|
||||||
|
!(Number(player.buyIns) > 0) && !(Number(player.stack) > 0))
|
||||||
|
.map(({ index }) => index);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sit the next person down. Their name goes on the chair and nothing else does:
|
||||||
|
// they haven't bought in yet, and guessing a stack for them would put chips on the
|
||||||
|
// table that nobody has paid for — which is the one thing the bank check exists to
|
||||||
|
// catch.
|
||||||
|
seatFromWaitingList(state, id, index) {
|
||||||
|
const seat = state.seats[index];
|
||||||
|
if (!seat) return { ok: false, reason: "There's no such seat." };
|
||||||
|
if ((seat.name || "").trim() || Number(seat.buyIns) > 0 || Number(seat.stack) > 0) {
|
||||||
|
return { ok: false, reason: `${this.displayName(seat, index)} is already in that seat.` };
|
||||||
|
}
|
||||||
|
const entry = this.removeFromWaitingList(state, id);
|
||||||
|
if (!entry) return { ok: false, reason: "They aren't on the list any more." };
|
||||||
|
|
||||||
|
seat.name = entry.name;
|
||||||
|
// Not dealt in and holding nothing: the next thing that happens is the dealer
|
||||||
|
// taking their money, and Add chips is what says so.
|
||||||
|
seat.inHand = false;
|
||||||
|
return { ok: true, name: entry.name, index };
|
||||||
|
},
|
||||||
|
|
||||||
// ---- Somebody leaving with their chips ----
|
// ---- Somebody leaving with their chips ----
|
||||||
//
|
//
|
||||||
// The most ordinary thing that happens at a home game after midnight, and the tool
|
// The most ordinary thing that happens at a home game after midnight, and the tool
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
// The people standing behind the chairs.
|
||||||
|
//
|
||||||
|
// A full home game always has a couple of them, and the list lives on a napkin or in
|
||||||
|
// the dealer's head — which is where the argument comes from an hour later about who
|
||||||
|
// said they were next. Order of arrival, because it is the only ordering anybody has
|
||||||
|
// ever agreed to.
|
||||||
|
const test = require("node:test");
|
||||||
|
const { describe } = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { HoldemState } = require("./load");
|
||||||
|
|
||||||
|
const table = (seated = 2, seats = 4) => {
|
||||||
|
const state = HoldemState.createDefault();
|
||||||
|
HoldemState.resizeSeats(state, seats);
|
||||||
|
["Ann", "Ben", "Cal", "Dee"].slice(0, seated).forEach((name, i) => {
|
||||||
|
state.seats[i].name = name;
|
||||||
|
state.seats[i].stack = 200;
|
||||||
|
state.seats[i].buyIns = 200;
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("keeping the list", () => {
|
||||||
|
test("names go on in the order they arrive", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
HoldemState.addToWaitingList(state, "Sam");
|
||||||
|
assert.deepEqual(state.waitingList.map(row => row.name), ["Priya", "Sam"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("whitespace is trimmed and nothing empty gets on", () => {
|
||||||
|
const state = table();
|
||||||
|
assert.ok(HoldemState.addToWaitingList(state, " Priya "));
|
||||||
|
assert.equal(HoldemState.addToWaitingList(state, " "), null);
|
||||||
|
assert.equal(HoldemState.addToWaitingList(state, ""), null);
|
||||||
|
assert.deepEqual(state.waitingList.map(row => row.name), ["Priya"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("two people with the same name are two people", () => {
|
||||||
|
// Home games have two Mikes. The list is keyed by id, not by name.
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Mike");
|
||||||
|
HoldemState.addToWaitingList(state, "Mike");
|
||||||
|
assert.equal(state.waitingList.length, 2);
|
||||||
|
assert.notEqual(state.waitingList[0].id, state.waitingList[1].id);
|
||||||
|
|
||||||
|
HoldemState.removeFromWaitingList(state, state.waitingList[0].id);
|
||||||
|
assert.equal(state.waitingList.length, 1, "only one of them leaves");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("taking somebody off gives them back, so the toast can name them", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
const gone = HoldemState.removeFromWaitingList(state, state.waitingList[0].id);
|
||||||
|
assert.equal(gone.name, "Priya");
|
||||||
|
assert.deepEqual(state.waitingList, []);
|
||||||
|
assert.equal(HoldemState.removeFromWaitingList(state, "not-a-real-id"), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("which chairs are open", () => {
|
||||||
|
test("the ones with nobody in them, in seat order", () => {
|
||||||
|
assert.deepEqual(HoldemState.openSeats(table(2, 4)), [2, 3]);
|
||||||
|
assert.deepEqual(HoldemState.openSeats(table(4, 4)), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a chair with chips or a buy-in on it is not open", () => {
|
||||||
|
// Somebody who has stepped away from the table still owns their seat.
|
||||||
|
const state = table(2, 4);
|
||||||
|
state.seats[2].stack = 120;
|
||||||
|
state.seats[3].buyIns = 100;
|
||||||
|
assert.deepEqual(HoldemState.openSeats(state), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sitting somebody down", () => {
|
||||||
|
test("their name goes on the chair and they come off the list", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
HoldemState.addToWaitingList(state, "Sam");
|
||||||
|
const result = HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 2);
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(state.seats[2].name, "Priya");
|
||||||
|
assert.deepEqual(state.waitingList.map(row => row.name), ["Sam"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("with nothing in front of them, because they haven't bought in yet", () => {
|
||||||
|
// Guessing a stack would put chips on the table nobody has paid for, which is
|
||||||
|
// the one thing the bank check exists to catch.
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 2);
|
||||||
|
|
||||||
|
assert.equal(state.seats[2].stack, null);
|
||||||
|
assert.equal(state.seats[2].buyIns, 0);
|
||||||
|
assert.equal(state.seats[2].inHand, false, "they're in the next hand, not this one");
|
||||||
|
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("never into a chair somebody is already in", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
const result = HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 0);
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.match(result.reason, /Ann is already/);
|
||||||
|
assert.equal(state.waitingList.length, 1, "and they keep their place in the queue");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("nor from a name that has already been seated", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
const id = state.waitingList[0].id;
|
||||||
|
HoldemState.seatFromWaitingList(state, id, 2);
|
||||||
|
const again = HoldemState.seatFromWaitingList(state, id, 3);
|
||||||
|
assert.equal(again.ok, false);
|
||||||
|
assert.equal(state.seats[3].name, "", "the second chair is untouched");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a seat that doesn't exist is refused rather than invented", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
assert.equal(HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 99).ok, false);
|
||||||
|
assert.equal(state.waitingList.length, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a seat opening up", () => {
|
||||||
|
test("cashing somebody out is what makes room for the next one", () => {
|
||||||
|
const state = table(4, 4);
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
assert.deepEqual(HoldemState.openSeats(state), [], "the table is full");
|
||||||
|
|
||||||
|
HoldemState.cashOut(state, 1);
|
||||||
|
assert.deepEqual(HoldemState.openSeats(state), [1]);
|
||||||
|
|
||||||
|
const seated = HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 1);
|
||||||
|
assert.equal(seated.ok, true);
|
||||||
|
assert.equal(state.seats[1].name, "Priya");
|
||||||
|
// Ben's night is still on the record, and the money still adds up.
|
||||||
|
assert.equal(state.cashedOut.length, 1);
|
||||||
|
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("what a saved table remembers", () => {
|
||||||
|
test("the list survives a reload", () => {
|
||||||
|
const state = table();
|
||||||
|
HoldemState.addToWaitingList(state, "Priya");
|
||||||
|
const migrated = HoldemState.migrate(JSON.parse(JSON.stringify(state)));
|
||||||
|
assert.deepEqual(migrated.waitingList.map(row => row.name), ["Priya"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a table saved before the list existed has nobody waiting", () => {
|
||||||
|
const parsed = JSON.parse(JSON.stringify(table()));
|
||||||
|
delete parsed.waitingList;
|
||||||
|
assert.deepEqual(HoldemState.migrate(parsed).waitingList, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("junk in a saved list is cleaned up rather than trusted", () => {
|
||||||
|
const parsed = JSON.parse(JSON.stringify(table()));
|
||||||
|
parsed.waitingList = [{ name: " Priya " }, { name: "" }, null, "Sam"];
|
||||||
|
const migrated = HoldemState.migrate(parsed);
|
||||||
|
assert.deepEqual(migrated.waitingList.map(row => row.name), ["Priya", "Sam"]);
|
||||||
|
assert.ok(migrated.waitingList.every(row => row.id), "and everybody gets an id");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user