reorg
This commit is contained in:
67
client/css.css
Normal file
67
client/css.css
Normal file
@@ -0,0 +1,67 @@
|
||||
players, player, pot {
|
||||
display: block;
|
||||
}
|
||||
|
||||
players {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
players > * {
|
||||
width: 10em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
player {
|
||||
background-color: var(--background);
|
||||
margin: .25em;
|
||||
padding: .25em;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
player > * {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
player:not([participating]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
player:not([active]) {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
player[current] {
|
||||
color: var(--code);
|
||||
}
|
||||
|
||||
player[me] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
player[winner] {
|
||||
font-weight: bold;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
pot {
|
||||
font-size: 3em;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
card {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
29
client/index.html
Normal file
29
client/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||
<link rel="stylesheet" href="css.css">
|
||||
<script src="js.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Poor Man's Blind Man's Poker</h1>
|
||||
<form id="control-games" onsubmit="return false;">
|
||||
<button onclick="start(); return false;">Start</button>
|
||||
<button onclick="join(); return false;">Join</button>
|
||||
<button onclick="drop(); return false;">Drop</button>
|
||||
<details>
|
||||
<summary>Debug</summary>
|
||||
<button onclick="resetGame(); return false;">RESET</button>
|
||||
<input min=0 type="number"/>
|
||||
<button onclick="setWallet(this); return false;">Set wallet cents</button>
|
||||
</details>
|
||||
</form>
|
||||
<form id="control-game" onsubmit="return false;" >
|
||||
<button onclick="fold(this); return false;">Fold</button>
|
||||
<button onclick="check(this); return false;">Check</button>
|
||||
<button onclick="collect(this); return false;">Collect</button>
|
||||
<input min=0 type="number"/>
|
||||
<button onclick="raise(this); return false;">Raise cents</button>
|
||||
</form>
|
||||
<div id="game"></div>
|
||||
</body>
|
||||
</html>
|
||||
481
client/js.js
Normal file
481
client/js.js
Normal file
@@ -0,0 +1,481 @@
|
||||
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 += "<br>" + 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 `<br>?`;
|
||||
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 `<br>${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 = `
|
||||
<pot>${this.formatCurrency(game.Pot)}</pot>
|
||||
<log>${game.Log.split("<br>").slice(-15).join("<br>")}</log>
|
||||
<players>
|
||||
`;
|
||||
var myseat = -1;
|
||||
game.Players.forEach((player, seatnum) => {
|
||||
if (player.ID != "") {
|
||||
state += `<player
|
||||
${this.me(player) ? "me" : ""}
|
||||
${player.Participating ? "participating" : ""}
|
||||
${player.Active ? "active" : ""}
|
||||
${player.Checked ? "checked" : ""}
|
||||
${game.Turn == seatnum ? "current" : ""}
|
||||
${complete && winning == seatnum ? "winner" : ""}
|
||||
>`;
|
||||
state += `
|
||||
<row>
|
||||
<name>${player.Name}</name>
|
||||
<balance>${this.formatCurrency(player.Balance)}</balance>
|
||||
</row>
|
||||
<row>
|
||||
<card>${this.formatCard(!complete && (this.me(player) || !player.Participating || !player.Active) ? -1 : player.Card)}</card>
|
||||
</row>
|
||||
`;
|
||||
if (this.me(player)) {
|
||||
myseat = seatnum;
|
||||
}
|
||||
state += `</player>`;
|
||||
}
|
||||
});
|
||||
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 += "</players>";
|
||||
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()
|
||||
Reference in New Issue
Block a user