Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38488f2978 |
@@ -1962,20 +1962,75 @@ body.hasActionBar #toastHost {
|
|||||||
margin-bottom: 0.6rem;
|
margin-bottom: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is the screen where the money gets decided, so it holds the same 44px+
|
||||||
|
targets as the rest of the tool rather than the smallest ones in it. Picking the
|
||||||
|
wrong winner is the one mistake here that costs somebody real money. */
|
||||||
.potWizardChoice {
|
.potWizardChoice {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.7rem;
|
||||||
|
min-height: 56px;
|
||||||
background: #141414;
|
background: #141414;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
border-radius: 6px;
|
border-radius: 10px;
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.6rem 0.8rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.85rem;
|
font-size: 0.95rem;
|
||||||
|
transition: border-color 0.12s ease, background 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.potWizardChoice:hover {
|
.potWizardChoice:hover {
|
||||||
border-color: #555;
|
border-color: #555;
|
||||||
|
background: #191919;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoice input {
|
||||||
|
flex: none;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
accent-color: #1e8449;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selected has to be obvious at a glance — a dealer confirms this with players
|
||||||
|
watching, and a checkbox alone is easy to misread across a table. */
|
||||||
|
.potWizardChoice.chosen {
|
||||||
|
border-color: #1e8449;
|
||||||
|
background: linear-gradient(180deg, #16301f, #12241a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoice.chosen .potWizardChoiceName {
|
||||||
|
color: #d6ffe0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoiceText {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoiceName {
|
||||||
|
color: #f0f0f0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoiceUpTo {
|
||||||
|
color: #9a9a9a;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoiceCards {
|
||||||
|
flex: none;
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoiceCards .cardRed {
|
||||||
|
color: #ff6b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.potWizardSummary ul {
|
.potWizardSummary ul {
|
||||||
@@ -1996,6 +2051,37 @@ body.hasActionBar #toastHost {
|
|||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Every button in this modal is one a dealer presses with players waiting. */
|
||||||
|
#potWizardBody .holdemBtn {
|
||||||
|
min-height: 48px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The confirm is the whole point of the step, so it gets the whole width rather
|
||||||
|
than being sized to the length of its own label. */
|
||||||
|
.potWizardRound > .holdemBtn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardActions .holdemBtn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardSummary li {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
/* Stacked, with the action that ends the hand furthest from a stray thumb. */
|
||||||
|
.potWizardActions {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.potWizardChoice {
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
#holdemLayout {
|
#holdemLayout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -184,19 +184,9 @@ const Betting = {
|
|||||||
|
|
||||||
const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1;
|
const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1;
|
||||||
state.street = this.STREETS[nextStreetIndex] || "river";
|
state.street = this.STREETS[nextStreetIndex] || "river";
|
||||||
|
state.actingSeatIndex = this.liveSeats(state).length > 1
|
||||||
if (this.liveSeats(state).length <= 1) {
|
? this.nextLiveIndex(state, state.dealerSeatIndex)
|
||||||
state.actingSeatIndex = null;
|
: null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// After the flop the first live player left of the button acts — which heads-up
|
|
||||||
// is the big blind, since the button is the small blind. Naming the big blind
|
|
||||||
// directly also covers a button parked on a seat nobody is sitting in.
|
|
||||||
const { bbIndex, headsUp } = HoldemState.getBlindSeats(state);
|
|
||||||
state.actingSeatIndex = headsUp && bbIndex !== null && !state.seats[bbIndex].folded
|
|
||||||
? bbIndex
|
|
||||||
: this.nextLiveIndex(state, state.dealerSeatIndex);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isHandOver(state) {
|
isHandOver(state) {
|
||||||
@@ -217,23 +207,15 @@ const Betting = {
|
|||||||
|
|
||||||
const sb = Number(state.smallBlind) || 0;
|
const sb = Number(state.smallBlind) || 0;
|
||||||
const bb = Number(state.bigBlind) || 0;
|
const bb = Number(state.bigBlind) || 0;
|
||||||
const { sbIndex, bbIndex, headsUp } = HoldemState.getBlindSeats(state);
|
const sbIndex = this.seatAt(state, state.dealerSeatIndex, 1);
|
||||||
|
const bbIndex = this.seatAt(state, state.dealerSeatIndex, 2);
|
||||||
|
|
||||||
if (sbIndex === null || bbIndex === null) {
|
if (sb > 0 && state.seats[sbIndex].inHand) this.putIn(state, sbIndex, sb);
|
||||||
state.currentBet = 0;
|
if (bb > 0 && state.seats[bbIndex].inHand) this.putIn(state, bbIndex, bb);
|
||||||
state.actingSeatIndex = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sb > 0) this.putIn(state, sbIndex, sb);
|
|
||||||
if (bb > 0) this.putIn(state, bbIndex, bb);
|
|
||||||
|
|
||||||
state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex])));
|
state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex])));
|
||||||
state.lastRaiseSize = bb || 0;
|
state.lastRaiseSize = bb || 0;
|
||||||
|
state.actingSeatIndex = this.nextLiveIndex(state, bbIndex);
|
||||||
// Heads-up the button is the small blind and acts first before the flop; with
|
|
||||||
// three or more it's the seat after the big blind.
|
|
||||||
state.actingSeatIndex = headsUp ? sbIndex : this.nextLiveIndex(state, bbIndex);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Winners get their share put back in front of them.
|
// Winners get their share put back in front of them.
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ const PotWizard = {
|
|||||||
this.playerLabelById = new Map(
|
this.playerLabelById = new Map(
|
||||||
players.map(p => [p.id, this.labelFor(p, state.seats.indexOf(p))])
|
players.map(p => [p.id, this.labelFor(p, state.seats.indexOf(p))])
|
||||||
);
|
);
|
||||||
|
// The label carries name and cards together for the log and the payout lines;
|
||||||
|
// the choice rows want them separately so each can be styled and read on its own.
|
||||||
|
this.nameById = new Map(
|
||||||
|
players.map(p => [p.id, HoldemState.displayName(p, state.seats.indexOf(p))])
|
||||||
|
);
|
||||||
|
this.cardsById = new Map(
|
||||||
|
players.map(p => [p.id, (p.cards || []).filter(Boolean)])
|
||||||
|
);
|
||||||
const contributions = players.map(p => ({
|
const contributions = players.map(p => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
contribution: HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount),
|
contribution: HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount),
|
||||||
@@ -105,13 +113,46 @@ const PotWizard = {
|
|||||||
contenders.forEach(c => {
|
contenders.forEach(c => {
|
||||||
const label = document.createElement("label");
|
const label = document.createElement("label");
|
||||||
label.className = "potWizardChoice";
|
label.className = "potWizardChoice";
|
||||||
|
|
||||||
const checkbox = document.createElement("input");
|
const checkbox = document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
checkbox.value = c.id;
|
checkbox.value = c.id;
|
||||||
const text = document.createElement("span");
|
// The row is the target, not the box — so the row has to look chosen too.
|
||||||
text.textContent = `${this.playerLabelById.get(c.id)} — up to ${Money.format(c.remaining)}`;
|
checkbox.addEventListener("change", () => {
|
||||||
|
label.classList.toggle("chosen", checkbox.checked);
|
||||||
|
});
|
||||||
label.appendChild(checkbox);
|
label.appendChild(checkbox);
|
||||||
|
|
||||||
|
const text = document.createElement("span");
|
||||||
|
text.className = "potWizardChoiceText";
|
||||||
|
|
||||||
|
const name = document.createElement("span");
|
||||||
|
name.className = "potWizardChoiceName";
|
||||||
|
name.textContent = this.nameById.get(c.id);
|
||||||
|
text.appendChild(name);
|
||||||
|
|
||||||
|
const upTo = document.createElement("span");
|
||||||
|
upTo.className = "potWizardChoiceUpTo";
|
||||||
|
upTo.textContent = `up to ${Money.format(c.remaining)}`;
|
||||||
|
text.appendChild(upTo);
|
||||||
label.appendChild(text);
|
label.appendChild(text);
|
||||||
|
|
||||||
|
// Cards sit apart from the name rather than inside it, so a dealer reading
|
||||||
|
// a row across the table sees the hand, not a sentence.
|
||||||
|
const cards = this.cardsById.get(c.id) || [];
|
||||||
|
if (cards.length > 0) {
|
||||||
|
const cardsEl = document.createElement("span");
|
||||||
|
cardsEl.className = "potWizardChoiceCards";
|
||||||
|
cards.forEach((card, i) => {
|
||||||
|
if (i > 0) cardsEl.appendChild(document.createTextNode(" "));
|
||||||
|
const span = document.createElement("span");
|
||||||
|
if (/[HD]$/i.test(card)) span.className = "cardRed";
|
||||||
|
span.textContent = Render.formatCard(card);
|
||||||
|
cardsEl.appendChild(span);
|
||||||
|
});
|
||||||
|
label.appendChild(cardsEl);
|
||||||
|
}
|
||||||
|
|
||||||
choices.appendChild(label);
|
choices.appendChild(label);
|
||||||
});
|
});
|
||||||
round.appendChild(choices);
|
round.appendChild(choices);
|
||||||
|
|||||||
@@ -163,36 +163,10 @@ const HoldemState = {
|
|||||||
state.dealerSeatIndex = this.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
state.dealerSeatIndex = this.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Seats dealt into this hand, in blind order starting from the seat after the
|
|
||||||
// button. Blinds follow the players, not the chairs — the seats immediately left
|
|
||||||
// of the button are empty often enough that counting chairs posts them to nobody.
|
|
||||||
liveOrderFromButton(state) {
|
|
||||||
const order = [];
|
|
||||||
for (let step = 1; step <= state.seatCount; step++) {
|
|
||||||
const index = this.nextSeatIndex(state, state.dealerSeatIndex, step);
|
|
||||||
if (state.seats[index].inHand) order.push(index);
|
|
||||||
}
|
|
||||||
return order;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Who posts what. Heads-up is its own rule: with two players the button posts the
|
|
||||||
// small blind, which is the opposite of the usual order.
|
|
||||||
getBlindSeats(state) {
|
getBlindSeats(state) {
|
||||||
const live = this.liveOrderFromButton(state);
|
const sbIndex = this.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
||||||
const dealerIndex = state.dealerSeatIndex;
|
const bbIndex = this.nextSeatIndex(state, state.dealerSeatIndex, 2);
|
||||||
|
return { dealerIndex: state.dealerSeatIndex, sbIndex, bbIndex };
|
||||||
if (live.length < 2) {
|
|
||||||
return { dealerIndex, sbIndex: null, bbIndex: null, headsUp: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (live.length === 2) {
|
|
||||||
const buttonIsPlaying = state.seats[dealerIndex].inHand;
|
|
||||||
const sbIndex = buttonIsPlaying ? dealerIndex : live[0];
|
|
||||||
const bbIndex = live.find(i => i !== sbIndex);
|
|
||||||
return { dealerIndex, sbIndex, bbIndex, headsUp: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { dealerIndex, sbIndex: live[0], bbIndex: live[1], headsUp: false };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startNextHand(state) {
|
startNextHand(state) {
|
||||||
|
|||||||
Reference in New Issue
Block a user