// The money bar: chips, wallet, buying in, cashing out. // // Buying chips is not instant and cannot be. gogobee owns the euros and has no // inbound API, so it polls Pete for the crossing, moves the money on its side, // and pushes the verdict back — a few seconds, end to end. So the button does // not lie about it: the chips show as "buying" until they are real, and this // file polls until they are. Nothing spendable appears until gogobee has said // it took the euros. // // Exposed as window.PeteGames so the table (blackjack.js) shares one view of // the money rather than keeping a second copy that drifts from this one. (function () { "use strict"; var bar = document.querySelector("[data-chipbar]"); if (!bar) return; var chipsEl = bar.querySelector("[data-chips]"); var pendingEl = bar.querySelector("[data-pending]"); var eurosEl = bar.querySelector("[data-euros]"); var amountEl = bar.querySelector("[data-buyin-amount]"); var buyBtn = bar.querySelector("[data-buyin]"); var cashBtn = bar.querySelector("[data-cashout]"); var msgEl = bar.querySelector("[data-chipbar-msg]"); var listeners = []; var view = null; var pollTimer = null; var pollUntil = 0; function money(n) { return (n || 0).toLocaleString(); } function say(text, tone) { if (!msgEl) return; if (!text) { msgEl.classList.add("hidden"); return; } msgEl.textContent = text; msgEl.classList.remove("hidden"); msgEl.style.color = tone === "bad" ? "#cc3d4a" : ""; } function paint(v) { view = v; if (chipsEl) chipsEl.textContent = money(v.chips); if (eurosEl) eurosEl.textContent = (v.euros || 0).toFixed(2); if (pendingEl) { if (v.pending > 0) { pendingEl.textContent = "+" + money(v.pending) + " buying…"; pendingEl.classList.remove("hidden"); } else { pendingEl.classList.add("hidden"); } } // You cannot cash out mid-hand: the stake is already on the table. if (cashBtn) cashBtn.disabled = v.chips <= 0 || !!v.hand; if (buyBtn) buyBtn.disabled = v.chips + v.pending >= v.cap; listeners.forEach(function (fn) { fn(v); }); } // pollPending keeps asking while a buy-in is in flight, and stops the moment // it lands — or is refused, which looks the same from here (pending drops to // zero) and is told apart by whether the chips arrived. function pollPending() { clearTimeout(pollTimer); if (Date.now() > pollUntil) { say("gogobee hasn't answered yet. Your euros are safe — give it a moment and reload."); return; } pollTimer = setTimeout(function () { get().then(function (v) { if (!v) return; if (v.pending > 0) { pollPending(); return; } if (v.chips > (pollPending.was || 0)) { say("Chips are yours. Good luck!"); } else { say("gogobee wouldn't cover that — not enough euros in your wallet.", "bad"); } }); }, 1200); } function request(path, body) { return fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body || {}), }).then(function (res) { return res.json().catch(function () { return {}; }).then(function (data) { if (!res.ok) throw new Error(data.error || "that didn't work"); return data; }); }); } function get() { return fetch("/api/games/table", { headers: { "Accept": "application/json" } }) .then(function (res) { return res.ok ? res.json() : null; }) .then(function (v) { if (v) paint(v); return v; }) .catch(function () { return null; }); } if (buyBtn) { buyBtn.addEventListener("click", function () { var amount = parseInt(amountEl && amountEl.value, 10); if (!(amount > 0)) { say("How many chips?", "bad"); return; } buyBtn.disabled = true; say("Asking gogobee for " + money(amount) + " euros…"); pollPending.was = view ? view.chips : 0; request("/api/games/buyin", { amount: amount }) .then(function (v) { paint(v); pollUntil = Date.now() + 60000; pollPending(); }) .catch(function (err) { say(err.message, "bad"); buyBtn.disabled = false; }); }); } if (cashBtn) { cashBtn.addEventListener("click", function () { cashBtn.disabled = true; say("Cashing you out…"); request("/api/games/cashout", { amount: 0 }) .then(function (v) { paint(v); say("Chips are on their way back to euros. They'll show in your wallet shortly."); // The euro balance Pete shows is whatever gogobee last told it, so it // only moves once the credit has actually gone through over there. setTimeout(get, 4000); }) .catch(function (err) { say(err.message, "bad"); cashBtn.disabled = false; }); }); } window.PeteGames = { // onUpdate registers a listener called on every fresh view of the money. onUpdate: function (fn) { listeners.push(fn); if (view) fn(view); }, // apply pushes a view the table already fetched (a deal answers with one), // so playing a hand doesn't need a second round-trip to refresh the chips. apply: paint, refresh: get, post: request, say: say, view: function () { return view; }, }; get(); })();