UNPKG

tsgammon-core

Version:

A Backgammon library for Typescript, formerly developed as a part of tsgammon-ui

178 lines (177 loc) 7.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.boardState = void 0; const EOGStatus_1 = require("./EOGStatus"); const GameConf_1 = require("./GameConf"); function countWhitePieces(pieces) { return pieces.filter((n) => n > 0).reduce((n, m) => n + m, 0); } function countRedPieces(pieces) { return countWhitePieces(pieces.map((n) => -n)); } /** * 駒の配置を格納した配列から、BoardStateを生成する * * @param pieces 駒の配置(省略時は標準ルールでの開始時な配置) * @param bornOffs すでにベアリングオフした駒の数の対(順に自分、相手:省略時は0) * @param innerPos この値とそれ以降がインナーボードとなる * @param isEoGFunc 終局判定関数(終局時にtrueを返す) * @returns 盤面 */ function boardState(pieces = GameConf_1.standardConf.initialPos, bornOffs = [0, 0], innerPos = 19, isEoGFunc = (board) => board.pieceCount === 0) { return initBoardState(pieces, bornOffs, innerPos, isEoGFunc); } exports.boardState = boardState; function posInverterFor(points) { return (pos) => points.length - 1 - pos; } function initBoardState(points, bornOffs = [0, 0], innerPos, isEoGFunc) { const myBornOff = bornOffs[0]; const opponentBornOff = bornOffs[1]; const pieceCount = countWhitePieces(points); const opponentPieceCount = countRedPieces(points); const bearOffPos = points.length - 1; const reverted = revertPoints(points); const invertPos = posInverterFor(points); const lastPiecePos = points.findIndex((n) => 0 < n); const found = reverted.findIndex((n) => 0 < n); const opponentLastPiecePos = found === -1 ? 0 : invertPos(found); const isBearable = innerPos <= lastPiecePos; const myPipCount = countPip(points); const opponentPipCount = countPip(reverted); const board = { points, pieceCount, opponentPieceCount, bearOffPos, invertPos, myBornOff, opponentBornOff, lastPiecePos, opponentLastPiecePos, isBearable, eogStatus() { const isEndOfGame = this.isEndOfGame(); const isGammon = isEndOfGame && this.isGammonish(); const isBackgammon = isGammon && this.isBackgammonishAlso(); return (0, EOGStatus_1.eog)({ isEndOfGame, isGammon, isBackgammon, }); }, isEndOfGame() { return isEoGFunc(this); }, isGammonish() { return this.opponentBornOff === 0; }, isBackgammonishAlso() { // 自分のinnerPosとバーの間が、相手のアウターになる const outerPos = points.length - innerPos; // 7 = 26 - 19 const opponentOuterAndBar = [...Array(outerPos)].map((_, index) => index + innerPos); return (opponentOuterAndBar .map((pos) => this.points[pos]) .filter((c) => c < 0) .reduce((m, n) => m + n, 0) < 0); }, isRunningGame() { return this.lastPiecePos < this.opponentLastPiecePos; }, piecesAt(n) { return this.points[n]; }, movePiece(from, pip) { return doMove(this, from, pip, innerPos); }, revert() { const lastPiecePos = invertPos(this.opponentLastPiecePos); const opponentLastPiecePos = invertPos(this.lastPiecePos); const isBearable = innerPos <= lastPiecePos; return Object.assign(Object.assign({}, this), { points: revertPoints(this.points), myBornOff: this.opponentBornOff, opponentBornOff: this.myBornOff, pieceCount: this.opponentPieceCount, opponentPieceCount: this.pieceCount, lastPiecePos, opponentLastPiecePos, isBearable, myPipCount: this.opponentPipCount, opponentPipCount: this.myPipCount }); }, myPipCount, opponentPipCount, innerPos, outerPos: points.length - 1 - innerPos, // 6 = 25 - 19 }; return board; } function revertPoints(points) { const invertPos = posInverterFor(points); return points.map((_, index) => { const n = -points[invertPos(index)]; // remove negative 0 return n === 0 ? 0 : n; }); } function doMove(board, from, pip, innerPos) { const boardSize = board.points.length - 1; // 25 // 動かそうとする駒の位置が範囲外 if (from < 0 || boardSize < from) { return board; } // 動かそうとする場所に駒がない const piecesToMove = board.points[from]; if (piecesToMove <= 0) { return board; } // ベアオフではなく、行先がブロックされている const to = from + pip > boardSize ? boardSize : from + pip; const isBearOff = boardSize <= to; if (!isBearOff && board.points[to] < -1) { return board; } // 駒を取り上げる const piecesAfter = board.points.slice(); piecesAfter[from] = piecesAfter[from] - 1; // 必要なら自分の最後尾の駒の位置を更新する // 上がりなら、上がり数を更新して終了 if (isBearOff) { const lastPiecePos = recalcLastPiecePos(from, board, piecesAfter); const myPipCount = board.myPipCount - (boardSize - from); return Object.assign(Object.assign({}, board), { points: piecesAfter, myBornOff: board.myBornOff + 1, pieceCount: board.pieceCount - 1, lastPiecePos, myPipCount }); } let opponentLastPiecePos; let opponentPipCount = board.opponentPipCount; // ヒット if (piecesAfter[to] === -1) { const bar = boardSize; piecesAfter[to] = 0; piecesAfter[bar] = piecesAfter[bar] - 1; // 相手の最後尾の駒の位置をバーに更新する opponentLastPiecePos = boardSize; // ヒットした分のpipcountを更新 opponentPipCount += to; } else { // ヒットでなければそのまま opponentLastPiecePos = board.opponentLastPiecePos; } // 移動先に駒を置く piecesAfter[to] = piecesAfter[to] + 1; const lastPiecePos = recalcLastPiecePos(from, board, piecesAfter); // 上がれるかどうかの更新が必要なのは、上がりでない時だけ const isBearable = innerPos <= lastPiecePos; // 自分の分のピップカウントは進めた分だけ減る const myPipCount = board.myPipCount - pip; return Object.assign(Object.assign({}, board), { points: piecesAfter, lastPiecePos, opponentLastPiecePos, isBearable, myPipCount, opponentPipCount }); } function recalcLastPiecePos(from, board, piecesAfter) { return from == board.lastPiecePos && board.points[from] == 1 ? piecesAfter.findIndex((n) => 0 < n) : board.lastPiecePos; } function countPip(points) { return points.reduce((prev, cur, idx) => { prev += cur > 0 ? cur * (points.length - 1 - idx) : 0; return prev; }, 0); }