Quarkus 3.40 on Java 25, built with the Maven wrapper. The engine is a line-by-line port of packages/mancala.js; a parity test replays games recorded with the TypeScript engine, including illegal moves, and compares every board, turn, error and the game JSON.
62 lines
2.9 KiB
JavaScript
62 lines
2.9 KiB
JavaScript
// Plays seeded random games with the TypeScript engine (packages/mancala.js) and writes every move and its outcome,
|
|
// so EngineParityTest can check that the Java engine plays exactly the same. Run it again after changing the rules:
|
|
//
|
|
// yarn --cwd ../../packages/mancala.js build && node scripts/record-engine-games.js
|
|
//
|
|
// Moves include illegal ones (the wrong player, an empty pit, a store, out of range) and moves after the end.
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { CommonMancalaGame } = require("../../../packages/mancala.js");
|
|
|
|
const GAME_COUNT = 15;
|
|
const MAX_MOVES = 400;
|
|
const OUTPUT = path.join(__dirname, "../src/test/resources/engine/recorded-games.json");
|
|
|
|
// mulberry32
|
|
function random(seed) {
|
|
return () => {
|
|
seed |= 0;
|
|
seed = (seed + 0x6d2b79f5) | 0;
|
|
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
};
|
|
}
|
|
|
|
function recordGame(number) {
|
|
const next = random(number + 1);
|
|
const pick = (items) => items[Math.floor(next() * items.length)];
|
|
const game = new CommonMancalaGame(`game-${number}`, `kc:player1-${number}`, `kc:player2-${number}`);
|
|
game.turnPlayerId = next() < 0.5 ? game.player1Id : game.player2Id;
|
|
const recorded = {
|
|
id: game.id, player1Id: game.player1Id, player2Id: game.player2Id, turnPlayerId: game.turnPlayerId, moves: [],
|
|
};
|
|
let movesAfterEnd = 0;
|
|
for (let i = 0; i < MAX_MOVES && movesAfterEnd < 2; i++) {
|
|
const roll = next();
|
|
const opponent = game.turnPlayerId === game.player1Id ? game.player2Id : game.player1Id;
|
|
const playerId = roll < 0.93 ? game.turnPlayerId : opponent;
|
|
const pitsWithStones = [0, 1, 2, 3, 4, 5].filter((pit) =>
|
|
game.board.pits[game.getBoardIndexByPlayerId(playerId, pit)].stoneCount > 0);
|
|
const pitIndex = roll < 0.85 && pitsWithStones.length > 0 ? pick(pitsWithStones)
|
|
: roll < 0.97 ? Math.floor(next() * 6) : pick([-1, 6, 7, 13, 14]);
|
|
const move = { playerId, pitIndex };
|
|
try {
|
|
game.moveByPlayerPit(playerId, pitIndex);
|
|
} catch (err) {
|
|
move.error = err.message;
|
|
}
|
|
Object.assign(move, { board: game.board.getStoneArray(), turnPlayerId: game.turnPlayerId, state: game.state });
|
|
recorded.moves.push(move);
|
|
if (game.state === "ended") movesAfterEnd++;
|
|
}
|
|
recorded.wonPlayerId = game.getWonPlayerId() ?? null;
|
|
recorded.game = JSON.parse(JSON.stringify(game));
|
|
return recorded;
|
|
}
|
|
|
|
const games = Array.from({ length: GAME_COUNT }, (_, number) => recordGame(number));
|
|
fs.writeFileSync(OUTPUT, JSON.stringify({ games }) + "\n");
|
|
const moves = games.reduce((sum, game) => sum + game.moves.length, 0);
|
|
console.log(`Recorded ${games.length} games, ${moves} moves, to ${path.relative(process.cwd(), OUTPUT)}`);
|