Let the dealer take back an action from the bar #26
@@ -1075,12 +1075,37 @@ body.hasActionBar #holdemSidebar {
|
||||
|
||||
.actionStatus {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 0.6rem;
|
||||
gap: 0.4rem 0.6rem;
|
||||
margin-bottom: 0.55rem;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
/* Always there while there's something to take back, rather than a toast per tap. */
|
||||
.actionUndo {
|
||||
flex: none;
|
||||
min-height: 30px;
|
||||
max-width: 100%;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #414141;
|
||||
background: #262626;
|
||||
color: #bdbdbd;
|
||||
font-family: inherit;
|
||||
font-size: 0.72rem;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actionUndo:hover {
|
||||
background: #333;
|
||||
border-color: #4f4f4f;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.actionStreet {
|
||||
color: #d8b46a;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -90,6 +90,20 @@ const ActionBar = {
|
||||
row.appendChild(tank);
|
||||
}
|
||||
|
||||
// A standing control rather than a toast per action: a dealer taps this bar
|
||||
// constantly, and a toast after every fold would be noise they'd learn to
|
||||
// ignore — which is exactly when they'd miss the one that mattered.
|
||||
const undoLabel = this.hooks.lastActionLabel();
|
||||
if (undoLabel) {
|
||||
const undo = document.createElement("button");
|
||||
undo.type = "button";
|
||||
undo.className = "actionUndo";
|
||||
undo.title = `Undo ${undoLabel}`;
|
||||
undo.textContent = `↶ ${undoLabel}`;
|
||||
undo.addEventListener("click", () => this.hooks.undoLast());
|
||||
row.appendChild(undo);
|
||||
}
|
||||
|
||||
return row;
|
||||
},
|
||||
|
||||
@@ -201,7 +215,11 @@ const ActionBar = {
|
||||
return btn;
|
||||
},
|
||||
|
||||
commit(mutate) {
|
||||
// Every action taken from the bar goes through here, which is also where it gets
|
||||
// recorded so it can be taken back. A misclick on Fold is the one mistake in this
|
||||
// tool a dealer can't talk their way out of.
|
||||
commit(label, mutate) {
|
||||
this.hooks.beginAction(label);
|
||||
mutate();
|
||||
this.hooks.save(this.state);
|
||||
this.hooks.rerender();
|
||||
@@ -212,20 +230,21 @@ const ActionBar = {
|
||||
const player = this.state.seats[index];
|
||||
const owed = Betting.toCall(this.state, player);
|
||||
const stack = Betting.stackOf(this.state, player);
|
||||
const who = HoldemState.displayName(player, index);
|
||||
|
||||
const row = document.createElement("div");
|
||||
row.className = "actionButtons";
|
||||
|
||||
row.appendChild(this.button("Fold", "actionFold", () => this.commit(() => Betting.fold(this.state, index))));
|
||||
row.appendChild(this.button("Fold", "actionFold", () => this.commit(`${who}'s fold`, () => Betting.fold(this.state, index))));
|
||||
|
||||
if (owed <= 0) {
|
||||
row.appendChild(this.button("Check", "actionCheck", () => this.commit(() => Betting.check(this.state, index))));
|
||||
row.appendChild(this.button("Check", "actionCheck", () => this.commit(`${who}'s check`, () => Betting.check(this.state, index))));
|
||||
} else if (owed >= stack) {
|
||||
// Calling would put them all-in, so say that rather than quoting a number
|
||||
// they can't cover.
|
||||
row.appendChild(this.button("Call all-in", "actionCall", () => this.commit(() => Betting.allIn(this.state, index)), Money.format(stack)));
|
||||
row.appendChild(this.button("Call all-in", "actionCall", () => this.commit(`${who}'s all-in`, () => Betting.allIn(this.state, index)), Money.format(stack)));
|
||||
} else {
|
||||
row.appendChild(this.button("Call", "actionCall", () => this.commit(() => Betting.call(this.state, index)), Money.format(owed)));
|
||||
row.appendChild(this.button("Call", "actionCall", () => this.commit(`${who}'s call`, () => Betting.call(this.state, index)), Money.format(owed)));
|
||||
}
|
||||
|
||||
if (stack > owed) {
|
||||
@@ -294,7 +313,8 @@ const ActionBar = {
|
||||
entry.appendChild(this.button("Raise to", "raiseConfirm", () => {
|
||||
const target = Math.max(0, Math.min(Number(this.raiseDraft) || 0, maxTo));
|
||||
this.raiseOpen = false;
|
||||
this.commit(() => Betting.raiseTo(this.state, index, target));
|
||||
const who = HoldemState.displayName(player, index);
|
||||
this.commit(`${who}'s raise to ${Money.format(target)}`, () => Betting.raiseTo(this.state, index, target));
|
||||
}));
|
||||
|
||||
wrap.appendChild(entry);
|
||||
@@ -313,7 +333,7 @@ const ActionBar = {
|
||||
this.hooks.resolvePot();
|
||||
return;
|
||||
}
|
||||
this.commit(() => Betting.closeStreet(this.state));
|
||||
this.commit(`dealing the ${Betting.streetLabel(Betting.STREETS[Betting.STREETS.indexOf(this.state.street) + 1]).toLowerCase()}`, () => Betting.closeStreet(this.state));
|
||||
}));
|
||||
|
||||
return row;
|
||||
|
||||
@@ -83,12 +83,39 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
};
|
||||
|
||||
// A short history of actions taken from the bar, so a misclicked Fold can be taken
|
||||
// back — and the one before it, since a dealer usually notices a beat late. Kept in
|
||||
// memory only: this is for the mistake you just made, not last night's hand.
|
||||
const MAX_UNDO_STEPS = 12;
|
||||
const actionHistory = [];
|
||||
|
||||
ActionBar.mount(state, {
|
||||
save: hooks.save,
|
||||
rerender: hooks.rerender,
|
||||
resolvePot: openPotWizard,
|
||||
|
||||
beginAction: label => {
|
||||
actionHistory.push({ label, snapshot: JSON.parse(JSON.stringify(state)) });
|
||||
if (actionHistory.length > MAX_UNDO_STEPS) actionHistory.shift();
|
||||
},
|
||||
|
||||
lastActionLabel: () => (actionHistory.length ? actionHistory[actionHistory.length - 1].label : null),
|
||||
|
||||
undoLast: () => {
|
||||
const step = actionHistory.pop();
|
||||
if (!step) return;
|
||||
Object.keys(state).forEach(key => delete state[key]);
|
||||
Object.assign(state, step.snapshot);
|
||||
hooks.save(state);
|
||||
Render.renderAll();
|
||||
Toast.show(`Took back ${step.label}.`, { duration: 2500 });
|
||||
},
|
||||
});
|
||||
|
||||
// Anything that ends the hand makes the per-action history meaningless — the
|
||||
// snapshots refer to a hand that no longer exists.
|
||||
const clearActionHistory = () => { actionHistory.length = 0; };
|
||||
|
||||
// Moving to the next hand used to ask "are you sure?" every single time —
|
||||
// the most-pressed button in a session, gated behind a system dialog. It now
|
||||
// just happens, and the toast offers the way back for the few seconds anyone
|
||||
@@ -97,6 +124,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const runUndoable = (message, mutate) => {
|
||||
const snapshot = JSON.parse(JSON.stringify(state));
|
||||
mutate();
|
||||
// Starting or settling a hand invalidates the per-action history: those
|
||||
// snapshots describe a hand that's over. The whole-step Undo below replaces it.
|
||||
clearActionHistory();
|
||||
hooks.save(state);
|
||||
Render.renderAll();
|
||||
|
||||
@@ -177,6 +207,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
HoldemState.clear();
|
||||
Object.assign(state, HoldemState.createDefault());
|
||||
clearActionHistory();
|
||||
Render.mount(state, hooks);
|
||||
Toast.show("Table reset.", { duration: 2500 });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user