UNPKG

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