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