UNPKG

tsgammon-core

Version:

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

171 lines (154 loc) 5.99 kB
import { BoardState, boardState } from './BoardState' import { buildNodesForDoublet } from './internals/buildNodesForDoublet' import { buildNodesForHeteroDice } from './internals/buildNodesForHeteroDice' import { Dice, DiceRoll, DicePip } from './Dices' import { Move } from './Move' /** * 駒の配置とダイスの使用状況を保持し、あわせて遷移可能な局面をツリー構造で表現するオブジェクト。 * * あるポイントから駒を1つ動かした局面が子局面で、各ポイントから紐付けられた形で子ノードになる。 * * ダイスの使う順序によって子局面は異なるので、majorFirst/minorFirstに分けて格納されている。 */ export type BoardStateNode = { /** 有効な局面である、すなわちこのオブジェクトがNO_MOVEではないことを示す */ hasValue: true /** ダイスの目と使用状況を示す */ dices: Dice[] /** 駒の配置を示す */ board: BoardState /** * 指定されたポイントに大きいほうの目を適用した後の局面を返す。 * * 適用できない、またはposが範囲外の場合、NoMoveが返る。 * * @param pos 目を適用したいポイント */ majorFirst: (pos: number) => BoardStateNode | NoMove /** * 指定されたポイントに小さいほうの目を適用した後の状態を返す。 * * 適用できない、またはposが範囲外の場合、NoMoveが返る。 * @param pos 目を適用したいポイント */ minorFirst: (pos: number) => BoardStateNode | NoMove /** * この局面にいたる直前までに適用した手。ロール直後の場合は空となる。 */ lastMoves(): Move[] /** * 冗長なノードを示す:ツリー端のノードで、同一局面でisRedundant=falseなノードが既に存在している場合true */ isRedundant: boolean /** コミット可能(これ以上手がない)ならtrue */ isCommitable: boolean } /** * {@link BoardStateNode["majorFirst"]} / {@link BoardStateNode["minorFirst"]}に対して、 * 該当がないことを示す */ export type NoMove = { hasValue: false } /** * NoMove型のインスタンス */ export const NO_MOVE: NoMove = { hasValue: false } /** * 与えられた盤面とダイス目のペアから、BoardStateNodeを生成する * * @param board 盤面 * @param dicePips ダイス目のペア * @param movesForDoublet ゾロ目の時に動かせる駒数 * @returns 局面 */ export function boardStateNode( board: BoardState, dicePips: DiceRoll, movesForDoublet = 4 ): BoardStateNode { const { dice1, dice2 } = dicePips return dice1 !== dice2 ? buildNodesForHeteroDice(board, dice1, dice2) : buildNodesForDoublet(board, dice1, movesForDoublet) } /** * 駒の配置を格納した配列からBoardStateNodeを生成する。 * * pieces[0]が自分のバーポイント、pieces[25]が相手のバーポイントとなり * pieces[1]-[24]は盤面の各ポイントに対応する。 * * @param pieces 駒の配置。正は自駒、負は相手の駒 * @param dice1 ダイス目 * @param dice2 ダイス目 * @param bornOffs すでにベアリングオフした駒の数の対(順に自分、相手:省略時は0) * @param movesForDoublet ゾロ目の時に動かせる駒数 * @returns 局面 */ export function boardStateNodeFromArray( pieces: number[], dice1: DicePip, dice2: DicePip, bornOffs: [number, number] = [0, 0], movesForDoublet = 4 ): BoardStateNode { const board = boardState(pieces, bornOffs) return boardStateNode(board, { dice1, dice2 }, movesForDoublet) } // ダイスを無視して何も次の手がない状態のBoardStateNodeを生成する function emptyNode(board: BoardState, dices: Dice[]): BoardStateNode { return { hasValue: true, dices, board, majorFirst: () => NO_MOVE, minorFirst: () => NO_MOVE, lastMoves: () => [], isRedundant: false, isCommitable: true, } } /** * 与えられた局面に対し、ダイスなし、可能な手なしのBoardStateNodeを生成する * @param board */ export function nodeWithEmptyDice(board: BoardState): BoardStateNode { return emptyNode(board, []) } /** * BoardStateNodeが保持する、適用可能な手を表したツリーに対して、 * 任意のノードを選択する関数を順次受け付けるためのラッパー */ export type Wrapped<T extends { hasValue: boolean }> = { /** * wrap()に渡した値、または直前のapply()/or()がhasValue=trueを満たすなら、指定された関数fを適用する。 */ apply: (f: (a: T) => T | { hasValue: false }) => Wrapped<T> /** * wrap()に渡した値、または直前のapply()/or()がhasValue=falseの場合のみ、指定された関数fを適用する */ or: (f: (a: T) => T | { hasValue: false }) => Wrapped<T> /** * 現時点で選択されたノード(または該当なし)を返す */ unwrap: T | { hasValue: false } } /** * 指定されたノードをWrapperに変換する * @param t Wrapperにしたいノード * @returns */ export function wrap<T extends { hasValue: true }>( t: T | { hasValue: false } ): Wrapped<T> { return _wrap(t, { hasValue: false }) } function _wrap<T extends { hasValue: true }>( t: T | { hasValue: false }, // 直前のor/applyで引数の関数で見つかったノード was: T | { hasValue: false } // or/applyの関数に、引数として渡されたノード ): Wrapped<T> { return { apply: (f: (arg: T) => T | { hasValue: false }) => _wrap(t.hasValue ? f(t) : t, t), or: (f: (arg: T) => T | { hasValue: false }) => _wrap(t.hasValue ? t : was.hasValue ? f(was) : was, was), unwrap: t, } }