@nodots-llc/backgammon-ai
Version:
AI and integration for nodots-backgammon using the @nodots-llc/gnubg-hints native addon.
169 lines (168 loc) • 5.68 kB
JavaScript
import { createHintRequestFromGame } from '@nodots-llc/gnubg-hints';
function createColorNormalization(clockwiseColor, counterClockwiseColor) {
return {
toGnu: {
[clockwiseColor]: 'white',
[counterClockwiseColor]: 'black',
},
fromGnu: {
white: clockwiseColor,
black: counterClockwiseColor,
},
};
}
function normalizeChecker(checker, colorMap) {
const mappedColor = colorMap[checker.color] ?? checker.color;
if (mappedColor === checker.color) {
return checker;
}
return {
...checker,
color: mappedColor,
};
}
function normalizeCheckerArray(checkers, colorMap) {
if (!Array.isArray(checkers)) {
return [];
}
return checkers.map((checker) => normalizeChecker(checker, colorMap));
}
export function normalizeBoardForHints(board, colorMap) {
return {
id: board.id,
points: board.points.map((point) => ({
id: point.id,
kind: 'point',
position: { ...point.position },
checkers: normalizeCheckerArray(point.checkers, colorMap),
})),
bar: {
clockwise: {
...board.bar.clockwise,
checkers: normalizeCheckerArray(board.bar.clockwise?.checkers, colorMap),
},
counterclockwise: {
...board.bar.counterclockwise,
checkers: normalizeCheckerArray(board.bar.counterclockwise?.checkers, colorMap),
},
},
off: {
clockwise: {
...board.off.clockwise,
checkers: normalizeCheckerArray(board.off.clockwise?.checkers, colorMap),
},
counterclockwise: {
...board.off.counterclockwise,
checkers: normalizeCheckerArray(board.off.counterclockwise?.checkers, colorMap),
},
},
};
}
function deriveNormalizationFromGame(game) {
const clockwisePlayer = game.players.find((player) => player.direction === 'clockwise');
const counterPlayer = game.players.find((player) => player.direction === 'counterclockwise');
if (!clockwisePlayer || !counterPlayer) {
throw new Error('Unable to determine player directions for GNU BG normalization.');
}
return createColorNormalization(clockwisePlayer.color, counterPlayer.color);
}
function deriveNormalizationFromPlay(play) {
const activeColor = play.player.color;
const opponentColor = activeColor === 'white' ? 'black' : 'white';
if (play.player.direction === 'clockwise') {
return createColorNormalization(activeColor, opponentColor);
}
return createColorNormalization(opponentColor, activeColor);
}
function normalizeMatchScore(game, colorMap) {
const rawScore = game?.matchScore ??
game?.metadata?.matchScore ??
game?.metadata?.matchInfo?.matchScore ??
game?.matchInfo?.matchScore;
const whiteScore = rawScore?.white ?? 0;
const blackScore = rawScore?.black ?? 0;
const mapped = [0, 0];
const assign = (actualColor, value) => {
const gnuColor = colorMap[actualColor] ?? actualColor;
if (gnuColor === 'white') {
mapped[0] = value;
}
else {
mapped[1] = value;
}
};
assign('white', whiteScore);
assign('black', blackScore);
return mapped;
}
function normalizeCubeOwner(owner, colorMap) {
if (!owner) {
return null;
}
return colorMap[owner] ?? owner;
}
function deriveDiceFromPlay(play) {
const currentRoll = play.player.dice?.currentRoll;
if (Array.isArray(currentRoll) && currentRoll.length === 2) {
return [currentRoll[0] ?? 0, currentRoll[1] ?? 0];
}
return [0, 0];
}
export function buildHintContextFromPlay(play) {
const normalization = deriveNormalizationFromPlay(play);
const board = normalizeBoardForHints(play.board, normalization.toGnu);
const request = {
board,
dice: deriveDiceFromPlay(play),
cubeValue: 1,
cubeOwner: null,
matchScore: [0, 0],
matchLength: 0,
crawford: false,
jacoby: false,
beavers: false,
};
return { request, normalization };
}
export function buildHintContextFromGame(game, overrides = {}) {
const normalization = deriveNormalizationFromGame(game);
const normalizedBoard = normalizeBoardForHints(game.board, normalization.toGnu);
const request = createHintRequestFromGame(game, {
...overrides,
board: normalizedBoard,
cubeOwner: overrides.cubeOwner ??
normalizeCubeOwner(game.cube?.owner?.color, normalization.toGnu),
matchScore: overrides.matchScore ?? normalizeMatchScore(game, normalization.toGnu),
});
return { request, normalization };
}
export function getNormalizedPosition(container, normalizedColor) {
if (!container) {
return null;
}
if (container.kind === 'bar' || container.kind === 'off') {
return 0;
}
if (container.kind === 'point') {
const position = container.position;
if (typeof position === 'object' && position !== null) {
const value = normalizedColor === 'white'
? position.clockwise
: position.counterclockwise;
return typeof value === 'number' ? value : null;
}
}
return null;
}
export function getContainerKind(container) {
if (!container) {
return 'point';
}
if (container.kind === 'bar') {
return 'bar';
}
if (container.kind === 'off') {
return 'off';
}
return 'point';
}