UNPKG

tsgammon-core

Version:

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

188 lines (187 loc) 8.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildNodesForHeteroDice = void 0; const BoardStateNode_1 = require("../BoardStateNode"); const applyDicePipToPoints_1 = require("./applyDicePipToPoints"); const NodeBuilder_1 = require("./NodeBuilder"); /** * ゾロ目でないダイスのペアに対して、バックギャモンのルールに基づいて可能な手を列挙し、BoardStateNodeとして返す * * @param board 盤面 * @param dice1 ダイスの目 * @param dice2 ダイスの目 * @returns */ function buildNodesForHeteroDice(board, dice1, dice2) { if (dice1 === dice2) { throw Error('Unexpected doublet: ' + dice1 + ',' + dice2); } const majorPip = dice1 > dice2 ? dice1 : dice2; const minorPip = dice1 < dice2 ? dice1 : dice2; // 大きい目を先に使った場合の候補手 const [majorTmp, majorMarked] = applyMajorPipFirst(board, majorPip, minorPip); // 小さい目を先に使う場合、上記の結果を踏まえた冗長判定がある const [minorTmp, minorMarked] = applyMinorPipFirst(board, majorPip, minorPip, majorTmp); // marked=使えないダイスの個数を見て、適用可能な手を絞り込む let major; let minor; let usableDice; if (majorMarked === 0) { // 大ぞろめにきい目を先に使えば、全てのダイスが使える major = majorTmp; minor = minorMarked === 0 // すべてのダイスを使えるなら、小さい目を先に使っても良い ? minorTmp : () => BoardStateNode_1.NO_MOVE; usableDice = [ { pip: dice1, used: false }, { pip: dice2, used: false }, ]; } else if (minorMarked === 0) { // 小さい目を先に使えば、すべてのダイスが使える minor = minorTmp; major = () => BoardStateNode_1.NO_MOVE; // majorMarked !== 0はすでに確定している usableDice = [ { pip: dice1, used: false }, { pip: dice2, used: false }, ]; } else { // 2個使うことはできない場合 // 1個しか使えない場合は、大きい目から使う if (majorMarked === 1) { major = majorTmp; minor = () => BoardStateNode_1.NO_MOVE; usableDice = [ { pip: majorPip, used: false }, { pip: minorPip, used: true }, ]; } else if (minorMarked === 1) { major = () => BoardStateNode_1.NO_MOVE; minor = minorTmp; usableDice = [ { pip: minorPip, used: false }, { pip: majorPip, used: true }, ]; } else { // どの目も使えない major = () => BoardStateNode_1.NO_MOVE; minor = () => BoardStateNode_1.NO_MOVE; usableDice = [ { pip: dice1, used: true }, { pip: dice2, used: true }, ]; } } return { hasValue: true, dices: usableDice, board: board, majorFirst: major, minorFirst: minor, lastMoves: () => [], isRedundant: false, // 使えるダイスは左側にある前提 isCommitable: usableDice.length > 0 && usableDice[0].used, }; } exports.buildNodesForHeteroDice = buildNodesForHeteroDice; // 大きい目を先に使う場合の手のリストアップ function applyMajorPipFirst(board, majorDice, minorDice) { const isRedundantForMajorPipFirstCase = (move1, move2) => { // 見かけ上同じ駒を2回動かすムーブ // すなわち、p/q/r に対してq/r p/qと動かす場合は冗長 if (move2.to === move1.from) { return true; } return false; }; return applyDices(board, majorDice, minorDice, isRedundantForMajorPipFirstCase); } // 小さい目を先に使う場合の手のリストアップ function applyMinorPipFirst(board, majorDice, minorDice, majorTmp) { // moves = p/p+n q/q+m に対し、すでにmajorTmpに着手を入れ替えた手q/q+m p/p+nがあるはずなので、原則としては冗長 const isRedundantFunc = (move1, move2) => { // 着手を入れ替えた手、q/q+m p/p+nがすでにあれば冗長 const node = majorTmp(move2.from); if (node.hasValue && node.majorFirst(move1.from).hasValue) { return true; } // 同じ駒を2回動かすムーブで、かつダイスを入れ替えた場合のムーブと比べる // すなわち、小さい目を先行して使う手p/p+m/rが、p/p+n/rと実質的に同じかどうかを検出する if (move1.to === move2.from) { const swappedMovesNode = majorTmp(move1.from); // 2' ダイスを入れ替えて動かせない(p/p+nが禁手)なら、冗長ではない if (swappedMovesNode.hasValue) { // ダイスを入れ替えても、二つ目のダイス(小さい目)では動かせない // (p+n/rが禁手)なら、冗長ではない(ベアリングオフの場合に発生する) { const majorPip = move2.pip; const swappedMoveTo = move1.from + majorPip; if (!swappedMovesNode.majorFirst(swappedMoveTo).hasValue) { return false; } } // どちらもヒットでない場合は、冗長 const isHit = move1.isHit; const swappedMove = swappedMovesNode.lastMoves()[0]; return !isHit && !swappedMove.isHit; } else { return false; } } return false; }; return applyDices(board, minorDice, majorDice, isRedundantFunc); } function applyDices(board, firstDice, secondDice, isRedundantFunc = () => false) { const dicesAfterUse = { pip: firstDice, used: true, }; // ビルダーの事前準備:最初の目を適用した後は、二つ目のダイスに対応するノードを生成する const nodeBuilder = leaveNodeAndParentBuilder(secondDice, dicesAfterUse, isRedundantFunc); // 各ポイントに最初の目を適用して子ノードを生成する // (内部で各ポイントについて上記のnodeBuilderが呼ばれ、孫ノードが生成される) return (0, applyDicePipToPoints_1.applyDicePipToPoints)(board, firstDice, nodeBuilder, 2); } // ダイスを1個使った状態を表すノードと、その子ノードとなる末端ノードを構築する function leaveNodeAndParentBuilder(pip, usedDice, isRedundantFunc = () => false) { return (board, firstMove) => { const lastMovesForLeave = [firstMove]; const isEog = board.eogStatus().isEndOfGame; if (isEog) { // 盤面が終了状態になっている場合は、Leafノードは生成せず、EoGを表すノードを返す // // この時点ではダイスを一個しか使っていないが、ダイスを入れ替えると二個使える場合、 // 一個しか使えない手は無効化されてしまうので、二個目のダイスも使用済とマークし、 // marked=0として返すことにより、ダイスを二個使った手として処理させている // (よって、ロール直後は、表示上ダイスは二つとも使える状態で表示される) return (0, NodeBuilder_1.buildNodeForEoG)(board, [usedDice, { pip, used: true }], lastMovesForLeave, 0); } // 二つ目のpipを使って末端のノードを構築する const nodeBuilder = (0, NodeBuilder_1.leaveNodeBuilder)([usedDice], pip, (secondMove) => [firstMove, secondMove], (secondMove) => isRedundantFunc(firstMove, secondMove)); const [major, unusedDices] = (0, applyDicePipToPoints_1.applyDicePipToPoints)(board, pip, nodeBuilder, 1); // applyDicePipToPointsは未使用のダイスの最小値を返すので、 // unusedDiceはどこかのポイントがムーブ可能であれば0、 // そうでなければ1(ムーブできないのでダイスが1個余る)となる。 // 末端ノードの親ノードを生成して返す return [ { hasValue: true, dices: [usedDice, { pip, used: unusedDices === 1 }], board: board, majorFirst: major, minorFirst: () => BoardStateNode_1.NO_MOVE, lastMoves: () => lastMovesForLeave, isRedundant: false, isCommitable: unusedDices === 1, }, unusedDices, ]; }; }