elo-worldle/chess.min.js
2023-04-01 23:11:32 -04:00

2 lines
28 KiB
JavaScript

export const WHITE="w";export const BLACK="b";export const PAWN="p";export const KNIGHT="n";export const BISHOP="b";export const ROOK="r";export const QUEEN="q";export const KING="k";export const PIECE_SYMBOLS=["p","n","b","r","q","k"];export const DEFAULT_POSITION="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";const EMPTY=-1;const FLAGS={NORMAL:"n",CAPTURE:"c",BIG_PAWN:"b",EP_CAPTURE:"e",PROMOTION:"p",KSIDE_CASTLE:"k",QSIDE_CASTLE:"q"};export const SQUARES=["a8","b8","c8","d8","e8","f8","g8","h8","a7","b7","c7","d7","e7","f7","g7","h7","a6","b6","c6","d6","e6","f6","g6","h6","a5","b5","c5","d5","e5","f5","g5","h5","a4","b4","c4","d4","e4","f4","g4","h4","a3","b3","c3","d3","e3","f3","g3","h3","a2","b2","c2","d2","e2","f2","g2","h2","a1","b1","c1","d1","e1","f1","g1","h1"];const BITS={NORMAL:1,CAPTURE:2,BIG_PAWN:4,EP_CAPTURE:8,PROMOTION:16,KSIDE_CASTLE:32,QSIDE_CASTLE:64};export const Ox88={a8:0,b8:1,c8:2,d8:3,e8:4,f8:5,g8:6,h8:7,a7:16,b7:17,c7:18,d7:19,e7:20,f7:21,g7:22,h7:23,a6:32,b6:33,c6:34,d6:35,e6:36,f6:37,g6:38,h6:39,a5:48,b5:49,c5:50,d5:51,e5:52,f5:53,g5:54,h5:55,a4:64,b4:65,c4:66,d4:67,e4:68,f4:69,g4:70,h4:71,a3:80,b3:81,c3:82,d3:83,e3:84,f3:85,g3:86,h3:87,a2:96,b2:97,c2:98,d2:99,e2:100,f2:101,g2:102,h2:103,a1:112,b1:113,c1:114,d1:115,e1:116,f1:117,g1:118,h1:119};const PAWN_OFFSETS={b:[16,32,17,15],w:[-16,-32,-17,-15]};const PIECE_OFFSETS={n:[-18,-33,-31,-14,18,33,31,14],b:[-17,-15,17,15],r:[-16,1,16,-1],q:[-17,-16,-15,1,17,16,15,-1],k:[-17,-16,-15,1,17,16,15,-1]};const ATTACKS=[20,0,0,0,0,0,0,24,0,0,0,0,0,0,20,0,0,20,0,0,0,0,0,24,0,0,0,0,0,20,0,0,0,0,20,0,0,0,0,24,0,0,0,0,20,0,0,0,0,0,0,20,0,0,0,24,0,0,0,20,0,0,0,0,0,0,0,0,20,0,0,24,0,0,20,0,0,0,0,0,0,0,0,0,0,20,2,24,2,20,0,0,0,0,0,0,0,0,0,0,0,2,53,56,53,2,0,0,0,0,0,0,24,24,24,24,24,24,56,0,56,24,24,24,24,24,24,0,0,0,0,0,0,2,53,56,53,2,0,0,0,0,0,0,0,0,0,0,0,20,2,24,2,20,0,0,0,0,0,0,0,0,0,0,20,0,0,24,0,0,20,0,0,0,0,0,0,0,0,20,0,0,0,24,0,0,0,20,0,0,0,0,0,0,20,0,0,0,0,24,0,0,0,0,20,0,0,0,0,20,0,0,0,0,0,24,0,0,0,0,0,20,0,0,20,0,0,0,0,0,0,24,0,0,0,0,0,0,20];const RAYS=[17,0,0,0,0,0,0,16,0,0,0,0,0,0,15,0,0,17,0,0,0,0,0,16,0,0,0,0,0,15,0,0,0,0,17,0,0,0,0,16,0,0,0,0,15,0,0,0,0,0,0,17,0,0,0,16,0,0,0,15,0,0,0,0,0,0,0,0,17,0,0,16,0,0,15,0,0,0,0,0,0,0,0,0,0,17,0,16,0,15,0,0,0,0,0,0,0,0,0,0,0,0,17,16,15,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,-15,-16,-17,0,0,0,0,0,0,0,0,0,0,0,0,-15,0,-16,0,-17,0,0,0,0,0,0,0,0,0,0,-15,0,0,-16,0,0,-17,0,0,0,0,0,0,0,0,-15,0,0,0,-16,0,0,0,-17,0,0,0,0,0,0,-15,0,0,0,0,-16,0,0,0,0,-17,0,0,0,0,-15,0,0,0,0,0,-16,0,0,0,0,0,-17,0,0,-15,0,0,0,0,0,0,-16,0,0,0,0,0,0,-17];const PIECE_MASKS={p:1,n:2,b:4,r:8,q:16,k:32};const SYMBOLS="pnbrqkPNBRQK";const PROMOTIONS=[KNIGHT,BISHOP,ROOK,QUEEN];const RANK_1=7;const RANK_2=6;const RANK_7=1;const RANK_8=0;const ROOKS={w:[{square:Ox88.a1,flag:BITS.QSIDE_CASTLE},{square:Ox88.h1,flag:BITS.KSIDE_CASTLE}],b:[{square:Ox88.a8,flag:BITS.QSIDE_CASTLE},{square:Ox88.h8,flag:BITS.KSIDE_CASTLE}]};const SECOND_RANK={b:RANK_7,w:RANK_2};const TERMINATION_MARKERS=["1-0","0-1","1/2-1/2","*"];export function rank(square){return square>>4}export function file(square){return square&15}function isDigit(c){return"0123456789".indexOf(c)!==-1}function algebraic(square){const f=file(square);const r=rank(square);return"abcdefgh".substring(f,f+1)+"87654321".substring(r,r+1)}function swapColor(color){return color===WHITE?BLACK:WHITE}export function validateFen(fen){const tokens=fen.split(/\s+/);if(tokens.length!==6){return{ok:false,error:"Invalid FEN: must contain six space-delimited fields"}}const moveNumber=parseInt(tokens[5],10);if(isNaN(moveNumber)||moveNumber<=0){return{ok:false,error:"Invalid FEN: move number must be a positive integer"}}const halfMoves=parseInt(tokens[4],10);if(isNaN(halfMoves)||halfMoves<0){return{ok:false,error:"Invalid FEN: half move counter number must be a non-negative integer"}}if(!/^(-|[abcdefgh][36])$/.test(tokens[3])){return{ok:false,error:"Invalid FEN: en-passant square is invalid"}}if(/[^kKqQ-]/.test(tokens[2])){return{ok:false,error:"Invalid FEN: castling availability is invalid"}}if(!/^(w|b)$/.test(tokens[1])){return{ok:false,error:"Invalid FEN: side-to-move is invalid"}}const rows=tokens[0].split("/");if(rows.length!==8){return{ok:false,error:"Invalid FEN: piece data does not contain 8 '/'-delimited rows"}}for(let i=0;i<rows.length;i++){let sumFields=0;let previousWasNumber=false;for(let k=0;k<rows[i].length;k++){if(isDigit(rows[i][k])){if(previousWasNumber){return{ok:false,error:"Invalid FEN: piece data is invalid (consecutive number)"}}sumFields+=parseInt(rows[i][k],10);previousWasNumber=true}else{if(!/^[prnbqkPRNBQK]$/.test(rows[i][k])){return{ok:false,error:"Invalid FEN: piece data is invalid (invalid piece)"}}sumFields+=1;previousWasNumber=false}}if(sumFields!==8){return{ok:false,error:"Invalid FEN: piece data is invalid (too many squares in rank)"}}}if(tokens[3][1]=="3"&&tokens[1]=="w"||tokens[3][1]=="6"&&tokens[1]=="b"){return{ok:false,error:"Invalid FEN: illegal en-passant square"}}const kings=[{color:"white",regex:/K/g},{color:"black",regex:/k/g}];for(const{color,regex}of kings){if(!regex.test(tokens[0])){return{ok:false,error:`Invalid FEN: missing ${color} king`}}if((tokens[0].match(regex)||[]).length>1){return{ok:false,error:`Invalid FEN: too many ${color} kings`}}}return{ok:true}}function getDisambiguator(move,moves){const from=move.from;const to=move.to;const piece=move.piece;let ambiguities=0;let sameRank=0;let sameFile=0;for(let i=0,len=moves.length;i<len;i++){const ambigFrom=moves[i].from;const ambigTo=moves[i].to;const ambigPiece=moves[i].piece;if(piece===ambigPiece&&from!==ambigFrom&&to===ambigTo){ambiguities++;if(rank(from)===rank(ambigFrom)){sameRank++}if(file(from)===file(ambigFrom)){sameFile++}}}if(ambiguities>0){if(sameRank>0&&sameFile>0){return algebraic(from)}else if(sameFile>0){return algebraic(from).charAt(1)}else{return algebraic(from).charAt(0)}}return""}function addMove(moves,color,from,to,piece,captured=undefined,flags=BITS.NORMAL){const r=rank(to);if(piece===PAWN&&(r===RANK_1||r===RANK_8)){for(let i=0;i<PROMOTIONS.length;i++){const promotion=PROMOTIONS[i];moves.push({color:color,from:from,to:to,piece:piece,captured:captured,promotion:promotion,flags:flags|BITS.PROMOTION})}}else{moves.push({color:color,from:from,to:to,piece:piece,captured:captured,flags:flags})}}function inferPieceType(san){let pieceType=san.charAt(0);if(pieceType>="a"&&pieceType<="h"){const matches=san.match(/[a-h]\d.*[a-h]\d/);if(matches){return undefined}return PAWN}pieceType=pieceType.toLowerCase();if(pieceType==="o"){return KING}return pieceType}function strippedSan(move){return move.replace(/=/,"").replace(/[+#]?[?!]*$/,"")}export class Chess{constructor(fen=DEFAULT_POSITION){this._board=new Array(128);this._turn=WHITE;this._header={};this._kings={w:EMPTY,b:EMPTY};this._epSquare=-1;this._halfMoves=0;this._moveNumber=0;this._history=[];this._comments={};this._castling={w:0,b:0};this.load(fen)}clear(keepHeaders=false){this._board=new Array(128);this._kings={w:EMPTY,b:EMPTY};this._turn=WHITE;this._castling={w:0,b:0};this._epSquare=EMPTY;this._halfMoves=0;this._moveNumber=1;this._history=[];this._comments={};this._header=keepHeaders?this._header:{};this._updateSetup(this.fen())}load(fen,keepHeaders=false){let tokens=fen.split(/\s+/);if(tokens.length>=2&&tokens.length<6){const adjustments=["-","-","0","1"];fen=tokens.concat(adjustments.slice(-(6-tokens.length))).join(" ")}tokens=fen.split(/\s+/);const{ok,error}=validateFen(fen);if(!ok){throw new Error(error)}const position=tokens[0];let square=0;this.clear(keepHeaders);for(let i=0;i<position.length;i++){const piece=position.charAt(i);if(piece==="/"){square+=8}else if(isDigit(piece)){square+=parseInt(piece,10)}else{const color=piece<"a"?WHITE:BLACK;this.put({type:piece.toLowerCase(),color:color},algebraic(square));square++}}this._turn=tokens[1];if(tokens[2].indexOf("K")>-1){this._castling.w|=BITS.KSIDE_CASTLE}if(tokens[2].indexOf("Q")>-1){this._castling.w|=BITS.QSIDE_CASTLE}if(tokens[2].indexOf("k")>-1){this._castling.b|=BITS.KSIDE_CASTLE}if(tokens[2].indexOf("q")>-1){this._castling.b|=BITS.QSIDE_CASTLE}this._epSquare=tokens[3]==="-"?EMPTY:Ox88[tokens[3]];this._halfMoves=parseInt(tokens[4],10);this._moveNumber=parseInt(tokens[5],10);this._updateSetup(this.fen())}fen(){var _a,_b;let empty=0;let fen="";for(let i=Ox88.a8;i<=Ox88.h1;i++){if(this._board[i]){if(empty>0){fen+=empty;empty=0}const{color,type:piece}=this._board[i];fen+=color===WHITE?piece.toUpperCase():piece.toLowerCase()}else{empty++}if(i+1&136){if(empty>0){fen+=empty}if(i!==Ox88.h1){fen+="/"}empty=0;i+=8}}let castling="";if(this._castling[WHITE]&BITS.KSIDE_CASTLE){castling+="K"}if(this._castling[WHITE]&BITS.QSIDE_CASTLE){castling+="Q"}if(this._castling[BLACK]&BITS.KSIDE_CASTLE){castling+="k"}if(this._castling[BLACK]&BITS.QSIDE_CASTLE){castling+="q"}castling=castling||"-";let epSquare="-";if(this._epSquare!==EMPTY){const bigPawnSquare=this._epSquare+(this._turn===WHITE?16:-16);const squares=[bigPawnSquare+1,bigPawnSquare-1];for(const square of squares){if(square&136){continue}const color=this._turn;if(((_a=this._board[square])===null||_a===void 0?void 0:_a.color)===color&&((_b=this._board[square])===null||_b===void 0?void 0:_b.type)===PAWN){this._makeMove({color:color,from:square,to:this._epSquare,piece:PAWN,captured:PAWN,flags:BITS.EP_CAPTURE});const isLegal=!this._isKingAttacked(color);this._undoMove();if(isLegal){epSquare=algebraic(this._epSquare);break}}}}return[fen,this._turn,castling,epSquare,this._halfMoves,this._moveNumber].join(" ")}_updateSetup(fen){if(this._history.length>0)return;if(fen!==DEFAULT_POSITION){this._header["SetUp"]="1";this._header["FEN"]=fen}else{delete this._header["SetUp"];delete this._header["FEN"]}}reset(){this.load(DEFAULT_POSITION)}get(square){var _a;return(_a=this._board[Ox88[square]])!==null&&_a!==void 0?_a:false}put({type,color},square){if(SYMBOLS.indexOf(type.toLowerCase())===-1){return false}if(!(square in Ox88)){return false}const sq=Ox88[square];if(type==KING&&!(this._kings[color]==EMPTY||this._kings[color]==sq)){return false}this._board[sq]={type:type,color:color};if(type===KING){this._kings[color]=sq}this._updateSetup(this.fen());return true}remove(square){const piece=this.get(square);delete this._board[Ox88[square]];if(piece&&piece.type===KING){this._kings[piece.color]=EMPTY}this._updateSetup(this.fen());return piece}_attacked(color,square){for(let i=Ox88.a8;i<=Ox88.h1;i++){if(i&136){i+=7;continue}if(this._board[i]===undefined||this._board[i].color!==color){continue}const piece=this._board[i];const difference=i-square;if(difference===0){continue}const index=difference+119;if(ATTACKS[index]&PIECE_MASKS[piece.type]){if(piece.type===PAWN){if(difference>0){if(piece.color===WHITE)return true}else{if(piece.color===BLACK)return true}continue}if(piece.type==="n"||piece.type==="k")return true;const offset=RAYS[index];let j=i+offset;let blocked=false;while(j!==square){if(this._board[j]!=null){blocked=true;break}j+=offset}if(!blocked)return true}}return false}_isKingAttacked(color){return this._attacked(swapColor(color),this._kings[color])}isAttacked(square,attackedBy){return this._attacked(attackedBy,Ox88[square])}isCheck(){return this._isKingAttacked(this._turn)}inCheck(){return this.isCheck()}isCheckmate(){return this.isCheck()&&this._moves().length===0}isStalemate(){return!this.isCheck()&&this._moves().length===0}isInsufficientMaterial(){const pieces={b:0,n:0,r:0,q:0,k:0,p:0};const bishops=[];let numPieces=0;let squareColor=0;for(let i=Ox88.a8;i<=Ox88.h1;i++){squareColor=(squareColor+1)%2;if(i&136){i+=7;continue}const piece=this._board[i];if(piece){pieces[piece.type]=piece.type in pieces?pieces[piece.type]+1:1;if(piece.type===BISHOP){bishops.push(squareColor)}numPieces++}}if(numPieces===2){return true}else if(numPieces===3&&(pieces[BISHOP]===1||pieces[KNIGHT]===1)){return true}else if(numPieces===pieces[BISHOP]+2){let sum=0;const len=bishops.length;for(let i=0;i<len;i++){sum+=bishops[i]}if(sum===0||sum===len){return true}}return false}isThreefoldRepetition(){const moves=[];const positions={};let repetition=false;while(true){const move=this._undoMove();if(!move)break;moves.push(move)}while(true){const fen=this.fen().split(" ").slice(0,4).join(" ");positions[fen]=fen in positions?positions[fen]+1:1;if(positions[fen]>=3){repetition=true}const move=moves.pop();if(!move){break}else{this._makeMove(move)}}return repetition}isDraw(){return this._halfMoves>=100||this.isStalemate()||this.isInsufficientMaterial()||this.isThreefoldRepetition()}isGameOver(){return this.isCheckmate()||this.isStalemate()||this.isDraw()}moves({verbose=false,square=undefined,piece=undefined}={}){const moves=this._moves({square:square,piece:piece});if(verbose){return moves.map(move=>this._makePretty(move))}else{return moves.map(move=>this._moveToSan(move,moves))}}_moves({legal=true,piece=undefined,square=undefined}={}){var _a;const forSquare=square?square.toLowerCase():undefined;const forPiece=piece===null||piece===void 0?void 0:piece.toLowerCase();const moves=[];const us=this._turn;const them=swapColor(us);let firstSquare=Ox88.a8;let lastSquare=Ox88.h1;let singleSquare=false;if(forSquare){if(!(forSquare in Ox88)){return[]}else{firstSquare=lastSquare=Ox88[forSquare];singleSquare=true}}for(let from=firstSquare;from<=lastSquare;from++){if(from&136){from+=7;continue}if(!this._board[from]||this._board[from].color===them){continue}const{type}=this._board[from];let to;if(type===PAWN){if(forPiece&&forPiece!==type)continue;to=from+PAWN_OFFSETS[us][0];if(!this._board[to]){addMove(moves,us,from,to,PAWN);to=from+PAWN_OFFSETS[us][1];if(SECOND_RANK[us]===rank(from)&&!this._board[to]){addMove(moves,us,from,to,PAWN,undefined,BITS.BIG_PAWN)}}for(let j=2;j<4;j++){to=from+PAWN_OFFSETS[us][j];if(to&136)continue;if(((_a=this._board[to])===null||_a===void 0?void 0:_a.color)===them){addMove(moves,us,from,to,PAWN,this._board[to].type,BITS.CAPTURE)}else if(to===this._epSquare){addMove(moves,us,from,to,PAWN,PAWN,BITS.EP_CAPTURE)}}}else{if(forPiece&&forPiece!==type)continue;for(let j=0,len=PIECE_OFFSETS[type].length;j<len;j++){const offset=PIECE_OFFSETS[type][j];to=from;while(true){to+=offset;if(to&136)break;if(!this._board[to]){addMove(moves,us,from,to,type)}else{if(this._board[to].color===us)break;addMove(moves,us,from,to,type,this._board[to].type,BITS.CAPTURE);break}if(type===KNIGHT||type===KING)break}}}}if(forPiece===undefined||forPiece===KING){if(!singleSquare||lastSquare===this._kings[us]){if(this._castling[us]&BITS.KSIDE_CASTLE){const castlingFrom=this._kings[us];const castlingTo=castlingFrom+2;if(!this._board[castlingFrom+1]&&!this._board[castlingTo]&&!this._attacked(them,this._kings[us])&&!this._attacked(them,castlingFrom+1)&&!this._attacked(them,castlingTo)){addMove(moves,us,this._kings[us],castlingTo,KING,undefined,BITS.KSIDE_CASTLE)}}if(this._castling[us]&BITS.QSIDE_CASTLE){const castlingFrom=this._kings[us];const castlingTo=castlingFrom-2;if(!this._board[castlingFrom-1]&&!this._board[castlingFrom-2]&&!this._board[castlingFrom-3]&&!this._attacked(them,this._kings[us])&&!this._attacked(them,castlingFrom-1)&&!this._attacked(them,castlingTo)){addMove(moves,us,this._kings[us],castlingTo,KING,undefined,BITS.QSIDE_CASTLE)}}}}if(!legal){return moves}const legalMoves=[];for(let i=0,len=moves.length;i<len;i++){this._makeMove(moves[i]);if(!this._isKingAttacked(us)){legalMoves.push(moves[i])}this._undoMove()}return legalMoves}move(move,{strict=false}={}){let moveObj=null;if(typeof move==="string"){moveObj=this._moveFromSan(move,strict)}else if(typeof move==="object"){const moves=this._moves();for(let i=0,len=moves.length;i<len;i++){if(move.from===algebraic(moves[i].from)&&move.to===algebraic(moves[i].to)&&(!("promotion"in moves[i])||move.promotion===moves[i].promotion)){moveObj=moves[i];break}}}if(!moveObj){if(typeof move==="string"){throw new Error(`Invalid move: ${move}`)}else{throw new Error(`Invalid move: ${JSON.stringify(move)}`)}}const prettyMove=this._makePretty(moveObj);this._makeMove(moveObj);return prettyMove}_push(move){this._history.push({move:move,kings:{b:this._kings.b,w:this._kings.w},turn:this._turn,castling:{b:this._castling.b,w:this._castling.w},epSquare:this._epSquare,halfMoves:this._halfMoves,moveNumber:this._moveNumber})}_makeMove(move){const us=this._turn;const them=swapColor(us);this._push(move);this._board[move.to]=this._board[move.from];delete this._board[move.from];if(move.flags&BITS.EP_CAPTURE){if(this._turn===BLACK){delete this._board[move.to-16]}else{delete this._board[move.to+16]}}if(move.promotion){this._board[move.to]={type:move.promotion,color:us}}if(this._board[move.to].type===KING){this._kings[us]=move.to;if(move.flags&BITS.KSIDE_CASTLE){const castlingTo=move.to-1;const castlingFrom=move.to+1;this._board[castlingTo]=this._board[castlingFrom];delete this._board[castlingFrom]}else if(move.flags&BITS.QSIDE_CASTLE){const castlingTo=move.to+1;const castlingFrom=move.to-2;this._board[castlingTo]=this._board[castlingFrom];delete this._board[castlingFrom]}this._castling[us]=0}if(this._castling[us]){for(let i=0,len=ROOKS[us].length;i<len;i++){if(move.from===ROOKS[us][i].square&&this._castling[us]&ROOKS[us][i].flag){this._castling[us]^=ROOKS[us][i].flag;break}}}if(this._castling[them]){for(let i=0,len=ROOKS[them].length;i<len;i++){if(move.to===ROOKS[them][i].square&&this._castling[them]&ROOKS[them][i].flag){this._castling[them]^=ROOKS[them][i].flag;break}}}if(move.flags&BITS.BIG_PAWN){if(us===BLACK){this._epSquare=move.to-16}else{this._epSquare=move.to+16}}else{this._epSquare=EMPTY}if(move.piece===PAWN){this._halfMoves=0}else if(move.flags&(BITS.CAPTURE|BITS.EP_CAPTURE)){this._halfMoves=0}else{this._halfMoves++}if(us===BLACK){this._moveNumber++}this._turn=them}undo(){const move=this._undoMove();return move?this._makePretty(move):null}_undoMove(){const old=this._history.pop();if(old===undefined){return null}const move=old.move;this._kings=old.kings;this._turn=old.turn;this._castling=old.castling;this._epSquare=old.epSquare;this._halfMoves=old.halfMoves;this._moveNumber=old.moveNumber;const us=this._turn;const them=swapColor(us);this._board[move.from]=this._board[move.to];this._board[move.from].type=move.piece;delete this._board[move.to];if(move.captured){if(move.flags&BITS.EP_CAPTURE){let index;if(us===BLACK){index=move.to-16}else{index=move.to+16}this._board[index]={type:PAWN,color:them}}else{this._board[move.to]={type:move.captured,color:them}}}if(move.flags&(BITS.KSIDE_CASTLE|BITS.QSIDE_CASTLE)){let castlingTo,castlingFrom;if(move.flags&BITS.KSIDE_CASTLE){castlingTo=move.to+1;castlingFrom=move.to-1}else{castlingTo=move.to-2;castlingFrom=move.to+1}this._board[castlingTo]=this._board[castlingFrom];delete this._board[castlingFrom]}return move}pgn({newline="\n",maxWidth=0}={}){const result=[];let headerExists=false;for(const i in this._header){result.push("["+i+' "'+this._header[i]+'"]'+newline);headerExists=true}if(headerExists&&this._history.length){result.push(newline)}const appendComment=moveString=>{const comment=this._comments[this.fen()];if(typeof comment!=="undefined"){const delimiter=moveString.length>0?" ":"";moveString=`${moveString}${delimiter}{${comment}}`}return moveString};const reversedHistory=[];while(this._history.length>0){reversedHistory.push(this._undoMove())}const moves=[];let moveString="";if(reversedHistory.length===0){moves.push(appendComment(""))}while(reversedHistory.length>0){moveString=appendComment(moveString);const move=reversedHistory.pop();if(!move){break}if(!this._history.length&&move.color==="b"){const prefix=`${this._moveNumber}. ...`;moveString=moveString?`${moveString} ${prefix}`:prefix}else if(move.color==="w"){if(moveString.length){moves.push(moveString)}moveString=this._moveNumber+"."}moveString=moveString+" "+this._moveToSan(move,this._moves({legal:true}));this._makeMove(move)}if(moveString.length){moves.push(appendComment(moveString))}if(typeof this._header.Result!=="undefined"){moves.push(this._header.Result)}if(maxWidth===0){return result.join("")+moves.join(" ")}const strip=function(){if(result.length>0&&result[result.length-1]===" "){result.pop();return true}return false};const wrapComment=function(width,move){for(const token of move.split(" ")){if(!token){continue}if(width+token.length>maxWidth){while(strip()){width--}result.push(newline);width=0}result.push(token);width+=token.length;result.push(" ");width++}if(strip()){width--}return width};let currentWidth=0;for(let i=0;i<moves.length;i++){if(currentWidth+moves[i].length>maxWidth){if(moves[i].includes("{")){currentWidth=wrapComment(currentWidth,moves[i]);continue}}if(currentWidth+moves[i].length>maxWidth&&i!==0){if(result[result.length-1]===" "){result.pop()}result.push(newline);currentWidth=0}else if(i!==0){result.push(" ");currentWidth++}result.push(moves[i]);currentWidth+=moves[i].length}return result.join("")}header(...args){for(let i=0;i<args.length;i+=2){if(typeof args[i]==="string"&&typeof args[i+1]==="string"){this._header[args[i]]=args[i+1]}}return this._header}loadPgn(pgn,{strict=false,newlineChar="\r?\n"}={}){function mask(str){return str.replace(/\\/g,"\\")}function parsePgnHeader(header){const headerObj={};const headers=header.split(new RegExp(mask(newlineChar)));let key="";let value="";for(let i=0;i<headers.length;i++){const regex=/^\s*\[\s*([A-Za-z]+)\s*"(.*)"\s*\]\s*$/;key=headers[i].replace(regex,"$1");value=headers[i].replace(regex,"$2");if(key.trim().length>0){headerObj[key]=value}}return headerObj}pgn=pgn.trim();const headerRegex=new RegExp("^(\\[((?:"+mask(newlineChar)+")|.)*\\])"+"((?:\\s*"+mask(newlineChar)+"){2}|(?:\\s*"+mask(newlineChar)+")*$)");const headerRegexResults=headerRegex.exec(pgn);const headerString=headerRegexResults?headerRegexResults.length>=2?headerRegexResults[1]:"":"";this.reset();const headers=parsePgnHeader(headerString);let fen="";for(const key in headers){if(key.toLowerCase()==="fen"){fen=headers[key]}this.header(key,headers[key])}if(!strict){if(fen){this.load(fen,true)}}else{if(headers["SetUp"]==="1"){if(!("FEN"in headers)){throw new Error("Invalid PGN: FEN tag must be supplied with SetUp tag")}this.load(headers["FEN"],true)}}function toHex(s){return Array.from(s).map(function(c){return c.charCodeAt(0)<128?c.charCodeAt(0).toString(16):encodeURIComponent(c).replace(/%/g,"").toLowerCase()}).join("")}function fromHex(s){return s.length==0?"":decodeURIComponent("%"+(s.match(/.{1,2}/g)||[]).join("%"))}const encodeComment=function(s){s=s.replace(new RegExp(mask(newlineChar),"g")," ");return`{${toHex(s.slice(1,s.length-1))}}`};const decodeComment=function(s){if(s.startsWith("{")&&s.endsWith("}")){return fromHex(s.slice(1,s.length-1))}};let ms=pgn.replace(headerString,"").replace(new RegExp(`({[^}]*})+?|;([^${mask(newlineChar)}]*)`,"g"),function(_match,bracket,semicolon){return bracket!==undefined?encodeComment(bracket):" "+encodeComment(`{${semicolon.slice(1)}}`)}).replace(new RegExp(mask(newlineChar),"g")," ");const ravRegex=/(\([^()]+\))+?/g;while(ravRegex.test(ms)){ms=ms.replace(ravRegex,"")}ms=ms.replace(/\d+\.(\.\.)?/g,"");ms=ms.replace(/\.\.\./g,"");ms=ms.replace(/\$\d+/g,"");let moves=ms.trim().split(new RegExp(/\s+/));moves=moves.filter(move=>move!=="");let result="";for(let halfMove=0;halfMove<moves.length;halfMove++){const comment=decodeComment(moves[halfMove]);if(comment!==undefined){this._comments[this.fen()]=comment;continue}const move=this._moveFromSan(moves[halfMove],strict);if(move==null){if(TERMINATION_MARKERS.indexOf(moves[halfMove])>-1){result=moves[halfMove]}else{throw new Error(`Invalid move in PGN: ${moves[halfMove]}`)}}else{result="";this._makeMove(move)}}if(result&&Object.keys(this._header).length&&!this._header["Result"]){this.header("Result",result)}}_moveToSan(move,moves){let output="";if(move.flags&BITS.KSIDE_CASTLE){output="O-O"}else if(move.flags&BITS.QSIDE_CASTLE){output="O-O-O"}else{if(move.piece!==PAWN){const disambiguator=getDisambiguator(move,moves);output+=move.piece.toUpperCase()+disambiguator}if(move.flags&(BITS.CAPTURE|BITS.EP_CAPTURE)){if(move.piece===PAWN){output+=algebraic(move.from)[0]}output+="x"}output+=algebraic(move.to);if(move.promotion){output+="="+move.promotion.toUpperCase()}}this._makeMove(move);if(this.isCheck()){if(this.isCheckmate()){output+="#"}else{output+="+"}}this._undoMove();return output}_moveFromSan(move,strict=false){const cleanMove=strippedSan(move);let pieceType=inferPieceType(cleanMove);let moves=this._moves({legal:true,piece:pieceType});for(let i=0,len=moves.length;i<len;i++){if(cleanMove===strippedSan(this._moveToSan(moves[i],moves))){return moves[i]}}if(strict){return null}let piece=undefined;let matches=undefined;let from=undefined;let to=undefined;let promotion=undefined;let overlyDisambiguated=false;matches=cleanMove.match(/([pnbrqkPNBRQK])?([a-h][1-8])x?-?([a-h][1-8])([qrbnQRBN])?/);if(matches){piece=matches[1];from=matches[2];to=matches[3];promotion=matches[4];if(from.length==1){overlyDisambiguated=true}}else{matches=cleanMove.match(/([pnbrqkPNBRQK])?([a-h]?[1-8]?)x?-?([a-h][1-8])([qrbnQRBN])?/);if(matches){piece=matches[1];from=matches[2];to=matches[3];promotion=matches[4];if(from.length==1){overlyDisambiguated=true}}}pieceType=inferPieceType(cleanMove);moves=this._moves({legal:true,piece:piece?piece:pieceType});for(let i=0,len=moves.length;i<len;i++){if(from&&to){if((!piece||piece.toLowerCase()==moves[i].piece)&&Ox88[from]==moves[i].from&&Ox88[to]==moves[i].to&&(!promotion||promotion.toLowerCase()==moves[i].promotion)){return moves[i]}else if(overlyDisambiguated){const square=algebraic(moves[i].from);if((!piece||piece.toLowerCase()==moves[i].piece)&&Ox88[to]==moves[i].to&&(from==square[0]||from==square[1])&&(!promotion||promotion.toLowerCase()==moves[i].promotion)){return moves[i]}}}}return null}ascii(){let s=" +------------------------+\n";for(let i=Ox88.a8;i<=Ox88.h1;i++){if(file(i)===0){s+=" "+"87654321"[rank(i)]+" |"}if(this._board[i]){const piece=this._board[i].type;const color=this._board[i].color;const symbol=color===WHITE?piece.toUpperCase():piece.toLowerCase();s+=" "+symbol+" "}else{s+=" . "}if(i+1&136){s+="|\n";i+=8}}s+=" +------------------------+\n";s+=" a b c d e f g h";return s}perft(depth){const moves=this._moves({legal:false});let nodes=0;const color=this._turn;for(let i=0,len=moves.length;i<len;i++){this._makeMove(moves[i]);if(!this._isKingAttacked(color)){if(depth-1>0){nodes+=this.perft(depth-1)}else{nodes++}}this._undoMove()}return nodes}_makePretty(uglyMove){const{color,piece,from,to,flags,captured,promotion}=uglyMove;let prettyFlags="";for(const flag in BITS){if(BITS[flag]&flags){prettyFlags+=FLAGS[flag]}}const fromAlgebraic=algebraic(from);const toAlgebraic=algebraic(to);const move={color:color,piece:piece,from:fromAlgebraic,to:toAlgebraic,san:this._moveToSan(uglyMove,this._moves({legal:true})),flags:prettyFlags,lan:fromAlgebraic+toAlgebraic,before:this.fen(),after:""};this._makeMove(uglyMove);move.after=this.fen();this._undoMove();if(captured){move.captured=captured}if(promotion){move.promotion=promotion;move.lan+=promotion}return move}turn(){return this._turn}board(){const output=[];let row=[];for(let i=Ox88.a8;i<=Ox88.h1;i++){if(!this._board[i]){row.push(false)}else{row.push({square:algebraic(i),type:this._board[i].type,color:this._board[i].color})}if(i+1&136){output.push(row);row=[];i+=8}}return output}squareColor(square){if(square in Ox88){const sq=Ox88[square];return(rank(sq)+file(sq))%2===0?"w":"b"}return null}history({verbose=false}={}){const reversedHistory=[];const moveHistory=[];while(this._history.length>0){reversedHistory.push(this._undoMove())}while(true){const move=reversedHistory.pop();if(!move){break}if(verbose){moveHistory.push(this._makePretty(move))}else{moveHistory.push(this._moveToSan(move,this._moves()))}this._makeMove(move)}return moveHistory}_pruneComments(){const reversedHistory=[];const currentComments={};const copyComment=fen=>{if(fen in this._comments){currentComments[fen]=this._comments[fen]}};while(this._history.length>0){reversedHistory.push(this._undoMove())}copyComment(this.fen());while(true){const move=reversedHistory.pop();if(!move){break}this._makeMove(move);copyComment(this.fen())}this._comments=currentComments}getComment(){return this._comments[this.fen()]}setComment(comment){this._comments[this.fen()]=comment.replace("{","[").replace("}","]")}deleteComment(){const comment=this._comments[this.fen()];delete this._comments[this.fen()];return comment}getComments(){this._pruneComments();return Object.keys(this._comments).map(fen=>{return{fen:fen,comment:this._comments[fen]}})}deleteComments(){this._pruneComments();return Object.keys(this._comments).map(fen=>{const comment=this._comments[fen];delete this._comments[fen];return{fen:fen,comment:comment}})}}