177 lines
4.4 KiB
JavaScript
177 lines
4.4 KiB
JavaScript
const windDistributions = [
|
|
["E"],
|
|
["E", "W"],
|
|
["E", "S", "N"],
|
|
["E", "S", "W", "N"],
|
|
["E", "S", "W", "N", "X"],
|
|
];
|
|
|
|
const scoreTokens = {
|
|
'm': 20,
|
|
'ps': 4,
|
|
'pt': 8,
|
|
'ph': 8,
|
|
'xps': 2,
|
|
'xpt': 4,
|
|
'xph': 4,
|
|
'ks': 16,
|
|
'kt': 32,
|
|
'kh': 32,
|
|
'xks': 8,
|
|
'xkt': 16,
|
|
'xkh': 16,
|
|
'pd': 2,
|
|
'pw': 2,
|
|
'b': 4,
|
|
'p': 1,
|
|
'd': -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.prevailing = "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 = [];
|
|
let gs = new GameState(players, rounds);
|
|
gs.save();
|
|
return gs;
|
|
}
|
|
|
|
static load() {
|
|
let json = localStorage.getItem('mahjong-scorecard');
|
|
if (!json) {
|
|
return null;
|
|
}
|
|
let o = JSON.parse(json);
|
|
let gs = new GameState(o.p, o.r);
|
|
gs.bumpWind = o.b;
|
|
gs.prevailing = o.w;
|
|
return gs;
|
|
}
|
|
|
|
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.prevailing;
|
|
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.prevailing = nr.nextPrevailing;
|
|
if (nr.windsBump) {
|
|
this.bumpWind++;
|
|
}
|
|
this.save();
|
|
}
|
|
|
|
save() {
|
|
localStorage.setItem('mahjong-scorecard', JSON.stringify({
|
|
p: this.players,
|
|
r: this.rounds,
|
|
b: this.bumpWind,
|
|
w: this.prevailing,
|
|
}));
|
|
}
|
|
}
|
|
|
|
let gameState = GameState.load();
|
|
|
|
export function getGameState() {
|
|
return gameState;
|
|
}
|
|
|
|
export function setGameState(gs) {
|
|
gameState = gs;
|
|
} |