+ // a4 --> {x:3,y:0}
+ static SquareToCoords(sq)
+ {
+ return {
+ x: V.size.x - parseInt(sq.substr(1)),
+ y: sq[0].charCodeAt() - 97
+ };
+ }
+
+ // {x:0,y:4} --> e8
+ static CoordsToSquare(coords)
+ {
+ return String.fromCharCode(97 + coords.y) + (V.size.x - coords.x);
+ }
+
+ // Aggregates flags into one object
+ aggregateFlags()
+ {
+ return this.castleFlags;
+ }
+
+ // Reverse operation
+ disaggregateFlags(flags)
+ {
+ this.castleFlags = flags;
+ }
+
+ // En-passant square, if any
+ getEpSquare(moveOrSquare)
+ {
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ return {
+ x: square[0].charCodeAt()-97,
+ y: V.size.x-parseInt(square[1])
+ };
+ }
+ // Argument is a move:
+ const move = moveOrSquare;
+ const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
+ if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+ {
+ return {
+ x: (sx + ex)/2,
+ y: sy
+ };
+ }
+ return undefined; //default
+ }
+
+ // Can thing on square1 take thing on square2
+ canTake([x1,y1], [x2,y2])
+ {
+ return this.getColor(x1,y1) !== this.getColor(x2,y2);
+ }
+
+ // Is (x,y) on the chessboard?
+ static OnBoard(x,y)
+ {
+ return (x>=0 && x<V.size.x && y>=0 && y<V.size.y);
+ }
+
+ // Used in interface: 'side' arg == player color
+ canIplay(side, [x,y])
+ {
+ return (this.turn == side && this.getColor(x,y) == side);
+ }
+
+ // On which squares is opponent under check after our move ? (for interface)
+ getCheckSquares(move)
+ {
+ this.play(move);
+ const color = this.turn; //opponent
+ let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color)])
+ ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+ : [];
+ this.undo(move);
+ return res;
+ }
+
+ /////////////
+ // FEN UTILS
+
+ // Setup the initial random (assymetric) position
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(8), "b": new Array(8) };
+ // Shuffle pieces on first and last rank
+ for (let c of ["w","b"])
+ {
+ let positions = _.range(8);
+
+ // Get random squares for bishops
+ let randIndex = 2 * _.random(3);
+ let bishop1Pos = positions[randIndex];
+ // The second bishop must be on a square of different color
+ let randIndex_tmp = 2 * _.random(3) + 1;
+ let bishop2Pos = positions[randIndex_tmp];
+ // Remove chosen squares
+ positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+ positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+ // Get random squares for knights
+ randIndex = _.random(5);
+ let knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ randIndex = _.random(4);
+ let knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Get random square for queen
+ randIndex = _.random(3);
+ let queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Rooks and king positions are now fixed, because of the ordering rook-king-rook
+ let rook1Pos = positions[0];
+ let kingPos = positions[1];
+ let rook2Pos = positions[2];
+
+ // Finally put the shuffled pieces in the board array
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ return pieces["b"].join("") +
+ "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ pieces["w"].join("").toUpperCase() +
+ " w 1111 -"; //add turn + flags + enpassant
+ }
+
+ // "Parse" FEN: just return untransformed string data
+ static ParseFen(fen)
+ {
+ const fenParts = fen.split(" ");
+ return {
+ position: fenParts[0],
+ turn: fenParts[1],
+ flags: fenParts[2],
+ enpassant: fenParts[3],
+ };
+ }
+
+ // Return current fen (game state)
+ getFen()
+ {
+ return this.getBaseFen() + " " + this.turn + " " +
+ this.getFlagsFen() + " " + this.getEnpassantFen();
+ }
+
+ // Position part of the FEN string
+ getBaseFen()
+ {
+ let position = "";
+ for (let i=0; i<V.size.x; i++)
+ {
+ let emptyCount = 0;
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] == V.EMPTY)
+ emptyCount++;
+ else
+ {
+ if (emptyCount > 0)
+ {
+ // Add empty squares in-between
+ position += emptyCount;
+ emptyCount = 0;
+ }
+ fen += V.board2fen(this.board[i][j]);
+ }
+ }
+ if (emptyCount > 0)
+ {
+ // "Flush remainder"
+ position += emptyCount;
+ }
+ if (i < V.size.x - 1)
+ position += "/"; //separate rows
+ }
+ return position;
+ }
+
+ // Flags part of the FEN string
+ getFlagsFen()
+ {
+ let flags = "";
+ // Add castling flags
+ for (let i of ['w','b'])
+ {
+ for (let j=0; j<2; j++)
+ flags += (this.castleFlags[i][j] ? '1' : '0');
+ }
+ return flags;
+ }
+
+ // Enpassant part of the FEN string
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (L == 0)
+ return "-"; //no en-passant
+ return V.CoordsToSquare(this.epSquares[L-1]);
+ }
+
+ // Turn position fen into double array ["wb","wp","bk",...]
+ static GetBoard(position)