--- /dev/null
+export function parse_datetime(input) {
+ const re = /^(?:(?<year>[0-9]{4})-(?<month>[0-9]{1,2})-(?<day>[0-9]{1,2})\s)?\s*(?<hour>[0-9]{1,2}):(?<minute>[0-9]{2})(?::(?<second>[0-9]{2}))?$/;
+ const match = input.match(re);
+ if (match === null) return null;
+ const datetime = new Date();
+ if (match.groups.year !== undefined) datetime.setFullYear(+match.groups.year);
+ if (match.groups.month !== undefined) datetime.setMonth(+match.groups.month - 1);
+ if (match.groups.day !== undefined) datetime.setDate(+match.groups.day);
+ datetime.setHours(+match.groups.hour);
+ datetime.setMinutes(+match.groups.minute);
+ datetime.setSeconds(match.groups.second !== undefined ? +match.groups.second : 0);
+ return datetime.getTime();
+}
+
+export function stringify_datetime(millis) {
+ if (millis === null || millis === undefined) return null;
+ const datetime = new Date(millis);
+ const pad = num => String(num).padStart(2, '0');
+ const now = new Date();
+ let date = "";
+ if (datetime.getFullYear() !== now.getFullYear() || datetime.getMonth() !== now.getMonth() || datetime.getDate() !== now.getDate()) {
+ date = datetime.getFullYear() + "-" + pad(datetime.getMonth() + 1) + "-" + pad(datetime.getDate()) + " ";
+ }
+ const time = pad(datetime.getHours()) + ":" + pad(datetime.getMinutes());
+ let seconds = "";
+ if (datetime.getSeconds() !== 0) {
+ seconds = ":" + pad(datetime.getSeconds());
+ }
+ return date + time + seconds;
+}
+
+export function parse_duration(input) {
+ const re = /^(?:(?<hours>[0-9]{1,2}):)?(?<minutes>[0-9]{2}):(?<seconds>[0-9]{2})$/;
+ const match = input.match(re);
+ if (match === null) return null;
+ return ((((match.groups.hours !== undefined ? +match.groups.hours : 0) * 60 + +match.groups.minutes) * 60) + +match.groups.seconds) * 1000;
+}
+
+export function stringify_duration(millis) {
+ if (millis === null || millis === undefined) return null;
+ const hours = ((millis / 1000 / 60 / 60) >> 0);
+ const minutes = ((millis / 1000 / 60) >> 0) % 60;
+ const seconds = ((millis / 1000) >> 0) % 60;
+ const pad = num => String(num).padStart(2, '0');
+ return (hours !== 0 ? pad(hours) + ":" : "") + pad(minutes) + ":" + pad(seconds);
+}
+import { parse_datetime, parse_duration, stringify_datetime, stringify_duration } from "./datetime.js";
import { random_title } from "./words.js";
export class GameList {
const id_row = document.createElement("tr");
const id_header = document.createElement("th");
- id_header.innerText = "id";
+ id_header.innerText = "ID";
const id_value = document.createElement("td");
id_value.innerText = game.id;
id_row.append(id_header);
id_row.append(id_value);
table.append(id_row);
+ const field_types = {
+ start_time: "datetime",
+ round_length: "duration",
+ tournament_length: "duration",
+ action_timeout: "duration",
+ };
+
for (const setting of Object.keys(game.settings)) {
if (setting === "title") continue;
const row = document.createElement("tr");
const header = document.createElement("th");
- header.innerText = setting;
+ header.innerText = setting.split("_").map(w => w[0].toUpperCase() + w.substr(1)).join(" ");
const value = document.createElement("td");
- value.innerText = game.settings[setting];
+ switch (field_types[setting]) {
+ case "datetime":
+ value.innerText = stringify_datetime(game.settings[setting]) || "When Full";
+ break;
+ case "duration":
+ value.innerText = stringify_duration(game.settings[setting]) || "";
+ break;
+ default:
+ value.innerText = game.settings[setting];
+ break;
+ }
row.append(header);
row.append(value);
table.append(row);
table.append(format_row);
const fields = [
- { name: "max_players", display: "Max Players", value: 2, formats: ["TexasHoldEm", "KnockOutWhist"] },
- { name: "small_blind", display: "Small Blind", value: 25, formats: ["TexasHoldEm"] },
- { name: "starting_stack", display: "Starting Stack", value: 1000, formats: ["TexasHoldEm"] },
- { name: "round_length", display: "Round Length", value: 60000, formats: ["TexasHoldEm"] },
- { name: "tournament_length", display: "Tournament Length", value: 600000, formats: ["TexasHoldEm"] },
- { name: "action_timeout", display: "Action Timeout", value: 30000, formats: ["TexasHoldEm"] },
+ { name: "start_time", display: "Start Time", value: "", type: "datetime", formats: ["TexasHoldEm", "KnockOutWhist"] },
+ { name: "max_players", display: "Max Players", value: 2, type: "number", formats: ["TexasHoldEm", "KnockOutWhist"] },
+ { name: "small_blind", display: "Small Blind", value: 25, type: "number", formats: ["TexasHoldEm"] },
+ { name: "starting_stack", display: "Starting Stack", value: 5000, step: 1000, type: "number", formats: ["TexasHoldEm"] },
+ { name: "round_length", display: "Round Length", value: "", type: "duration", formats: ["TexasHoldEm"] },
+ { name: "tournament_length", display: "Tournament Length", value: "", type: "duration", formats: ["TexasHoldEm"] },
+ { name: "action_timeout", display: "Action Timeout", value: "", type: "duration", formats: ["TexasHoldEm"] },
];
- for (const {name, display, value, formats} of fields) {
+ for (const {name, display, value, type, step, formats} of fields) {
const row = document.createElement("tr");
row.classList.add("game-field");
for (const format of formats) {
row.append(title);
const cell = document.createElement("td");
const input = document.createElement("input");
- input.setAttribute("type", "number");
+ switch (type) {
+ case "datetime":
+ input.setAttribute("placeholder", "[yyyy-mm-dd] hh:mm[:ss]");
+ input.onchange = () => {
+ settings[name] = parse_datetime(input.value);
+ input.classList.toggle("invalid-input", settings[name] === null && input.value !== "");
+ if (settings[name] === null) {
+ delete settings[name];
+ }
+ };
+ break;
+ case "duration":
+ input.setAttribute("placeholder", "[hh:]mm:ss");
+ input.onchange = () => {
+ settings[name] = parse_duration(input.value);
+ input.classList.toggle("invalid-input", settings[name] === null && input.value !== "");
+ if (settings[name] === null) {
+ delete settings[name];
+ }
+ };
+ break;
+ case "number":
+ input.setAttribute("type", "number");
+ input.setAttribute("min", 0);
+ if (step !== undefined) {
+ input.setAttribute("step", step);
+ }
+ input.onchange = () => {
+ settings[name] = Number(input.value);
+ if (settings[name] === 0) {
+ delete settings[name];
+ }
+ };
+ break;
+ }
input.value = value;
- input.onchange = () => settings[name] = Number(input.value);
input.onchange();
cell.append(input);
row.append(cell);
const svgns = "http://www.w3.org/2000/svg";
import { card_href, suit_href } from "./card.js";
+import { Chatroom } from "./chatroom.js";
import { CongratulateWinner } from "./winner.js";
export class TexasHoldEm {
bet_control_label.classList.add("bet-control-label");
this.svg.append(bet_control_label);
+ this.chat_control = document.createElementNS(svgns, "rect");
+ this.chat_control.setAttribute("x", "5");
+ this.chat_control.setAttribute("y", "470");
+ this.chat_control.setAttribute("width", "25");
+ this.chat_control.setAttribute("height", "25");
+ this.chat_control.setAttribute("rx", "2");
+ this.chat_control.onclick = () => this.chatroom_container.classList.toggle("hidden");
+ this.chat_control.classList.add("chat-control");
+ this.svg.append(this.chat_control);
+
this.container.append(this.svg);
for (const user_action of this.actions) {
this.take_action(user_action);
}
+ this.chatroom_container = document.createElement("div");
+ this.chatroom_container.classList.add("embedded-chatroom");
+ this.chatroom = new Chatroom(this.chatroom_container, summary, actions, username, send);
+
+ this.container.append(this.chatroom_container);
+
if (!this.seats.has(this.username)) {
this.send({type: "TakeAction", action: {action: "Join", seat: 0, chips: this.summary.settings.starting_stack}});
}