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) { this.get(id, (game) => { cb(game); games.update(id, game, (game) => {ui.drawGame(game)}); }); } forUser(id, uid, cb) { this.get(id, (game) => { game.Players.forEach((player, idx) => { if (player.ID == uid) { cb(game.Players[idx]); } }); games.update(id, game, (game) => {ui.drawGame(game)}); }); } get(id, cb) { this.requests.get("/api/games/"+id, (text, status) => { if (status != 200) { throw new Error("bad status getting game: "+status+": "+text); } var game = JSON.parse(text); cb(game); }); } create(id, cb) { this.requests.post("/api/games/"+id, null, (text, status) => { if (status != 200) { throw new Error("bad status creating game: "+status+": "+text); } var game = JSON.parse(text); cb(game); }); } update(id, game, cb) { // TODO compute turn this.requests.put("/api/games/"+id, JSON.stringify(game), (text, status) => { if (status != 200) { throw new Error("bad status updating game: "+status+": "+text); } var game2 = JSON.parse(text); cb(game2); }); } } class UI { constructor(games) { this.games = games; this.ts = 0; this.threshold = 2; setInterval(() => { this.refresh() }, 2000); } now() { return new Date() / 1000; } refresh() { if (this.now() - this.ts < this.threshold) { return; } this.games.get("id", (game) => { this.drawGame(game); }); } drawGame(game) { this.ts = this.now(); var state = ` ${game.Pot} `; game.Players.forEach((player, seatnum) => { if (player.ID != "") { state += ``; state += ` ${player.Name} ${player.Balance} ${this.me(player) ? "?" : player.Card} `; if (this.me(player)) { var myturn = seatnum == game.Turn var enabled = player.Active && myturn; var eleControlGame = this.eleControlGame(); var enabledWas = eleControlGame.getAttribute("disabled") == null; if (enabled != enabledWas) { if (enabled) { eleControlGame.removeAttribute("disabled"); } else { eleControlGame.setAttribute("disabled", true); } } } state += ``; } }); 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 idx; } } } card(idx) { var suit = idx % 4; switch (suit) { case 0: suit = "hearts"; break; case 1: suit = "clubs"; break; case 2: suit = "diamonds"; break; case 3: suit = "spades"; break; } var num = idx % 13; num += 2 switch (num) { case 10: num = "jack"; break; case 11: num = "queen"; break; case 12: num = "king"; break; case 13: num = "ace"; break; } return `${num} of ${suit}`; } } 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)); }); games.get("id", (game) => { ui.drawGame(game); }); } function start() { var db = new DB(); var deck = new Deck() var id = db.get("id"); 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].Card = deck.draw(); } }); if (n == 0) { throw new Error("will not start game with no participants"); } 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)}); }); } function join() { var db = new DB(); var id = db.get("id"); games.forUser("id", id, (player) => { if (player.Participating) throw new Error("redundant join"); player.Participating = true; player.Active = false; }); } function drop() { var db = new DB(); var id = db.get("id"); games.forUser("id", id, (player) => { if (!player.Participating) throw new Error("redundant drop"); player.Participating = false; player.Active = false; }); } function fold(ele) { var db = new DB(); var id = db.get("id"); games.forUser("id", id, (player) => { if (!player.Active) throw new Error("redundant fold"); player.Active = false; }); } function check(ele) { var db = new DB(); var id = db.get("id"); games.forGame("id", (game) => { game.Turn += 1; }); } function raise(ele) { var db = new DB(); var id = db.get("id"); var bump = ele.parentNode.getElementsByTagName("input")[0].value; games.forGame("id", (game) => { game.Turn += 1; game.Players.forEach((player, idx) => { if (player.ID == id) { // todo it's supposed to be a string, should render client-side player.Balance -= bump; if (player.Balance < 0) throw new Error("cannot bet more than you have"); game.Pot += bump; } }); }); } var games = new Games(); var ui = new UI(games); init()