+ getFen() {
+ return super.getFen() + " " + this.getAmoveFen();
+ }
+
+ getFenForRepeat() {
+ return super.getFenForRepeat() + "_" + this.getAmoveFen();
+ }
+
+ getAmoveFen() {
+ const L = this.amoves.length;
+ if (L == 0) return "-";
+ return (
+ ["appear","vanish"].map(
+ mpart => {
+ if (this.amoves[L-1][mpart].length == 0) return "-";
+ return (
+ this.amoves[L-1][mpart].map(
+ av => {
+ const square = V.CoordsToSquare({ x: av.x, y: av.y });
+ return av.c + av.p + square;
+ }
+ ).join(".")
+ );
+ }
+ ).join("/")
+ );
+ }
+
+ canTake() {
+ // Captures don't occur (only pulls & pushes)
+ return false;
+ }
+
+ // Step is right, just add (push/pull) moves in this direction
+ // Direction is assumed normalized.
+ getMovesInDirection([x, y], [dx, dy], nbSteps) {
+ nbSteps = nbSteps || 8; //max 8 steps anyway
+ let [i, j] = [x + dx, y + dy];
+ let moves = [];
+ const color = this.getColor(x, y);
+ const piece = this.getPiece(x, y);
+ const lastRank = (color == 'w' ? 0 : 7);
+ let counter = 1;
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ if (i == lastRank && piece == V.PAWN) {
+ // Promotion by push or pull
+ V.PawnSpecs.promotions.forEach(p => {
+ let move = super.getBasicMove([x, y], [i, j], { c: color, p: p });
+ moves.push(move);
+ });
+ }
+ else moves.push(super.getBasicMove([x, y], [i, j]));
+ if (++counter > nbSteps) break;
+ i += dx;
+ j += dy;
+ }
+ if (!V.OnBoard(i, j) && piece != V.KING) {
+ // Add special "exit" move, by "taking king"
+ moves.push(
+ new Move({
+ start: { x: x, y: y },
+ end: { x: this.kingPos[color][0], y: this.kingPos[color][1] },
+ appear: [],
+ vanish: [{ x: x, y: y, c: color, p: piece }]
+ })
+ );
+ }
+ return moves;
+ }
+
+ // Normalize direction to know the step
+ getNormalizedDirection([dx, dy]) {
+ const absDir = [Math.abs(dx), Math.abs(dy)];
+ let divisor = 0;
+ if (absDir[0] != 0 && absDir[1] != 0 && absDir[0] != absDir[1])
+ // Knight
+ divisor = Math.min(absDir[0], absDir[1]);
+ else
+ // Standard slider (or maybe a pawn or king: same)
+ divisor = Math.max(absDir[0], absDir[1]);
+ return [dx / divisor, dy / divisor];
+ }
+
+ // There was something on x2,y2, maybe our color, pushed/pulled.
+ isAprioriValidExit([x1, y1], [x2, y2], color2) {
+ const color1 = this.getColor(x1, y1);
+ const pawnShift = (color1 == 'w' ? -1 : 1);
+ const lastRank = (color1 == 'w' ? 0 : 7);
+ const deltaX = Math.abs(x1 - x2);
+ const deltaY = Math.abs(y1 - y2);
+ const checkSlider = () => {
+ const dir = this.getNormalizedDirection([x2 - x1, y2 - y1]);
+ let [i, j] = [x1 + dir[0], y1 + dir[1]];
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ i += dir[0];
+ j += dir[1];
+ }
+ return !V.OnBoard(i, j);
+ };
+ switch (this.getPiece(x1, y1)) {
+ case V.PAWN:
+ return (
+ x1 + pawnShift == x2 &&
+ (
+ (color1 == color2 && x2 == lastRank && y1 == y2) ||
+ (color1 != color2 && deltaY == 1 && !V.OnBoard(x2, 2 * y2 - y1))
+ )
+ );
+ case V.ROOK:
+ if (x1 != x2 && y1 != y2) return false;
+ return checkSlider();
+ case V.KNIGHT:
+ return (
+ deltaX + deltaY == 3 &&
+ (deltaX == 1 || deltaY == 1) &&
+ !V.OnBoard(2 * x2 - x1, 2 * y2 - y1)
+ );
+ case V.BISHOP:
+ if (deltaX != deltaY) return false;
+ return checkSlider();
+ case V.QUEEN:
+ if (deltaX != 0 && deltaY != 0 && deltaX != deltaY) return false;
+ return checkSlider();
+ case V.KING:
+ return (
+ deltaX <= 1 &&
+ deltaY <= 1 &&
+ !V.OnBoard(2 * x2 - x1, 2 * y2 - y1)
+ );
+ }
+ return false;
+ }
+
+ isAprioriValidVertical([x1, y1], x2) {
+ const piece = this.getPiece(x1, y1);
+ const deltaX = Math.abs(x1 - x2);
+ const startRank = (this.getColor(x1, y1) == 'w' ? 6 : 1);
+ return (
+ [V.QUEEN, V.ROOK].includes(piece) ||
+ (
+ [V.KING, V.PAWN].includes(piece) &&
+ (
+ deltaX == 1 ||
+ (deltaX == 2 && piece == V.PAWN && x1 == startRank)
+ )
+ )
+ );
+ }
+
+ // NOTE: for pushes, play the pushed piece first.
+ // for pulls: play the piece doing the action first
+ // NOTE: to push a piece out of the board, make it slide until its king