From 041a8167ea01d1b393b58d74ce48b5d61496b943 Mon Sep 17 00:00:00 2001 From: mehbark Date: Sat, 1 Apr 2023 23:11:32 -0400 Subject: [PATCH] minify --- Makefile | 9 +- chess.min.js | 1 + gchessboard.js | 3120 ++++++++++++++++++++++++++++++++++++++++++++ gchessboard.min.js | 1 + index.html | 156 ++- main.min.js | 6 + 6 files changed, 3247 insertions(+), 46 deletions(-) create mode 100644 chess.min.js create mode 100644 gchessboard.js create mode 100644 gchessboard.min.js create mode 100644 main.min.js diff --git a/Makefile b/Makefile index d7ece2d..9021db1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build serve clean scp -build: chess.js main.js Makefile +build: chess.min.js main.min.js gchessboard.min.js Makefile serve: build python -m http.server @@ -12,11 +12,16 @@ upload: build full-upload: build scp * root@pyrope.net:/var/www/mysite/elo-worldle/ +# https://unix.stackexchange.com/questions/153862/remove-all-files-directories-except-for-one-file clean: - rm *.js + find . -name "*.js" ! -name "gchessboard*" -exec rm {} + # main.js: main.ts # tsc main.ts +%.min.js: %.js + npx uglify-js $< | sed s/chess\.js/chess.min.js/g > $@ + %.js: %.ts tsconfig.json tsc + diff --git a/chess.min.js b/chess.min.js new file mode 100644 index 0000000..03155eb --- /dev/null +++ b/chess.min.js @@ -0,0 +1 @@ +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;i1){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;i0){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="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-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=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{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;imaxWidth){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;i0){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-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;i0){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}})}} diff --git a/gchessboard.js b/gchessboard.js new file mode 100644 index 0000000..cfe613a --- /dev/null +++ b/gchessboard.js @@ -0,0 +1,3120 @@ +/** + * This code is a TypeScript port of code in https://github.com/addaleax/munkres-js. + * + * Some changes include porting to TypeScript, simplifying some loop logic, and + * other formatting and name changes. + * + * Original copyright details: + * + * Copyright 2014 Anna Henningsen (Conversion to JS) + * Copyright 2008 Brian M. Clapper + * + * Original Copyright and License + * ============================== + * + * Copyright 2008-2016 Brian M. Clapper + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const STAR = 1; +const PRIME = 2; +class Munkres { + constructor(costMatrix, padValue) { + Object.defineProperty(this, "C", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "n", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "originalRows", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "originalCols", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "marked", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "rowCovered", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "colCovered", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "Z0Row", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "Z0Col", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + const maxNumColumns = costMatrix.reduce((acc, row) => Math.max(acc, row.length), 0); + this.n = Math.max(costMatrix.length, maxNumColumns); + this.originalRows = costMatrix.length; + this.originalCols = maxNumColumns; + this.C = []; + for (let i = 0; i < this.n; i++) { + const row = costMatrix[i] === undefined ? [] : costMatrix[i].slice(); + while (row.length < this.n) { + row.push(padValue || 0); + } + this.C.push(row); + } + this.marked = this._makeMatrix(this.n, 0); + this.rowCovered = Array(this.n).fill(false); + this.colCovered = Array(this.n).fill(false); + } + /** + * Compute the indices for the lowest-cost pairings between rows and columns + * in the database. Returns a list of (row, column) tuples that can be used + * to traverse the matrix. + * + * **WARNING**: This code handles square and rectangular matrices. + * It does *not* handle irregular matrices. + */ + compute() { + let step = 1; + const steps = { + 1: this._step1, + 2: this._step2, + 3: this._step3, + 4: this._step4, + 5: this._step5, + 6: this._step6, + }; + while (step < 7) { + const func = steps[step]; + step = func.apply(this); + } + const results = []; + for (let i = 0; i < this.originalRows; i++) { + for (let j = 0; j < this.originalCols; j++) { + if (this.marked[i][j] == STAR) { + results.push([i, j]); + } + } + } + return results; + } + /** + * Create an n×n matrix, populating it with the specific value. + */ + _makeMatrix(n, val) { + const matrix = []; + for (let i = 0; i < n; i++) { + const row = []; + for (let j = 0; j < n; j++) { + row.push(val); + } + matrix.push(row); + } + return matrix; + } + /** + * Produce at least one zero in each row by subtracting the smallest + * element of each row from every element in a row. Go to Step 2. + */ + _step1() { + for (let i = 0; i < this.n; i++) { + const minval = Math.min(...this.C[i]); + for (let j = 0; j < this.n; j++) { + this.C[i][j] -= minval; + } + } + return 2; + } + /** + * Assign as many tasks as possible: + * 1. Find a zero in the matrix, and star it. Temporarily mark row and column. + * 2. Find the next zero that is not in an already marked row and column. + * 3. Repeat 1. + * Go to Step 3. + */ + _step2() { + for (let i = 0; i < this.n; i++) { + for (let j = 0; j < this.n; j++) { + if (this.C[i][j] === 0 && !this.rowCovered[i] && !this.colCovered[j]) { + this.marked[i][j] = STAR; + this.rowCovered[i] = true; + this.colCovered[j] = true; + break; + } + } + } + this._clearCovers(); + return 3; + } + /** + * Cover each column containing an assignment (starred zero). If K columns + * are covered, the starred zeros describe a complete set of unique + * assignments. In this case, go to DONE, otherwise, go to Step 4. + */ + _step3() { + let count = 0; + for (let i = 0; i < this.n; i++) { + for (let j = 0; j < this.n; j++) { + if (this.marked[i][j] === STAR && !this.colCovered[j]) { + this.colCovered[j] = true; + count++; + } + } + } + return count >= this.n ? 7 : 4; + } + /** + * Find an uncovered zero and prime it. If there is no starred zero + * on that row, go to Step 6. If there is a starred zero on that row, + * cover the row, and uncover the column containing the starred + * zero. Continue doing this, until we find an uncovered zero with no + * starred zero on the same row. Go to Step 5. + */ + _step4() { + let colWithStar = -1; + for (;;) { + const [row, col] = this._findFirstUncoveredZero(); + if (row < 0) { + return 6; + } + this.marked[row][col] = PRIME; + colWithStar = this._findStarInRow(row); + if (colWithStar >= 0) { + this.rowCovered[row] = true; + this.colCovered[colWithStar] = false; + } + else { + this.Z0Row = row; + this.Z0Col = col; + return 5; + } + } + } + /** + * Construct a series of alternating primed and starred zeros as + * follows. Let Z0 represent the uncovered primed zero found in Step 4. + * Let Z1 denote the starred zero in the column of Z0 (if any). + * Let Z2 denote the primed zero in the row of Z1 (there will always + * be one). Continue until the series terminates at a primed zero + * that has no starred zero in its column. Unstar each starred zero + * of the series, star each primed zero of the series, erase all + * primes and uncover every line in the matrix. Return to Step 3 + */ + _step5() { + let count = 0; + const path = [[this.Z0Row, this.Z0Col]]; + for (;;) { + const row = this._findStarInCol(path[count][1]); + if (row < 0) { + break; + } + path.push([row, path[count][1]]); + count++; + const col = this._findPrimeInRow(path[count][0]); + path.push([path[count][0], col]); + count++; + } + for (let i = 0; i <= count; i++) { + const [row, col] = path[i]; + // Element at row, col is either starred or primed. + // if star -> unstar + // if prime -> star + this.marked[row][col] = this.marked[row][col] == STAR ? 0 : STAR; + } + this._clearCovers(); + this._erasePrimes(); + return 3; + } + /** + * From the uncovered elements, find the smallest element. + * Add that value to every element of each covered row, and subtract it + * from every element of each uncovered column. Return to Step 4 without + * altering any stars, primes, or covered lines. + */ + _step6() { + const minval = this._findSmallestUncovered(); + for (let i = 0; i < this.n; i++) { + for (let j = 0; j < this.n; j++) { + if (this.rowCovered[i]) { + this.C[i][j] += minval; + } + if (!this.colCovered[j]) { + this.C[i][j] -= minval; + } + } + } + return 4; + } + /** + * Clear all covered matrix cells. + */ + _clearCovers() { + for (let i = 0; i < this.n; i++) { + this.rowCovered[i] = false; + this.colCovered[i] = false; + } + } + /** + * Erase all prime markings. + */ + _erasePrimes() { + for (let i = 0; i < this.n; i++) { + for (let j = 0; j < this.n; j++) + if (this.marked[i][j] === PRIME) { + this.marked[i][j] = 0; + } + } + } + /** + * Find the first uncovered element with value 0. If none found, return [-1, -1]. + */ + _findFirstUncoveredZero() { + for (let i = 0; i < this.n; i++) + for (let j = 0; j < this.n; j++) + if (this.C[i][j] === 0 && !this.rowCovered[i] && !this.colCovered[j]) + return [i, j]; + return [-1, -1]; + } + /** + * Find the first starred element in the specified row. Returns + * the column index, or -1 if no starred element was found. + */ + _findStarInRow(row) { + for (let j = 0; j < this.n; j++) { + if (this.marked[row][j] == STAR) { + return j; + } + } + return -1; + } + /** + * Find the first starred element in the specified column. Returns + * the row index, or -1 if no starred element was found. + */ + _findStarInCol(col) { + for (let i = 0; i < this.n; i++) { + if (this.marked[i][col] == STAR) { + return i; + } + } + return -1; + } + /** + * Find the first prime element in the specified row. Returns the column + * index, or -1 if no prime element was found. + */ + _findPrimeInRow(row) { + for (let j = 0; j < this.n; j++) { + if (this.marked[row][j] == PRIME) { + return j; + } + } + return -1; + } + /** + * Find the smallest uncovered value in the matrix. + */ + _findSmallestUncovered() { + let minval = Number.MAX_SAFE_INTEGER; + for (let i = 0; i < this.n; i++) { + for (let j = 0; j < this.n; j++) { + if (!this.rowCovered[i] && + !this.colCovered[j] && + minval > this.C[i][j]) { + minval = this.C[i][j]; + } + } + } + return minval; + } +} +function munkres(costMatrix, padValue) { + const m = new Munkres(costMatrix, padValue); + return m.compute(); +} + +/** + * Collection of 0x88-based methods to represent chessboard state. + * + * https://www.chessprogramming.org/0x88 + */ +const SIDE_COLORS = ["white", "black"]; +// prettier-ignore +const SQUARES_MAP = { + 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 SQUARES = Object.keys(SQUARES_MAP); +// prettier-ignore +const SQUARE_DISTANCE_TABLE = [ + 14, 13, 12, 11, 10, 9, 8, 7, 8, 9, 10, 11, 12, 13, 14, 0, + 13, 12, 11, 10, 9, 8, 7, 6, 7, 8, 9, 10, 11, 12, 13, 0, + 12, 11, 10, 9, 8, 7, 6, 5, 6, 7, 8, 9, 10, 11, 12, 0, + 11, 10, 9, 8, 7, 6, 5, 4, 5, 6, 7, 8, 9, 10, 11, 0, + 10, 9, 8, 7, 6, 5, 4, 3, 4, 5, 6, 7, 8, 9, 10, 0, + 9, 8, 7, 6, 5, 4, 3, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 8, 7, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 8, 0, + 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 0, + 8, 7, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 8, 0, + 9, 8, 7, 6, 5, 4, 3, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 10, 9, 8, 7, 6, 5, 4, 3, 4, 5, 6, 7, 8, 9, 10, 0, + 11, 10, 9, 8, 7, 6, 5, 4, 5, 6, 7, 8, 9, 10, 11, 0, + 12, 11, 10, 9, 8, 7, 6, 5, 6, 7, 8, 9, 10, 11, 12, 0, + 13, 12, 11, 10, 9, 8, 7, 6, 7, 8, 9, 10, 11, 12, 13, 0, + 14, 13, 12, 11, 10, 9, 8, 7, 8, 9, 10, 11, 12, 13, 14, 0, +]; +const REVERSE_SQUARES_MAP = SQUARES.reduce((acc, key) => { + acc[SQUARES_MAP[key]] = key; + return acc; +}, {}); +const FEN_PIECE_TYPE_MAP = { + p: "pawn", + n: "knight", + b: "bishop", + r: "rook", + q: "queen", + k: "king", +}; +const REVERSE_FEN_PIECE_TYPE_MAP = Object.keys(FEN_PIECE_TYPE_MAP).reduce((acc, key) => { + acc[FEN_PIECE_TYPE_MAP[key]] = key; + return acc; +}, {}); +/** + * Parse a FEN string and return an object that maps squares to pieces. + * + * Also accepts the special string "initial" or "start" to represent + * standard game starting position. + * + * Note that only the first part of the FEN string (piece placement) is + * parsed; any additional components are ignored. + * + * @param fen the FEN string + * @returns an object where key is of type Square (string) and value is + * of type Piece + */ +function getPosition(fen) { + if (fen === "initial" || fen === "start") { + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; + } + const parts = fen.split(" "); + const ranks = parts[0].split("/"); + if (ranks.length !== 8) { + return undefined; + } + const position = {}; + for (let i = 0; i < 8; i++) { + const rank = 8 - i; + let fileOffset = 0; + for (let j = 0; j < ranks[i].length; j++) { + const pieceLetter = ranks[i][j].toLowerCase(); + if (pieceLetter in FEN_PIECE_TYPE_MAP) { + const square = (String.fromCharCode(97 + fileOffset) + rank); + position[square] = { + pieceType: FEN_PIECE_TYPE_MAP[pieceLetter], + color: pieceLetter === ranks[i][j] ? "black" : "white", + }; + fileOffset += 1; + } + else { + const emptySpaces = parseInt(ranks[i][j]); + if (isNaN(emptySpaces) || emptySpaces === 0 || emptySpaces > 8) { + return undefined; + } + else { + fileOffset += emptySpaces; + } + } + } + if (fileOffset !== 8) { + return undefined; + } + } + return position; +} +/** + * Get FEN string corresponding to Position object. Note that this only returns + * the first (piece placement) component of the FEN string. + */ +function getFen(position) { + const rankSpecs = []; + for (let i = 0; i < 8; i++) { + let rankSpec = ""; + let gap = 0; + for (let j = 0; j < 8; j++) { + const square = REVERSE_SQUARES_MAP[16 * i + j]; + const piece = position[square]; + if (piece !== undefined) { + const pieceStr = REVERSE_FEN_PIECE_TYPE_MAP[piece.pieceType]; + if (gap > 0) { + rankSpec += gap; + } + rankSpec += piece.color === "white" ? pieceStr.toUpperCase() : pieceStr; + gap = 0; + } + else { + gap += 1; + } + } + if (gap > 0) { + rankSpec += gap; + } + rankSpecs.push(rankSpec); + } + return rankSpecs.join("/"); +} +/** + * Return square identifier for visual index in a grid, depending on + * orientation. If `orientation` is "white", then a8 is on the top + * left (0) and h8 is on the bottom right (63): + * + * a8 ...... . + * . ...... . + * . ...... h1 + * + * otherwise h1 is on the top left: + * + * h1 ...... . + * . ...... . + * . ...... a8 + * + * https://www.chessprogramming.org/0x88#Coordinate_Transformation + */ +function getSquare(visualIndex, orientation) { + const idx = visualIndex + (visualIndex & ~0x7); + return REVERSE_SQUARES_MAP[orientation === "black" ? 0x77 - idx : idx]; +} +/** + * Get the "visual" index for `square` depending on `orientation`. + * If `orientation` is "white", then a8 is on the top left (0) and h8 is + * on the bottom right (63): + * + * a8 ...... . + * . ...... . + * . ...... h1 + * + * otherwise h1 is on the top left: + * + * h1 ...... . + * . ...... . + * . ...... a8 + * + * https://www.chessprogramming.org/0x88#Coordinate_Transformation + * + * @param square square to convert to visual index. + * @param orientation what side is at the bottom ("white" = a1 on bottom left) + * @returns a visual index for the square in question. + */ +function getVisualIndex(square, orientation) { + const idx = SQUARES_MAP[square]; + const orientedIdx = orientation === "black" ? 0x77 - idx : idx; + return (orientedIdx + (orientedIdx & 0x7)) >> 1; +} +/** + * Like `getVisualIndex`, but returns a row and column combination. + * + * @param square square to convert to visual row and column. + * @param orientation what side is at the bottom ("white" = a1 on bottom left) + * @returns an array containing [row, column] for the square in question. + */ +function getVisualRowColumn(square, orientation) { + const idx = getVisualIndex(square, orientation); + return [idx >> 3, idx & 0x7]; +} +/** + * https://www.chessprogramming.org/Color_of_a_Square#By_Anti-Diagonal_Index + */ +function getSquareColor(square) { + const idx0x88 = SQUARES_MAP[square]; + const idx = (idx0x88 + (idx0x88 & 0x7)) >> 1; + return ((idx * 9) & 8) === 0 ? "light" : "dark"; +} +/** + * Type guard to check if `key` (string) is a valid chess square. + */ +function keyIsSquare(key) { + return key !== undefined && key in SQUARES_MAP; +} +/** + * Deep equality check for two Piece objects. + */ +function pieceEqual(a, b) { + return ((a === undefined && b === undefined) || + (a !== undefined && + b !== undefined && + a.color === b.color && + a.pieceType === b.pieceType)); +} +/** + * Type guard for string values that need to conform to a `Side` definition. + */ +function isSide(s) { + return SIDE_COLORS.includes(s); +} +/** + * Deep equality check for Position objects. + */ +function positionsEqual(a, b) { + return SQUARES.every((square) => pieceEqual(a[square], b[square])); +} +function calcPositionDiff(oldPosition, newPosition) { + // Limit old and new positions only to squares that are different + const oldPositionLimited = { ...oldPosition }; + const newPositionLimited = { ...newPosition }; + Object.keys(newPosition).forEach((k) => { + const square = k; + if (pieceEqual(newPosition[square], oldPosition[square])) { + delete oldPositionLimited[square]; + delete newPositionLimited[square]; + } + }); + const added = []; + const removed = []; + const moved = []; + function groupByPiece(position) { + return Object.entries(position).reduce((groups, [square, piece]) => { + const key = `${piece.color}_${piece.pieceType}`; + if (!(key in groups)) { + groups[key] = { squares: [], piece }; + } + groups[key].squares.push(square); + return groups; + }, {}); + } + function matchSquares(oldSquares, newSquares) { + const costMatrix = []; + for (let i = 0; i < oldSquares.length; i++) { + const row = []; + for (let j = 0; j < newSquares.length; j++) { + row.push(squareDistance(oldSquares[i], newSquares[j])); + } + costMatrix.push(row); + } + const matches = munkres(costMatrix, 15); + const moved = []; + const added = []; + const removed = []; + const oldSquaresCopy = oldSquares.slice(); + const newSquaresCopy = newSquares.slice(); + for (const [i, j] of matches || []) { + moved.push({ + oldSquare: oldSquaresCopy[i], + newSquare: newSquaresCopy[j], + }); + delete oldSquaresCopy[i]; + delete newSquaresCopy[j]; + } + oldSquaresCopy + .filter((s) => s !== undefined) + .forEach((s) => { + removed.push(s); + }); + newSquaresCopy + .filter((s) => s !== undefined) + .forEach((s) => { + added.push(s); + }); + return { moved, added, removed }; + } + const oldPositionGrouped = groupByPiece(oldPositionLimited); + const newPositionGrouped = groupByPiece(newPositionLimited); + Object.entries(newPositionGrouped).forEach(([k, pieceSquares]) => { + if (k in oldPositionGrouped) { + const matches = matchSquares(oldPositionGrouped[k].squares, pieceSquares.squares); + matches.moved.forEach(({ oldSquare, newSquare }) => { + moved.push({ piece: pieceSquares.piece, oldSquare, newSquare }); + }); + matches.added.forEach((square) => { + added.push({ piece: pieceSquares.piece, square }); + }); + matches.removed.forEach((square) => { + removed.push({ piece: pieceSquares.piece, square }); + }); + delete oldPositionGrouped[k]; + } + else { + // Piece only in new position - it is added + pieceSquares.squares.forEach((square) => { + added.push({ piece: pieceSquares.piece, square }); + }); + } + }); + // All pieces in old position now are removed + Object.entries(oldPositionGrouped).forEach(([, pieceSquares]) => { + pieceSquares.squares.forEach((square) => { + removed.push({ piece: pieceSquares.piece, square }); + }); + }); + return { added, removed, moved }; +} +function squareDistance(a, b) { + return SQUARE_DISTANCE_TABLE[SQUARES_MAP[a] - SQUARES_MAP[b] + 0x77]; +} + +/** + * Convenience functions for creating and removing DOM elements. + */ +/** + * Make HTML element, with optional `attributes`, `data` key/values and `classes` + * specified through `options. + */ +function makeHTMLElement(tag, options) { + return addOptionsToElement(document.createElement(tag), options); +} +/** + * Make SVG element, with optional `attributes`, `data` key/values and `classes` + * specified through `options. + */ +function makeSVGElement(tag, options) { + return addOptionsToElement(document.createElementNS("http://www.w3.org/2000/svg", tag), options); +} +function addOptionsToElement(e, options) { + if (options !== undefined) { + for (const key in options.attributes) { + e.setAttribute(key, options.attributes[key]); + } + for (const key in options.data) { + e.dataset[key] = options.data[key]; + } + if (options.classes) { + e.classList.add(...options.classes); + } + } + return e; +} + +/** + * Wrapper for Typescript `never` type to be used in exhaustive type checks. + */ +// istanbul ignore next +function assertUnreachable(x) { + throw new Error(`Unreachable code reached with value ${x}`); +} + +/** + * Visual representation of a chessboard piece. + */ +class BoardPiece { + constructor(container, config) { + Object.defineProperty(this, "piece", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "animationFinished", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_element", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_parentElement", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_explicitPosition", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.piece = config.piece; + this._parentElement = container; + this._element = makeHTMLElement("span", { + attributes: { + role: "presentation", + "aria-hidden": "true", + part: `piece-${BoardPiece.PIECE_CLASS_MAP[this.piece.color][this.piece.pieceType]}`, + }, + classes: [ + "piece", + BoardPiece.PIECE_CLASS_MAP[this.piece.color][this.piece.pieceType], + ], + }); + if (config.animation !== undefined) { + this._setAnimation(config.animation); + } + if (config.secondary) { + this._element.classList.add("secondary"); + } + container.appendChild(this._element); + } + /** + * Remove piece for square it is contained on, along with any animation + * listeners. + */ + remove(animationDurationMs) { + if (animationDurationMs) { + this._setAnimation({ type: "fade-out", durationMs: animationDurationMs }); + } + else { + this._parentElement.removeChild(this._element); + } + } + /** + * Set explicit offset for piece relative to default location in square. + */ + setExplicitPosition(explicitPosition) { + this._explicitPosition = explicitPosition; + const coords = this._getTranslateValues(explicitPosition); + if (coords) { + this._element.style.transform = `translate(${coords.x}, ${coords.y})`; + } + const scaleValue = getComputedStyle(this._element).getPropertyValue(BoardPiece.PIECE_DRAG_SCALE_PROP); + if (scaleValue) { + this._element.style.transform += ` scale(${scaleValue})`; + } + } + /** + * Reset any explicit position set on the piece. If `transition` is true, then + * the change is accompanied with a transition. + */ + resetPosition(animateDurationMs) { + if (animateDurationMs && this._explicitPosition) { + this._setAnimation({ + type: "slide-in", + from: this._explicitPosition, + durationMs: animateDurationMs, + }); + } + this._element.style.removeProperty("transform"); + this._explicitPosition = undefined; + } + /** + * Return explicit position of piece on square, if any. + */ + get explicitPosition() { + return this._explicitPosition; + } + /** + * Finish any animations, if in progress. + */ + finishAnimations() { + this._element.getAnimations().forEach((a) => { + a.finish(); + }); + } + _getTranslateValues(explicitPosition) { + if (explicitPosition.type === "coordinates") { + const squareDims = this._parentElement.getBoundingClientRect(); + const deltaX = explicitPosition.x - squareDims.left - squareDims.width / 2; + const deltaY = explicitPosition.y - squareDims.top - (3 * squareDims.height) / 4; + if (deltaX !== 0 || deltaY !== 0) { + return { x: `${deltaX}px`, y: `${deltaY}px` }; + } + } + else { + if (explicitPosition.deltaCols !== 0 || + explicitPosition.deltaRows !== 0) { + return { + x: `${explicitPosition.deltaCols * 100}%`, + y: `${explicitPosition.deltaRows * 100}%`, + }; + } + } + return undefined; + } + _setAnimation(animationSpec) { + let keyframes; + let onfinish; + // Always have exactly one animation running at a time. + this.finishAnimations(); + switch (animationSpec.type) { + case "slide-in": + { + const coords = this._getTranslateValues(animationSpec.from); + if (coords) { + keyframes = [ + { transform: `translate(${coords.x}, ${coords.y})` }, + { transform: "none" }, + ]; + this._element.classList.add("moving"); + } + onfinish = () => { + this._element.classList.remove("moving"); + }; + } + break; + case "fade-in": + keyframes = [{ opacity: 0 }, { opacity: 1 }]; + break; + case "fade-out": + { + keyframes = [{ opacity: 1 }, { opacity: 0 }]; + const elementCopy = this._element; + onfinish = () => { + this._parentElement.removeChild(elementCopy); + }; + } + break; + default: + assertUnreachable(animationSpec); + } + if (keyframes !== undefined && + typeof this._element.animate === "function") { + const animation = this._element.animate(keyframes, { + duration: Math.max(0, animationSpec.durationMs), + }); + this.animationFinished = new Promise((resolve) => { + animation.onfinish = () => { + if (onfinish !== undefined) { + onfinish(); + } + this.animationFinished = undefined; + resolve(); + }; + }); + } + else if (onfinish !== undefined) { + onfinish(); + } + } +} +/** + * Map of piece to background image CSS class name. + */ +Object.defineProperty(BoardPiece, "PIECE_CLASS_MAP", { + enumerable: true, + configurable: true, + writable: true, + value: { + white: { + queen: "wq", + king: "wk", + knight: "wn", + pawn: "wp", + bishop: "wb", + rook: "wr", + }, + black: { + queen: "bq", + king: "bk", + knight: "bn", + pawn: "bp", + bishop: "bb", + rook: "br", + }, + } +}); +/** + * CSS custom property for scale applied to piece while draggging. + * This is overridden per input method within CSS styles. + */ +Object.defineProperty(BoardPiece, "PIECE_DRAG_SCALE_PROP", { + enumerable: true, + configurable: true, + writable: true, + value: "--p-piece-drag-scale" +}); + +/** + * Visual representation of a chessboard square, along with attributes + * that aid in interactivity (ARIA role, labels etc). + */ +class BoardSquare { + constructor(container, label) { + Object.defineProperty(this, "_tdElement", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_contentElement", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_slotWrapper", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_slotElement", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_label", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_interactive", { + enumerable: true, + configurable: true, + writable: true, + value: false + }); + Object.defineProperty(this, "_tabbable", { + enumerable: true, + configurable: true, + writable: true, + value: false + }); + Object.defineProperty(this, "_moveable", { + enumerable: true, + configurable: true, + writable: true, + value: false + }); + Object.defineProperty(this, "_boardPiece", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_secondaryBoardPiece", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_hasContent", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_hover", { + enumerable: true, + configurable: true, + writable: true, + value: false + }); + Object.defineProperty(this, "_markedTarget", { + enumerable: true, + configurable: true, + writable: true, + value: false + }); + Object.defineProperty(this, "_moveState", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this._tdElement = makeHTMLElement("td", { attributes: { role: "cell" } }); + this._label = label; + this._contentElement = makeHTMLElement("div", { classes: ["content"] }); + this._slotWrapper = makeHTMLElement("div", { + classes: ["slot"], + attributes: { role: "presentation" }, + }); + this._slotElement = document.createElement("slot"); + this._slotWrapper.appendChild(this._slotElement); + this._contentElement.appendChild(this._slotWrapper); + this._updateLabelVisuals(); + this._tdElement.appendChild(this._contentElement); + container.appendChild(this._tdElement); + } + /** + * Label associated with the square (depends on orientation of square + * on the board). + */ + get label() { + return this._label; + } + set label(value) { + this._label = value; + this._updateLabelVisuals(); + } + /** + * Whether the square is used in an interactive grid. Decides whether + * the square should get visual attributes like tabindex, labels etc. + */ + get interactive() { + return this._interactive; + } + set interactive(value) { + this._interactive = value; + this._moveState = undefined; + // Aria roles + this._tdElement.setAttribute("role", value ? "gridcell" : "cell"); + if (value) { + this._contentElement.setAttribute("role", "button"); + } + else { + this._contentElement.removeAttribute("role"); + } + this._updateTabIndex(); + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + } + /** + * Whether this square can be tabbed to by the user (tabindex = 0). By default, + * all chessboard squares are focusable but not user-tabbable (tabindex = -1). + */ + get tabbable() { + return this._tabbable; + } + set tabbable(value) { + this._tabbable = value; + this._updateTabIndex(); + } + /** + * Whether this square should be marked as containing any slotted content. + */ + get hasContent() { + return !!this._hasContent; + } + set hasContent(value) { + this._hasContent = value; + this._contentElement.classList.toggle("has-content", value); + } + /** + * Whether the piece on this square is moveable through user interaction. + * To be set to true, a piece must actually exist on the square. + */ + get moveable() { + return this._moveable; + } + set moveable(value) { + if (!value || this._boardPiece) { + this._moveable = value; + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + } + } + /** + * Whether this square is a valid move target. These are highlighted + * when move is in progress, indicating squares that we can move to. + */ + get moveTarget() { + return this._moveState === "move-target"; + } + set moveTarget(value) { + this._moveState = value ? "move-target" : "move-nontarget"; + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + } + removeMoveState() { + this._moveState = undefined; + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + } + /** + * Whether this square is currently a "hover" target: the equivalent of a + * :hover pseudoclass while mousing over a target square, but for drag + * and keyboard moves. + */ + get hover() { + return this._hover; + } + set hover(value) { + this._hover = value; + this._contentElement.classList.toggle("hover", value); + } + /** + * Whether this square is currently a marked destination of a move. This + * is usually shown with a marker or other indicator on the square. + */ + get markedTarget() { + return this._markedTarget; + } + set markedTarget(value) { + this._markedTarget = value; + this._contentElement.classList.toggle("marked-target", value); + } + /** + * Rendered width of element (in integer), used in making drag threshold calculations. + */ + get width() { + return this._contentElement.clientWidth; + } + /** + * Get explicit position of primary piece, if set. + */ + get explicitPiecePosition() { + return this._boardPiece?.explicitPosition; + } + /** + * Focus element associated with square. + */ + focus() { + this._contentElement.focus(); + } + /** + * Blur element associated with square. + */ + blur() { + this._contentElement.blur(); + } + /** + * Return BoardPiece on this square, if it exists. + */ + get boardPiece() { + return this._boardPiece; + } + /** + * Set primary piece associated with the square. This piece is rendered either + * directly onto the square (default) or optionally, animating in from an + * explicit position `animateFromPosition`. + * + * If the piece being set is the same as the one already present on the + * square, and the new piece is not animating in from anywhere, this will + * be a no-op since the position of the two pieces would otherwise be exactly + * the same. + */ + setPiece(piece, moveable, animation) { + if (!pieceEqual(this._boardPiece?.piece, piece) || animation) { + this.clearPiece(animation?.durationMs); + this._boardPiece = new BoardPiece(this._contentElement, { + piece, + animation, + }); + this.moveable = moveable; + this._updateSquareAfterPieceChange(); + } + } + clearPiece(animationDurationMs) { + if (this._boardPiece !== undefined) { + this.moveable = false; + this._boardPiece.remove(animationDurationMs); + this._boardPiece = undefined; + this._updateSquareAfterPieceChange(); + } + } + /** + * Optionally, squares may have a secondary piece, such as a ghost piece shown + * while dragging. The secondary piece is always shown *behind* the primary + * piece in the DOM. + */ + toggleSecondaryPiece(show) { + if (show && !this._secondaryBoardPiece && this._boardPiece) { + this._secondaryBoardPiece = new BoardPiece(this._contentElement, { + piece: this._boardPiece.piece, + secondary: true, + }); + } + if (!show) { + if (this._secondaryBoardPiece !== undefined) { + this._secondaryBoardPiece.remove(); + } + this._secondaryBoardPiece = undefined; + } + } + /** + * Mark this square as being interacted with. + */ + startInteraction() { + if (this._boardPiece !== undefined && this.moveable) { + this._moveState = "move-start"; + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + this._boardPiece.finishAnimations(); + } + } + /** + * Set piece to explicit pixel location. Ignore if square has no piece. + */ + displacePiece(x, y) { + this._boardPiece?.setExplicitPosition({ type: "coordinates", x, y }); + } + /** + * Set piece back to original location. Ignore if square has no piece. + */ + resetPiecePosition(animateDurationMs) { + this._boardPiece?.resetPosition(animateDurationMs); + } + /** + * Cancel ongoing interaction and reset position. + */ + cancelInteraction(animateDurationMs) { + this._moveState = undefined; + this._updateMoveStateVisuals(); + this._updateLabelVisuals(); + this.resetPiecePosition(animateDurationMs); + } + _updateLabelVisuals() { + this._contentElement.dataset.square = this.label; + this._contentElement.dataset.squareColor = getSquareColor(this.label); + const labelParts = [ + this._boardPiece + ? `${this.label}, ${this._boardPiece.piece.color} ${this._boardPiece.piece.pieceType}` + : `${this.label}`, + ]; + if (this._moveState === "move-start") { + labelParts.push("start of move"); + } + if (this._moveState === "move-target") { + labelParts.push("target square"); + } + this._contentElement.setAttribute("aria-label", labelParts.join(", ")); + this._slotElement.name = this.label; + } + _updateTabIndex() { + if (this.interactive) { + this._contentElement.tabIndex = this.tabbable ? 0 : -1; + } + else { + this._contentElement.removeAttribute("tabindex"); + } + } + _updateMoveStateVisuals() { + this._updateInteractiveCssClass("moveable", this.moveable && !this._moveState); + this._updateInteractiveCssClass("move-start", this._moveState === "move-start"); + this._updateInteractiveCssClass("move-target", this._moveState === "move-target"); + this._contentElement.setAttribute("aria-disabled", (!this._moveState && !this.moveable).toString()); + } + _updateInteractiveCssClass(name, value) { + this._contentElement.classList.toggle(name, this.interactive && value); + } + _updateSquareAfterPieceChange() { + this._contentElement.classList.toggle("has-piece", !!this._boardPiece); + // Always cancel ongoing interactions when piece changes + this._moveState = undefined; + this._updateMoveStateVisuals(); + // Ensure secondary piece is toggled off if piece is changed + this.toggleSecondaryPiece(false); + // Update label + this._updateLabelVisuals(); + } +} + +class Board { + /** + * Creates a set of elements representing chessboard squares, as well + * as managing and displaying pieces rendered on the squares. + */ + constructor(initValues, dispatchEvent, shadowRef) { + Object.defineProperty(this, "_table", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_boardSquares", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_dispatchEvent", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_shadowRef", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_orientation", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_turn", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_interactive", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_position", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_boardState", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_tabbableSquare", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_defaultTabbableSquare", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + /** + * Certain move "finishing" logic is included in `pointerup` (e.g. drags). To + * prevent re-handling this in the `click` handler, we prevent handling of click + * events for a certain period after pointerup. + */ + Object.defineProperty(this, "_preventClickHandling", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + // Event handlers + Object.defineProperty(this, "_pointerDownHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_pointerUpHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_pointerMoveHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_clickHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_focusInHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_keyDownHandler", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + /** + * Duration (in milliseconds) for all animations. + */ + Object.defineProperty(this, "animationDurationMs", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_slotChangeHandler", { + enumerable: true, + configurable: true, + writable: true, + value: (e) => { + if (Board._isSlotElement(e.target) && keyIsSquare(e.target.name)) { + this._getBoardSquare(e.target.name).hasContent = + e.target.assignedElements().length > 0; + } + } + }); + Object.defineProperty(this, "_transitionHandler", { + enumerable: true, + configurable: true, + writable: true, + value: (e) => { + // Delete transition-property style at the end of all transitions + if (e.target && e.target.style !== undefined) { + const style = e.target.style; + style.removeProperty("transition-property"); + } + } + }); + this._boardSquares = new Array(64); + this._orientation = initValues.orientation; + this.animationDurationMs = initValues.animationDurationMs; + this._interactive = false; + this._position = {}; + this._boardState = { id: "default" }; + this._dispatchEvent = dispatchEvent; + this._shadowRef = shadowRef; + // Bottom left corner + this._defaultTabbableSquare = getSquare(56, initValues.orientation); + this._table = makeHTMLElement("table", { + attributes: { + role: "table", + "aria-label": "Chess board", + }, + classes: ["board"], + }); + for (let i = 0; i < 8; i++) { + const row = makeHTMLElement("tr", { + attributes: { role: "row" }, + }); + for (let j = 0; j < 8; j++) { + const idx = 8 * i + j; + const square = getSquare(idx, this.orientation); + this._boardSquares[idx] = new BoardSquare(row, square); + } + this._table.appendChild(row); + } + this._getBoardSquare(this._defaultTabbableSquare).tabbable = true; + this._pointerDownHandler = this._makeEventHandler(this._handlePointerDown); + this._pointerUpHandler = this._makeEventHandler(this._handlePointerUp); + this._pointerMoveHandler = this._makeEventHandler(this._handlePointerMove); + this._clickHandler = this._makeEventHandler(this._handleClick); + this._keyDownHandler = this._makeEventHandler(this._handleKeyDown); + this._focusInHandler = this._makeEventHandler(this._handleFocusIn); + this._table.addEventListener("pointerdown", this._pointerDownHandler); + this._table.addEventListener("click", this._clickHandler); + this._table.addEventListener("focusin", this._focusInHandler); + this._table.addEventListener("keydown", this._keyDownHandler); + this._table.addEventListener("slotchange", this._slotChangeHandler); + this._table.addEventListener("transitionend", this._transitionHandler); + this._table.addEventListener("transitioncancel", this._transitionHandler); + } + /** + * Add event listeners that operate outside shadow DOM (pointer up and move). + * These listeners should be unbound when the element is removed from the DOM. + */ + addGlobalListeners() { + document.addEventListener("pointerup", this._pointerUpHandler); + document.addEventListener("pointermove", this._pointerMoveHandler); + } + /** + * Removes global listeners for pointer up and move. + */ + removeGlobalListeners() { + document.removeEventListener("pointerup", this._pointerUpHandler); + document.removeEventListener("pointermove", this._pointerMoveHandler); + } + /** + * HTML element associated with board. + */ + get element() { + return this._table; + } + /** + * What side's perspective to render squares from (what color appears on + * the bottom as viewed on the screen). + */ + get orientation() { + return this._orientation; + } + set orientation(value) { + this._cancelMove(false); + this._orientation = value; + this._refreshDefaultTabbableSquare(); + for (let i = 0; i < 64; i++) { + const square = getSquare(i, value); + const piece = this._position[square]; + this._boardSquares[i].label = square; + this._boardSquares[i].tabbable = this.tabbableSquare === square; + if (piece) { + this._boardSquares[i].setPiece(piece, this._pieceMoveable(piece)); + } + else { + this._boardSquares[i].clearPiece(); + } + } + // Switch focused square, if any, on orientation change + if (this._focusedSquare) { + this._focusTabbableSquare(); + } + } + /** + * Whether the grid is interactive. This determines the roles and attributes, + * like tabindex, associated with the grid. + */ + get interactive() { + return this._interactive; + } + set interactive(value) { + this._cancelMove(false); + this._interactive = value; + this._blurTabbableSquare(); + this._table.setAttribute("role", value ? "grid" : "table"); + this._boardSquares.forEach((s) => { + s.interactive = value; + }); + this._resetBoardStateAndMoves(); + } + get turn() { + return this._turn; + } + /** + * What side is allowed to move pieces. This may be undefined, in which + * pieces from either side can be moved around. + */ + set turn(value) { + this._cancelMove(false); + this._turn = value; + for (let idx = 0; idx < 64; idx++) { + const square = getSquare(idx, this.orientation); + const piece = this._position[square]; + this._boardSquares[idx].moveable = !piece || this._pieceMoveable(piece); + } + } + /** + * Current `Position` object of board. + */ + get position() { + return this._position; + } + set position(value) { + if (!positionsEqual(this._position, value)) { + this._cancelMove(false); + const diff = calcPositionDiff(this._position, value); + this._position = { ...value }; + diff.moved.forEach(({ oldSquare }) => { + // Remove all copies of moved piece from starting squares, without animation + this._getBoardSquare(oldSquare).clearPiece(); + }); + diff.removed.forEach(({ square }) => { + this._getBoardSquare(square).clearPiece(this.animationDurationMs); + }); + diff.moved.forEach(({ piece, oldSquare, newSquare }) => { + // Render moved piece at location of old square, and animate in to new square + const startingPosition = this._getStartingPositionForMove(oldSquare, newSquare); + this._getBoardSquare(newSquare).setPiece(piece, this._pieceMoveable(piece), { + type: "slide-in", + from: startingPosition, + durationMs: this.animationDurationMs, + }); + }); + diff.added.forEach(({ piece, square }) => { + this._getBoardSquare(square).setPiece(piece, this._pieceMoveable(piece), { + type: "fade-in", + durationMs: this.animationDurationMs, + }); + }); + // Default tabbable square might change with position change + this._refreshDefaultTabbableSquare(); + } + } + /** + * Square that is considered "tabbable", if any. Keyboard navigation + * on the board uses a roving tabindex, which means that only one square is + * "tabbable" at a time (the rest are navigable using up and down keys on + * the keyboard). + */ + get tabbableSquare() { + return this._tabbableSquare || this._defaultTabbableSquare; + } + set tabbableSquare(value) { + if (this.tabbableSquare !== value) { + // Unset previous tabbable square so that tabindex is changed to -1 + this._getBoardSquare(this.tabbableSquare).tabbable = false; + this._getBoardSquare(value).tabbable = true; + this._tabbableSquare = value; + } + } + /** + * Start a move on the board at `square`, optionally with specified targets + * at `targetSquares`. + */ + startMove(square, targetSquares) { + if (this._interactable(square)) { + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: square, + }); + this._startInteraction(square, targetSquares); + } + } + /** + * Cancels in-progress moves, if any. + */ + cancelMove() { + this._cancelMove(false); + } + get _focusedSquare() { + return Board._extractSquareData(this._shadowRef.activeElement); + } + _startInteraction(square, forceTargetSquares) { + const piece = this._position[square]; + if (piece) { + let targetsLimited = false; + const targetSquares = []; + if (forceTargetSquares !== undefined) { + targetsLimited = true; + forceTargetSquares.forEach((s) => { + if (keyIsSquare(s)) { + targetSquares.push(s); + } + }); + } + else { + this._dispatchEvent(new CustomEvent("movestart", { + bubbles: true, + detail: { + from: square, + piece, + setTargets: (squares) => { + targetsLimited = true; + for (const s of squares) { + if (keyIsSquare(s)) { + targetSquares.push(s); + } + } + }, + }, + })); + } + this._getBoardSquare(square).startInteraction(); + this.tabbableSquare = square; + this._boardSquares.forEach((s) => { + if (s.label !== square) { + s.moveTarget = !targetsLimited || targetSquares.includes(s.label); + s.markedTarget = targetsLimited && s.moveTarget; + } + }); + } + } + _finishMove(to, animate) { + if (this._boardState.startSquare) { + const from = this._boardState.startSquare; + const piece = this._position[from]; + if (piece !== undefined) { + const endEvent = new CustomEvent("moveend", { + bubbles: true, + cancelable: true, + detail: { from, to, piece }, + }); + this._dispatchEvent(endEvent); + if (endEvent.defaultPrevented) { + return false; + } + const startingPosition = this._getStartingPositionForMove(from, to); + this._getBoardSquare(from).clearPiece(); + this._getBoardSquare(to).setPiece(piece, this._pieceMoveable(piece), + // Animate transition only when piece is displaced to a specific location + animate + ? { + type: "slide-in", + from: startingPosition, + durationMs: this.animationDurationMs, + } + : undefined); + // Tabbable square always updates to target square + this.tabbableSquare = to; + this._position[to] = this._position[from]; + delete this._position[from]; + const finishedEvent = new CustomEvent("movefinished", { + bubbles: true, + detail: { from, to, piece }, + }); + if (animate) { + this._getBoardSquare(to).boardPiece?.animationFinished?.then(() => { + this._dispatchEvent(finishedEvent); + }); + } + else { + this._dispatchEvent(finishedEvent); + } + } + this._resetBoardStateAndMoves(); + return true; + } + return false; + } + _userCancelMove(animate) { + if (this._boardState.startSquare) { + const e = new CustomEvent("movecancel", { + bubbles: true, + cancelable: true, + detail: { + from: this._boardState.startSquare, + piece: this._position[this._boardState.startSquare], + }, + }); + this._dispatchEvent(e); + if (!e.defaultPrevented) { + this._cancelMove(animate); + return true; + } + } + return false; + } + _cancelMove(animate) { + if (this._boardState.startSquare) { + const square = this._getBoardSquare(this._boardState.startSquare); + square.cancelInteraction(animate ? this.animationDurationMs : undefined); + } + this._resetBoardStateAndMoves(); + } + _focusTabbableSquare() { + if (this.tabbableSquare) { + this._getBoardSquare(this.tabbableSquare).focus(); + } + } + _blurTabbableSquare() { + if (this.tabbableSquare) { + this._getBoardSquare(this.tabbableSquare).blur(); + } + } + _resetBoardStateAndMoves() { + this._boardSquares.forEach((s) => { + s.removeMoveState(); + s.markedTarget = false; + }); + this._setBoardState({ + id: this.interactive ? "awaiting-input" : "default", + }); + } + _pieceMoveable(piece) { + return !this.turn || piece.color === this.turn; + } + _interactable(square) { + const piece = this._position[square]; + return !!piece && this._pieceMoveable(piece); + } + _isValidMove(from, to) { + return from !== to && this._getBoardSquare(to).moveTarget; + } + _getBoardSquare(square) { + return this._boardSquares[getVisualIndex(square, this.orientation)]; + } + /** + * Compute an explicit position to apply to a piece that is being moved + * from `from` to `to`. This can either be the explicit piece position, + * if already set, for that piece, or it is computed as the offset or + * difference in rows and columns between the two squares. + */ + _getStartingPositionForMove(from, to) { + const [fromRow, fromCol] = getVisualRowColumn(from, this.orientation); + const [toRow, toCol] = getVisualRowColumn(to, this.orientation); + return (this._getBoardSquare(from).explicitPiecePosition || { + type: "squareOffset", + deltaRows: fromRow - toRow, + deltaCols: fromCol - toCol, + }); + } + /** + * When no tabbable square has been explicitly set (usually, when user has + * not yet tabbed into or interacted with the board, we want to calculate + * the tabbable square dynamically. It is either: + * - the first occupied square from the player's orientation (i.e. from + * bottom left of board), or + * - the bottom left square of the board. + */ + _refreshDefaultTabbableSquare() { + const oldDefaultSquare = this._defaultTabbableSquare; + let pieceFound = false; + if (Object.keys(this._position).length > 0) { + for (let row = 7; row >= 0 && !pieceFound; row--) { + for (let col = 0; col <= 7 && !pieceFound; col++) { + const square = getSquare(8 * row + col, this.orientation); + if (this._position[square]) { + this._defaultTabbableSquare = square; + pieceFound = true; + } + } + } + } + if (!pieceFound) { + this._defaultTabbableSquare = getSquare(56, this.orientation); + } + // If tabbable square is set to default and has changed, then + // update the two squares accordingly. + if (this._tabbableSquare === undefined && + oldDefaultSquare !== this._defaultTabbableSquare) { + this._getBoardSquare(oldDefaultSquare).tabbable = false; + this._getBoardSquare(this._defaultTabbableSquare).tabbable = true; + } + } + _setBoardState(state) { + const oldState = this._boardState; + this._boardState = state; + if (this._boardState.id !== oldState.id) { + this._table.classList.toggle("dragging", this._isDragState()); + } + if (this._boardState.highlightedSquare !== oldState.highlightedSquare) { + if (oldState.highlightedSquare) { + this._getBoardSquare(oldState.highlightedSquare).hover = false; + } + if (this._boardState.highlightedSquare) { + this._getBoardSquare(this._boardState.highlightedSquare).hover = true; + } + } + } + _handlePointerDown(square, e) { + // We will control focus entirely ourselves + e.preventDefault(); + // Primary clicks only + if (e.button !== 0) { + return; + } + switch (this._boardState.id) { + case "awaiting-input": + if (square && this._interactable(square)) { + this._setBoardState({ + id: "touching-first-square", + startSquare: square, + touchStartX: e.clientX, + touchStartY: e.clientY, + }); + this._startInteraction(square); + this._getBoardSquare(square).toggleSecondaryPiece(true); + } + break; + case "awaiting-second-touch": + case "moving-piece-kb": + if (this._boardState.startSquare === square) { + // Second pointerdown on the same square *may* be a cancel, but could + // also be a misclick/readjustment in order to begin dragging. Wait + // till corresponding pointerup event in order to cancel. + this._setBoardState({ + id: "canceling-second-touch", + startSquare: square, + touchStartX: e.clientX, + touchStartY: e.clientY, + }); + // Show secondary piece while pointer is down + this._getBoardSquare(square).toggleSecondaryPiece(true); + } + else if (square) { + this._setBoardState({ + id: "touching-second-square", + startSquare: this._boardState.startSquare, + }); + } + break; + case "dragging": + case "dragging-outside": + case "canceling-second-touch": + case "touching-first-square": + case "touching-second-square": + // Noop: pointer is already down while dragging or touching square + break; + case "default": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + } + _handlePointerUp(square) { + let newFocusedSquare = square; + switch (this._boardState.id) { + case "touching-first-square": + this._getBoardSquare(this._boardState.startSquare).toggleSecondaryPiece(false); + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: this._boardState.startSquare, + }); + newFocusedSquare = this._boardState.startSquare; + break; + case "canceling-second-touch": + // User cancels by clicking on the same square. + if (!this._userCancelMove(false)) { + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: this._boardState.startSquare, + }); + } + newFocusedSquare = this._boardState.startSquare; + break; + case "dragging": + case "dragging-outside": + case "touching-second-square": + { + this._getBoardSquare(this._boardState.startSquare).toggleSecondaryPiece(false); + let done = false; + if (square && + this._isValidMove(this._boardState.startSquare, square)) { + done = this._finishMove(square, !this._isDragState()); + if (!done) { + newFocusedSquare = this._boardState.startSquare; + } + } + else { + newFocusedSquare = this._boardState.startSquare; + done = this._userCancelMove(square !== this._boardState.startSquare); + } + if (!done) { + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: this._boardState.startSquare, + }); + this._getBoardSquare(this._boardState.startSquare).resetPiecePosition(this.animationDurationMs); + } + } + break; + case "awaiting-input": + case "moving-piece-kb": + case "awaiting-second-touch": + // noop: Either we are in a non-mouse state or we are delegating to click + break; + case "default": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + // If board currently has focus, move focus to newly clicked square. + if (this._focusedSquare && newFocusedSquare) { + this.tabbableSquare = newFocusedSquare; + this._focusTabbableSquare(); + } + // Prevent click handling for a certain duration + this._preventClickHandling = true; + setTimeout(() => { + this._preventClickHandling = false; + }, Board.POINTERUP_CLICK_PREVENT_DURATION_MS); + } + _handlePointerMove(square, e) { + switch (this._boardState.id) { + case "canceling-second-touch": + case "touching-first-square": + { + const delta = Math.sqrt((e.clientX - this._boardState.touchStartX) ** 2 + + (e.clientY - this._boardState.touchStartY) ** 2); + const squareWidth = this._getBoardSquare(this._boardState.startSquare).width; + const threshold = Math.max(Board.DRAG_THRESHOLD_MIN_PIXELS, Board.DRAG_THRESHOLD_SQUARE_WIDTH_FRACTION * squareWidth); + // Consider a "dragging" action to be when we have moved the pointer a sufficient + // threshold, or we are now in a different square from where we started. + if (delta > threshold || square !== this._boardState.startSquare) { + this._getBoardSquare(this._boardState.startSquare).displacePiece(e.clientX, e.clientY); + if (square) { + this._setBoardState({ + id: "dragging", + startSquare: this._boardState.startSquare, + highlightedSquare: this._isValidMove(this._boardState.startSquare, square) + ? square + : undefined, + }); + } + else { + this._setBoardState({ + id: "dragging-outside", + startSquare: this._boardState.startSquare, + }); + } + } + } + break; + case "dragging": + case "dragging-outside": + this._getBoardSquare(this._boardState.startSquare).displacePiece(e.clientX, e.clientY); + if (square && square !== this._boardState.highlightedSquare) { + this._setBoardState({ + id: "dragging", + startSquare: this._boardState.startSquare, + highlightedSquare: this._isValidMove(this._boardState.startSquare, square) + ? square + : undefined, + }); + } + else if (!square && this._boardState.id !== "dragging-outside") { + this._setBoardState({ + id: "dragging-outside", + startSquare: this._boardState.startSquare, + }); + } + break; + case "awaiting-input": + case "awaiting-second-touch": + case "default": + case "moving-piece-kb": + case "touching-second-square": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + } + _handleClick(square) { + if (this._preventClickHandling) { + return; + } + switch (this._boardState.id) { + case "awaiting-input": + if (square && this._interactable(square)) { + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: square, + }); + this._startInteraction(square); + } + break; + case "awaiting-second-touch": + case "moving-piece-kb": + { + const done = square && this._isValidMove(this._boardState.startSquare, square) + ? this._finishMove(square, true) + : this._userCancelMove(square !== this._boardState.startSquare); + if (!done) { + this._setBoardState({ + id: "awaiting-second-touch", + startSquare: this._boardState.startSquare, + }); + this._getBoardSquare(this._boardState.startSquare).resetPiecePosition(this.animationDurationMs); + } + } + break; + case "touching-first-square": + case "touching-second-square": + case "canceling-second-touch": + case "dragging": + case "dragging-outside": + case "default": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + // If board currently has focus, move focus to newly clicked square. + if (this._focusedSquare && square) { + this.tabbableSquare = square; + this._focusTabbableSquare(); + } + } + _handleFocusIn(square) { + if (square) { + if ( + // Some browsers (Safari) focus on board squares that are not tabbable + // (tabindex = -1). If that happens, update tabbable square manually. + square !== this.tabbableSquare || + // Assign tabbable square if none is explicitly assigned yet. + this._tabbableSquare === undefined) { + this.tabbableSquare = square; + } + } + } + _handleKeyDown(square, e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + switch (this._boardState.id) { + case "awaiting-input": + if (square && this._interactable(square)) { + this._setBoardState({ + id: "moving-piece-kb", + startSquare: square, + highlightedSquare: undefined, + }); + this._startInteraction(square); + } + break; + case "moving-piece-kb": + case "awaiting-second-touch": + // Only move if enter was inside squares area and if start + // and end square are not the same. + if (square && + this._isValidMove(this._boardState.startSquare, square)) { + this._finishMove(square, true); + } + else { + this._userCancelMove(false); + } + break; + case "dragging": + case "dragging-outside": + case "touching-first-square": + case "touching-second-square": + case "canceling-second-touch": + // Noop: don't handle keypresses in active pointer states + break; + case "default": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + } + else { + const currentIdx = getVisualIndex(this.tabbableSquare, this.orientation); + const currentRow = currentIdx >> 3; + const currentCol = currentIdx & 0x7; + let newIdx = currentIdx; + let keyHandled = false; + switch (e.key) { + case "ArrowRight": + case "Right": + newIdx = 8 * currentRow + Math.min(7, currentCol + 1); + keyHandled = true; + break; + case "ArrowLeft": + case "Left": + newIdx = 8 * currentRow + Math.max(0, currentCol - 1); + keyHandled = true; + break; + case "ArrowDown": + case "Down": + newIdx = 8 * Math.min(7, currentRow + 1) + currentCol; + keyHandled = true; + break; + case "ArrowUp": + case "Up": + newIdx = 8 * Math.max(0, currentRow - 1) + currentCol; + keyHandled = true; + break; + case "Home": + newIdx = e.ctrlKey ? 0 : 8 * currentRow; + keyHandled = true; + break; + case "End": + newIdx = e.ctrlKey ? 63 : 8 * currentRow + 7; + keyHandled = true; + break; + case "PageUp": + newIdx = currentCol; + keyHandled = true; + break; + case "PageDown": + newIdx = 56 + currentCol; + keyHandled = true; + break; + } + if (keyHandled) { + // Prevent native browser scrolling via any of the + // navigation keys since the focus below will auto-scroll + e.preventDefault(); + } + if (newIdx !== currentIdx) { + this.tabbableSquare = getSquare(newIdx, this.orientation); + this._focusTabbableSquare(); + // If we are currently in a non-keyboard friendly state, we should + // still transition to one since we started keyboard navigation. + switch (this._boardState.id) { + case "moving-piece-kb": + case "awaiting-second-touch": + this._setBoardState({ + id: "moving-piece-kb", + startSquare: this._boardState.startSquare, + highlightedSquare: this._boardState.startSquare !== this.tabbableSquare + ? this._tabbableSquare + : undefined, + }); + break; + case "awaiting-input": + case "touching-first-square": + case "touching-second-square": + case "canceling-second-touch": + case "dragging": + case "dragging-outside": + break; + case "default": + break; + // istanbul ignore next + default: + assertUnreachable(this._boardState); + } + } + } + } + /** + * Convenience wrapper to make pointer, blur, or keyboard event handler for + * square elements. Attempts to extract square label from the element in + * question, then passes square label and current event to `callback`. + */ + _makeEventHandler(callback) { + const boundCallback = callback.bind(this); + return (e) => { + // For mouse events, use client X and Y location to find target reliably. + const square = Board._isMouseEvent(e) + ? this._shadowRef + .elementsFromPoint(e.clientX, e.clientY) + .map((e) => Board._extractSquareData(e)) + .find((e) => !!e) + : Board._extractSquareData(e.target); + boundCallback(square, e); + }; + } + _isDragState() { + return ["dragging", "dragging-outside"].includes(this._boardState.id); + } + static _extractSquareData(target) { + if (!!target && !!target.dataset) { + const dataset = target.dataset; + return keyIsSquare(dataset.square) ? dataset.square : undefined; + } + return undefined; + } + static _isMouseEvent(e) { + return e.clientX !== undefined; + } + static _isSlotElement(e) { + return !!e && e.assignedElements !== undefined; + } +} +/** + * Fraction of square width that pointer must be moved to be + * considered a "drag" action. + */ +Object.defineProperty(Board, "DRAG_THRESHOLD_SQUARE_WIDTH_FRACTION", { + enumerable: true, + configurable: true, + writable: true, + value: 0.1 +}); +/** + * Minimum number of pixels to enable dragging. + */ +Object.defineProperty(Board, "DRAG_THRESHOLD_MIN_PIXELS", { + enumerable: true, + configurable: true, + writable: true, + value: 2 +}); +/** + * Amount of time (in ms) to suppress click handling after a pointerup event. + */ +Object.defineProperty(Board, "POINTERUP_CLICK_PREVENT_DURATION_MS", { + enumerable: true, + configurable: true, + writable: true, + value: 250 +}); + +var css_248z = ":host{--square-color-dark:#4c946a;--square-color-light:#e0ddcc;--square-color-dark-hover:#1cc45f;--square-color-light-hover:#fde968;--square-color-dark-active:#19c257;--square-color-light-active:#fadd4c;--outline-color-dark-active:rgba(33,237,94,.95);--outline-color-light-active:hsla(66,97%,72%,.95);--outline-color-focus:rgba(248,140,32,.9);--outline-blur-radius:3px;--outline-spread-radius:4px;--coords-font-size:0.7rem;--coords-font-family:sans-serif;--outer-gutter-width:4%;--inner-border-width:1px;--coords-inside-coord-padding-left:0.5%;--coords-inside-coord-padding-right:0.5%;--move-target-marker-color-dark-square:rgba(8,38,20,.9);--move-target-marker-color-light-square:rgba(8,38,20,.9);--move-target-marker-radius:24%;--move-target-marker-radius-occupied:82%;--ghost-piece-opacity:0.35;--piece-drag-z-index:9999;--piece-drag-coarse-scale:2.4;--piece-padding:3%;--arrow-color-primary:rgba(255,170,0,.8);--arrow-color-secondary:rgba(248,85,63,.8);display:block}:host([hidden]){display:none}.board{border:var(--inner-border-width) solid var(--inner-border-color,var(--square-color-dark));border-collapse:collapse;box-sizing:border-box;table-layout:fixed;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.board>tr>td{padding:12.5% 0 0;position:relative}[data-square]{background-color:var(--p-square-color);bottom:0;color:var(--p-label-color);font-family:var(--coords-font-family);font-size:var(--coords-font-size);height:100%;left:0;position:absolute;right:0;top:0;width:100%}[data-square]:focus{box-shadow:inset 0 0 var(--outline-blur-radius) var(--outline-spread-radius) var(--outline-color-focus);outline:none}[data-square].marked-target{background:radial-gradient(var(--p-move-target-marker-color) var(--move-target-marker-radius),var(--p-square-color) calc(var(--move-target-marker-radius) + 1px))}[data-square].has-content.marked-target,[data-square].has-piece.marked-target{background:radial-gradient(var(--p-square-color) var(--move-target-marker-radius-occupied),var(--p-move-target-marker-color) calc(var(--move-target-marker-radius-occupied) + 1px))}[data-square].move-start{--p-square-color:var(--p-square-color-active)}[data-square].move-start:not(:focus){box-shadow:inset 0 0 var(--outline-blur-radius) var(--outline-spread-radius) var(--p-outline-color-active)}@media (hover:hover){[data-square]:is(.moveable,.move-target):hover{--p-square-color:var(--p-square-color-hover)}}[data-square].hover{--p-square-color:var(--p-square-color-hover)}table:not(.dragging) [data-square]:is(.moveable,.move-start,.move-target){cursor:pointer}table.dragging{cursor:grab}.wrapper{position:relative}.coords{display:none;font-family:var(--coords-font-family);font-size:var(--coords-font-size);pointer-events:none;position:absolute;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.coord{box-sizing:border-box;display:flex}.coords.file>.coord{width:12.5%}.coords.rank{flex-direction:column}.coords.rank>.coord{height:12.5%}.wrapper.outside{background-color:var(--square-color-light);padding:var(--outer-gutter-width)}.wrapper.outside>.coords{color:var(--square-color-dark);display:flex}.wrapper.outside>.coords>.coord{align-items:center;justify-content:center}.wrapper.outside>.coords.file{bottom:0;height:var(--outer-gutter-width);left:var(--outer-gutter-width);right:var(--outer-gutter-width);width:calc(100% - var(--outer-gutter-width)*2)}.wrapper.outside>.coords.rank{bottom:var(--outer-gutter-width);height:calc(100% - var(--outer-gutter-width)*2);left:0;top:var(--outer-gutter-width);width:var(--outer-gutter-width)}.wrapper.inside>.coords{bottom:0;display:flex;height:100%;left:0;right:0;top:0;width:100%}.wrapper.inside>.coords>.coord.light{color:var(--square-color-dark)}.wrapper.inside>.coords>.coord.dark{color:var(--square-color-light)}.wrapper.inside>.coords.file>.coord{align-items:flex-end;justify-content:flex-end;padding-right:var(--coords-inside-coord-padding-right)}.wrapper.inside>.coords.rank>.coord{padding-left:var(--coords-inside-coord-padding-left)}[data-square-color=dark]{--p-square-color:var(--square-color-dark);--p-label-color:var(--square-color-light);--p-square-color-hover:var(--square-color-dark-hover);--p-move-target-marker-color:var(--move-target-marker-color-dark-square);--p-square-color-active:var(--square-color-dark-active);--p-outline-color-active:var(--outline-color-dark-active)}[data-square-color=light]{--p-square-color:var(--square-color-light);--p-label-color:var(--square-color-dark);--p-square-color-hover:var(--square-color-light-hover);--p-move-target-marker-color:var(--move-target-marker-color-light-square);--p-square-color-active:var(--square-color-light-active);--p-outline-color-active:var(--outline-color-light-active)}[data-square] .piece,[data-square] .slot{bottom:0;height:100%;left:0;pointer-events:none;position:absolute;right:0;top:0;width:100%}[data-square] .piece{background-origin:content-box;background-repeat:no-repeat;background-size:cover;box-sizing:border-box;padding:var(--piece-padding);z-index:10}[data-square] .piece.moving{z-index:15}[data-square] .piece.secondary{opacity:var(--ghost-piece-opacity);z-index:5}[data-square].move-start .piece:not(.secondary){z-index:var(--piece-drag-z-index)}@media (pointer:coarse){[data-square] .piece{--p-piece-drag-scale:var(--piece-drag-coarse-scale)}}.bb{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-rule:evenodd;fill-opacity:1;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cg style='fill:%23000;stroke:%23000;stroke-linecap:butt'%3E%3Cpath d='M9 36.6c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.35.49-2.32.47-3-.5 1.35-1.46 3-2 3-2z'/%3E%3Cpath d='M15 32.6c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z'/%3E%3Cpath d='M25 8.6a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z'/%3E%3C/g%3E%3Cpath d='M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5' style='fill:none;stroke:%23fff;stroke-linejoin:miter' transform='translate(0 .6)'/%3E%3C/g%3E%3C/svg%3E\")}.bk{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22.5 11.63V6' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5' style='fill:%23000;fill-opacity:1;stroke-linecap:butt;stroke-linejoin:miter'/%3E%3Cpath d='M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7' style='fill:%23000;stroke:%23000'/%3E%3Cpath d='M20 8h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M32 29.5s8.5-4 6.03-9.65C34.15 14 25 18 22.5 24.5v2.1-2.1C20 18 10.85 14 6.97 19.85 4.5 25.5 13 29.5 13 29.5' style='fill:none;stroke:%23fff'/%3E%3Cpath d='M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0' style='fill:none;stroke:%23fff'/%3E%3C/g%3E%3C/svg%3E\")}.bn{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0z' style='fill:%23fff;stroke:%23fff' transform='translate(0 .3)'/%3E%3Cpath d='M15 15.5a.5 1.5 0 1 1-1 0 .5 1.5 0 1 1 1 0z' transform='rotate(30 13.94 15.65)' style='fill:%23fff;stroke:%23fff'/%3E%3Cpath d='m24.55 10.4-.45 1.45.5.15c3.15 1 5.65 2.49 7.9 6.75S35.75 29.06 35.25 39l-.05.5h2.25l.05-.5c.5-10.06-.88-16.85-3.25-21.34-2.37-4.49-5.79-6.64-9.19-7.16l-.51-.1z' style='fill:%23fff;stroke:none' transform='translate(0 .3)' stroke='none'/%3E%3C/g%3E%3C/svg%3E\")}.bp{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z' style='opacity:1;fill:%23000;fill-opacity:1;fill-rule:nonzero;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'/%3E%3C/svg%3E\")}.bq{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23000;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round'%3E%3Cpath d='M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z' style='stroke-linecap:butt;fill:%23000'/%3E%3Cpath d='M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1 2.5-1 2.5-1.5 1.5 0 2.5 0 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z'/%3E%3Cpath d='M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0'/%3E%3Ccircle cx='6' cy='12' r='2'/%3E%3Ccircle cx='14' cy='9' r='2'/%3E%3Ccircle cx='22.5' cy='8' r='2'/%3E%3Ccircle cx='31' cy='9' r='2'/%3E%3Ccircle cx='39' cy='12' r='2'/%3E%3Cpath d='M11 38.5a35 35 1 0 0 23 0' style='fill:none;stroke:%23000;stroke-linecap:butt'/%3E%3Cpath d='M11 29a35 35 1 0 1 23 0m-21.5 2.5h20m-21 3a35 35 1 0 0 22 0m-23 3a35 35 1 0 0 24 0' style='fill:none;stroke:%23fff'/%3E%3C/g%3E%3C/svg%3E\")}.br{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:%23000;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M9 39h27v-3H9v3zm3.5-7 1.5-2.5h17l1.5 2.5h-20zm-.5 4v-4h21v4H12z' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='M14 29.5v-13h17v13H14z' style='stroke-linecap:butt;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3Cpath d='M14 16.5 11 14h23l-3 2.5H14zM11 14V9h4v2h5V9h5v2h5V9h4v5H11z' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='M12 35.5h21m-20-4h19m-18-2h17m-17-13h17M11 14h23' style='fill:none;stroke:%23fff;stroke-width:1;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3C/g%3E%3C/svg%3E\")}.wb{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-rule:evenodd;fill-opacity:1;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1' transform='translate(0 .6)'%3E%26gt;%3Cg style='fill:%23fff;stroke:%23000;stroke-linecap:butt'%3E%3Cpath d='M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.35.49-2.32.47-3-.5 1.35-1.46 3-2 3-2z'/%3E%3Cpath d='M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z'/%3E%3Cpath d='M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z'/%3E%3C/g%3E%3Cpath d='M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3C/g%3E%3C/svg%3E\")}.wk{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22.5 11.63V6M20 8h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5' style='fill:%23fff;stroke:%23000;stroke-linecap:butt;stroke-linejoin:miter'/%3E%3Cpath d='M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7' style='fill:%23fff;stroke:%23000'/%3E%3Cpath d='M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0' style='fill:none;stroke:%23000'/%3E%3C/g%3E%3C/svg%3E\")}.wn{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21' style='fill:%23fff;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3' style='fill:%23fff;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0z' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M15 15.5a.5 1.5 0 1 1-1 0 .5 1.5 0 1 1 1 0z' transform='rotate(30 13.94 15.65)' style='fill:%23000;stroke:%23000'/%3E%3C/g%3E%3C/svg%3E\")}.wp{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z' style='opacity:1;fill:%23fff;fill-opacity:1;fill-rule:nonzero;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'/%3E%3C/svg%3E\")}.wq{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23fff;stroke:%23000;stroke-width:1.5;stroke-linejoin:round'%3E%3Cpath d='M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z'/%3E%3Cpath d='M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1 2.5-1 2.5-1.5 1.5 0 2.5 0 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z'/%3E%3Cpath d='M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0' style='fill:none'/%3E%3Ccircle cx='6' cy='12' r='2'/%3E%3Ccircle cx='14' cy='9' r='2'/%3E%3Ccircle cx='22.5' cy='8' r='2'/%3E%3Ccircle cx='31' cy='9' r='2'/%3E%3Ccircle cx='39' cy='12' r='2'/%3E%3C/g%3E%3C/svg%3E\")}.wr{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:%23fff;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M9 39h27v-3H9v3zm3-3v-4h21v4H12zm-1-22V9h4v2h5V9h5v2h5V9h4v5' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='m34 14.3-3 3H14l-3-3'/%3E%3Cpath d='M31 17v12.5H14V17' style='stroke-linecap:butt;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3Cpath d='m31 29.8 1.5 2.5h-20l1.5-2.5'/%3E%3Cpath d='M11 14h23' style='fill:none;stroke:%23000;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3C/g%3E%3C/svg%3E\")}.arrows{border:var(--inner-border-width) solid transparent;bottom:0;box-sizing:border-box;height:100%;left:0;pointer-events:none;position:absolute;right:0;top:0;touch-action:none;width:100%;z-index:20}.arrow-primary{color:var(--arrow-color-primary)}.arrow-secondary{color:var(--arrow-color-secondary)}"; + +const COORDINATES_PLACEMENTS = ["inside", "outside", "hidden"]; +class Coordinates { + constructor(props) { + Object.defineProperty(this, "element", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_coordElements", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_orientation", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_direction", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.element = makeHTMLElement("div", { + attributes: { + role: "presentation", + "aria-hidden": "true", + }, + classes: ["coords", props.direction], + }); + this._direction = props.direction; + this._orientation = props.orientation; + this._coordElements = new Array(8); + const evenSquareColor = props.direction === "file" ? "dark" : "light"; + const oddSquareColor = props.direction === "file" ? "light" : "dark"; + for (let i = 0; i < 8; i++) { + const color = i % 2 === 0 ? evenSquareColor : oddSquareColor; + const textElement = makeHTMLElement("div", { classes: ["coord", color] }); + this._coordElements[i] = textElement; + this.element.appendChild(textElement); + } + this._updateCoordsText(); + } + /** + * Orientation of the board; this determines labels for ranks and files. + */ + get orientation() { + return this._orientation; + } + set orientation(value) { + this._orientation = value; + this._updateCoordsText(); + } + _updateCoordsText() { + for (let i = 0; i < 8; i++) { + if (this._direction === "file") { + this._coordElements[i].textContent = String.fromCharCode("a".charCodeAt(0) + (this.orientation === "white" ? i : 7 - i)); + } + else { + this._coordElements[i].textContent = `${this.orientation === "white" ? 8 - i : i + 1}`; + } + } + } +} +/** + * Type guard for string values that need to conform to a + * `CoordinatesPlacement` definition. + */ +function isCoordinatesPlacement(value) { + return COORDINATES_PLACEMENTS.includes(value); +} + +class Arrows { + constructor(orientation) { + Object.defineProperty(this, "element", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_defs", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_group", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_orientation", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_arrows", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_arrowElements", { + enumerable: true, + configurable: true, + writable: true, + value: new Map() + }); + Object.defineProperty(this, "_markerElements", { + enumerable: true, + configurable: true, + writable: true, + value: new Map() + }); + this.element = makeSVGElement("svg", { + attributes: { + viewBox: "0 0 80 80", + }, + classes: ["arrows"], + }); + this._orientation = orientation; + this._defs = makeSVGElement("defs"); + this.element.appendChild(this._defs); + this._group = makeSVGElement("g"); + this.element.appendChild(this._group); + } + get arrows() { + return this._arrows; + } + set arrows(arrows) { + const validArrows = arrows?.filter((a) => a.from !== a.to); + // Update brushes + const brushes = validArrows + ? new Set(validArrows.map((a) => Arrows._escapedBrushName(a.brush))) + : new Set(); + const oldBrushes = new Set(this._markerElements.keys()); + oldBrushes.forEach((key) => { + if (!brushes.has(key)) { + const marker = this._markerElements.get(key); + if (marker) { + this._defs.removeChild(marker); + this._markerElements.delete(key); + } + } + }); + brushes.forEach((key) => { + if (!oldBrushes.has(key)) { + const marker = Arrows._makeMarker(key); + this._defs.appendChild(marker); + this._markerElements.set(key, marker); + } + }); + // Update arrows + const oldHashes = new Set(this._arrowElements.keys()); + const newHashes = validArrows + ? new Set(validArrows.map((arrow) => Arrows._arrowHash(arrow))) + : new Set(); + oldHashes.forEach((hash) => { + if (!newHashes.has(hash)) { + const element = this._arrowElements.get(hash); + if (element) { + this._group.removeChild(element); + this._arrowElements.delete(hash); + } + } + }); + validArrows?.forEach((arrow) => { + const hash = Arrows._arrowHash(arrow); + if (!this._arrowElements.has(hash)) { + const element = this._makeArrow(arrow); + this._arrowElements.set(hash, element); + this._group.appendChild(element); + } + }); + this._arrows = validArrows ? [...validArrows] : undefined; + } + /** + * Orientation of the board; this determines direction to draw arrows. + */ + get orientation() { + return this._orientation; + } + set orientation(value) { + if (value !== this._orientation) { + this._orientation = value; + this._arrows?.forEach((arrow) => { + const hash = Arrows._arrowHash(arrow); + const element = this._arrowElements.get(hash); + if (element) { + this._group.removeChild(element); + } + const newElement = this._makeArrow(arrow); + this._group.appendChild(newElement); + this._arrowElements.set(hash, newElement); + }); + } + } + _makeArrow(arrow) { + const strokeWidth = Arrows._getSvgStrokeWidth(arrow.weight || Arrows._DEFAULT_ARROW_WEIGHT); + const fromRowCol = getVisualRowColumn(arrow.from, this.orientation); + const toRowCol = getVisualRowColumn(arrow.to, this.orientation); + const coords = { + x1: fromRowCol[1] * 10 + 5, + y1: fromRowCol[0] * 10 + 5, + x2: toRowCol[1] * 10 + 5, + y2: toRowCol[0] * 10 + 5, + }; + const endOffset = Arrows._computeXYProjections(strokeWidth * Arrows._ARROW_LENGTH, coords); + const startOffset = Arrows._computeXYProjections(Arrows._ARROW_START_MARGIN, coords); + const escapedBrushName = Arrows._escapedBrushName(arrow.brush || Arrows._DEFAULT_BRUSH_NAME); + const className = Arrows._makeArrowClass(escapedBrushName); + const line = makeSVGElement("line", { + attributes: { + x1: `${coords.x1 + startOffset.x}`, + y1: `${coords.y1 + startOffset.y}`, + x2: `${coords.x2 - endOffset.x}`, + y2: `${coords.y2 - endOffset.y}`, + stroke: "currentColor", + "stroke-width": `${strokeWidth}`, + "marker-end": `url(#${Arrows._makeArrowHeadId(escapedBrushName)})`, + part: className, + }, + classes: [className], + }); + return line; + } + static _makeMarker(escapedBrushName) { + const marker = makeSVGElement("marker", { + attributes: { + id: Arrows._makeArrowHeadId(escapedBrushName), + refX: "0", + refY: `${Arrows._ARROW_WIDTH / 2}`, + orient: "auto", + markerWidth: `${Arrows._ARROW_LENGTH}`, + markerHeight: `${Arrows._ARROW_WIDTH}`, + }, + }); + const className = Arrows._makeArrowClass(escapedBrushName); + const polygon = makeSVGElement("polygon", { + attributes: { + fill: "currentColor", + points: `0,0 ${Arrows._ARROW_LENGTH},${Arrows._ARROW_WIDTH / 2} 0,${Arrows._ARROW_WIDTH}`, + part: className, + }, + classes: [className], + }); + marker.appendChild(polygon); + return marker; + } + static _getSvgStrokeWidth(weight) { + switch (weight) { + case "bold": + return 2.5; + case "light": + return 1; + case "normal": + default: + return 1.8; + } + } + static _escapedBrushName(brush) { + return CSS.escape(brush || Arrows._DEFAULT_BRUSH_NAME); + } + static _makeArrowHeadId(escapedBrushName) { + return `arrowhead-${escapedBrushName}`; + } + static _makeArrowClass(escapedBrushName) { + return `arrow-${escapedBrushName}`; + } + static _computeXYProjections(length, arrow) { + const angle = Math.atan2(arrow.y2 - arrow.y1, arrow.x2 - arrow.x1); + return { x: length * Math.cos(angle), y: length * Math.sin(angle) }; + } + static _arrowHash(arrow) { + return `${arrow.from}_${arrow.to}_${arrow.brush || Arrows._DEFAULT_BRUSH_NAME}_${arrow.weight || Arrows._DEFAULT_ARROW_WEIGHT}`; + } +} +/** + * Length of arrow from base to tip, in terms of line "stroke width" units. + */ +Object.defineProperty(Arrows, "_ARROW_LENGTH", { + enumerable: true, + configurable: true, + writable: true, + value: 2.4 +}); +/** + * Width of arrow base, in terms of line "stroke width" units. + */ +Object.defineProperty(Arrows, "_ARROW_WIDTH", { + enumerable: true, + configurable: true, + writable: true, + value: 2 +}); +/** + * Margin applied at start of line, along direction of arrow. In CSS viewport units. + */ +Object.defineProperty(Arrows, "_ARROW_START_MARGIN", { + enumerable: true, + configurable: true, + writable: true, + value: 2.7 +}); +/** + * Default brush name when none is specified for an arrow. + */ +Object.defineProperty(Arrows, "_DEFAULT_BRUSH_NAME", { + enumerable: true, + configurable: true, + writable: true, + value: "primary" +}); +/** + * Default arrow weight when none is specified. + */ +Object.defineProperty(Arrows, "_DEFAULT_ARROW_WEIGHT", { + enumerable: true, + configurable: true, + writable: true, + value: "normal" +}); + +/** + * A component that displays a chess board, with optional interactivity. Allows + * click, drag and keyboard-based moves. + * + * @fires movestart - Fired when the user initiates a move by clicking, dragging or + * via the keyboard. + * + * The event has a `detail` object with the `from` and + * `piece` values for the move. It also has a function, `setTargets(squares)`, + * that the caller can invoke with an array of square labels. This limits the + * set of targets that the piece can be moved to. Note that calling this + * function with an empty list will still allow the piece to be dragged around, + * but no square will accept the piece and thus it will always return to the + * starting square. + * + * @fires moveend - Fired when user is completing a move. This move can be prevented + * from completing by calling `preventDefault()` on the event. If that is called, + * the move itself remains in progress. The event has a `detail` object with `from` + * and `to` set to the square labels of the move, and `piece` containing information + * about the piece that was moved. + * + * @fires movefinished - Fired after a move is completed _and_ animations are resolved. + * The event has a `detail` object with `from` and `to` set to the square labels + * of the move, and `piece` containing information about the piece that was moved. + * + * The `movefinished` event is the best time to update board position in response to + * a move. For example, after a king is moved for castling, the rook can be subsequently + * moved by updating the board position in `movefinished` by setting the `position` + * property. + * + * @fires movecancel - Fired as a move is being canceled by the user. The event + * is *itself* cancelable, ie. a caller can call `preventDefault()` on the event + * to prevent the move from being canceled. Any pieces being dragged will be returned + * to the start square, but the move will remain in progress. + * + * The event has a `detail` object with `from` set to the square label where + * the move was started, and `piece` containing information about the piece that was + * moved. + * + * @cssprop [--square-color-dark=hsl(145deg 32% 44%)] - Color for dark squares. + * @cssprop [--square-color-light=hsl(51deg 24% 84%)] - Color for light squares. + * + * @cssprop [--square-color-dark-hover=hsl(144deg 75% 44%)] - Hover color + * for a dark square. Applied when mouse is hovering over an interactable square + * or a square has keyboard focus during a move. + * @cssprop [--square-color-light-hover=hsl(52deg 98% 70%)] - Hover color + * for a dark square. Applied when mouse is hovering over an interactable square + * or a square has keyboard focus during a move. + * + * @cssprop [--square-color-dark-active=hsl(142deg 77% 43%)] - Color applied to + * dark square when it is involved in (the starting point) of a move. By default + * this color is similar to, but slightly different from, `--square-color-dark-hover`. + * @cssprop [--square-color-light-active=hsl(50deg 95% 64%)] - Color applied to + * light square when it is involved in (the starting point) of a move. By default + * this color is similar to, but slightly different from, `--square-color-light-hover`. + * + * @cssprop [--outline-color-dark-active=hsl(138deg 85% 53% / 95%)] - Color of + * outline applied to dark square when it is the starting point of a move. + * It is applied in addition to `--square-color-dark-active`, and is visible + * when the square does not have focus. + * @cssprop [--outline-color-light-active=hsl(66deg 97% 72% / 95%)] - Color of + * outline applied to light square when it is the starting point of a move. + * It is applied in addition to `--square-color-light-active`, and is visible + * when the square does not have focus. + * @cssprop [--outline-color-focus=hsl(30deg 94% 55% / 90%)] - Color of outline applied to square when it has focus. + * + * @cssprop [--outer-gutter-width=4%] - When the `coordinates` property is `outside`, + * this CSS property controls the width of the gutter outside the board where coords are shown. + * @cssprop [--inner-border-width=1px] - Width of the inside border drawn around the board. + * @cssprop [--inner-border-color=var(--square-color-dark)] - Color of the inside border drawn + * around the board. + * + * @cssprop [--move-target-marker-color-dark-square=hsl(144deg 64% 9% / 90%)] - + * Color of marker shown on dark square when it is an eligible move target. + * @cssprop [--move-target-marker-color-light-square=hsl(144deg 64% 9% / 90%)] - + * Color of marker shown on light square when it is an eligible move target. + * + * @cssprop [--move-target-marker-radius=24%] - Radius of marker on a move target + * square. + * @cssprop [--move-target-marker-radius-occupied=82%] - Radius of marker on + * a move target square that is occupied (by a piece or custom content). + * + * @cssprop [--outline-blur-radius=3px] - Blur radius of all outlines applied to square. + * @cssprop [--outline-spread-radius=4px] - Spread radius of all outlines applied to square. + * + * @cssprop [--coords-font-size=0.7rem] - Font size of coord labels shown on board. + * @cssprop [--coords-font-family=sans-serif] - Font family of coord labels shown on board. + * @cssprop [--coords-inside-coord-padding-left=0.5%] - Left padding applied to coordinates + * when shown inside the board. Percentage values are relative to the width of the board. + * @cssprop [--coords-inside-coord-padding-right=0.5%] - Right padding applied to coordinates + * when shown inside the board. Percentage values are relative to the width of the board. + * + * @cssprop [--ghost-piece-opacity=0.35] - Opacity of ghost piece shown while dragging. + * Set to 0 to hide ghost piece altogether. + * @cssprop [--piece-drag-z-index=9999] - Z-index applied to piece while being dragged. + * @cssprop [--piece-drag-coarse-scale=2.4] - Amount to scale up a piece when doing a + * coarse (touch) drag. On mobile devices, pieces will be scaled up in size to + * make them easier to see. + * @cssprop [--piece-padding=3%] - Padding applied to square when piece is placed in it. + * + * @cssprop [--arrow-color-primary=hsl(40deg 100% 50% / 80%)] - Color applied to arrow + * with brush `primary`. + * @cssprop [--arrow-color-secondary=hsl(7deg 93% 61% / 80%)] - Color applied to arrow + * with brush `secondary`. + * + * @slot a1,a2,...,h8 - Slots that allow placement of custom content -- SVGs, text, or + * any other annotation -- on the corresponding square. + * + * @csspart piece- - CSS parts for each of the piece classes. The part + * name is of the form `piece-xy`, where `x` corresponds to the color of the piece -- + * either `w` for white or `b` for black, and `y` is the piece type -- one of `p` (pawn), + * `r` (rook), `n` (knight), `b` (bishop), `k` (king), `q` (queen). Thus, `piece-wr` + * would be the CSS part corresponding to the white rook. + * + * The CSS parts can be used to set custom CSS for the pieces (such as changing the image + * for a piece by changing the `background-image` property). + * + * @csspart arrow- - CSS parts for any arrow brushes configured using the + * `brush` field on an arrow specification (see the `arrows` property for more details). + */ +class GChessBoardElement extends HTMLElement { + static get observedAttributes() { + return [ + "orientation", + "turn", + "interactive", + "fen", + "coordinates", + "animation-duration", + ]; + } + constructor() { + super(); + Object.defineProperty(this, "_shadow", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_style", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_wrapper", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_board", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_fileCoords", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_rankCoords", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "_arrows", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this._shadow = this.attachShadow({ mode: "open" }); + this._style = document.createElement("style"); + this._style.textContent = css_248z; + this._shadow.appendChild(this._style); + this._wrapper = makeHTMLElement("div", { + classes: ["wrapper", GChessBoardElement._DEFAULT_COORDS_PLACEMENT], + }); + this._shadow.appendChild(this._wrapper); + this._board = new Board({ + orientation: GChessBoardElement._DEFAULT_SIDE, + animationDurationMs: GChessBoardElement._DEFAULT_ANIMATION_DURATION_MS, + }, (e) => this.dispatchEvent(e), this._shadow); + this._wrapper.appendChild(this._board.element); + this._fileCoords = new Coordinates({ + direction: "file", + placement: GChessBoardElement._DEFAULT_COORDS_PLACEMENT, + orientation: GChessBoardElement._DEFAULT_SIDE, + }); + this._rankCoords = new Coordinates({ + direction: "rank", + placement: GChessBoardElement._DEFAULT_COORDS_PLACEMENT, + orientation: GChessBoardElement._DEFAULT_SIDE, + }); + this._wrapper.appendChild(this._fileCoords.element); + this._wrapper.appendChild(this._rankCoords.element); + this._arrows = new Arrows(GChessBoardElement._DEFAULT_SIDE); + this._wrapper.appendChild(this._arrows.element); + } + connectedCallback() { + this._board.addGlobalListeners(); + } + disconnectedCallback() { + this._board.removeGlobalListeners(); + } + attributeChangedCallback(name, _, newValue) { + switch (name) { + case "interactive": + this._board.interactive = this.interactive; + break; + case "coordinates": + this._wrapper.classList.toggle("outside", this.coordinates === "outside"); + this._wrapper.classList.toggle("inside", this.coordinates === "inside"); + break; + case "orientation": + this._board.orientation = this.orientation; + this._fileCoords.orientation = this.orientation; + this._rankCoords.orientation = this.orientation; + this._arrows.orientation = this.orientation; + break; + case "turn": + this._board.turn = this.turn; + break; + case "fen": + if (newValue !== null) { + this.fen = newValue; + } + else { + this.position = {}; + } + break; + case "animation-duration": + this._board.animationDurationMs = this.animationDuration; + break; + default: + assertUnreachable(name); + } + } + /** + * What side's perspective to render squares from (what color appears on + * the bottom as viewed on the screen). Either `"white"` or `"black"`. + * + * @attr [orientation=white] + */ + get orientation() { + return this._parseRestrictedStringAttributeWithDefault("orientation", isSide, GChessBoardElement._DEFAULT_SIDE); + } + set orientation(value) { + this.setAttribute("orientation", value); + } + /** + * What side is allowed to move pieces. Either `"white`, `"black"`, or + * unset. When unset, pieces from either side can be moved around. + * + * @attr + */ + get turn() { + return this._parseRestrictedStringAttribute("turn", isSide); + } + set turn(value) { + if (value) { + this.setAttribute("turn", value); + } + else { + this.removeAttribute("turn"); + } + } + /** + * Whether the squares are interactive, i.e. user can interact with squares, + * move pieces etc. By default, this is false; i.e a board is only for displaying + * a position. + * + * @attr [interactive=false] + */ + get interactive() { + return this._hasBooleanAttribute("interactive"); + } + set interactive(interactive) { + this._setBooleanAttribute("interactive", interactive); + } + /** + * A map-like object representing the board position, where object keys are square + * labels, and values are `Piece` objects. Note that changes to this property are + * mirrored in the value of the `fen` property of the element, but **not** the + * corresponding attribute. All changes to position are animated, using the duration + * specified by the `animationDuration` property. + * + * Example: + * + * ```js + * board.position = { + * a2: { + * pieceType: "king", + * color: "white" + * }, + * g4: { + * pieceType: "knight", + * color: "black" + * }, + * }; + * ``` + */ + get position() { + return this._board.position; + } + set position(value) { + this._board.position = { ...value }; + } + /** + * FEN string representing the board position. Note that changes to the corresponding + * `fen` _property_ will **not** reflect onto the "fen" _attribute_ of the element. + * In other words, to get the latest FEN string for the board position, use the `fen` + * _property_. + * + * Accepts the special string `"start"` as shorthand for the starting position + * of a chess game. An empty string represents an empty board. Invalid FEN values + * are ignored with an error. + * + * Note that a FEN string normally contains 6 components, separated by slashes, + * but only the first component (the "piece placement" component) is used by this + * attribute. + * + * @attr + */ + get fen() { + return getFen(this._board.position); + } + set fen(value) { + const position = getPosition(value); + if (position !== undefined) { + this.position = position; + } + else { + throw new Error(`Invalid FEN position: ${value}`); + } + } + /** + * How to display coordinates for squares. Could be `"inside"` the board (default), + * `"outside"`, or `"hidden"`. + * + * @attr [coordinates=inside] + */ + get coordinates() { + return this._parseRestrictedStringAttributeWithDefault("coordinates", isCoordinatesPlacement, GChessBoardElement._DEFAULT_COORDS_PLACEMENT); + } + set coordinates(value) { + this.setAttribute("coordinates", value); + } + /** + * Duration, in milliseconds, of animation when adding/removing/moving pieces. + * + * @attr [animation-duration=200] + */ + get animationDuration() { + return this._parseNumberAttribute("animation-duration", GChessBoardElement._DEFAULT_ANIMATION_DURATION_MS); + } + set animationDuration(value) { + this._setNumberAttribute("animation-duration", value); + } + /** + * Set of arrows to draw on the board. This is an array of objects specifying + * arrow characteristics, with the following properties: (1) `from` and `to` + * corresponding to the start and end squares for the arrow, (2) optional + * `weight` for the line (values: `"light"`, `"normal"`, `"bold"`), and + * (3) `brush`, which is a string that will be used to make a CSS part + * where one can customize the color, opacity, and other styles of the + * arrow. For example, a value for `brush` of `"foo"` will apply a + * CSS part named `arrow-foo` to the arrow. + * + * Note: because the value of `brush` becomes part of a CSS part name, it + * should be usable as a valid CSS identifier. + * + * In addition to allowing arbitrary part names, arrows support a few + * out-of-the-box brush names, `primary` and `secondary`, which colors + * defined with CSS custom properties `--arrow-color-primary` and + * `--arrow-color-secondary`. + * + * Example: + * + * ```js + * board.arrows = [ + * { from: "e2", to: "e4" }, + * { + * from: "g1", + * to: "f3", + * brush: "foo" + * }, + * { + * from: "c7", + * to: "c5", + * brush: "secondary" + * }, + * ]; + */ + get arrows() { + return this._arrows.arrows; + } + set arrows(arrows) { + this._arrows.arrows = arrows; + } + addEventListener(type, listener, options) { + super.addEventListener(type, listener, options); + } + removeEventListener(type, listener, options) { + super.removeEventListener(type, listener, options); + } + /** + * Start a move on the board at `square`, optionally with specified targets + * at `targetSquares`. + */ + startMove(square, targetSquares) { + this._board.startMove(square, targetSquares); + } + /** + * Imperatively cancel any in-progress moves. + */ + cancelMove() { + this._board.cancelMove(); + } + _hasBooleanAttribute(name) { + return (this.hasAttribute(name) && + this.getAttribute(name)?.toLowerCase() !== "false"); + } + _setBooleanAttribute(name, value) { + if (value) { + this.setAttribute(name, ""); + } + else { + this.removeAttribute(name); + } + } + _setNumberAttribute(name, value) { + this.setAttribute(name, value.toString()); + } + _parseRestrictedStringAttribute(name, guard) { + const value = this.getAttribute(name); + return guard(value) ? value : undefined; + } + _parseRestrictedStringAttributeWithDefault(name, guard, defaultValue) { + const parsed = this._parseRestrictedStringAttribute(name, guard); + return parsed !== undefined ? parsed : defaultValue; + } + _parseNumberAttribute(name, defaultValue) { + const value = this.getAttribute(name); + return value === null || Number.isNaN(Number(value)) + ? defaultValue + : Number(value); + } +} +Object.defineProperty(GChessBoardElement, "_DEFAULT_SIDE", { + enumerable: true, + configurable: true, + writable: true, + value: "white" +}); +Object.defineProperty(GChessBoardElement, "_DEFAULT_ANIMATION_DURATION_MS", { + enumerable: true, + configurable: true, + writable: true, + value: 200 +}); +Object.defineProperty(GChessBoardElement, "_DEFAULT_COORDS_PLACEMENT", { + enumerable: true, + configurable: true, + writable: true, + value: "inside" +}); + +customElements.define("g-chess-board", GChessBoardElement); + +export { GChessBoardElement }; +//# sourceMappingURL=index.es.js.map diff --git a/gchessboard.min.js b/gchessboard.min.js new file mode 100644 index 0000000..b19ba44 --- /dev/null +++ b/gchessboard.min.js @@ -0,0 +1 @@ +const STAR=1;const PRIME=2;class Munkres{constructor(costMatrix,padValue){Object.defineProperty(this,"C",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"n",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"originalRows",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"originalCols",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"marked",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"rowCovered",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"colCovered",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"Z0Row",{enumerable:true,configurable:true,writable:true,value:0});Object.defineProperty(this,"Z0Col",{enumerable:true,configurable:true,writable:true,value:0});const maxNumColumns=costMatrix.reduce((acc,row)=>Math.max(acc,row.length),0);this.n=Math.max(costMatrix.length,maxNumColumns);this.originalRows=costMatrix.length;this.originalCols=maxNumColumns;this.C=[];for(let i=0;i=this.n?7:4}_step4(){let colWithStar=-1;for(;;){const[row,col]=this._findFirstUncoveredZero();if(row<0){return 6}this.marked[row][col]=PRIME;colWithStar=this._findStarInRow(row);if(colWithStar>=0){this.rowCovered[row]=true;this.colCovered[colWithStar]=false}else{this.Z0Row=row;this.Z0Col=col;return 5}}}_step5(){let count=0;const path=[[this.Z0Row,this.Z0Col]];for(;;){const row=this._findStarInCol(path[count][1]);if(row<0){break}path.push([row,path[count][1]]);count++;const col=this._findPrimeInRow(path[count][0]);path.push([path[count][0],col]);count++}for(let i=0;i<=count;i++){const[row,col]=path[i];this.marked[row][col]=this.marked[row][col]==STAR?0:STAR}this._clearCovers();this._erasePrimes();return 3}_step6(){const minval=this._findSmallestUncovered();for(let i=0;ithis.C[i][j]){minval=this.C[i][j]}}}return minval}}function munkres(costMatrix,padValue){const m=new Munkres(costMatrix,padValue);return m.compute()}const SIDE_COLORS=["white","black"];const SQUARES_MAP={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 SQUARES=Object.keys(SQUARES_MAP);const SQUARE_DISTANCE_TABLE=[14,13,12,11,10,9,8,7,8,9,10,11,12,13,14,0,13,12,11,10,9,8,7,6,7,8,9,10,11,12,13,0,12,11,10,9,8,7,6,5,6,7,8,9,10,11,12,0,11,10,9,8,7,6,5,4,5,6,7,8,9,10,11,0,10,9,8,7,6,5,4,3,4,5,6,7,8,9,10,0,9,8,7,6,5,4,3,2,3,4,5,6,7,8,9,0,8,7,6,5,4,3,2,1,2,3,4,5,6,7,8,0,7,6,5,4,3,2,1,0,1,2,3,4,5,6,7,0,8,7,6,5,4,3,2,1,2,3,4,5,6,7,8,0,9,8,7,6,5,4,3,2,3,4,5,6,7,8,9,0,10,9,8,7,6,5,4,3,4,5,6,7,8,9,10,0,11,10,9,8,7,6,5,4,5,6,7,8,9,10,11,0,12,11,10,9,8,7,6,5,6,7,8,9,10,11,12,0,13,12,11,10,9,8,7,6,7,8,9,10,11,12,13,0,14,13,12,11,10,9,8,7,8,9,10,11,12,13,14,0];const REVERSE_SQUARES_MAP=SQUARES.reduce((acc,key)=>{acc[SQUARES_MAP[key]]=key;return acc},{});const FEN_PIECE_TYPE_MAP={p:"pawn",n:"knight",b:"bishop",r:"rook",q:"queen",k:"king"};const REVERSE_FEN_PIECE_TYPE_MAP=Object.keys(FEN_PIECE_TYPE_MAP).reduce((acc,key)=>{acc[FEN_PIECE_TYPE_MAP[key]]=key;return acc},{});function getPosition(fen){if(fen==="initial"||fen==="start"){fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"}const parts=fen.split(" ");const ranks=parts[0].split("/");if(ranks.length!==8){return undefined}const position={};for(let i=0;i<8;i++){const rank=8-i;let fileOffset=0;for(let j=0;j8){return undefined}else{fileOffset+=emptySpaces}}}if(fileOffset!==8){return undefined}}return position}function getFen(position){const rankSpecs=[];for(let i=0;i<8;i++){let rankSpec="";let gap=0;for(let j=0;j<8;j++){const square=REVERSE_SQUARES_MAP[16*i+j];const piece=position[square];if(piece!==undefined){const pieceStr=REVERSE_FEN_PIECE_TYPE_MAP[piece.pieceType];if(gap>0){rankSpec+=gap}rankSpec+=piece.color==="white"?pieceStr.toUpperCase():pieceStr;gap=0}else{gap+=1}}if(gap>0){rankSpec+=gap}rankSpecs.push(rankSpec)}return rankSpecs.join("/")}function getSquare(visualIndex,orientation){const idx=visualIndex+(visualIndex&~7);return REVERSE_SQUARES_MAP[orientation==="black"?119-idx:idx]}function getVisualIndex(square,orientation){const idx=SQUARES_MAP[square];const orientedIdx=orientation==="black"?119-idx:idx;return orientedIdx+(orientedIdx&7)>>1}function getVisualRowColumn(square,orientation){const idx=getVisualIndex(square,orientation);return[idx>>3,idx&7]}function getSquareColor(square){const idx0x88=SQUARES_MAP[square];const idx=idx0x88+(idx0x88&7)>>1;return(idx*9&8)===0?"light":"dark"}function keyIsSquare(key){return key!==undefined&&key in SQUARES_MAP}function pieceEqual(a,b){return a===undefined&&b===undefined||a!==undefined&&b!==undefined&&a.color===b.color&&a.pieceType===b.pieceType}function isSide(s){return SIDE_COLORS.includes(s)}function positionsEqual(a,b){return SQUARES.every(square=>pieceEqual(a[square],b[square]))}function calcPositionDiff(oldPosition,newPosition){const oldPositionLimited={...oldPosition};const newPositionLimited={...newPosition};Object.keys(newPosition).forEach(k=>{const square=k;if(pieceEqual(newPosition[square],oldPosition[square])){delete oldPositionLimited[square];delete newPositionLimited[square]}});const added=[];const removed=[];const moved=[];function groupByPiece(position){return Object.entries(position).reduce((groups,[square,piece])=>{const key=`${piece.color}_${piece.pieceType}`;if(!(key in groups)){groups[key]={squares:[],piece:piece}}groups[key].squares.push(square);return groups},{})}function matchSquares(oldSquares,newSquares){const costMatrix=[];for(let i=0;is!==undefined).forEach(s=>{removed.push(s)});newSquaresCopy.filter(s=>s!==undefined).forEach(s=>{added.push(s)});return{moved:moved,added:added,removed:removed}}const oldPositionGrouped=groupByPiece(oldPositionLimited);const newPositionGrouped=groupByPiece(newPositionLimited);Object.entries(newPositionGrouped).forEach(([k,pieceSquares])=>{if(k in oldPositionGrouped){const matches=matchSquares(oldPositionGrouped[k].squares,pieceSquares.squares);matches.moved.forEach(({oldSquare,newSquare})=>{moved.push({piece:pieceSquares.piece,oldSquare:oldSquare,newSquare:newSquare})});matches.added.forEach(square=>{added.push({piece:pieceSquares.piece,square:square})});matches.removed.forEach(square=>{removed.push({piece:pieceSquares.piece,square:square})});delete oldPositionGrouped[k]}else{pieceSquares.squares.forEach(square=>{added.push({piece:pieceSquares.piece,square:square})})}});Object.entries(oldPositionGrouped).forEach(([,pieceSquares])=>{pieceSquares.squares.forEach(square=>{removed.push({piece:pieceSquares.piece,square:square})})});return{added:added,removed:removed,moved:moved}}function squareDistance(a,b){return SQUARE_DISTANCE_TABLE[SQUARES_MAP[a]-SQUARES_MAP[b]+119]}function makeHTMLElement(tag,options){return addOptionsToElement(document.createElement(tag),options)}function makeSVGElement(tag,options){return addOptionsToElement(document.createElementNS("http://www.w3.org/2000/svg",tag),options)}function addOptionsToElement(e,options){if(options!==undefined){for(const key in options.attributes){e.setAttribute(key,options.attributes[key])}for(const key in options.data){e.dataset[key]=options.data[key]}if(options.classes){e.classList.add(...options.classes)}}return e}function assertUnreachable(x){throw new Error(`Unreachable code reached with value ${x}`)}class BoardPiece{constructor(container,config){Object.defineProperty(this,"piece",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"animationFinished",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_element",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_parentElement",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_explicitPosition",{enumerable:true,configurable:true,writable:true,value:void 0});this.piece=config.piece;this._parentElement=container;this._element=makeHTMLElement("span",{attributes:{role:"presentation","aria-hidden":"true",part:`piece-${BoardPiece.PIECE_CLASS_MAP[this.piece.color][this.piece.pieceType]}`},classes:["piece",BoardPiece.PIECE_CLASS_MAP[this.piece.color][this.piece.pieceType]]});if(config.animation!==undefined){this._setAnimation(config.animation)}if(config.secondary){this._element.classList.add("secondary")}container.appendChild(this._element)}remove(animationDurationMs){if(animationDurationMs){this._setAnimation({type:"fade-out",durationMs:animationDurationMs})}else{this._parentElement.removeChild(this._element)}}setExplicitPosition(explicitPosition){this._explicitPosition=explicitPosition;const coords=this._getTranslateValues(explicitPosition);if(coords){this._element.style.transform=`translate(${coords.x}, ${coords.y})`}const scaleValue=getComputedStyle(this._element).getPropertyValue(BoardPiece.PIECE_DRAG_SCALE_PROP);if(scaleValue){this._element.style.transform+=` scale(${scaleValue})`}}resetPosition(animateDurationMs){if(animateDurationMs&&this._explicitPosition){this._setAnimation({type:"slide-in",from:this._explicitPosition,durationMs:animateDurationMs})}this._element.style.removeProperty("transform");this._explicitPosition=undefined}get explicitPosition(){return this._explicitPosition}finishAnimations(){this._element.getAnimations().forEach(a=>{a.finish()})}_getTranslateValues(explicitPosition){if(explicitPosition.type==="coordinates"){const squareDims=this._parentElement.getBoundingClientRect();const deltaX=explicitPosition.x-squareDims.left-squareDims.width/2;const deltaY=explicitPosition.y-squareDims.top-3*squareDims.height/4;if(deltaX!==0||deltaY!==0){return{x:`${deltaX}px`,y:`${deltaY}px`}}}else{if(explicitPosition.deltaCols!==0||explicitPosition.deltaRows!==0){return{x:`${explicitPosition.deltaCols*100}%`,y:`${explicitPosition.deltaRows*100}%`}}}return undefined}_setAnimation(animationSpec){let keyframes;let onfinish;this.finishAnimations();switch(animationSpec.type){case"slide-in":{const coords=this._getTranslateValues(animationSpec.from);if(coords){keyframes=[{transform:`translate(${coords.x}, ${coords.y})`},{transform:"none"}];this._element.classList.add("moving")}onfinish=()=>{this._element.classList.remove("moving")}}break;case"fade-in":keyframes=[{opacity:0},{opacity:1}];break;case"fade-out":{keyframes=[{opacity:1},{opacity:0}];const elementCopy=this._element;onfinish=()=>{this._parentElement.removeChild(elementCopy)}}break;default:assertUnreachable(animationSpec)}if(keyframes!==undefined&&typeof this._element.animate==="function"){const animation=this._element.animate(keyframes,{duration:Math.max(0,animationSpec.durationMs)});this.animationFinished=new Promise(resolve=>{animation.onfinish=()=>{if(onfinish!==undefined){onfinish()}this.animationFinished=undefined;resolve()}})}else if(onfinish!==undefined){onfinish()}}}Object.defineProperty(BoardPiece,"PIECE_CLASS_MAP",{enumerable:true,configurable:true,writable:true,value:{white:{queen:"wq",king:"wk",knight:"wn",pawn:"wp",bishop:"wb",rook:"wr"},black:{queen:"bq",king:"bk",knight:"bn",pawn:"bp",bishop:"bb",rook:"br"}}});Object.defineProperty(BoardPiece,"PIECE_DRAG_SCALE_PROP",{enumerable:true,configurable:true,writable:true,value:"--p-piece-drag-scale"});class BoardSquare{constructor(container,label){Object.defineProperty(this,"_tdElement",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_contentElement",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_slotWrapper",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_slotElement",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_label",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_interactive",{enumerable:true,configurable:true,writable:true,value:false});Object.defineProperty(this,"_tabbable",{enumerable:true,configurable:true,writable:true,value:false});Object.defineProperty(this,"_moveable",{enumerable:true,configurable:true,writable:true,value:false});Object.defineProperty(this,"_boardPiece",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_secondaryBoardPiece",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_hasContent",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_hover",{enumerable:true,configurable:true,writable:true,value:false});Object.defineProperty(this,"_markedTarget",{enumerable:true,configurable:true,writable:true,value:false});Object.defineProperty(this,"_moveState",{enumerable:true,configurable:true,writable:true,value:void 0});this._tdElement=makeHTMLElement("td",{attributes:{role:"cell"}});this._label=label;this._contentElement=makeHTMLElement("div",{classes:["content"]});this._slotWrapper=makeHTMLElement("div",{classes:["slot"],attributes:{role:"presentation"}});this._slotElement=document.createElement("slot");this._slotWrapper.appendChild(this._slotElement);this._contentElement.appendChild(this._slotWrapper);this._updateLabelVisuals();this._tdElement.appendChild(this._contentElement);container.appendChild(this._tdElement)}get label(){return this._label}set label(value){this._label=value;this._updateLabelVisuals()}get interactive(){return this._interactive}set interactive(value){this._interactive=value;this._moveState=undefined;this._tdElement.setAttribute("role",value?"gridcell":"cell");if(value){this._contentElement.setAttribute("role","button")}else{this._contentElement.removeAttribute("role")}this._updateTabIndex();this._updateMoveStateVisuals();this._updateLabelVisuals()}get tabbable(){return this._tabbable}set tabbable(value){this._tabbable=value;this._updateTabIndex()}get hasContent(){return!!this._hasContent}set hasContent(value){this._hasContent=value;this._contentElement.classList.toggle("has-content",value)}get moveable(){return this._moveable}set moveable(value){if(!value||this._boardPiece){this._moveable=value;this._updateMoveStateVisuals();this._updateLabelVisuals()}}get moveTarget(){return this._moveState==="move-target"}set moveTarget(value){this._moveState=value?"move-target":"move-nontarget";this._updateMoveStateVisuals();this._updateLabelVisuals()}removeMoveState(){this._moveState=undefined;this._updateMoveStateVisuals();this._updateLabelVisuals()}get hover(){return this._hover}set hover(value){this._hover=value;this._contentElement.classList.toggle("hover",value)}get markedTarget(){return this._markedTarget}set markedTarget(value){this._markedTarget=value;this._contentElement.classList.toggle("marked-target",value)}get width(){return this._contentElement.clientWidth}get explicitPiecePosition(){return this._boardPiece?.explicitPosition}focus(){this._contentElement.focus()}blur(){this._contentElement.blur()}get boardPiece(){return this._boardPiece}setPiece(piece,moveable,animation){if(!pieceEqual(this._boardPiece?.piece,piece)||animation){this.clearPiece(animation?.durationMs);this._boardPiece=new BoardPiece(this._contentElement,{piece:piece,animation:animation});this.moveable=moveable;this._updateSquareAfterPieceChange()}}clearPiece(animationDurationMs){if(this._boardPiece!==undefined){this.moveable=false;this._boardPiece.remove(animationDurationMs);this._boardPiece=undefined;this._updateSquareAfterPieceChange()}}toggleSecondaryPiece(show){if(show&&!this._secondaryBoardPiece&&this._boardPiece){this._secondaryBoardPiece=new BoardPiece(this._contentElement,{piece:this._boardPiece.piece,secondary:true})}if(!show){if(this._secondaryBoardPiece!==undefined){this._secondaryBoardPiece.remove()}this._secondaryBoardPiece=undefined}}startInteraction(){if(this._boardPiece!==undefined&&this.moveable){this._moveState="move-start";this._updateMoveStateVisuals();this._updateLabelVisuals();this._boardPiece.finishAnimations()}}displacePiece(x,y){this._boardPiece?.setExplicitPosition({type:"coordinates",x:x,y:y})}resetPiecePosition(animateDurationMs){this._boardPiece?.resetPosition(animateDurationMs)}cancelInteraction(animateDurationMs){this._moveState=undefined;this._updateMoveStateVisuals();this._updateLabelVisuals();this.resetPiecePosition(animateDurationMs)}_updateLabelVisuals(){this._contentElement.dataset.square=this.label;this._contentElement.dataset.squareColor=getSquareColor(this.label);const labelParts=[this._boardPiece?`${this.label}, ${this._boardPiece.piece.color} ${this._boardPiece.piece.pieceType}`:`${this.label}`];if(this._moveState==="move-start"){labelParts.push("start of move")}if(this._moveState==="move-target"){labelParts.push("target square")}this._contentElement.setAttribute("aria-label",labelParts.join(", "));this._slotElement.name=this.label}_updateTabIndex(){if(this.interactive){this._contentElement.tabIndex=this.tabbable?0:-1}else{this._contentElement.removeAttribute("tabindex")}}_updateMoveStateVisuals(){this._updateInteractiveCssClass("moveable",this.moveable&&!this._moveState);this._updateInteractiveCssClass("move-start",this._moveState==="move-start");this._updateInteractiveCssClass("move-target",this._moveState==="move-target");this._contentElement.setAttribute("aria-disabled",(!this._moveState&&!this.moveable).toString())}_updateInteractiveCssClass(name,value){this._contentElement.classList.toggle(name,this.interactive&&value)}_updateSquareAfterPieceChange(){this._contentElement.classList.toggle("has-piece",!!this._boardPiece);this._moveState=undefined;this._updateMoveStateVisuals();this.toggleSecondaryPiece(false);this._updateLabelVisuals()}}class Board{constructor(initValues,dispatchEvent,shadowRef){Object.defineProperty(this,"_table",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_boardSquares",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_dispatchEvent",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_shadowRef",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_orientation",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_turn",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_interactive",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_position",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_boardState",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_tabbableSquare",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_defaultTabbableSquare",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_preventClickHandling",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_pointerDownHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_pointerUpHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_pointerMoveHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_clickHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_focusInHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_keyDownHandler",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"animationDurationMs",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_slotChangeHandler",{enumerable:true,configurable:true,writable:true,value:e=>{if(Board._isSlotElement(e.target)&&keyIsSquare(e.target.name)){this._getBoardSquare(e.target.name).hasContent=e.target.assignedElements().length>0}}});Object.defineProperty(this,"_transitionHandler",{enumerable:true,configurable:true,writable:true,value:e=>{if(e.target&&e.target.style!==undefined){const style=e.target.style;style.removeProperty("transition-property")}}});this._boardSquares=new Array(64);this._orientation=initValues.orientation;this.animationDurationMs=initValues.animationDurationMs;this._interactive=false;this._position={};this._boardState={id:"default"};this._dispatchEvent=dispatchEvent;this._shadowRef=shadowRef;this._defaultTabbableSquare=getSquare(56,initValues.orientation);this._table=makeHTMLElement("table",{attributes:{role:"table","aria-label":"Chess board"},classes:["board"]});for(let i=0;i<8;i++){const row=makeHTMLElement("tr",{attributes:{role:"row"}});for(let j=0;j<8;j++){const idx=8*i+j;const square=getSquare(idx,this.orientation);this._boardSquares[idx]=new BoardSquare(row,square)}this._table.appendChild(row)}this._getBoardSquare(this._defaultTabbableSquare).tabbable=true;this._pointerDownHandler=this._makeEventHandler(this._handlePointerDown);this._pointerUpHandler=this._makeEventHandler(this._handlePointerUp);this._pointerMoveHandler=this._makeEventHandler(this._handlePointerMove);this._clickHandler=this._makeEventHandler(this._handleClick);this._keyDownHandler=this._makeEventHandler(this._handleKeyDown);this._focusInHandler=this._makeEventHandler(this._handleFocusIn);this._table.addEventListener("pointerdown",this._pointerDownHandler);this._table.addEventListener("click",this._clickHandler);this._table.addEventListener("focusin",this._focusInHandler);this._table.addEventListener("keydown",this._keyDownHandler);this._table.addEventListener("slotchange",this._slotChangeHandler);this._table.addEventListener("transitionend",this._transitionHandler);this._table.addEventListener("transitioncancel",this._transitionHandler)}addGlobalListeners(){document.addEventListener("pointerup",this._pointerUpHandler);document.addEventListener("pointermove",this._pointerMoveHandler)}removeGlobalListeners(){document.removeEventListener("pointerup",this._pointerUpHandler);document.removeEventListener("pointermove",this._pointerMoveHandler)}get element(){return this._table}get orientation(){return this._orientation}set orientation(value){this._cancelMove(false);this._orientation=value;this._refreshDefaultTabbableSquare();for(let i=0;i<64;i++){const square=getSquare(i,value);const piece=this._position[square];this._boardSquares[i].label=square;this._boardSquares[i].tabbable=this.tabbableSquare===square;if(piece){this._boardSquares[i].setPiece(piece,this._pieceMoveable(piece))}else{this._boardSquares[i].clearPiece()}}if(this._focusedSquare){this._focusTabbableSquare()}}get interactive(){return this._interactive}set interactive(value){this._cancelMove(false);this._interactive=value;this._blurTabbableSquare();this._table.setAttribute("role",value?"grid":"table");this._boardSquares.forEach(s=>{s.interactive=value});this._resetBoardStateAndMoves()}get turn(){return this._turn}set turn(value){this._cancelMove(false);this._turn=value;for(let idx=0;idx<64;idx++){const square=getSquare(idx,this.orientation);const piece=this._position[square];this._boardSquares[idx].moveable=!piece||this._pieceMoveable(piece)}}get position(){return this._position}set position(value){if(!positionsEqual(this._position,value)){this._cancelMove(false);const diff=calcPositionDiff(this._position,value);this._position={...value};diff.moved.forEach(({oldSquare})=>{this._getBoardSquare(oldSquare).clearPiece()});diff.removed.forEach(({square})=>{this._getBoardSquare(square).clearPiece(this.animationDurationMs)});diff.moved.forEach(({piece,oldSquare,newSquare})=>{const startingPosition=this._getStartingPositionForMove(oldSquare,newSquare);this._getBoardSquare(newSquare).setPiece(piece,this._pieceMoveable(piece),{type:"slide-in",from:startingPosition,durationMs:this.animationDurationMs})});diff.added.forEach(({piece,square})=>{this._getBoardSquare(square).setPiece(piece,this._pieceMoveable(piece),{type:"fade-in",durationMs:this.animationDurationMs})});this._refreshDefaultTabbableSquare()}}get tabbableSquare(){return this._tabbableSquare||this._defaultTabbableSquare}set tabbableSquare(value){if(this.tabbableSquare!==value){this._getBoardSquare(this.tabbableSquare).tabbable=false;this._getBoardSquare(value).tabbable=true;this._tabbableSquare=value}}startMove(square,targetSquares){if(this._interactable(square)){this._setBoardState({id:"awaiting-second-touch",startSquare:square});this._startInteraction(square,targetSquares)}}cancelMove(){this._cancelMove(false)}get _focusedSquare(){return Board._extractSquareData(this._shadowRef.activeElement)}_startInteraction(square,forceTargetSquares){const piece=this._position[square];if(piece){let targetsLimited=false;const targetSquares=[];if(forceTargetSquares!==undefined){targetsLimited=true;forceTargetSquares.forEach(s=>{if(keyIsSquare(s)){targetSquares.push(s)}})}else{this._dispatchEvent(new CustomEvent("movestart",{bubbles:true,detail:{from:square,piece:piece,setTargets:squares=>{targetsLimited=true;for(const s of squares){if(keyIsSquare(s)){targetSquares.push(s)}}}}}))}this._getBoardSquare(square).startInteraction();this.tabbableSquare=square;this._boardSquares.forEach(s=>{if(s.label!==square){s.moveTarget=!targetsLimited||targetSquares.includes(s.label);s.markedTarget=targetsLimited&&s.moveTarget}})}}_finishMove(to,animate){if(this._boardState.startSquare){const from=this._boardState.startSquare;const piece=this._position[from];if(piece!==undefined){const endEvent=new CustomEvent("moveend",{bubbles:true,cancelable:true,detail:{from:from,to:to,piece:piece}});this._dispatchEvent(endEvent);if(endEvent.defaultPrevented){return false}const startingPosition=this._getStartingPositionForMove(from,to);this._getBoardSquare(from).clearPiece();this._getBoardSquare(to).setPiece(piece,this._pieceMoveable(piece),animate?{type:"slide-in",from:startingPosition,durationMs:this.animationDurationMs}:undefined);this.tabbableSquare=to;this._position[to]=this._position[from];delete this._position[from];const finishedEvent=new CustomEvent("movefinished",{bubbles:true,detail:{from:from,to:to,piece:piece}});if(animate){this._getBoardSquare(to).boardPiece?.animationFinished?.then(()=>{this._dispatchEvent(finishedEvent)})}else{this._dispatchEvent(finishedEvent)}}this._resetBoardStateAndMoves();return true}return false}_userCancelMove(animate){if(this._boardState.startSquare){const e=new CustomEvent("movecancel",{bubbles:true,cancelable:true,detail:{from:this._boardState.startSquare,piece:this._position[this._boardState.startSquare]}});this._dispatchEvent(e);if(!e.defaultPrevented){this._cancelMove(animate);return true}}return false}_cancelMove(animate){if(this._boardState.startSquare){const square=this._getBoardSquare(this._boardState.startSquare);square.cancelInteraction(animate?this.animationDurationMs:undefined)}this._resetBoardStateAndMoves()}_focusTabbableSquare(){if(this.tabbableSquare){this._getBoardSquare(this.tabbableSquare).focus()}}_blurTabbableSquare(){if(this.tabbableSquare){this._getBoardSquare(this.tabbableSquare).blur()}}_resetBoardStateAndMoves(){this._boardSquares.forEach(s=>{s.removeMoveState();s.markedTarget=false});this._setBoardState({id:this.interactive?"awaiting-input":"default"})}_pieceMoveable(piece){return!this.turn||piece.color===this.turn}_interactable(square){const piece=this._position[square];return!!piece&&this._pieceMoveable(piece)}_isValidMove(from,to){return from!==to&&this._getBoardSquare(to).moveTarget}_getBoardSquare(square){return this._boardSquares[getVisualIndex(square,this.orientation)]}_getStartingPositionForMove(from,to){const[fromRow,fromCol]=getVisualRowColumn(from,this.orientation);const[toRow,toCol]=getVisualRowColumn(to,this.orientation);return this._getBoardSquare(from).explicitPiecePosition||{type:"squareOffset",deltaRows:fromRow-toRow,deltaCols:fromCol-toCol}}_refreshDefaultTabbableSquare(){const oldDefaultSquare=this._defaultTabbableSquare;let pieceFound=false;if(Object.keys(this._position).length>0){for(let row=7;row>=0&&!pieceFound;row--){for(let col=0;col<=7&&!pieceFound;col++){const square=getSquare(8*row+col,this.orientation);if(this._position[square]){this._defaultTabbableSquare=square;pieceFound=true}}}}if(!pieceFound){this._defaultTabbableSquare=getSquare(56,this.orientation)}if(this._tabbableSquare===undefined&&oldDefaultSquare!==this._defaultTabbableSquare){this._getBoardSquare(oldDefaultSquare).tabbable=false;this._getBoardSquare(this._defaultTabbableSquare).tabbable=true}}_setBoardState(state){const oldState=this._boardState;this._boardState=state;if(this._boardState.id!==oldState.id){this._table.classList.toggle("dragging",this._isDragState())}if(this._boardState.highlightedSquare!==oldState.highlightedSquare){if(oldState.highlightedSquare){this._getBoardSquare(oldState.highlightedSquare).hover=false}if(this._boardState.highlightedSquare){this._getBoardSquare(this._boardState.highlightedSquare).hover=true}}}_handlePointerDown(square,e){e.preventDefault();if(e.button!==0){return}switch(this._boardState.id){case"awaiting-input":if(square&&this._interactable(square)){this._setBoardState({id:"touching-first-square",startSquare:square,touchStartX:e.clientX,touchStartY:e.clientY});this._startInteraction(square);this._getBoardSquare(square).toggleSecondaryPiece(true)}break;case"awaiting-second-touch":case"moving-piece-kb":if(this._boardState.startSquare===square){this._setBoardState({id:"canceling-second-touch",startSquare:square,touchStartX:e.clientX,touchStartY:e.clientY});this._getBoardSquare(square).toggleSecondaryPiece(true)}else if(square){this._setBoardState({id:"touching-second-square",startSquare:this._boardState.startSquare})}break;case"dragging":case"dragging-outside":case"canceling-second-touch":case"touching-first-square":case"touching-second-square":break;case"default":break;default:assertUnreachable(this._boardState)}}_handlePointerUp(square){let newFocusedSquare=square;switch(this._boardState.id){case"touching-first-square":this._getBoardSquare(this._boardState.startSquare).toggleSecondaryPiece(false);this._setBoardState({id:"awaiting-second-touch",startSquare:this._boardState.startSquare});newFocusedSquare=this._boardState.startSquare;break;case"canceling-second-touch":if(!this._userCancelMove(false)){this._setBoardState({id:"awaiting-second-touch",startSquare:this._boardState.startSquare})}newFocusedSquare=this._boardState.startSquare;break;case"dragging":case"dragging-outside":case"touching-second-square":{this._getBoardSquare(this._boardState.startSquare).toggleSecondaryPiece(false);let done=false;if(square&&this._isValidMove(this._boardState.startSquare,square)){done=this._finishMove(square,!this._isDragState());if(!done){newFocusedSquare=this._boardState.startSquare}}else{newFocusedSquare=this._boardState.startSquare;done=this._userCancelMove(square!==this._boardState.startSquare)}if(!done){this._setBoardState({id:"awaiting-second-touch",startSquare:this._boardState.startSquare});this._getBoardSquare(this._boardState.startSquare).resetPiecePosition(this.animationDurationMs)}}break;case"awaiting-input":case"moving-piece-kb":case"awaiting-second-touch":break;case"default":break;default:assertUnreachable(this._boardState)}if(this._focusedSquare&&newFocusedSquare){this.tabbableSquare=newFocusedSquare;this._focusTabbableSquare()}this._preventClickHandling=true;setTimeout(()=>{this._preventClickHandling=false},Board.POINTERUP_CLICK_PREVENT_DURATION_MS)}_handlePointerMove(square,e){switch(this._boardState.id){case"canceling-second-touch":case"touching-first-square":{const delta=Math.sqrt((e.clientX-this._boardState.touchStartX)**2+(e.clientY-this._boardState.touchStartY)**2);const squareWidth=this._getBoardSquare(this._boardState.startSquare).width;const threshold=Math.max(Board.DRAG_THRESHOLD_MIN_PIXELS,Board.DRAG_THRESHOLD_SQUARE_WIDTH_FRACTION*squareWidth);if(delta>threshold||square!==this._boardState.startSquare){this._getBoardSquare(this._boardState.startSquare).displacePiece(e.clientX,e.clientY);if(square){this._setBoardState({id:"dragging",startSquare:this._boardState.startSquare,highlightedSquare:this._isValidMove(this._boardState.startSquare,square)?square:undefined})}else{this._setBoardState({id:"dragging-outside",startSquare:this._boardState.startSquare})}}}break;case"dragging":case"dragging-outside":this._getBoardSquare(this._boardState.startSquare).displacePiece(e.clientX,e.clientY);if(square&&square!==this._boardState.highlightedSquare){this._setBoardState({id:"dragging",startSquare:this._boardState.startSquare,highlightedSquare:this._isValidMove(this._boardState.startSquare,square)?square:undefined})}else if(!square&&this._boardState.id!=="dragging-outside"){this._setBoardState({id:"dragging-outside",startSquare:this._boardState.startSquare})}break;case"awaiting-input":case"awaiting-second-touch":case"default":case"moving-piece-kb":case"touching-second-square":break;default:assertUnreachable(this._boardState)}}_handleClick(square){if(this._preventClickHandling){return}switch(this._boardState.id){case"awaiting-input":if(square&&this._interactable(square)){this._setBoardState({id:"awaiting-second-touch",startSquare:square});this._startInteraction(square)}break;case"awaiting-second-touch":case"moving-piece-kb":{const done=square&&this._isValidMove(this._boardState.startSquare,square)?this._finishMove(square,true):this._userCancelMove(square!==this._boardState.startSquare);if(!done){this._setBoardState({id:"awaiting-second-touch",startSquare:this._boardState.startSquare});this._getBoardSquare(this._boardState.startSquare).resetPiecePosition(this.animationDurationMs)}}break;case"touching-first-square":case"touching-second-square":case"canceling-second-touch":case"dragging":case"dragging-outside":case"default":break;default:assertUnreachable(this._boardState)}if(this._focusedSquare&&square){this.tabbableSquare=square;this._focusTabbableSquare()}}_handleFocusIn(square){if(square){if(square!==this.tabbableSquare||this._tabbableSquare===undefined){this.tabbableSquare=square}}}_handleKeyDown(square,e){if(e.key==="Enter"||e.key===" "){e.preventDefault();switch(this._boardState.id){case"awaiting-input":if(square&&this._interactable(square)){this._setBoardState({id:"moving-piece-kb",startSquare:square,highlightedSquare:undefined});this._startInteraction(square)}break;case"moving-piece-kb":case"awaiting-second-touch":if(square&&this._isValidMove(this._boardState.startSquare,square)){this._finishMove(square,true)}else{this._userCancelMove(false)}break;case"dragging":case"dragging-outside":case"touching-first-square":case"touching-second-square":case"canceling-second-touch":break;case"default":break;default:assertUnreachable(this._boardState)}}else{const currentIdx=getVisualIndex(this.tabbableSquare,this.orientation);const currentRow=currentIdx>>3;const currentCol=currentIdx&7;let newIdx=currentIdx;let keyHandled=false;switch(e.key){case"ArrowRight":case"Right":newIdx=8*currentRow+Math.min(7,currentCol+1);keyHandled=true;break;case"ArrowLeft":case"Left":newIdx=8*currentRow+Math.max(0,currentCol-1);keyHandled=true;break;case"ArrowDown":case"Down":newIdx=8*Math.min(7,currentRow+1)+currentCol;keyHandled=true;break;case"ArrowUp":case"Up":newIdx=8*Math.max(0,currentRow-1)+currentCol;keyHandled=true;break;case"Home":newIdx=e.ctrlKey?0:8*currentRow;keyHandled=true;break;case"End":newIdx=e.ctrlKey?63:8*currentRow+7;keyHandled=true;break;case"PageUp":newIdx=currentCol;keyHandled=true;break;case"PageDown":newIdx=56+currentCol;keyHandled=true;break}if(keyHandled){e.preventDefault()}if(newIdx!==currentIdx){this.tabbableSquare=getSquare(newIdx,this.orientation);this._focusTabbableSquare();switch(this._boardState.id){case"moving-piece-kb":case"awaiting-second-touch":this._setBoardState({id:"moving-piece-kb",startSquare:this._boardState.startSquare,highlightedSquare:this._boardState.startSquare!==this.tabbableSquare?this._tabbableSquare:undefined});break;case"awaiting-input":case"touching-first-square":case"touching-second-square":case"canceling-second-touch":case"dragging":case"dragging-outside":break;case"default":break;default:assertUnreachable(this._boardState)}}}}_makeEventHandler(callback){const boundCallback=callback.bind(this);return e=>{const square=Board._isMouseEvent(e)?this._shadowRef.elementsFromPoint(e.clientX,e.clientY).map(e=>Board._extractSquareData(e)).find(e=>!!e):Board._extractSquareData(e.target);boundCallback(square,e)}}_isDragState(){return["dragging","dragging-outside"].includes(this._boardState.id)}static _extractSquareData(target){if(!!target&&!!target.dataset){const dataset=target.dataset;return keyIsSquare(dataset.square)?dataset.square:undefined}return undefined}static _isMouseEvent(e){return e.clientX!==undefined}static _isSlotElement(e){return!!e&&e.assignedElements!==undefined}}Object.defineProperty(Board,"DRAG_THRESHOLD_SQUARE_WIDTH_FRACTION",{enumerable:true,configurable:true,writable:true,value:.1});Object.defineProperty(Board,"DRAG_THRESHOLD_MIN_PIXELS",{enumerable:true,configurable:true,writable:true,value:2});Object.defineProperty(Board,"POINTERUP_CLICK_PREVENT_DURATION_MS",{enumerable:true,configurable:true,writable:true,value:250});var css_248z=":host{--square-color-dark:#4c946a;--square-color-light:#e0ddcc;--square-color-dark-hover:#1cc45f;--square-color-light-hover:#fde968;--square-color-dark-active:#19c257;--square-color-light-active:#fadd4c;--outline-color-dark-active:rgba(33,237,94,.95);--outline-color-light-active:hsla(66,97%,72%,.95);--outline-color-focus:rgba(248,140,32,.9);--outline-blur-radius:3px;--outline-spread-radius:4px;--coords-font-size:0.7rem;--coords-font-family:sans-serif;--outer-gutter-width:4%;--inner-border-width:1px;--coords-inside-coord-padding-left:0.5%;--coords-inside-coord-padding-right:0.5%;--move-target-marker-color-dark-square:rgba(8,38,20,.9);--move-target-marker-color-light-square:rgba(8,38,20,.9);--move-target-marker-radius:24%;--move-target-marker-radius-occupied:82%;--ghost-piece-opacity:0.35;--piece-drag-z-index:9999;--piece-drag-coarse-scale:2.4;--piece-padding:3%;--arrow-color-primary:rgba(255,170,0,.8);--arrow-color-secondary:rgba(248,85,63,.8);display:block}:host([hidden]){display:none}.board{border:var(--inner-border-width) solid var(--inner-border-color,var(--square-color-dark));border-collapse:collapse;box-sizing:border-box;table-layout:fixed;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.board>tr>td{padding:12.5% 0 0;position:relative}[data-square]{background-color:var(--p-square-color);bottom:0;color:var(--p-label-color);font-family:var(--coords-font-family);font-size:var(--coords-font-size);height:100%;left:0;position:absolute;right:0;top:0;width:100%}[data-square]:focus{box-shadow:inset 0 0 var(--outline-blur-radius) var(--outline-spread-radius) var(--outline-color-focus);outline:none}[data-square].marked-target{background:radial-gradient(var(--p-move-target-marker-color) var(--move-target-marker-radius),var(--p-square-color) calc(var(--move-target-marker-radius) + 1px))}[data-square].has-content.marked-target,[data-square].has-piece.marked-target{background:radial-gradient(var(--p-square-color) var(--move-target-marker-radius-occupied),var(--p-move-target-marker-color) calc(var(--move-target-marker-radius-occupied) + 1px))}[data-square].move-start{--p-square-color:var(--p-square-color-active)}[data-square].move-start:not(:focus){box-shadow:inset 0 0 var(--outline-blur-radius) var(--outline-spread-radius) var(--p-outline-color-active)}@media (hover:hover){[data-square]:is(.moveable,.move-target):hover{--p-square-color:var(--p-square-color-hover)}}[data-square].hover{--p-square-color:var(--p-square-color-hover)}table:not(.dragging) [data-square]:is(.moveable,.move-start,.move-target){cursor:pointer}table.dragging{cursor:grab}.wrapper{position:relative}.coords{display:none;font-family:var(--coords-font-family);font-size:var(--coords-font-size);pointer-events:none;position:absolute;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.coord{box-sizing:border-box;display:flex}.coords.file>.coord{width:12.5%}.coords.rank{flex-direction:column}.coords.rank>.coord{height:12.5%}.wrapper.outside{background-color:var(--square-color-light);padding:var(--outer-gutter-width)}.wrapper.outside>.coords{color:var(--square-color-dark);display:flex}.wrapper.outside>.coords>.coord{align-items:center;justify-content:center}.wrapper.outside>.coords.file{bottom:0;height:var(--outer-gutter-width);left:var(--outer-gutter-width);right:var(--outer-gutter-width);width:calc(100% - var(--outer-gutter-width)*2)}.wrapper.outside>.coords.rank{bottom:var(--outer-gutter-width);height:calc(100% - var(--outer-gutter-width)*2);left:0;top:var(--outer-gutter-width);width:var(--outer-gutter-width)}.wrapper.inside>.coords{bottom:0;display:flex;height:100%;left:0;right:0;top:0;width:100%}.wrapper.inside>.coords>.coord.light{color:var(--square-color-dark)}.wrapper.inside>.coords>.coord.dark{color:var(--square-color-light)}.wrapper.inside>.coords.file>.coord{align-items:flex-end;justify-content:flex-end;padding-right:var(--coords-inside-coord-padding-right)}.wrapper.inside>.coords.rank>.coord{padding-left:var(--coords-inside-coord-padding-left)}[data-square-color=dark]{--p-square-color:var(--square-color-dark);--p-label-color:var(--square-color-light);--p-square-color-hover:var(--square-color-dark-hover);--p-move-target-marker-color:var(--move-target-marker-color-dark-square);--p-square-color-active:var(--square-color-dark-active);--p-outline-color-active:var(--outline-color-dark-active)}[data-square-color=light]{--p-square-color:var(--square-color-light);--p-label-color:var(--square-color-dark);--p-square-color-hover:var(--square-color-light-hover);--p-move-target-marker-color:var(--move-target-marker-color-light-square);--p-square-color-active:var(--square-color-light-active);--p-outline-color-active:var(--outline-color-light-active)}[data-square] .piece,[data-square] .slot{bottom:0;height:100%;left:0;pointer-events:none;position:absolute;right:0;top:0;width:100%}[data-square] .piece{background-origin:content-box;background-repeat:no-repeat;background-size:cover;box-sizing:border-box;padding:var(--piece-padding);z-index:10}[data-square] .piece.moving{z-index:15}[data-square] .piece.secondary{opacity:var(--ghost-piece-opacity);z-index:5}[data-square].move-start .piece:not(.secondary){z-index:var(--piece-drag-z-index)}@media (pointer:coarse){[data-square] .piece{--p-piece-drag-scale:var(--piece-drag-coarse-scale)}}.bb{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-rule:evenodd;fill-opacity:1;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cg style='fill:%23000;stroke:%23000;stroke-linecap:butt'%3E%3Cpath d='M9 36.6c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.35.49-2.32.47-3-.5 1.35-1.46 3-2 3-2z'/%3E%3Cpath d='M15 32.6c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z'/%3E%3Cpath d='M25 8.6a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z'/%3E%3C/g%3E%3Cpath d='M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5' style='fill:none;stroke:%23fff;stroke-linejoin:miter' transform='translate(0 .6)'/%3E%3C/g%3E%3C/svg%3E\")}.bk{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22.5 11.63V6' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5' style='fill:%23000;fill-opacity:1;stroke-linecap:butt;stroke-linejoin:miter'/%3E%3Cpath d='M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7' style='fill:%23000;stroke:%23000'/%3E%3Cpath d='M20 8h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M32 29.5s8.5-4 6.03-9.65C34.15 14 25 18 22.5 24.5v2.1-2.1C20 18 10.85 14 6.97 19.85 4.5 25.5 13 29.5 13 29.5' style='fill:none;stroke:%23fff'/%3E%3Cpath d='M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0' style='fill:none;stroke:%23fff'/%3E%3C/g%3E%3C/svg%3E\")}.bn{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0z' style='fill:%23fff;stroke:%23fff' transform='translate(0 .3)'/%3E%3Cpath d='M15 15.5a.5 1.5 0 1 1-1 0 .5 1.5 0 1 1 1 0z' transform='rotate(30 13.94 15.65)' style='fill:%23fff;stroke:%23fff'/%3E%3Cpath d='m24.55 10.4-.45 1.45.5.15c3.15 1 5.65 2.49 7.9 6.75S35.75 29.06 35.25 39l-.05.5h2.25l.05-.5c.5-10.06-.88-16.85-3.25-21.34-2.37-4.49-5.79-6.64-9.19-7.16l-.51-.1z' style='fill:%23fff;stroke:none' transform='translate(0 .3)' stroke='none'/%3E%3C/g%3E%3C/svg%3E\")}.bp{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z' style='opacity:1;fill:%23000;fill-opacity:1;fill-rule:nonzero;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'/%3E%3C/svg%3E\")}.bq{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23000;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round'%3E%3Cpath d='M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z' style='stroke-linecap:butt;fill:%23000'/%3E%3Cpath d='M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1 2.5-1 2.5-1.5 1.5 0 2.5 0 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z'/%3E%3Cpath d='M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0'/%3E%3Ccircle cx='6' cy='12' r='2'/%3E%3Ccircle cx='14' cy='9' r='2'/%3E%3Ccircle cx='22.5' cy='8' r='2'/%3E%3Ccircle cx='31' cy='9' r='2'/%3E%3Ccircle cx='39' cy='12' r='2'/%3E%3Cpath d='M11 38.5a35 35 1 0 0 23 0' style='fill:none;stroke:%23000;stroke-linecap:butt'/%3E%3Cpath d='M11 29a35 35 1 0 1 23 0m-21.5 2.5h20m-21 3a35 35 1 0 0 22 0m-23 3a35 35 1 0 0 24 0' style='fill:none;stroke:%23fff'/%3E%3C/g%3E%3C/svg%3E\")}.br{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:%23000;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M9 39h27v-3H9v3zm3.5-7 1.5-2.5h17l1.5 2.5h-20zm-.5 4v-4h21v4H12z' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='M14 29.5v-13h17v13H14z' style='stroke-linecap:butt;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3Cpath d='M14 16.5 11 14h23l-3 2.5H14zM11 14V9h4v2h5V9h5v2h5V9h4v5H11z' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='M12 35.5h21m-20-4h19m-18-2h17m-17-13h17M11 14h23' style='fill:none;stroke:%23fff;stroke-width:1;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3C/g%3E%3C/svg%3E\")}.wb{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-rule:evenodd;fill-opacity:1;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1' transform='translate(0 .6)'%3E%26gt;%3Cg style='fill:%23fff;stroke:%23000;stroke-linecap:butt'%3E%3Cpath d='M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.35.49-2.32.47-3-.5 1.35-1.46 3-2 3-2z'/%3E%3Cpath d='M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z'/%3E%3Cpath d='M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z'/%3E%3C/g%3E%3Cpath d='M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3C/g%3E%3C/svg%3E\")}.wk{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22.5 11.63V6M20 8h5' style='fill:none;stroke:%23000;stroke-linejoin:miter'/%3E%3Cpath d='M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5' style='fill:%23fff;stroke:%23000;stroke-linecap:butt;stroke-linejoin:miter'/%3E%3Cpath d='M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7' style='fill:%23fff;stroke:%23000'/%3E%3Cpath d='M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0' style='fill:none;stroke:%23000'/%3E%3C/g%3E%3C/svg%3E\")}.wn{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21' style='fill:%23fff;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3' style='fill:%23fff;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0z' style='fill:%23000;stroke:%23000' transform='translate(0 .3)'/%3E%3Cpath d='M15 15.5a.5 1.5 0 1 1-1 0 .5 1.5 0 1 1 1 0z' transform='rotate(30 13.94 15.65)' style='fill:%23000;stroke:%23000'/%3E%3C/g%3E%3C/svg%3E\")}.wp{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cpath d='M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z' style='opacity:1;fill:%23fff;fill-opacity:1;fill-rule:nonzero;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'/%3E%3C/svg%3E\")}.wq{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='fill:%23fff;stroke:%23000;stroke-width:1.5;stroke-linejoin:round'%3E%3Cpath d='M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z'/%3E%3Cpath d='M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1 2.5-1 2.5-1.5 1.5 0 2.5 0 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z'/%3E%3Cpath d='M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0' style='fill:none'/%3E%3Ccircle cx='6' cy='12' r='2'/%3E%3Ccircle cx='14' cy='9' r='2'/%3E%3Ccircle cx='22.5' cy='8' r='2'/%3E%3Ccircle cx='31' cy='9' r='2'/%3E%3Ccircle cx='39' cy='12' r='2'/%3E%3C/g%3E%3C/svg%3E\")}.wr{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='45'%3E%3Cg style='opacity:1;fill:%23fff;fill-opacity:1;fill-rule:evenodd;stroke:%23000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1'%3E%3Cpath d='M9 39h27v-3H9v3zm3-3v-4h21v4H12zm-1-22V9h4v2h5V9h5v2h5V9h4v5' style='stroke-linecap:butt' transform='translate(0 .3)'/%3E%3Cpath d='m34 14.3-3 3H14l-3-3'/%3E%3Cpath d='M31 17v12.5H14V17' style='stroke-linecap:butt;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3Cpath d='m31 29.8 1.5 2.5h-20l1.5-2.5'/%3E%3Cpath d='M11 14h23' style='fill:none;stroke:%23000;stroke-linejoin:miter' transform='translate(0 .3)'/%3E%3C/g%3E%3C/svg%3E\")}.arrows{border:var(--inner-border-width) solid transparent;bottom:0;box-sizing:border-box;height:100%;left:0;pointer-events:none;position:absolute;right:0;top:0;touch-action:none;width:100%;z-index:20}.arrow-primary{color:var(--arrow-color-primary)}.arrow-secondary{color:var(--arrow-color-secondary)}";const COORDINATES_PLACEMENTS=["inside","outside","hidden"];class Coordinates{constructor(props){Object.defineProperty(this,"element",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_coordElements",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_orientation",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_direction",{enumerable:true,configurable:true,writable:true,value:void 0});this.element=makeHTMLElement("div",{attributes:{role:"presentation","aria-hidden":"true"},classes:["coords",props.direction]});this._direction=props.direction;this._orientation=props.orientation;this._coordElements=new Array(8);const evenSquareColor=props.direction==="file"?"dark":"light";const oddSquareColor=props.direction==="file"?"light":"dark";for(let i=0;i<8;i++){const color=i%2===0?evenSquareColor:oddSquareColor;const textElement=makeHTMLElement("div",{classes:["coord",color]});this._coordElements[i]=textElement;this.element.appendChild(textElement)}this._updateCoordsText()}get orientation(){return this._orientation}set orientation(value){this._orientation=value;this._updateCoordsText()}_updateCoordsText(){for(let i=0;i<8;i++){if(this._direction==="file"){this._coordElements[i].textContent=String.fromCharCode("a".charCodeAt(0)+(this.orientation==="white"?i:7-i))}else{this._coordElements[i].textContent=`${this.orientation==="white"?8-i:i+1}`}}}}function isCoordinatesPlacement(value){return COORDINATES_PLACEMENTS.includes(value)}class Arrows{constructor(orientation){Object.defineProperty(this,"element",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_defs",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_group",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_orientation",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_arrows",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_arrowElements",{enumerable:true,configurable:true,writable:true,value:new Map});Object.defineProperty(this,"_markerElements",{enumerable:true,configurable:true,writable:true,value:new Map});this.element=makeSVGElement("svg",{attributes:{viewBox:"0 0 80 80"},classes:["arrows"]});this._orientation=orientation;this._defs=makeSVGElement("defs");this.element.appendChild(this._defs);this._group=makeSVGElement("g");this.element.appendChild(this._group)}get arrows(){return this._arrows}set arrows(arrows){const validArrows=arrows?.filter(a=>a.from!==a.to);const brushes=validArrows?new Set(validArrows.map(a=>Arrows._escapedBrushName(a.brush))):new Set;const oldBrushes=new Set(this._markerElements.keys());oldBrushes.forEach(key=>{if(!brushes.has(key)){const marker=this._markerElements.get(key);if(marker){this._defs.removeChild(marker);this._markerElements.delete(key)}}});brushes.forEach(key=>{if(!oldBrushes.has(key)){const marker=Arrows._makeMarker(key);this._defs.appendChild(marker);this._markerElements.set(key,marker)}});const oldHashes=new Set(this._arrowElements.keys());const newHashes=validArrows?new Set(validArrows.map(arrow=>Arrows._arrowHash(arrow))):new Set;oldHashes.forEach(hash=>{if(!newHashes.has(hash)){const element=this._arrowElements.get(hash);if(element){this._group.removeChild(element);this._arrowElements.delete(hash)}}});validArrows?.forEach(arrow=>{const hash=Arrows._arrowHash(arrow);if(!this._arrowElements.has(hash)){const element=this._makeArrow(arrow);this._arrowElements.set(hash,element);this._group.appendChild(element)}});this._arrows=validArrows?[...validArrows]:undefined}get orientation(){return this._orientation}set orientation(value){if(value!==this._orientation){this._orientation=value;this._arrows?.forEach(arrow=>{const hash=Arrows._arrowHash(arrow);const element=this._arrowElements.get(hash);if(element){this._group.removeChild(element)}const newElement=this._makeArrow(arrow);this._group.appendChild(newElement);this._arrowElements.set(hash,newElement)})}}_makeArrow(arrow){const strokeWidth=Arrows._getSvgStrokeWidth(arrow.weight||Arrows._DEFAULT_ARROW_WEIGHT);const fromRowCol=getVisualRowColumn(arrow.from,this.orientation);const toRowCol=getVisualRowColumn(arrow.to,this.orientation);const coords={x1:fromRowCol[1]*10+5,y1:fromRowCol[0]*10+5,x2:toRowCol[1]*10+5,y2:toRowCol[0]*10+5};const endOffset=Arrows._computeXYProjections(strokeWidth*Arrows._ARROW_LENGTH,coords);const startOffset=Arrows._computeXYProjections(Arrows._ARROW_START_MARGIN,coords);const escapedBrushName=Arrows._escapedBrushName(arrow.brush||Arrows._DEFAULT_BRUSH_NAME);const className=Arrows._makeArrowClass(escapedBrushName);const line=makeSVGElement("line",{attributes:{x1:`${coords.x1+startOffset.x}`,y1:`${coords.y1+startOffset.y}`,x2:`${coords.x2-endOffset.x}`,y2:`${coords.y2-endOffset.y}`,stroke:"currentColor","stroke-width":`${strokeWidth}`,"marker-end":`url(#${Arrows._makeArrowHeadId(escapedBrushName)})`,part:className},classes:[className]});return line}static _makeMarker(escapedBrushName){const marker=makeSVGElement("marker",{attributes:{id:Arrows._makeArrowHeadId(escapedBrushName),refX:"0",refY:`${Arrows._ARROW_WIDTH/2}`,orient:"auto",markerWidth:`${Arrows._ARROW_LENGTH}`,markerHeight:`${Arrows._ARROW_WIDTH}`}});const className=Arrows._makeArrowClass(escapedBrushName);const polygon=makeSVGElement("polygon",{attributes:{fill:"currentColor",points:`0,0 ${Arrows._ARROW_LENGTH},${Arrows._ARROW_WIDTH/2} 0,${Arrows._ARROW_WIDTH}`,part:className},classes:[className]});marker.appendChild(polygon);return marker}static _getSvgStrokeWidth(weight){switch(weight){case"bold":return 2.5;case"light":return 1;case"normal":default:return 1.8}}static _escapedBrushName(brush){return CSS.escape(brush||Arrows._DEFAULT_BRUSH_NAME)}static _makeArrowHeadId(escapedBrushName){return`arrowhead-${escapedBrushName}`}static _makeArrowClass(escapedBrushName){return`arrow-${escapedBrushName}`}static _computeXYProjections(length,arrow){const angle=Math.atan2(arrow.y2-arrow.y1,arrow.x2-arrow.x1);return{x:length*Math.cos(angle),y:length*Math.sin(angle)}}static _arrowHash(arrow){return`${arrow.from}_${arrow.to}_${arrow.brush||Arrows._DEFAULT_BRUSH_NAME}_${arrow.weight||Arrows._DEFAULT_ARROW_WEIGHT}`}}Object.defineProperty(Arrows,"_ARROW_LENGTH",{enumerable:true,configurable:true,writable:true,value:2.4});Object.defineProperty(Arrows,"_ARROW_WIDTH",{enumerable:true,configurable:true,writable:true,value:2});Object.defineProperty(Arrows,"_ARROW_START_MARGIN",{enumerable:true,configurable:true,writable:true,value:2.7});Object.defineProperty(Arrows,"_DEFAULT_BRUSH_NAME",{enumerable:true,configurable:true,writable:true,value:"primary"});Object.defineProperty(Arrows,"_DEFAULT_ARROW_WEIGHT",{enumerable:true,configurable:true,writable:true,value:"normal"});class GChessBoardElement extends HTMLElement{static get observedAttributes(){return["orientation","turn","interactive","fen","coordinates","animation-duration"]}constructor(){super();Object.defineProperty(this,"_shadow",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_style",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_wrapper",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_board",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_fileCoords",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_rankCoords",{enumerable:true,configurable:true,writable:true,value:void 0});Object.defineProperty(this,"_arrows",{enumerable:true,configurable:true,writable:true,value:void 0});this._shadow=this.attachShadow({mode:"open"});this._style=document.createElement("style");this._style.textContent=css_248z;this._shadow.appendChild(this._style);this._wrapper=makeHTMLElement("div",{classes:["wrapper",GChessBoardElement._DEFAULT_COORDS_PLACEMENT]});this._shadow.appendChild(this._wrapper);this._board=new Board({orientation:GChessBoardElement._DEFAULT_SIDE,animationDurationMs:GChessBoardElement._DEFAULT_ANIMATION_DURATION_MS},e=>this.dispatchEvent(e),this._shadow);this._wrapper.appendChild(this._board.element);this._fileCoords=new Coordinates({direction:"file",placement:GChessBoardElement._DEFAULT_COORDS_PLACEMENT,orientation:GChessBoardElement._DEFAULT_SIDE});this._rankCoords=new Coordinates({direction:"rank",placement:GChessBoardElement._DEFAULT_COORDS_PLACEMENT,orientation:GChessBoardElement._DEFAULT_SIDE});this._wrapper.appendChild(this._fileCoords.element);this._wrapper.appendChild(this._rankCoords.element);this._arrows=new Arrows(GChessBoardElement._DEFAULT_SIDE);this._wrapper.appendChild(this._arrows.element)}connectedCallback(){this._board.addGlobalListeners()}disconnectedCallback(){this._board.removeGlobalListeners()}attributeChangedCallback(name,_,newValue){switch(name){case"interactive":this._board.interactive=this.interactive;break;case"coordinates":this._wrapper.classList.toggle("outside",this.coordinates==="outside");this._wrapper.classList.toggle("inside",this.coordinates==="inside");break;case"orientation":this._board.orientation=this.orientation;this._fileCoords.orientation=this.orientation;this._rankCoords.orientation=this.orientation;this._arrows.orientation=this.orientation;break;case"turn":this._board.turn=this.turn;break;case"fen":if(newValue!==null){this.fen=newValue}else{this.position={}}break;case"animation-duration":this._board.animationDurationMs=this.animationDuration;break;default:assertUnreachable(name)}}get orientation(){return this._parseRestrictedStringAttributeWithDefault("orientation",isSide,GChessBoardElement._DEFAULT_SIDE)}set orientation(value){this.setAttribute("orientation",value)}get turn(){return this._parseRestrictedStringAttribute("turn",isSide)}set turn(value){if(value){this.setAttribute("turn",value)}else{this.removeAttribute("turn")}}get interactive(){return this._hasBooleanAttribute("interactive")}set interactive(interactive){this._setBooleanAttribute("interactive",interactive)}get position(){return this._board.position}set position(value){this._board.position={...value}}get fen(){return getFen(this._board.position)}set fen(value){const position=getPosition(value);if(position!==undefined){this.position=position}else{throw new Error(`Invalid FEN position: ${value}`)}}get coordinates(){return this._parseRestrictedStringAttributeWithDefault("coordinates",isCoordinatesPlacement,GChessBoardElement._DEFAULT_COORDS_PLACEMENT)}set coordinates(value){this.setAttribute("coordinates",value)}get animationDuration(){return this._parseNumberAttribute("animation-duration",GChessBoardElement._DEFAULT_ANIMATION_DURATION_MS)}set animationDuration(value){this._setNumberAttribute("animation-duration",value)}get arrows(){return this._arrows.arrows}set arrows(arrows){this._arrows.arrows=arrows}addEventListener(type,listener,options){super.addEventListener(type,listener,options)}removeEventListener(type,listener,options){super.removeEventListener(type,listener,options)}startMove(square,targetSquares){this._board.startMove(square,targetSquares)}cancelMove(){this._board.cancelMove()}_hasBooleanAttribute(name){return this.hasAttribute(name)&&this.getAttribute(name)?.toLowerCase()!=="false"}_setBooleanAttribute(name,value){if(value){this.setAttribute(name,"")}else{this.removeAttribute(name)}}_setNumberAttribute(name,value){this.setAttribute(name,value.toString())}_parseRestrictedStringAttribute(name,guard){const value=this.getAttribute(name);return guard(value)?value:undefined}_parseRestrictedStringAttributeWithDefault(name,guard,defaultValue){const parsed=this._parseRestrictedStringAttribute(name,guard);return parsed!==undefined?parsed:defaultValue}_parseNumberAttribute(name,defaultValue){const value=this.getAttribute(name);return value===null||Number.isNaN(Number(value))?defaultValue:Number(value)}}Object.defineProperty(GChessBoardElement,"_DEFAULT_SIDE",{enumerable:true,configurable:true,writable:true,value:"white"});Object.defineProperty(GChessBoardElement,"_DEFAULT_ANIMATION_DURATION_MS",{enumerable:true,configurable:true,writable:true,value:200});Object.defineProperty(GChessBoardElement,"_DEFAULT_COORDS_PLACEMENT",{enumerable:true,configurable:true,writable:true,value:"inside"});customElements.define("g-chess-board",GChessBoardElement);export{GChessBoardElement}; diff --git a/index.html b/index.html index c2fa5b0..425fd21 100644 --- a/index.html +++ b/index.html @@ -2,21 +2,34 @@ Elo Worldle - + - - - - + + + + + - - - + + + - + - + - +

