diff --git a/site/mahjong/guide.html b/site/mahjong/guide.html
deleted file mode 100644
index 43ceeb7..0000000
--- a/site/mahjong/guide.html
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
End Round
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/site/mahjong/main.js b/site/mahjong/main.js
deleted file mode 100644
index f9ba03c..0000000
--- a/site/mahjong/main.js
+++ /dev/null
@@ -1,227 +0,0 @@
-import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js";
-import { GameState, getGameState, setGameState, calculateScore } from "./models.js";
-
-function emitGameStateEvent(details) {
- // window.setTimeout(() => {
- let ev = new CustomEvent("gamestatechanged", { detail: details });
- window.dispatchEvent(ev);
- // }, 1);
-}
-
-
-window.Stimulus = Application.start();
-
-Stimulus.register("scorecard", class extends Controller {
- static targets = ["prevailing"];
-
- initialize() {
- }
-
- connect() {
- // this._rebuildTable();
- // this.element.classList.remove("hidden");
- }
-
- 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.classList.add("winner");
- td.textContent += " 🏆";
- }
- 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.prevaling}`;
- }
-});
-
-Stimulus.register("newgame", class extends Controller {
- static targets = ["playerName"];
-
- connect() {
- this.element.classList.remove("hidden");
- }
-
- 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"];
-
- connect() {
- // this.element.classList.remove("hidden");
- }
-
- handleGameState(ev) {
- switch (ev.detail.mode) {
- case "startgame":
- case "nextround":
- case "newgame":
- this.element.classList.add("hidden");
- break;
- case "endround":
- this._prepForms();
- this.element.classList.remove("hidden");
- break;
- }
- }
-
- updatePreview(ev) {
- ev.preventDefault();
-
- 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.textContent += " 🏆";
- }
- }
-
- if (nextRound.windsBump) {
- this.windTarget.textContent = `${getGameState().players[nextRound.nextRoundEast].name} will be East next round`;
- } else {
- this.windTarget.textContent = `${getGameState().players[nextRound.nextRoundEast].name} will remain East next round`;
- }
- }
-
- 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;
- }
-});
\ No newline at end of file
diff --git a/site/mahjong/models.js b/site/mahjong/models.js
deleted file mode 100644
index 48d1762..0000000
--- a/site/mahjong/models.js
+++ /dev/null
@@ -1,151 +0,0 @@
-const windDistributions = [
- ["E"],
- ["E", "W"],
- ["E", "S", "N"],
- ["E", "S", "W", "N"],
- ["E", "S", "W", "N", "X"],
-];
-
-const scoreTokens = {
- 'm': 1,
- 'ps': 1,
- 'pt': 1,
- 'ph': 1,
- 'xps': 1,
- 'xpt': 1,
- 'xph': 1,
- 'ks': 1,
- 'kt': 1,
- 'kh': 1,
- 'xks': 1,
- 'xkt': 1,
- 'xkh': 1,
- 'pd': 1,
- 'pw': 1,
- 'b': 1,
-}
-
-function parseTokens(str) {
- // Split on whitespace and filter out empty strings
- const tokens = str.split(/\s+/).filter(token => token.length > 0);
-
- const pattern = /^([0-9]*)([a-z]+)$/;
- const result = [];
-
- for (const token of tokens) {
- const match = token.match(pattern);
- if (match) {
- result.push({
- number: match[1] ? parseInt(match[1], 10) : null,
- letters: match[2]
- });
- }
- }
-
- return result;
-}
-
-export function calculateScore(str) {
- const tokens = parseTokens(str);
- let o = {score: 0, winner: false};
-
- for (const token of tokens) {
- if (!(token.letters in scoreTokens)) {
- continue;
- }
- o.score += scoreTokens[token.letters] * (token.number || 1);
- if (token.letters === 'm') {
- o.winner = true;
- }
- }
- return o;
-}
-
-
-export class GameState {
- constructor(players, rounds) {
- this.players = players;
- this.rounds = rounds;
- this.bumpWind = 0;
- this.prevaling = "E";
- }
-
- static newGame(playerNames) {
- let players = playerNames.map(name => { return { name: name } });
- let windDistribution = windDistributions[players.length - 1];
- for (let i = 0; i < players.length; i++) {
- players[i].wind = windDistribution[i];
- }
-
- let rounds = [];
- return new GameState(players, rounds);
- }
-
- determineNextRound(playerScoreExprs) {
- let roundScores = playerScoreExprs.map(calculateScore);
- let roundWinner = roundScores.findIndex(rs => rs.winner);
-
- let currentEast = this.players.findIndex(p => p.wind === 'E');
- let nextRoundEast = currentEast;
- if (roundWinner !== currentEast) {
- nextRoundEast = (currentEast + 1) % this.players.length;
- }
-
- let windsBump = roundWinner !== currentEast;
- let nextPrevailing = this.prevaling;
- if (windsBump) {
- nextPrevailing = windDistributions[3][((this.bumpWind + 1) / this.players.length)|0];
- }
-
- return {
- roundScores: roundScores,
- roundWinner: roundWinner,
- nextRoundEast: nextRoundEast,
- windsBump: roundWinner !== currentEast,
- nextPrevailing: nextPrevailing,
- }
- }
-
- playerTotals() {
- let scores = this.players.map(p => 0);
- for (let i = 0; i < this.rounds.length; i++) {
- for (let j = 0; j < this.rounds[i].length; j++) {
- scores[j] += this.rounds[i][j].s;
- }
- }
-
- return scores;
- }
-
- startNextRound(playerScoreExprs) {
- let nr = this.determineNextRound(playerScoreExprs);
-
- this.rounds.push(nr.roundScores.map((s, i) => {
- if (i === nr.roundWinner) {
- return { s: s.score, w: true };
- }
- return { s: s.score };
- }));
-
- let windDistribution = windDistributions[this.players.length - 1];
- for (let i = 0; i < this.players.length; i++) {
- let pi = (nr.nextRoundEast + i) % this.players.length;
- this.players[pi].wind = windDistribution[i];
- }
-
- this.prevaling = nr.nextPrevailing;
- if (nr.windsBump) {
- this.bumpWind++;
- }
- }
-}
-
-let gameState = new GameState();
-
-export function getGameState() {
- return gameState;
-}
-
-export function setGameState(gs) {
- gameState = gs;
-}
\ No newline at end of file
diff --git a/site/mahjong/style.css b/site/mahjong/style.css
deleted file mode 100644
index 1940ccb..0000000
--- a/site/mahjong/style.css
+++ /dev/null
@@ -1,21 +0,0 @@
-.hidden {
- display: none;
-}
-
-.main-table {
- table-layout: fixed;
-}
-
-.score-input-group {
- display: grid;
- gap: 15px;
- grid-template-columns: 2fr 1fr;
- align-items: baseline;
-}
-
-.btns {
- display: flex;
- gap: 10px;
- justify-content: space-between;
- align-items: center;
-}
\ No newline at end of file