tsgammon-core
Version:
A Backgammon library for Typescript, formerly developed as a part of tsgammon-ui
178 lines (177 loc) • 7.11 kB
JavaScript
;
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);
}