class DB { constructor() { if (typeof(localStorage) === "undefined") { throw new Error('Hmmm... no "localStorage" support'); } } get(key) { var b = localStorage.getItem(key); if (b) { b = JSON.parse(b); } else { b = null; } return b } set(key, value) { var b = JSON.stringify(value); localStorage.setItem(key, b); } del(key) { localStorage.removeItem(key); } } class Requests { put(remote, body, callback) { this.http("put", remote, callback, body); } post(remote, body, callback) { this.http("post", remote, callback, body); } get(remote, callback) { this.http("get", remote, callback, null); } http(method, remote, callback, body) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE) { callback(xmlhttp.responseText, xmlhttp.status); } }; xmlhttp.open(method, remote, true); if (typeof body == "undefined") { body = null; } xmlhttp.send(body); } } class Games { constructor() { this.db = new DB(); this.requests = new Requests(); } list(cb) { this.requests.get("/api/games", (text) => { var list = JSON.parse(text); cb(list); }); } forGame(id, cb, log) { this.get(id, (game) => { cb(game); games.update(id, game, (game) => {ui.drawGame(game)}, console.log, log); }); } forUser(id, uid, cb, log) { this.get(id, (game) => { var found = false; game.Players.forEach((player, idx) => { if (player.ID == uid) { cb(game.Players[idx]); found = true; } }); if (found) games.update(id, game, (game) => {ui.drawGame(game)}, console.log, log); }); } get(id, cb, catcher) { if (!catcher) catcher = console.log; this.requests.get("/api/games/"+id, (text, status) => { if (status != 200) { catcher("bad status getting game:", text, status); return; } var game = JSON.parse(text); cb(game); }); } create(id, cb, catcher) { if (!catcher) catcher = console.log; this.requests.post("/api/games/"+id, null, (text, status) => { if (status != 200) { catcher("bad status creating game:", text, status); return; } var game = JSON.parse(text); cb(game); }); } update(id, game, cb, catcher, log) { var n = 0; var m = game.Players.length; game.Players.forEach((player) => n += player.Participating ? 1 : 0); game.Turn = game.Turn % (m || 1); game.Log += "
" + log; if (n) while (m > 0 && (!game.Players[game.Turn].Active || !game.Players[game.Turn].Participating)) { game.Turn = (game.Turn + 1) % game.Players.length; m -= 1; } if (!catcher) catcher = console.log; this.requests.put("/api/games/"+id, JSON.stringify(game), (text, status) => { if (status != 200) { catcher("bad status updating game: ", text, status); return; } var game2 = JSON.parse(text); cb(game2); }); } } class UI { constructor(games) { this.games = games; this.ts = 0; this.threshold = 2; this.refresh(); setInterval(() => { this.refresh() }, 500); } now() { return new Date() / 1000; } refresh() { if (this.now() - this.ts < this.threshold) { return; } this.games.get("id", (game) => { this.drawGame(game); }); } formatCurrency(currency) { return `${Math.floor(currency/100)}.${currency%100 < 10 ? "0" : ""}${currency%100}` } formatCard(card) { if (card == -1) return `
?`; var suit = card.Suit; var value = card.Value + 2; switch (suit) { case 0 : suit = "hearts"; break; case 1 : suit = "spades"; break; case 2 : suit = "diamonds"; break; case 3 : suit = "clubs"; break; } switch (value) { case 11 : value = "jack"; break; case 12 : value = "queen"; break; case 13 : value = "king"; break; case 14 : value = "ace"; break; } return `
${value} of ${suit}`; } drawGame(game) { var playersActive = 0; var activePlayersChecked = 0; var winning = -1; game.Players.forEach((player, idx) => { if (player.ID != "" && player.Participating && player.Active) { if (winning == -1 || game.Players[winning].Card.Value <= player.Card.Value) { winning = idx; } playersActive += 1; if (player.Checked) { activePlayersChecked += 1; } } }); var complete = playersActive < 2 || playersActive == activePlayersChecked; this.ts = this.now(); var state = ` ${this.formatCurrency(game.Pot)} ${game.Log.split("
").slice(-15).join("
")}
`; var myseat = -1; game.Players.forEach((player, seatnum) => { if (player.ID != "") { state += ``; state += ` ${player.Name} ${this.formatCurrency(player.Balance)} ${this.formatCard(!complete && (this.me(player) || !player.Participating || !player.Active) ? -1 : player.Card)} `; if (this.me(player)) { myseat = seatnum; } state += ``; } }); var enabled = myseat == game.Turn && game.Players[myseat].Participating && game.Players[myseat].Active; var eleControlGame = this.eleControlGame(); var enabledWas = eleControlGame.getAttribute("disabled") == null; if (enabled != enabledWas) { var foo = (ele) => { ele.removeAttribute("disabled"); }; if (!enabled) { foo = (ele) => { ele.setAttribute("disabled", true); }; } foo(eleControlGame) Array.from(eleControlGame.children).forEach(foo); } state += ""; this.ele().innerHTML = state; } me(player) { return new DB().get("id") == player.ID } clear() { this.ele().innerHTML = ""; } eleControlGame() { return document.getElementById("control-game"); } eleControlGames() { return document.getElementById("control-games"); } ele() { return document.getElementById("game"); } } function uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } class Deck { constructor() { this.cards = new Array(52); } draw() { while (true) { var idx = Math.floor(Math.random() * 52); if (!this.cards[idx]) { this.cards[idx] = 1; return {Suit: idx%4, Value: idx%13}; } } } } function init() { var db = new DB(); if (!db.get("id")) { db.set("id", uuid()); } if (!db.get("name")) { var name = prompt("who are you?"); db.set("name", name); } var id = db.get("id"); var name = db.get("name"); games.create("id", () => { games.update("id", { Pot: 0, Players: [ {ID: id, Name: name}, ], }, (game) => ui.drawGame(game)); resetGame(); }); } function start() { var db = new DB(); var deck = new Deck() var id = db.get("id"); var name = db.get("name"); games.get("id", (game) => { var myturn = 0; var n = 0; game.Players.forEach((player, idx) => { myturn = player.ID == id ? idx : myturn; if (player.Participating) { n += 1; game.Players[idx].Active = true; game.Players[idx].Checked = false; game.Players[idx].Card = deck.draw(); } }); if (n == 0) { throw new Error("will not start game with no participants"); } game.Pot = 0; game.Log = "start!"; game.Turn = myturn; while (!game.Players[game.Turn].Participating) { game.Turn = (game.Turn+1) % game.Players.length; } games.update("id", game, (game) => {ui.drawGame(game)}, console.log, `${name} started the game`); }); } function join() { var db = new DB(); var id = db.get("id"); var name = db.get("name"); games.forGame("id", (game) => { var redundant = false; var set = false; for (var i = 0; i < game.Players.length; i++) { if (!set && game.Players[i].ID == id) { game.Players[i].Name = name game.Players[i].Participating = true set = true } } if (!set) { for (var i = 0; i < game.Players.length; i++) { if (!set && game.Players[i].ID == "") { game.Players[i] = { ID: id, Name: name, Participating: true, } set = true; } else { redundant = redundant || game.Players[i].ID == id; } } } if (!set) { throw new Error("did not take a seat"); } if (redundant) { throw new Error("cannot take 2 seats"); } }, `${name} joined the game`); } function drop() { var db = new DB(); var id = db.get("id"); var name = db.get("name"); games.forUser("id", id, (player) => { player.Participating = false; player.Active = false; }, `${name} dropped`); } function fold(ele) { var db = new DB(); var id = db.get("id"); var name = db.get("name"); games.forUser("id", id, (player) => { player.Active = false; }, `${name} folded`); } function check(ele) { var db = new DB(); var id = db.get("id"); games.forGame("id", (game) => { game.Turn += 1; game.Players.forEach((player) => { if (player.ID == id) player.Checked = true; }); }, `${name} checked`); } function collect(ele) { var db = new DB(); var id = db.get("id"); var name = db.get("name"); games.forGame("id", (game) => { var found = false; game.Players.forEach((player, idx) => { if (player.ID == id) { player.Balance += game.Pot; found = true; } }); if (!found) { throw new Error("nil player cannot collect pot"); } game.Pot = 0; }, `${name} collected the pot`); } function resetGame() { console.log("resetting game"); var db = new DB(); var name = db.get("name"); games.forGame("id", (game) => { game.Players = []; game.Turn = 0; game.Pot = 0; game.Log = "reset"; }, `${name} reset the game`); } function setWallet(ele) { var db = new DB(); var id = db.get("id"); var balance = parseInt(ele.parentNode.getElementsByTagName("input")[0].value); var name = db.get("name"); games.forUser("id", id, (player) => { player.Balance = balance; }, `${name} set their wallet to ${balance}`); } function raise(ele) { var db = new DB(); var id = db.get("id"); var bump = parseInt(ele.parentNode.getElementsByTagName("input")[0].value); var name = db.get("name"); games.forGame("id", (game) => { game.Turn += 1; game.Players.forEach((player, idx) => { if (player.ID == id) { player.Balance -= bump; if (player.Balance < 0) throw new Error("cannot bet more than you have"); game.Pot += bump; } else { player.Checked = false; } }); }, `${name} raises by ${bump}`); } var games = new Games(); var ui = new UI(games); init()