Setup.parseFen constructor

Setup.parseFen(
  1. String fen
)

Parses a Forsyth-Edwards-Notation string and returns a Setup.

The parser is relaxed:

  • Supports X-FEN and Shredder-FEN for castling right notation.
  • Accepts missing FEN fields (except the board) and fills them with default values of 8/8/8/8/8/8/8/8 w - - 0 1.
  • Accepts multiple spaces and underscores (_) as separators between FEN fields.

Throws a FenException if the provided FEN is not valid.

Implementation

factory Setup.parseFen(String fen) {
  final parts = fen.split(RegExp(r'[\s_]+'));
  if (parts.isEmpty) throw const FenException(IllegalFenCause.format);

  // board and pockets
  final boardPart = parts.removeAt(0);
  Pockets? pockets;
  Board board;
  if (boardPart.endsWith(']')) {
    final pocketStart = boardPart.indexOf('[');
    if (pocketStart == -1) {
      throw const FenException(IllegalFenCause.format);
    }
    board = Board.parseFen(boardPart.substring(0, pocketStart));
    pockets = _parsePockets(
        boardPart.substring(pocketStart + 1, boardPart.length - 1));
  } else {
    final pocketStart = _nthIndexOf(boardPart, '/', 7);
    if (pocketStart == -1) {
      board = Board.parseFen(boardPart);
    } else {
      board = Board.parseFen(boardPart.substring(0, pocketStart));
      pockets = _parsePockets(boardPart.substring(pocketStart + 1));
    }
  }

  // turn
  Side turn;
  if (parts.isEmpty) {
    turn = Side.white;
  } else {
    final turnPart = parts.removeAt(0);
    if (turnPart == 'w') {
      turn = Side.white;
    } else if (turnPart == 'b') {
      turn = Side.black;
    } else {
      throw const FenException(IllegalFenCause.turn);
    }
  }

  // Castling
  SquareSet castlingRights;
  if (parts.isEmpty) {
    castlingRights = SquareSet.empty;
  } else {
    final castlingPart = parts.removeAt(0);
    castlingRights = _parseCastlingFen(board, castlingPart);
  }

  // En passant square
  Square? epSquare;
  if (parts.isNotEmpty) {
    final epPart = parts.removeAt(0);
    if (epPart != '-') {
      epSquare = Square.parse(epPart);
      if (epSquare == null) {
        throw const FenException(IllegalFenCause.enPassant);
      }
    }
  }

  // move counters or remainingChecks
  String? halfmovePart = parts.isNotEmpty ? parts.removeAt(0) : null;
  (int, int)? earlyRemainingChecks;
  if (halfmovePart != null && halfmovePart.contains('+')) {
    earlyRemainingChecks = _parseRemainingChecks(halfmovePart);
    halfmovePart = parts.isNotEmpty ? parts.removeAt(0) : null;
  }
  final halfmoves = halfmovePart != null ? _parseSmallUint(halfmovePart) : 0;
  if (halfmoves == null) {
    throw const FenException(IllegalFenCause.halfmoveClock);
  }

  final fullmovesPart = parts.isNotEmpty ? parts.removeAt(0) : null;
  final fullmoves =
      fullmovesPart != null ? _parseSmallUint(fullmovesPart) : 1;
  if (fullmoves == null) {
    throw const FenException(IllegalFenCause.fullmoveNumber);
  }

  final remainingChecksPart = parts.isNotEmpty ? parts.removeAt(0) : null;
  (int, int)? remainingChecks;
  if (remainingChecksPart != null) {
    if (earlyRemainingChecks != null) {
      throw const FenException(IllegalFenCause.remainingChecks);
    }
    remainingChecks = _parseRemainingChecks(remainingChecksPart);
  } else if (earlyRemainingChecks != null) {
    remainingChecks = earlyRemainingChecks;
  }

  if (parts.isNotEmpty) {
    throw const FenException(IllegalFenCause.format);
  }

  return Setup(
    board: board,
    pockets: pockets,
    turn: turn,
    castlingRights: castlingRights,
    epSquare: epSquare,
    halfmoves: halfmoves,
    fullmoves: fullmoves,
    remainingChecks: remainingChecks,
  );
}