tsgammon-core
Version:
A Backgammon library for Typescript, formerly developed as a part of tsgammon-ui
188 lines (176 loc) • 6.38 kB
text/typescript
import { BoardState } from '../BoardState'
import { BoardStateNode, NO_MOVE } from '../BoardStateNode'
import { Dice, DicePip, dices } from '../Dices'
import { Move } from '../Move'
import { applyDicePipToPoints } from './applyDicePipToPoints'
import { buildNodeForEoG, leaveNodeBuilder, NodeBuilder } from './NodeBuilder'
/**
* ゾロ目に対して、バックギャモンのルールに基づいて可能な手を列挙し、BoardStateNodeとして返す
* @param board 盤面
* @param dicePip ダイスの目
* @param count ダイスの目を使える回数(省略時は4)
* @returns
*/
export function buildNodesForDoublet(
board: BoardState,
dicePip: DicePip,
count: number
): BoardStateNode {
const unusedDices = dices(dicePip, count)
const [node] = buildNodesForDoubletRec(
board,
unusedDices,
dicePip,
[],
[],
false
)
return node
}
/**
* 未使用のダイスが残り2個になるまで再帰しつつ、可能な局面のツリーを構築する
*
* @param board 盤面
* @param unusedDices 未使用のダイス
* @param pip 適用しようとしている目
* @param usedDices 使用済みのダイス
* @param lastMoves この局面に至るまでの手
* @param isAlreadyRedundant すでに冗長かどうか。ゾロ目の場合、冗長なノードの下位のムーブは全て冗長となる。
* @returns
*/
function buildNodesForDoubletRec(
board: BoardState,
unusedDices: Dice[],
pip: DicePip,
usedDices: Dice[],
lastMoves: Move[],
isAlreadyRedundant: boolean
): [BoardStateNode, number] {
// 子ノードに格納する内容を整えて、子ノードのビルダーを準備する
// ダイスは1つ使用済みになる
const dicesUsedUp = usedDices.concat({ pip, used: true })
// 未使用ダイスが残り2個なら末端(とその親)ノードの生成、そうでなければ再帰
const nodeBuilder: NodeBuilder =
unusedDices.length === 2
? leaveNodesAndParentBuilder(
pip,
dicesUsedUp,
lastMoves,
isAlreadyRedundant
)
: recursiveNodeBuilder(
unusedDices,
pip,
dicesUsedUp,
lastMoves,
isAlreadyRedundant
)
// 各ポイントについて、子ノードを構築する
const [major, marked] = applyDicePipToPoints(
board,
pip,
nodeBuilder,
unusedDices.length
)
// 子ノードで使えなかったダイスの数=markedを、使用不可としてマークする
const markAfter = unusedDices.length - marked
const dices = usedDices.concat(
Array.from({ length: unusedDices.length }, (_, idx) => {
return idx < marked ? { pip, used: true } : { pip, used: false }
})
)
return [
{
hasValue: true,
dices,
board: board,
majorFirst: major,
minorFirst: () => NO_MOVE,
lastMoves: () => lastMoves,
isRedundant: false,
isCommitable: markAfter === 0,
},
marked,
]
}
// 最後の二つになるまでは、未使用ダイスを1つず減らしながら再帰するだけ
function recursiveNodeBuilder(
unusedDices: Dice[],
pip: DicePip,
usedDices: Dice[],
lastMoves: Move[],
isAlreadyRedundant: boolean
): NodeBuilder {
return (board: BoardState, move: Move) => {
return buildNodesForDoubletRec(
board,
unusedDices.slice(1),
pip,
usedDices,
lastMoves.concat(move),
isAlreadyRedundant || lastMoveIsRedundant(lastMoves, move)
)
}
}
// 最後に適用した手が、それまでに適用した手より手前の駒を動かす場合は、冗長なムーブ
function lastMoveIsRedundant(lastMoves: Move[], move: Move): boolean {
return (
lastMoves.length > 0 && move.from < lastMoves[lastMoves.length - 1].from
)
}
// buildNodes()で構築するツリーの、最末端ノードを構築する
// すなわち、最後の一つのダイスを各ポイントに適用した結果を返す
function leaveNodesAndParentBuilder(
pip: DicePip,
usedDices: Dice[],
lastMoves: Move[],
isAlreadyRedundant: boolean
): NodeBuilder {
return (board: BoardState, moveBeforeFinal: Move) => {
const lastMovesForLeave = lastMoves.concat(moveBeforeFinal)
const isEog = board.eogStatus().isEndOfGame
if (isEog) {
// ゾロ目の場合、ダイスの入れ替えを気にする必要はないので、
// marked=1とすることで、EoGに達して余ったダイスは使用済み扱いになる。
// (よって、ロール直後は最後の一つが使えない状態としてマークされる)
return buildNodeForEoG(
board,
usedDices.concat({ pip, used: true }),
lastMovesForLeave,
1
)
}
// 最後のpipを適用して末端ノードを生成する
const isAlreadyRedundantForLeave =
isAlreadyRedundant ||
lastMoveIsRedundant(lastMoves, moveBeforeFinal)
const nodeBuilder = leaveNodeBuilder(
usedDices,
pip,
(finalMove) => lastMovesForLeave.concat(finalMove),
(finalMove) =>
isAlreadyRedundantForLeave ||
lastMoveIsRedundant(lastMovesForLeave, finalMove)
)
const [major, unusedDices] = applyDicePipToPoints(
board,
pip,
nodeBuilder,
1
)
// 末端ノードの親ノードを生成して返す
return [
{
hasValue: true,
dices: usedDices.concat([{ pip, used: unusedDices === 1 }]),
board: board,
majorFirst: major,
minorFirst: () => NO_MOVE,
lastMoves: () => lastMovesForLeave,
isRedundant: false,
isCommitable: unusedDices === 1,
},
unusedDices,
]
}
}