UNPKG

tsgammon-core

Version:

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

225 lines (201 loc) 6.63 kB
#!/usr/bin/env node import { AbsoluteMove, absoluteMovesRed, absoluteMovesWhite, } from '../AbsoluteMove' import { BoardState, boardState } from '../BoardState' import { BoardStateNode, boardStateNode } from '../BoardStateNode' import { Dice, DiceRoll } from '../Dices' import { matchStateForUnlimitedMatch } from '../MatchState' import { simpleNNEngine } from '../engines/SimpleNNGammon' import { EOGStatus } from '../EOGStatus' import { GameConf, standardConf } from '../GameConf' import { Ply } from '../Ply' import { addPlyRecord, eogRecord, matchRecordInPlay, MatchRecordEoG, MatchRecordInPlay, } from '../records/MatchRecord' import { plyRecordForCheckerPlay, plyRecordForEoG } from '../records/PlyRecord' import { SGResult } from '../records/SGResult' import { formatMatchRecord } from '../records/utils/formatMatchRecord' import { Score, score, scoreAsRed, scoreAsWhite } from '../Score' import { DiceSource, randomDiceSource } from '../utils/DiceSource' import { MoveFormatDirection } from '../utils/formatAbsMove' import { formatBoard } from '../utils/formatBoard' import { formatPly } from '../utils/formatPly' import { formatStake } from '../utils/formatStake' import { toPositionID } from '../utils/toPositionID' const engine = simpleNNEngine const conf = { ...standardConf, jacobyRule: false } type Event = PlayEvent | EOGEvent type PlayEvent = { tag: 'play' before: BoardState after: BoardState ply: Ply } type EOGEvent = { tag: 'eog' stake: Score eogStatus: EOGStatus sgResult: SGResult } function* runAutoMatch( gameScoreBefore: Score = score(), diceSource: DiceSource = randomDiceSource ): Generator<Event, Score> { const initialBoardState = boardState(conf.initialPos) const roll = diceSource.openingRoll() let { boardStateNode: node, lastPly: ply } = doCheckerPlay( initialBoardState, roll, roll.dice1 > roll.dice2 ) yield { tag: 'play', before: initialBoardState, after: node.board, ply } while (!isEoG(node)) { const roll = diceSource.roll() const { boardStateNode: nextNode, lastPly } = doCheckerPlay( node.board.revert(), roll, !ply.isRed ) yield { tag: 'play', before: node.board, after: nextNode.board, ply: lastPly, } node = nextNode ply = lastPly } const eogStatus = node.board.eogStatus() const stake = (ply.isRed ? scoreAsRed : scoreAsWhite)( eogStatus.calcStake(1) ) const gameScore = gameScoreBefore.add(stake) yield { tag: 'eog', stake, eogStatus, sgResult: ply.isRed ? SGResult.REDWON : SGResult.WHITEWON, } return gameScore } function doCheckerPlay(boardState: BoardState, roll: DiceRoll, isRed: boolean) { const node = boardStateNode(boardState, roll) const nextNode = engine.checkerPlay(node) const amoves: AbsoluteMove[] = ( isRed ? absoluteMovesRed : absoluteMovesWhite )(nextNode.lastMoves()) const lastPly: Ply = { moves: amoves, dices: nextNode.dices.map((dice: Dice) => dice.pip), isRed, } return { boardStateNode: nextNode, lastPly } } function isEoG(boardStateNode: BoardStateNode): boolean { return boardStateNode.board.eogStatus().isEndOfGame } type Driver = { doPlay: (evt: PlayEvent) => void doEoG: (evt: EOGEvent) => void doResult: (evt: { gameScore: Score }) => void } const defaultDriver: Driver = { doPlay: (evt) => console.log(formatPly(evt.ply)), doEoG: (evt) => console.log(formatStake(evt.stake, evt.eogStatus)), doResult: (evt) => console.log( `Result: red ${evt.gameScore.redScore} - white ${evt.gameScore.whiteScore}` ), } function runDriver(driver: Driver = defaultDriver) { const evtIt = runAutoMatch() let e while (!(e = evtIt.next()).done) { const ev = e.value switch (ev.tag) { case 'play': driver.doPlay(ev) break case 'eog': driver.doEoG(ev) } } const gameScore = e.value driver.doResult({ gameScore }) } function xgDriver(conf: GameConf): Driver { const matchState = matchStateForUnlimitedMatch(score(), conf.jacobyRule) const record: { match: MatchRecordInPlay<undefined> eog?: MatchRecordEoG<undefined> } = { match: matchRecordInPlay(conf, matchState), } return { ...defaultDriver, doPlay: (evt) => { record.match = addPlyRecord( record.match, plyRecordForCheckerPlay(evt.ply), undefined ) }, doEoG: (evt) => { record.eog = eogRecord( record.match, plyRecordForEoG(evt.stake, evt.sgResult, evt.eogStatus) ) }, doResult: () => { formatMatchRecord(record.eog ?? record.match) .split('\n') .forEach((l) => console.log(l)) }, } } function boardDriver(): Driver { return { ...defaultDriver, doPlay: (evt: { before: BoardState; after: BoardState; ply: Ply }) => { const { after, ply } = evt // 移動後の盤面を、X=white 側の視点で出力 const whiteBoard = ply.isRed ? after.revert() : after // 移動後の盤面から、次の手番プレイヤーの視点でPosIDを生成 const posID = toPositionID(after.revert()) // Moveも画面での座標に合わせて出力(redは1->24, whiteは24->1) const plyStr = formatPly(ply, MoveFormatDirection.ABSOLUTE_INV) console.log('') console.log((ply.isRed ? 'red: ' : 'white: ') + plyStr) console.log('') console.log(` tsgammon Position ID: ${posID}`) console.log('') formatBoard(whiteBoard).forEach((s) => console.log(s)) }, } } function main() { if (process.argv.length < 3) { runDriver() return } const arg = process.argv[2] if (arg === '-?' || arg === '--help') { console.log('usage:') console.log(' -x: output as text log (.mat format) ') console.log(' -b: output as visual board') } else if (arg.startsWith('-x')) { runDriver(xgDriver(standardConf)) } else if (arg.startsWith('-b')) { runDriver(boardDriver()) } else { runDriver() } } main()