webtools/site/mahjong/models.js
Leon Mika 1f8394f23b
Some checks failed
/ publish (push) Failing after 34s
Added the end game
2025-12-20 22:32:30 +11:00

151 lines
3.8 KiB
JavaScript

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;
}