import { Color } from '../../../protocol/color' ; import { Move } from '../../../protocol/move' ; import { Repertoire } from '../../../protocol/storage' ; import { Annotator } from '../annotation/annotator' ; import { NullAnnotator } from '../annotation/nullannotator' ; import { Config } from '../common/config' ; import { ViewInfo } from '../common/viewinfo' ; import { AddMoveFailureReason , AddMoveResult } from './addmoveresult' ; import { FenNormalizer } from './fennormalizer' ; import { FenToPgnMap } from './fentopgnmap' ; import { PgnToNodeMap } from './pgntonodemap' ; import { TreeNode } from './treenode' ; declare var Chess ; export class TreeModel { private chess_ : any ; private rootNode_ : TreeNode | null ; private selectedNode_ : TreeNode | null ; private pgnToNode_ : PgnToNodeMap ; private fenToPgn_ : FenToPgnMap ; private repertoireColor_ : Color ; private repertoireName_ : string ; private numNodes_ : number ; constructor ( ) { this . chess_ = null ; this . rootNode_ = null ; this . selectedNode_ = null ; this . pgnToNode_ = { } ; this . fenToPgn_ = { } ; this . repertoireColor_ = Color . WHITE ; this . repertoireName_ = '' ; this . numNodes_ = 0 ; this . makeEmpty_ ( ) ; } getChessForState ( ) { return this . chess_ ; } isEmpty ( ) { if ( ! this . rootNode_ || ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } const viewInfo = this . rootNode_ . toViewInfo ( this . selectedNode_ , this . pgnToNode_ , this . fenToPgn_ , this . repertoireColor_ , NullAnnotator . INSTANCE ) ; return ! viewInfo . numChildren ; } addMove ( pgn , move ) { if ( this . numNodes_ >= Config . MAXIMUM_TREE_NODES_PER_REPERTOIRE ) { return { success : false , failureReason : AddMoveFailureReason . EXCEEDED_MAXIMUM_NUM_NODES } ; } if ( ! pgn ) { this . chess_ . reset ( ) ; } if ( pgn && ! this . chess_ . load_pgn ( pgn ) ) { throw new Error ( 'Tried to add move from invalid PGN: ' + pgn ) ; } if ( this . chess_ . history ( ) . length >= Config . MAXIMUM_LINE_DEPTH_IN_PLY ) { return { success : false , failureReason : AddMoveFailureReason . EXCEEDED_MAXIMUM_LINE_DEPTH } ; } const chessMove = typeof move === 'string' ? move : { from : move . fromSquare , to : move . toSquare , promotion : 'q' } ; if ( ! this . chess_ . move ( chessMove ) ) { return { success : false , failureReason : AddMoveFailureReason . ILLEGAL_MOVE } ; } let childPosition = this . chess_ . fen ( ) ; let childPgn = this . chess_ . pgn ( ) ; let childNode = this . pgnToNode_ [ childPgn ] ; if ( ! childNode ) { let history = this . chess_ . history ( { verbose : true } ) ; const lastMove = history [ history . length - 1 ] ; childNode = this . addNewMove_ ( pgn , childPosition , childPgn , this . chess_ . moves ( ) . length , { fromSquare : lastMove . from , toSquare : lastMove . to } , lastMove . san ) ; } this . selectedNode_ = childNode ; return { success : true , failureReason : null } ; } private addNewMove_ ( parentPgn , childPosition , childPgn , numLegalMoves , lastMove , lastMoveString ) { const parentNode = this . pgnToNode_ [ parentPgn ] ; if ( ! parentNode ) { throw new Error ( 'No node exists for PGN: ' + parentPgn ) ; } const childNode = parentNode . addChild ( childPosition , childPgn , numLegalMoves , lastMove , lastMoveString ) ; this . pgnToNode_ [ childPgn ] = childNode ; const normalizedFen = FenNormalizer . normalize ( childPosition , numLegalMoves ) ; if ( ! this . fenToPgn_ [ normalizedFen ] ) { this . fenToPgn_ [ normalizedFen ] = [ ] ; } this . fenToPgn_ [ normalizedFen ] . push ( childPgn ) ; this . numNodes_ ++ ; return childNode ; } canRemoveSelectedPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . parentOrSelf ( ) != this . selectedNode_ ; } removeSelectedPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } const nodeToDelete = this . selectedNode_ ; if ( ! nodeToDelete . pgn ) { return ; } this . selectedNode_ = nodeToDelete . parentOrSelf ( ) ; this . selectedNode_ . removeChildPgn ( nodeToDelete . pgn ) ; this . chess_ . load_pgn ( this . selectedNode_ . pgn ) ; let numDeletedNodes = 0 ; nodeToDelete . traverseDepthFirst ( viewInfo => { delete this . pgnToNode_ [ viewInfo . pgn ] ; const normalizedFen = FenNormalizer . normalize ( viewInfo . position , viewInfo . numLegalMoves ) ; if ( ! this . fenToPgn_ [ normalizedFen ] ) { throw new Error ( 'Unexpected state.' ) ; } this . fenToPgn_ [ normalizedFen ] = this . fenToPgn_ [ normalizedFen ] . filter ( e => e != viewInfo . pgn ) ; numDeletedNodes ++ ; } , this . selectedNode_ , this . pgnToNode_ , this . fenToPgn_ , this . repertoireColor_ , NullAnnotator . INSTANCE ) ; this . numNodes_ -= numDeletedNodes ; } /**
     * Traverses the nodes in the tree depth-first and pre-order.
     *
     * That is, parents are visited before their children and children are visited
     * before siblings.
     */ traverseDepthFirst < ANNOTATION > ( callbackFn , annotator ) { if ( ! this . rootNode_ || ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } this . rootNode_ . traverseDepthFirst ( callbackFn , this . selectedNode_ , this . pgnToNode_ , this . fenToPgn_ , this . repertoireColor_ , annotator ) ; } getRepertoireColor ( ) { return this . repertoireColor_ ; } flipRepertoireColor ( ) { const newColor = this . repertoireColor_ == Color . WHITE ? Color . BLACK : Color . WHITE ; this . setRepertoireColor ( newColor ) ; } setRepertoireColor ( color ) { this . repertoireColor_ = color ; } getRepertoireName ( ) { return this . repertoireName_ ; } setRepertoireName ( repertoireName ) { this . repertoireName_ = repertoireName ; } selectPgn ( pgn ) { if ( ! pgn ) { this . chess_ . reset ( ) ; } if ( pgn && ! this . chess_ . load_pgn ( pgn ) ) { throw new Error ( 'Tried to select invalid PGN: ' + pgn ) ; } let node = this . pgnToNode_ [ pgn ] ; if ( ! node ) { throw new Error ( 'No node exists for PGN: ' + pgn ) ; } this . selectedNode_ = node ; } hasPreviousPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . parentOrSelf ( ) != this . selectedNode_ ; } selectPreviousPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } this . selectedNode_ = this . selectedNode_ . parentOrSelf ( ) ; this . chess_ . load_pgn ( this . selectedNode_ . pgn ) ; } hasNextPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . firstChildOrSelf ( ) != this . selectedNode_ ; } selectNextPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } this . selectedNode_ = this . selectedNode_ . firstChildOrSelf ( ) ; this . chess_ . load_pgn ( this . selectedNode_ . pgn ) ; } hasPreviousSiblingPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . previousSiblingOrSelf ( false ) != this . selectedNode_ ; } selectPreviousSiblingPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } this . selectedNode_ = this . selectedNode_ . previousSiblingOrSelf ( false ) ; this . chess_ . load_pgn ( this . selectedNode_ . pgn ) ; } hasNextSiblingPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . nextSiblingOrSelf ( false ) != this . selectedNode_ ; } selectNextSiblingPgn ( ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } this . selectedNode_ = this . selectedNode_ . nextSiblingOrSelf ( false ) ; this . chess_ . load_pgn ( this . selectedNode_ . pgn ) ; } getSelectedViewInfo < ANNOTATION > ( annotator ) { if ( ! this . selectedNode_ ) { throw new Error ( 'Model not ready.' ) ; } return this . selectedNode_ . toViewInfo ( this . selectedNode_ , this . pgnToNode_ , this . fenToPgn_ , this . repertoireColor_ , annotator ) ; } serializeAsRepertoire ( ) { if ( ! this . rootNode_ ) { throw new Error ( 'Model not ready.' ) ; } return { name : this . repertoireName_ , color : this . repertoireColor_ , root : this . rootNode_ . serializeAsRepertoireNode ( ) } ; } exportToPgn ( ) { if ( ! this . rootNode_ ) { throw new Error ( 'Model not ready.' ) ; } const rootPgn = this . rootNode_ . exportChildrenToPgn ( false ) ; return rootPgn ? rootPgn + ' *' : '*' ; } private makeEmpty_ ( ) { this . chess_ = new Chess ( ) ; const initialFen = FenNormalizer . normalize ( this . chess_ . fen ( ) , this . chess_ . moves ( ) . length ) ; const initialPgn = this . chess_ . pgn ( ) ; this . rootNode_ = new TreeNode ( null , initialFen , initialPgn , this . chess_ . moves ( ) . length , null , '' , 0 ) ; this . pgnToNode_ = { } ; this . pgnToNode_ [ initialPgn ] = this . rootNode_ ; this . fenToPgn_ = { } ; this . fenToPgn_ [ initialFen ] = [ initialPgn ] ; this . selectedNode_ = this . rootNode_ ; this . repertoireColor_ = Color . WHITE ; this . numNodes_ = 1 ; } loadRepertoire ( repertoire ) { this . makeEmpty_ ( ) ; if ( repertoire ) { this . repertoireName_ = repertoire . name ; this . repertoireColor_ = repertoire . color ; if ( repertoire . root ) { this . parseRecursive_ ( repertoire . root ) ; } } this . selectPgn ( '' ) ; } private parseRecursive_ ( node ) { const children = node . children || node . c ; for ( let i = 0 ; i < children . length ; i ++ ) { let child = children [ i ] ; if ( child . fen ) { this . addNewMove_ ( node . pgn , child . fen , child . pgn , child . nlm , { fromSquare : child . lmf , toSquare : child . lmt } , child . lms ) ; } else { console . log ( 'Parsing legacy storage format.' ) ; this . addMove ( node . pgn , { fromSquare : child . lastMoveFrom , toSquare : child . lastMoveTo } ) ; } this . parseRecursive_ ( child ) ; } } }