import { count , concat } from '@/prelude/array' ; /**
 * true ... 黒
 * false ... 白
 */ export type Color = boolean ; const BLACK = true ; const WHITE = false ; export type MapPixel = 'null' | 'empty' ; export type Options = { isLlotheo : boolean ; canPutEverywhere : boolean ; loopedBoard : boolean ; } ; export type Undo = { /**
     * 色
     */ color : Color ; /**
     * どこに打ったか
     */ pos : number ; /**
     * 反転した石の位置の配列
     */ effects : number [ ] ; /**
     * ターン
     */ turn : Color | null ; } ; /**
 * リバーシエンジン
 */ export default class Reversi { public map : MapPixel [ ] ; public mapWidth : number ; public mapHeight : number ; public board : ( Color | null | undefined ) [ ] ; public turn : Color | null = BLACK ; public opts : Options ; public prevPos = - 1 ; public prevColor : Color | null = null ; private logs : Undo [ ] = [ ] ; /**
     * ゲームを初期化します
     */ constructor ( map , opts ) { this . put = this . put . bind ( this ) ; this . opts = opts ; if ( this . opts . isLlotheo == null ) this . opts . isLlotheo = false ; if ( this . opts . canPutEverywhere == null ) this . opts . canPutEverywhere = false ; if ( this . opts . loopedBoard == null ) this . opts . loopedBoard = false ; this . mapWidth = map [ 0 ] . length ; this . mapHeight = map . length ; const mapData = map . join ( '' ) ; this . board = mapData . split ( '' ) . map ( d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined ) ; this . map = mapData . split ( '' ) . map ( d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null' ) ; if ( ! this . canPutSomewhere ( BLACK ) ) this . turn = this . canPutSomewhere ( WHITE ) ? WHITE : null ; } /**
     * 黒石の数
     */ public get blackCount ( ) { return count ( BLACK , this . board ) ; } /**
     * 白石の数
     */ public get whiteCount ( ) { return count ( WHITE , this . board ) ; } public transformPosToXy ( pos ) { const x = pos % this . mapWidth ; const y = Math . floor ( pos / this . mapWidth ) ; return [ x , y ] ; } public transformXyToPos ( x , y ) { return x + ( y * this . mapWidth ) ; } param color param pos public put ( color , pos ) { this . prevPos = pos ; this . prevColor = color ; this . board [ pos ] = color ; const effects = this . effects ( color , pos ) ; for ( const pos of effects ) { this . board [ pos ] = color ; } const turn = this . turn ; this . logs . push ( { color , pos , effects , turn } ) ; this . calcTurn ( ) ; } private calcTurn ( ) { this . turn = this . canPutSomewhere ( ! this . prevColor ) ? ! this . prevColor : this . canPutSomewhere ( this . prevColor ! ) ? this . prevColor : null ; } public undo ( ) { const undo = this . logs . pop ( ) ! ; this . prevColor = undo . color ; this . prevPos = undo . pos ; this . board [ undo . pos ] = null ; for ( const pos of undo . effects ) { const color = this . board [ pos ] ; this . board [ pos ] = ! color ; } this . turn = undo . turn ; } param pos public mapDataGet ( pos ) { const [ x , y ] = this . transformPosToXy ( pos ) ; return x < 0 || y < 0 || x >= this . mapWidth || y >= this . mapHeight ? 'null' : this . map [ pos ] ; } /**
     * 打つことができる場所を取得します
     */ public puttablePlaces ( color ) { return Array . from ( this . board . keys ( ) ) . filter ( i => this . canPut ( color , i ) ) ; } /**
     * 打つことができる場所があるかどうかを取得します
     */ public canPutSomewhere ( color ) { return this . puttablePlaces ( color ) . length > 0 ; } param color param pos public canPut ( color , pos ) { return ( this . board [ pos ] !== null ? false : this . opts . canPutEverywhere ? this . mapDataGet ( pos ) == 'empty' : this . effects ( color , pos ) . length !== 0 ) ; } param color param initPos public effects ( color , initPos ) { const enemyColor = ! color ; const diffVectors = [ [ 0 , - 1 ] , [ + 1 , - 1 ] , [ + 1 , 0 ] , [ + 1 , + 1 ] , [ 0 , + 1 ] , [ - 1 , + 1 ] , [ - 1 , 0 ] , [ - 1 , - 1 ] ] ; const effectsInLine = ( [ dx , dy ] ) : number [ ] => { const nextPos = ( x , y ) : [ number , number ] => [ x + dx , y + dy ] ; const found = [ ] ; let [ x , y ] = this . transformPosToXy ( initPos ) ; while ( true ) { [ x , y ] = nextPos ( x , y ) ; if ( this . opts . loopedBoard && this . transformXyToPos ( ( x = ( ( x % this . mapWidth ) + this . mapWidth ) % this . mapWidth ) , ( y = ( ( y % this . mapHeight ) + this . mapHeight ) % this . mapHeight ) ) === initPos ) return found ; else if ( x === - 1 || y === - 1 || x === this . mapWidth || y === this . mapHeight ) return [ ] ; const pos = this . transformXyToPos ( x , y ) ; if ( this . mapDataGet ( pos ) === 'null' ) return [ ] ; const stone = this . board [ pos ] ; if ( stone === null ) return [ ] ; if ( stone === enemyColor ) found . push ( pos ) ; if ( stone === color ) return found ; } } ; return concat ( diffVectors . map ( effectsInLine ) ) ; } /**
     * ゲームが終了したか否か
     */ public get isEnded ( ) : boolean { return this . turn === null ; } /**
     * ゲームの勝者 (null = 引き分け)
     */ public get winner ( ) : Color | null { return this . isEnded ? this . blackCount == this . whiteCount ? null : this . opts . isLlotheo === this . blackCount > this . whiteCount ? WHITE : BLACK : undefined as never ; } }