tsgammon-core
Version:
A Backgammon library for Typescript, formerly developed as a part of tsgammon-ui
140 lines (129 loc) • 4.34 kB
text/typescript
import { BoardState } from '../BoardState'
import { BoardStateNode } from '../BoardStateNode'
import { collectNodes } from '../utils/collectNodes'
import { CubeState } from '../CubeState'
import { Evaluator } from './Evaluator'
/**
* チェッカープレイとキューブアクションを判断する思考エンジンが実装するインターフェース
*/
export type GammonEngine = {
/**
* ゲームの開始前に一度呼ばれる
*/
initialized(): void
/**
* ダブルをするかどうかを返す
*
* @param boardState 局面
* @param cubeState キューブ(値はダブル前の値)
* @returns ダブルするならisDouble=trueを返す
*/
cubeAction(
boardState: BoardState,
cubeState: CubeState
): { isDouble: boolean }
/**
* テイクするかどうかを返す
*
* @param boardState 局面
* @param cubeState キューブ(値はダブル前の値)
* @returns テイクするならisDouble=trueを返す
*/
cubeResponse(
boardState: BoardState,
cubeState: CubeState
): { isTake: boolean }
/**
* チェッカープレイを行い、その結果の局面を返す。
*
* @param boardStateNode 局面
* @returns プレイ後の局面(引数のboardStateNodeと同じ視点)。boardStateNodeの子局面が想定されているが、任意の局面を設定してもよい。また、手がない場合は引数をそのまま返す。
*/
checkerPlay(boardStateNode: BoardStateNode): BoardStateNode
/**
* ゲーム終了後に一度呼ばれる。
*
* @param boardStateNode 局面
*/
endOfGame(boardStateNode: BoardStateNode): void
}
/**
* 乱数エンジンを返す。キューブアクションは判断せず(ダブルはしない・常にテイク)、
* チェッカープレイは候補手からランダムに選ぶ。
*
* @returns 乱数エンジン
*/
export function randomEngine(): GammonEngine {
return {
initialized(): void {
//
},
cubeAction(): { isDouble: boolean } {
return { isDouble: false }
},
cubeResponse(): { isTake: boolean } {
return { isTake: true }
},
checkerPlay(node: BoardStateNode): BoardStateNode {
const nodes = collectNodes(node).filter((node) => !node.isRedundant)
if (nodes.length === 0) {
return node
}
const n = Math.floor(Math.random() * nodes.length)
return nodes[n]
},
endOfGame(): void {
// do nothing.
},
}
}
/**
* 局面評価をする関数から、簡易なGammonEngineを生成する。
*
* キューブアクションは、期待値0.40(勝率70%)でダブル、-0.5(勝率25%)より上でテイク。
*
* @param evaluate 局面評価をする関数
* @returns GammonEngine
*/
export function simpleEvalEngine(
evaluate: (board: BoardState) => number
): GammonEngine {
const evaluator: Evaluator = {
initialize: () => {
//
},
endOfGame: () => {
//
},
evaluate,
}
return simpleEvalEngineWithEvaluator(evaluator)
}
export function simpleEvalEngineWithEvaluator(ev: Evaluator): GammonEngine {
return {
initialized(): void {
ev.initialize()
},
cubeAction(board: BoardState): { isDouble: boolean } {
return { isDouble: ev.evaluate(board) > 0.4 }
},
cubeResponse(board: BoardState): { isTake: boolean } {
return { isTake: ev.evaluate(board) > -0.5 }
},
checkerPlay(node: BoardStateNode): BoardStateNode {
const candidates = collectNodes(node).filter(
(node) => !node.isRedundant
)
const bestEv = candidates
.map((node) => ({ node, e: ev.evaluate(node.board) }))
.reduce((prev, cur) => (prev.e > cur.e ? prev : cur), {
node,
e: Number.NEGATIVE_INFINITY,
})
return bestEv.node
},
endOfGame(boardStateNode: BoardStateNode): void {
ev.endOfGame(boardStateNode)
},
}
}