tsgammon-core
Version:
A Backgammon library for Typescript, formerly developed as a part of tsgammon-ui
241 lines (223 loc) • 7.68 kB
text/typescript
import { wrap } from '../BoardStateNode'
import { Dice } from '../Dices'
import { findMove } from '../utils/findMove'
import { makeLeap } from '../utils/makeLeap'
import { makePoint } from '../utils/makePoint'
import { CheckerPlayState, CheckerPlayStateCommitted } from './CheckerPlayState'
/**
* 未確定の状態のチェッカープレイを管理する:CheckerPlayでは機能追加の必要性が薄いので、
* CheckerPlayHandlerを設けず簡素な定義のままになっている
*/
export type CheckerPlayDispatcher = {
// チェッカープレイを行う
doCheckerPlay: (
state: CheckerPlayState,
absPos: number,
dices: Dice[]
) => void
// ダイスの使用順序を入れ替える
doRevertDices: (state: CheckerPlayState) => void
// チェッカープレイを取り消す
doUndo: (state: CheckerPlayState) => void
// チェッカープレイを確定させる
doCommitCheckerPlay: (state: CheckerPlayState) => void
// 取り消したチェッカープレイをやり直す
doRedo: (state: CheckerPlayState) => void
}
/**
* 未確定の状態のチェッカープレイによって発生するイベントの通知先
*/
export type CheckerPlayListeners = {
onCheckerPlay: (state: CheckerPlayState) => void
onRevertDices: (state: CheckerPlayState) => void
onUndo: (state: CheckerPlayState) => void
onCommitCheckerPlay: (state: CheckerPlayStateCommitted) => void
onRedo: (state: CheckerPlayState) => void
}
/**
* CheckerPlayDispatcherオブジェクトを生成する
* @param listeners
* @returns
*/
export function checkerPlayDispatcher(
listeners: CheckerPlayListeners
): CheckerPlayDispatcher {
return {
doCheckerPlay,
doRevertDices,
doUndo,
doCommitCheckerPlay,
doRedo,
}
function doCheckerPlay(
state: CheckerPlayState,
absPos: number,
dices: Dice[]
) {
const ret = applyCheckerPlay(state, absPos, dices)
if (ret.isValid) {
listeners.onCheckerPlay(ret.state)
}
}
function doRevertDices(state: CheckerPlayState) {
const reverted = { ...state, revertDicesFlag: !state.revertDicesFlag }
listeners.onRevertDices(reverted)
}
function doUndo(state: CheckerPlayState) {
const reverted: CheckerPlayState = {
...state,
curPly: {
moves: [],
dices: state.boardStateNodeRevertTo.dices.map(
(dice) => dice.pip
),
isRed: state.curPly.isRed,
},
curBoardState: state.boardStateNodeRevertTo,
absBoard: state.absBoardRevertTo,
isUndoable: false,
// revertDicesFlag: false ダイスの入れ替え状態はアンドゥでも維持
}
listeners.onUndo(reverted)
}
function doCommitCheckerPlay(state: CheckerPlayState) {
const committed: CheckerPlayStateCommitted = {
isCommitted: true,
boardStateNode: state.curBoardState,
}
listeners.onCommitCheckerPlay(committed)
}
function doRedo(state: CheckerPlayState) {
listeners.onRedo(state)
}
}
/**
* 任意のポイントがクリックされたとき、それに対応するムーブを決定し、
* そのムーブの適用後の局面を返す
*
* @param state 現局面(そこから可能な局面のツリーを含む)
* @param absPos クリックされたポイントの絶対座標(=White視点の座標)
* @param dices ダイス状態の配列:インデックスの小さいものを優先して使用する
* @returns
*/
export function applyCheckerPlay(
state: CheckerPlayState,
absPos: number,
dices: Dice[]
): { isValid: true; state: CheckerPlayState } | { isValid: false } {
const pos = state.toPos(absPos)
// Board上の実配置に基づき、小さい目を先に使うかどうかを判定する
// ゾロ目でなく、両方使える時で、左側が小さい目の時だけ、
// 小さい方が優先
const useMinorFirst: boolean =
dices.length === 2 &&
dices[0].pip < dices[1].pip &&
!dices[0].used &&
!dices[1].used
const node = wrap(state.curBoardState)
// クリック位置から動かせる駒がある
.apply((node) => findMove(node, pos, useMinorFirst))
// クリック位置でポイントを作れる
.or((node) => makePoint(node, pos))
// クリック位置へ動かせる
.or((node) => makeLeap(node, pos, useMinorFirst)).unwrap
return node.hasValue
? {
isValid: true,
state: {
...state,
curBoardState: node,
absBoard: state.toAbsBoard(node.board),
curPly: state.toPly(node),
isUndoable: true,
},
}
: { isValid: false }
}
export function fill(
listeners: Partial<CheckerPlayListeners>
): CheckerPlayListeners {
const doNothing: CheckerPlayListeners = {
onCheckerPlay: () => {
//
},
onRevertDices: () => {
//
},
onUndo: () => {
//
},
onCommitCheckerPlay: () => {
//
},
onRedo: () => {
//
},
}
return {
...doNothing,
...listeners,
}
}
export function decorate(
base: CheckerPlayListeners,
...listeners: Partial<CheckerPlayListeners>[]
): CheckerPlayListeners {
return listeners.reduce(
(prev: CheckerPlayListeners, cur: Partial<CheckerPlayListeners>) => {
const {
onCheckerPlay,
onRevertDices,
onUndo,
onCommitCheckerPlay,
onRedo,
} = cur
return {
onCheckerPlay: onCheckerPlay
? (state: CheckerPlayState) => {
prev.onCheckerPlay(state)
onCheckerPlay(state)
}
: prev.onCheckerPlay,
onRevertDices: onRevertDices
? (state: CheckerPlayState) => {
prev.onRevertDices(state)
onRevertDices(state)
}
: prev.onRevertDices,
onUndo: onUndo
? (state: CheckerPlayState) => {
prev.onUndo(state)
onUndo(state)
}
: prev.onUndo,
onCommitCheckerPlay: onCommitCheckerPlay
? (state: CheckerPlayStateCommitted) => {
prev.onCommitCheckerPlay(state)
onCommitCheckerPlay(state)
}
: prev.onCommitCheckerPlay,
onRedo: onRedo
? (state: CheckerPlayState) => {
prev.onRedo(state)
onRedo(state)
}
: prev.onRedo,
}
},
base
)
}
export function setCPStateListener(
setState: (state: CheckerPlayState | undefined) => void
): CheckerPlayListeners {
return {
onCheckerPlay: setState,
onRevertDices: setState,
onUndo: setState,
onCommitCheckerPlay: () => {
setState(undefined)
}, // Commitした後は格納しない
onRedo: setState,
}
}