- +

         
-        
+

Players: (click name to toggle strikethrough)

-
-
+
- -
diff --git a/main.min.js b/main.min.js new file mode 100644 index 0000000..c43325e --- /dev/null +++ b/main.min.js @@ -0,0 +1,6 @@ +import{Chess,Ox88,DEFAULT_POSITION,SQUARES,rank,file,WHITE,BLACK,KING}from"./chess.min.js";function with_move(board,move){let new_board=new Chess(board.fen());new_board.move(move);return new_board}function shuffle(array){let currentIndex=array.length,randomIndex;while(currentIndex!=0){randomIndex=Math.floor(Math.random()*currentIndex);currentIndex--;[array[currentIndex],array[randomIndex]]=[array[randomIndex],array[currentIndex]]}return array}function min_move_by(board,score){let best_score=Infinity;let best_move="";let moves=board.moves();shuffle(moves);for(const move of moves){try{let new_board=with_move(board,move);let new_score=score(new_board);if(new_score{const moves=board.moves();const move=moves[Math.floor(Math.random()*moves.length)];return move};function make_lexo(m){let{rank:src_row,file:src_col}=rank_and_file(m.from);let{rank:dst_row,file:dst_col}=rank_and_file(m.to);let promote_to=m.promotion;return[src_row,src_col,dst_row,dst_col,promote_to]}function lexo_sort_moves(a,b){let al=make_lexo(a);let bl=make_lexo(a);for(const i in al){if(al[i]bl[i]){return 1}}return 0}const first_move=board=>{const moves=board.moves({verbose:true}).sort(lexo_sort_moves);return moves[0].san};const alphabetical=board=>{const moves=board.moves();moves.sort();return moves[0]};const equalizer_state_default={piece_moves:{p:0,b:0,n:0,r:0,q:0,k:0},square_visits:{a8:0,b8:0,c8:0,d8:0,e8:0,f8:0,g8:0,h8:0,a7:0,b7:0,c7:0,d7:0,e7:0,f7:0,g7:0,h7:0,a6:0,b6:0,c6:0,d6:0,e6:0,f6:0,g6:0,h6:0,a5:0,b5:0,c5:0,d5:0,e5:0,f5:0,g5:0,h5:0,a4:0,b4:0,c4:0,d4:0,e4:0,f4:0,g4:0,h4:0,a3:0,b3:0,c3:0,d3:0,e3:0,f3:0,g3:0,h3:0,a2:0,b2:0,c2:0,d2:0,e2:0,f2:0,g2:0,h2:0,a1:0,b1:0,c1:0,d1:0,e1:0,f1:0,g1:0,h1:0}};var equalizer_state=equalizer_state_default;const equalizer=board=>{let best_move;let least_moved_found=Infinity;let least_visited_found=Infinity;let piece_moving="k";let square_visiting="a1";for(const move of board.moves({verbose:true})){if(equalizer_state.piece_moves[move.piece]{let num_moves=b=>b.moves().length;return min_move_by(board,num_moves)};function num_pieces_on_own_color(b){let count=0;for(const square of SQUARES){let piece=b.get(square);if(typeof piece=="boolean"){continue}if(piece.color==b.squareColor(square)){count+=1}}return count}const same_color=board=>{return min_move_by(board,b=>64-num_pieces_on_own_color(b))};const opposite_color=board=>{return min_move_by(board,num_pieces_on_own_color)};function rank_and_file(s){return{rank:rank(Ox88[s]),file:file(Ox88[s])}}function chebyshev_distance(a,b){let{rank:r1,file:f1}=rank_and_file(a);let{rank:r2,file:f2}=rank_and_file(b);return Math.max(Math.abs(r2-r1),Math.abs(f2-f1))}const START_BOARD=new Chess(DEFAULT_POSITION);function move_distance(piece){let rank=rank_and_file(piece.square).rank;let file=rank_and_file(piece.square).file;switch(piece.type){case"k":return chebyshev_distance(piece.square,find({color:HUMAN_COLOR,type:"k"},START_BOARD));case"p":return Math.abs(rank-6);case"r":return Math.abs(rank-7)+Math.min(Math.abs(file-7),Math.abs(file-0));case"n":return pieces_of_color(START_BOARD,HUMAN_COLOR).filter(p=>p.type=="k").map(p=>chebyshev_distance(piece.square,p.square)).reduce((a,b)=>Math.max(a,b));case"b":let is_black=START_BOARD.squareColor(piece.square)=="b";let dest_col=is_black?2:5;let dr=Math.abs(rank-7);let dc=Math.abs(file-dest_col);return Math.max(dr,dc);case"q":return Math.max(Math.abs(rank-7),Math.abs(file-3))}}function minimize_distances(distance_fn){const player_score=board=>{let total_distance=0;for(const piece of pieces_of_color(board,ROBOT_COLOR)){total_distance+=distance_fn(piece)}return total_distance};return b=>min_move_by(b,player_score)}const reverse_starting=minimize_distances(move_distance);function mirror_player(mirror_fn){let player_score=b=>{let total=0;for(const square of SQUARES){let reflected=mirror_fn(square);console.log(square,reflected);let piece=b.get(square);let reflection=b.get(reflected);if(typeof piece=="boolean"&&typeof reflection=="boolean"){continue}if(typeof piece=="boolean"){total+=10;continue}if(typeof reflection=="boolean"){total+=10;continue}if(piece.color==reflection.color){total+=5;continue}if(piece.type!=reflection.type){total+=1;continue}}return total};return b=>min_move_by(b,player_score)}function rank_and_file_to_algebraic(rf){let r="8";let f="h";switch(rf.rank){case 0:r="8";break;case 1:r="7";break;case 2:r="6";break;case 3:r="5";break;case 4:r="4";break;case 5:r="3";break;case 6:r="7";break;case 7:r="1";break}switch(rf.file){case 0:f="a";break;case 1:f="b";break;case 2:f="c";break;case 3:f="d";break;case 4:f="e";break;case 5:f="f";break;case 6:f="g";break;case 7:f="h";break}return`${f}${r}`}function mirror_y(s){let rf=rank_and_file(s);rf.rank=7-rf.rank;return rank_and_file_to_algebraic(rf)}const sym_mirror_y=mirror_player(mirror_y);function mirror_x(s){let rf=rank_and_file(s);rf.file=7-rf.file;return rank_and_file_to_algebraic(rf)}const sym_mirror_x=mirror_player(mirror_x);function flip(s){let rf=rank_and_file(s);rf.rank=7-rf.rank;rf.file=7-rf.file;return rank_and_file_to_algebraic(rf)}const sym_180=mirror_player(flip);function find(p,b){for(const square of SQUARES){let got=b.get(square);if(typeof got=="boolean"){continue}if(got.color==p.color&&got.type==p.type){return square}}console.log(`didn't find this :O ${p}`);return null}const suicide_king=board=>{let distance_between_kings=b=>{var _a,_b;return chebyshev_distance((_a=find({color:WHITE,type:KING},b))!==null&&_a!==void 0?_a:"a1",(_b=find({color:BLACK,type:KING},b))!==null&&_b!==void 0?_b:"a1")};return min_move_by(board,distance_between_kings)};function pieces_of_color(b,color){let flat_board=b.board().flat();let non_empties=[];for(const s of flat_board){if(typeof s=="boolean"){continue}non_empties.push(s)}return non_empties.filter(p=>p.color==color)}function num_pieces_of_color(b,color){return pieces_of_color(b,color).length}function value_piece(type){switch(type){case"p":return 1;case"b":case"n":return 3;case"r":return 5;case"q":return 9;case"k":return 413}}function value_of_pieces_of_color(b,color){return pieces_of_color(b,color).map(p=>value_piece(p.type)).reduce((a,b)=>a+b)}const HUMAN_COLOR=WHITE;const ROBOT_COLOR=BLACK;const cccp=board=>{let cccp_score=b=>{if(b.isCheckmate()){console.log("Found checkmate! :D");return 0}if(b.isCheck()){console.log("Found check! :T");return 1}let prev_value=value_of_pieces_of_color(board,HUMAN_COLOR);let new_value=value_of_pieces_of_color(b,HUMAN_COLOR);if(new_value8-rank_and_file(b.square).rank).reduce((a,b)=>a+b)};return min_move_by(board,cccp_score)};function total_chebyshev_distances_of_pieces_of_color_to_square(board,color,square){return pieces_of_color(board,color).map(p=>chebyshev_distance(p.square,square)).reduce((a,b)=>a+b)}const huddle=board=>{return min_move_by(board,b=>{var _a;return total_chebyshev_distances_of_pieces_of_color_to_square(b,ROBOT_COLOR,(_a=find({color:ROBOT_COLOR,type:KING},b))!==null&&_a!==void 0?_a:"a1")})};const swarm=board=>{return min_move_by(board,b=>{var _a;return total_chebyshev_distances_of_pieces_of_color_to_square(b,ROBOT_COLOR,(_a=find({color:HUMAN_COLOR,type:KING},b))!==null&&_a!==void 0?_a:"a1")})};function sum_of_possible_capture_value(b){let prev_value=value_of_pieces_of_color(b,ROBOT_COLOR);let sum=0;for(const move of b.moves()){let new_board=with_move(b,move);let new_value=value_of_pieces_of_color(new_board,ROBOT_COLOR);sum+=prev_value-new_value}return sum}const generous=board=>{let generous_score=b=>{return-sum_of_possible_capture_value(b)};return min_move_by(board,generous_score)};const coward=board=>min_move_by(board,sum_of_possible_capture_value);function no_i_insist_score(b){if(b.isCheckmate()){return 612}if(b.isCheck()){return 413}let prev_value=value_of_pieces_of_color(b,ROBOT_COLOR);return-(sum_of_possible_capture_value(b)/b.moves().length)}const no_i_insist=board=>{return min_move_by(board,no_i_insist_score)};const pacifist=board=>{let pacifist_score=b=>{if(b.isCheckmate()){return 612}if(b.isCheck()){return 413}let prev_value=value_of_pieces_of_color(board,HUMAN_COLOR);let new_value=value_of_pieces_of_color(b,HUMAN_COLOR);if(new_value`#${p.name}`==window.location.hash)}function get_todays_player(){var _a;let player=(_a=find_specified_in_hash())!==null&&_a!==void 0?_a:players[Math.floor(Math.random()*players.length)];console.log(`SPOILER: "today"'s player is ${player.name}`);return player}var todays_player=get_todays_player();var board=document.getElementById("board");var game=new Chess;function computer_move(){num_moves++;history+="M";return todays_player.f(game)}function show_game_over(){let game_over=document.getElementById("game-over");game_over.style.display="block";game_over.innerText=`GAME OVER; player was ${todays_player.name}`;console.log("GAME OVER")}function copy_stats_to_clipboard(){navigator.clipboard.writeText(document.getElementById("stats").innerText)}function show_win(){wins++;in_game_over=true;let win_count=document.getElementById("win-count");win_count.style.display="block";win_count.innerText=`You have won ${wins} time${wins==1?"":"s"} without refreshing${wins_exclamation_marks()}`;let game_over=document.getElementById("game-over");game_over.style.display="block";game_over.innerText="YOU WIN YOU WIN YOU WIN YOU WIN HOLY CRAP";let stats=document.getElementById("stats");stats.style.display="block";let stats_text=`#elo_worldle +#ew_${todays_player.name} +Win #${wins} +Guesses: ${num_guesses} +Moves: ${num_moves} +${history}`;stats.innerText=stats_text;let copy_stats=document.getElementById("copy-stats");copy_stats.style.display="block";copy_stats.addEventListener("click",_=>copy_stats_to_clipboard());let play_again=document.getElementById("play-again");play_again.style.display="block";play_again.addEventListener("click",_=>reset());console.log("PLAYER IS WIN")}var num_guesses=0;var num_moves=0;var history="";function append_players_to(n){for(const player of players){let child=document.createElement("div");child.classList.add("player");let child_name=document.createElement("h1");child_name.classList.add("player-name");child_name.innerText=player.name;let child_desc=document.createElement("p");child_desc.classList.add("player-desc");child_desc.innerText=player.description;let guess_button=document.createElement("button");guess_button.innerText="^ guess ^";guess_button.onclick=_=>{num_guesses++;history+="G";if(todays_player.name==player.name){show_win();child.classList.add("correct")}else{child.classList.add("wrong")}guess_button.onclick=()=>{}};child.appendChild(child_name);child.appendChild(child_desc);child.appendChild(guess_button);child_name.addEventListener("click",_=>child.classList.toggle("strikethrough"));n.appendChild(child)}}var wins=0;function wins_exclamation_marks(){let base_10_log_of_wins=Math.floor(Math.log10(wins));return base_10_log_of_wins>0?"!".repeat(base_10_log_of_wins):"."}var in_game_over=false;function reset(){let play_again=document.getElementById("play-again");play_again.style.display="none";play_again.onclick=_=>console.log("you can't play again again sorry :]");in_game_over=false;todays_player=get_todays_player();game=new Chess;board.fen=game.fen();board.turn="white";board.interactive=true;num_guesses=0;num_moves=0;history="";equalizer_state=equalizer_state_default;let players=document.getElementById("players");players.innerHTML="";append_players_to(players);let stats=document.getElementById("stats");stats.innerHTML="";stats.style.display="none";document.getElementById("copy-stats").style.display="none";document.getElementById("game-over").style.display="none"}window.addEventListener("DOMContentLoaded",()=>{board.addEventListener("movestart",e=>{console.log(`Move started: ${e.detail.from}, ${e.detail.piece.color} ${e.detail.piece.pieceType}`);e.detail.setTargets(game.moves({square:e.detail.from,verbose:true}).map(m=>m.to));if(game.isGameOver()){show_game_over()}});board.addEventListener("moveend",e=>{console.log(`Move ending: ${e.detail.from} -> ${e.detail.to}, ${e.detail.piece.color} ${e.detail.piece.pieceType}`);const move=game.move({from:e.detail.from,to:e.detail.to,promotion:"q"});if(move===null){e.preventDefault()}if(game.isGameOver()){show_game_over()}});board.addEventListener("movefinished",e=>{board.fen=game.fen();board.turn=game.turn()==="w"?"white":"black";board.interactive=game.turn()==="w";if(!board.interactive){let move=computer_move();if(!move){console.log("Didn't get a move from the computer (this is fine if the game is over)");return}console.log(`Computer move: ${move}`);game.move(move);board.fen=game.fen();board.turn="white";board.interactive=true}});document.getElementById("finish").addEventListener("click",_=>show_game_over());document.addEventListener("keypress",e=>{if(e.key=="r"&&in_game_over){reset()}else if(e.key=="c"&&in_game_over){copy_stats_to_clipboard()}});append_players_to(document.getElementById("players"))});