makeStandardMove method

BishopState? makeStandardMove(
  1. BishopState state,
  2. Move move
)

Implementation

BishopState? makeStandardMove(BishopState state, Move move) {
  if (move is! StandardMove && move is! DropMove) {
    return null;
  }
  Square fromSq =
      move.from >= Bishop.boardStart ? state.board[move.from] : Bishop.empty;

  List<int> board = [...state.board];
  if ((move.from != Bishop.hand && !size.onBoard(move.from)) ||
      !size.onBoard(move.to)) {
    return null;
  }

  int hash = state.hash;
  hash ^= zobrist.table[zobrist.turn][Zobrist.meta];
  List<Hand>? hands = state.hands != null
      ? List.generate(state.hands!.length, (i) => List.from(state.hands![i]))
      : null;
  List<List<int>> virginFiles = List.generate(
    state.virginFiles.length,
    (i) => List.from(state.virginFiles[i]),
  );
  List<int> pieces = List.from(state.pieces);
  GameResult? result;

  // TODO: more validation?
  final int fromRank = size.rank(move.from);
  final PieceType fromPiece = variant.pieces[fromSq.type].type;
  if (fromSq.isNotEmpty &&
      (fromSq.colour != state.turn &&
          fromSq.colour != Bishop.neutralPassive)) {
    return null;
  }
  final int colour = turn;
  // Remove the moved piece, if this piece came from on the board.
  if (move.from >= Bishop.boardStart) {
    hash ^= zobrist.table[move.from][fromSq.piece];
    if (move.promotion) {
      pieces[fromSq.piece]--;
    }

    board[move.from] = Bishop.empty;

    // Mark the file as touched.
    if ((fromRank == 0 && colour == Bishop.white) ||
        (fromRank == size.v - 1 && colour == Bishop.black)) {
      virginFiles[colour].remove(size.file(move.from));
    }
  }

  // Add captured piece to hand
  if (variant.addCapturesToHand && move.capture) {
    int piece = move.capturedPiece!.hasInternalType
        ? move.capturedPiece!.internalType
        : move.capturedPiece!.type;
    hands![colour].add(piece);
    pieces[makePiece(piece, colour)]++;
  }

  // Remove captured piece from hash and pieces list
  if (move.capture && !move.enPassant) {
    int p = board[move.to].piece;
    hash ^= zobrist.table[move.to][p];
    pieces[p]--;
  }

  if (!move.castling && !move.promotion) {
    // Move the piece to the new square
    int putPiece = move.from >= Bishop.boardStart
        ? fromSq.setInitialState(false)
        : makePiece(move.dropPiece!, colour);
    hash ^= zobrist.table[move.to][putPiece.piece];
    board[move.to] = putPiece;
    // note that it's possible to have a drop move without hands (e.g. duck chess)
    if (move.from == Bishop.hand) hands?[colour].remove(move.dropPiece!);
  } else if (move.promotion) {
    // Place the promoted piece
    board[move.to] =
        makePiece(move.promoPiece!, state.turn, internalType: fromSq.type);
    hash ^= zobrist.table[move.to][board[move.to].piece];
    pieces[board[move.to].piece]++;
  }
  // Manage halfmove counter
  int halfMoves = state.halfMoves;
  if (move.capture || fromPiece.promoOptions.canPromote) {
    halfMoves = 0;
  } else {
    halfMoves++;
  }

  int castlingRights = state.castlingRights;
  List<int> royalSquares = List.from(state.royalSquares);

  if (move.enPassant) {
    // Remove the captured ep piece
    int captureSq = state.move?.to ??
        (move.to + Bishop.playerDirection[colour.opponent] * size.north);
    hash ^= zobrist.table[captureSq][board[captureSq].piece];
    pieces[board[captureSq].piece]--;
    board[captureSq] = Bishop.empty;
  }

  int? epSquare;
  if (move.setEnPassant) {
    // Set the new ep square
    int dir = (move.to - move.from) ~/ 2;
    epSquare = move.from + dir;
    hash ^= zobrist.table[epSquare][Zobrist.meta];
  } else {
    epSquare = null;
  }
  if (state.epSquare != null) {
    // XOR the old ep square away from the hash
    hash ^= zobrist.table[state.epSquare!][Zobrist.meta];
  }

  if (move.castling) {
    move = move as StandardMove;
    bool kingside = move.castlingDir == Castling.k;
    int castlingFile = kingside
        ? variant.castlingOptions.kTarget!
        : variant.castlingOptions.qTarget!;
    int rookFile = kingside ? castlingFile - 1 : castlingFile + 1;
    int rookSq = size.square(rookFile, fromRank);
    int kingSq = size.square(castlingFile, fromRank);
    int rook = board[move.castlingPieceSquare!];
    hash ^= zobrist.table[move.castlingPieceSquare!][rook.piece];
    if (board[kingSq].isNotEmpty) {
      hash ^= zobrist.table[kingSq][board[kingSq].piece];
    }
    hash ^= zobrist.table[kingSq][fromSq.piece];
    if (board[rookSq].isNotEmpty) {
      hash ^= zobrist.table[rookSq][board[rookSq].piece];
    }
    hash ^= zobrist.table[rookSq][rook.piece];
    board[move.castlingPieceSquare!] = Bishop.empty;
    board[kingSq] = fromSq.setInitialState(false);
    board[rookSq] = rook;
    castlingRights = castlingRights.remove(colour);
    royalSquares[colour] = kingSq;
  } else if (fromPiece.royal) {
    // king moved
    castlingRights = castlingRights.remove(colour);
    royalSquares[colour] = move.to;
  } else {
    // If the player's rook moved, remove relevant castling rights
    if (fromSq.type == variant.castlingPiece) {
      int fromFile = size.file(move.from);
      bool onFirstRank = size.rank(move.from) == size.firstRank(colour);
      int ks = colour == Bishop.white ? Castling.k : Castling.bk;
      int qs = colour == Bishop.white ? Castling.q : Castling.bq;
      if (fromFile == castlingTargetK &&
          onFirstRank &&
          castlingRights.hasRight(ks)) {
        castlingRights = castlingRights.flip(ks);
      } else if (fromFile == castlingTargetQ &&
          onFirstRank &&
          castlingRights.hasRight(qs)) {
        castlingRights = castlingRights.flip(qs);
      }
    }
    // If the opponent's rook was captured, remove relevant castling rights
    if (move.capture && move.capturedPiece!.type == variant.castlingPiece) {
      // rook captured
      int toFile = size.file(move.to);
      int opponent = colour.opponent;
      bool onFirstRank = size.rank(move.to) == size.firstRank(opponent);
      int ks = opponent == Bishop.white ? Castling.k : Castling.bk;
      int qs = opponent == Bishop.white ? Castling.q : Castling.bq;
      if (toFile == castlingTargetK &&
          onFirstRank &&
          castlingRights.hasRight(ks)) {
        castlingRights = castlingRights.flip(ks);
      } else if (toFile == castlingTargetQ &&
          onFirstRank &&
          castlingRights.hasRight(qs)) {
        castlingRights = castlingRights.flip(qs);
      }
    }
  }
  if (castlingRights != state.castlingRights) {
    hash ^= zobrist.table[zobrist.castling][state.castlingRights];
    hash ^= zobrist.table[zobrist.castling][castlingRights];
  }
  if (variant.hasWinRegions) {
    int p = board[move.to].piece;
    if (variant.pieceHasWinRegions(p) && variant.inWinRegion(p, move.to)) {
      result = WonGameEnteredRegion(winner: state.turn, square: move.to);
    }
  }

  BishopState newState = BishopState(
    board: board,
    move: move,
    turn: 1 - state.turn,
    halfMoves: halfMoves,
    fullMoves:
        state.turn == Bishop.black ? state.fullMoves + 1 : state.fullMoves,
    castlingRights: castlingRights,
    royalSquares: royalSquares,
    virginFiles: virginFiles,
    epSquare: epSquare,
    hash: hash,
    hands: hands,
    gates: state.gates,
    pieces: pieces,
    checks: List.from(state.checks),
    result: result,
  );
  return newState;
}