webtools/site/mahjong/main.js
Leon Mika c5420c97eb
All checks were successful
/ publish (push) Successful in 50s
Finished the scorecard
2025-12-21 10:56:49 +11:00

241 lines
6.9 KiB
JavaScript

import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js";
import { GameState, getGameState, setGameState, calculateScore } from "./models.js";
function emitGameStateEvent(details) {
let ev = new CustomEvent("gamestatechanged", { detail: details });
window.dispatchEvent(ev);
}
window.Stimulus = Application.start();
Stimulus.register("scorecard", class extends Controller {
static targets = ["prevailing"];
initialize() {
}
connect() {
}
handleGameState(ev) {
switch (ev.detail.mode) {
case "newgame":
case "endround":
this.element.classList.add("hidden");
break;
case "startgame":
case "nextround":
this._rebuildTable();
this.element.classList.remove("hidden");
break;
}
}
endRound(ev) {
ev.preventDefault();
emitGameStateEvent({mode: "endround"});
}
endGame(ev) {
ev.preventDefault();
if (!confirm("Are you sure you want to end the game?")) {
return;
}
emitGameStateEvent({mode: "newgame"});
}
_rebuildTable() {
let gameState = getGameState();
let tbl = document.createElement("table");
tbl.classList.add("scorecard");
let thead = document.createElement("thead");
let thr = document.createElement("tr");
let headRows = gameState.players.map(player => {
let th = document.createElement("th");
th.textContent = player.name + " (" + player.wind + ")";
return th;
});
thr.append(...headRows);
thead.append(thr);
tbl.append(thead);
let tbody = document.createElement("tbody");
for (let r of gameState.rounds) {
let tr = document.createElement("tr");
for (let c of r) {
let td = document.createElement("td");
td.textContent = c.s;
if (c.w) {
td.textContent += " 🀄";
td.classList.add("winner");
}
tr.append(td);
}
tbody.append(tr);
}
tbl.append(tbody);
let tfoot = document.createElement("tfoot");
for (let t of gameState.playerTotals()) {
let td = document.createElement("td");
td.textContent = t;
tfoot.append(td);
}
tbl.append(tfoot);
this.element.querySelector("table.scorecard").replaceWith(tbl);
this.prevailingTarget.textContent = `Prevailing: ${gameState.prevailing}`;
}
});
Stimulus.register("newgame", class extends Controller {
static targets = ["playerName"];
connect() {
}
handleGameState(ev) {
switch (ev.detail.mode) {
case "startgame":
case "endround":
case "nextround":
this.element.classList.add("hidden");
break;
case "newgame":
this.element.classList.remove("hidden");
break;
}
}
startGame() {
let players = [];
this.playerNameTargets.forEach(el => {
if (el.value != "") {
players.push(el.value);
}
});
setGameState(GameState.newGame(players));
this.element.classList.add("hidden");
emitGameStateEvent({mode: "startgame"});
}
});
Stimulus.register("endround", class extends Controller {
static targets = ["form", "input", "wind", "prevailing"];
connect() {
}
handleGameState(ev) {
switch (ev.detail.mode) {
case "startgame":
case "nextround":
case "newgame":
this.element.classList.add("hidden");
break;
case "endround":
this._prepForms();
this._updatePreview();
this.element.classList.remove("hidden");
break;
}
}
updatePreview(ev) {
ev.preventDefault();
this._updatePreview();
}
_updatePreview() {
let scoreExprs = this._readScoreExprs();
let nextRound = getGameState().determineNextRound(scoreExprs);
for (let i in nextRound.roundScores) {
let previewElem = this.element.querySelector(`.preview[data-player="${i}"]`);
previewElem.textContent = nextRound.roundScores[i].score;
if (parseInt(i) === nextRound.roundWinner) {
previewElem.classList.add("winner");
previewElem.textContent += " 🀄";
} else {
previewElem.classList.remove("winner");
}
}
if (nextRound.windsBump) {
this.windTarget.textContent = ` will be East next round`;
} else {
this.windTarget.textContent = ` will remain East next round`;
}
let nextWindNameElem = document.createElement("strong");
nextWindNameElem.textContent = getGameState().players[nextRound.nextRoundEast].name;
this.windTarget.prepend(nextWindNameElem);
this.prevailingTarget.textContent = `Next prevailing: ${nextRound.nextPrevailing}`;
}
goBack(ev) {
emitGameStateEvent({mode: "startgame"});
}
nextRound(ev) {
ev.preventDefault();
let scoreExprs = this._readScoreExprs();
getGameState().startNextRound(scoreExprs);
emitGameStateEvent({mode: "nextround"});
}
_prepForms() {
let newFormElems = getGameState().players.map((player, i) => {
let outerDiv = document.createElement("div");
let label = document.createElement("label");
label.textContent = player.name + " (" + player.wind + ")";
label.setAttribute("for", `player-${i}`);
let div = document.createElement("div");
div.classList.add("score-input-group");
let input = document.createElement("input");
input.name = `player-${i}`;
input.dataset["endroundTarget"] = "input";
input.dataset["action"] = "keyup->endround#updatePreview";
input.dataset["player"] = i;
let preview = document.createElement("div");
preview.classList.add("preview");
preview.textContent = "0";
preview.dataset["player"] = i;
div.append(input, preview);
outerDiv.append(label, div);
return outerDiv;
});
this.formTarget.replaceChildren(...newFormElems);
}
_readScoreExprs() {
let scoreExprs = [];
for (let playerIndex in getGameState().players) {
let thisScoreExpr = this.formTarget.querySelector(`input[data-player="${playerIndex}"]`).value;
scoreExprs.push(thisScoreExpr);
}
return scoreExprs;
}
});
document.addEventListener("DOMContentLoaded", () => {
let game = getGameState();
if (game !== null) {
emitGameStateEvent({mode: "startgame"});
} else {
emitGameStateEvent({mode: "newgame"});
}
})