+ // 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.
+ // Also, the pushed/pulled piece must exit the board.
+ 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;
+ }
+
+ // 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
+ getPotentialMovesFrom([x, y]) {
+ const color = this.turn;
+ if (this.subTurn == 1) {
+ const getMoveHash = (m) => {
+ return V.CoordsToSquare(m.start) + V.CoordsToSquare(m.end);
+ };
+ const addMoves = (dir, nbSteps) => {
+ const newMoves =
+ this.getMovesInDirection([x, y], [-dir[0], -dir[1]], nbSteps)
+ .filter(m => !movesHash[getMoveHash(m)]);
+ newMoves.forEach(m => { movesHash[getMoveHash(m)] = true; });
+ Array.prototype.push.apply(moves, newMoves);
+ };
+ // Free to play any move (if piece of my color):
+ let moves =
+ this.getColor(x, y) == color
+ ? super.getPotentialMovesFrom([x, y])
+ : [];
+ // There may be several suicide moves: keep only one
+ let hasExit = false;
+ moves = moves.filter(m => {
+ const suicide = (m.appear.length == 0);
+ if (suicide) {
+ if (hasExit) return false;
+ hasExit = true;
+ }
+ return true;
+ });
+ const pawnShift = (color == 'w' ? -1 : 1);
+ const pawnStartRank = (color == 'w' ? 6 : 1);
+ // Structure to avoid adding moves twice (can be action & move)
+ let movesHash = {};
+ moves.forEach(m => { movesHash[getMoveHash(m)] = true; });
+ // [x, y] is pushed by 'color'