validateFen static method

Map validateFen(
  1. dynamic fen
)

Check the formatting of a FEN String is correct Returns a Map with keys valid, error_number, and error

Implementation

static Map validateFen(fen) {
  Map errors = {
    0: 'No errors.',
    1: 'FEN string must contain six space-delimited fields.',
    2: '6th field (move number) must be a positive integer.',
    3: '5th field (half move counter) must be a non-negative integer.',
    4: '4th field (en-passant square) is invalid.',
    5: '3rd field (castling availability) is invalid.',
    6: '2nd field (side to move) is invalid.',
    7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.',
    8: '1st field (piece positions) is invalid [consecutive numbers].',
    9: '1st field (piece positions) is invalid [invalid piece].',
    10: '1st field (piece positions) is invalid [row too large].',
  };

  /// Fields of the FEN are separated by spaces
  /// retrieve fields form the FEN -> must be 6 of them
  /// Wikipedia:
  /// A FEN record contains six fields.
  /// The separator between fields is a space
  List tokens = fen.split(RegExp(r"\s+"));
  if (tokens.length != 6) {
    return {'valid': false, 'error_number': 1, 'error': errors[1]};
  }

  /// the last Field of FEN should be the number of moves
  /// this should be a positive integer since starts at 1
  /// Wikipedia: "Fullmove number: The number of the full move.
  /// It starts at 1, and is incremented after Black's move."
  int? temp = int.tryParse(tokens[5]);
  if (temp != null) {
    if (temp <= 0) {
      return {'valid': false, 'error_number': 2, 'error': errors[2]};
    }
  } else {
    return {'valid': false, 'error_number': 2, 'error': errors[2]};
  }

  /// The second to last field is the counter
  /// of moves for the 50 moves draw rule
  temp = int.tryParse(tokens[4]);
  if (temp != null) {
    if (temp < 0) {
      return {'valid': false, 'error_number': 3, 'error': errors[3]};
    }
  } else {
    return {'valid': false, 'error_number': 3, 'error': errors[3]};
  }

  /// Wikipedia: En passant target square in algebraic notation.
  /// If there's no en passant target square, this is "-".
  /// If a pawn has just made a two-square move,
  /// this is the position "behind" the pawn.
  /// This is recorded regardless of whether there is a pawn
  /// in position to make an en passant capture.
  RegExp checkEnPassant = new RegExp(r"^(-|[abcdefgh][36])$");
  if (checkEnPassant.firstMatch(tokens[3]) == null) {
    return {'valid': false, 'error_number': 4, 'error': errors[4]};
  }

  /// Wikipedia:
  /// Castling availability. If neither side can castle, this is "-".
  /// Otherwise, this has one or more letters: "K" (White can castle kingside),
  /// "Q" (White can castle queenside), "k" (Black can castle kingside),
  /// and/or "q" (Black can castle queenside).
  /// A move that temporarily prevents castling does not negate this notation.
  RegExp checkCastling = new RegExp(r"^(KQ?k?q?|Qk?q?|kq?|q|-)$");
  if (checkCastling.firstMatch(tokens[2]) == null) {
    return {'valid': false, 'error_number': 5, 'error': errors[5]};
  }

  /// Wikipedia:
  /// Active color. "w" means White moves next,
  /// "b" means Black moves next.
  RegExp moveTurn = new RegExp(r"^(w|b)$");
  if (moveTurn.firstMatch(tokens[1]) == null) {
    return {'valid': false, 'error_number': 6, 'error': errors[6]};
  }

  /// Wikipedia:
  /// Piece placement (from White's perspective).
  /// Each rank is described, starting with rank 8 and ending with rank 1
  /// within each rank, the contents of each square are described from file
  /// "a" through file "h". Using the Standard Algebraic Notation (SAN)

  /// There must be 8 rows
  List rows = tokens[0].split('/');
  if (rows.length != 8) {
    return {'valid': false, 'error_number': 7, 'error': errors[7]};
  }

  /// Check validity of rows
  for (int i = 0; i < rows.length; i++) {
    /// there should be 8 rows, increment
    /// sumFields after check each valid row
    int sumFields = 0;
    bool previousWasNumber = false;

    /// Check content of rows, there shouldn't be two successive numbers
    /// since each number is the number of empty squares between two pieces
    for (int k = 0; k < rows[i].length; k++) {
      int? temp2 = int.tryParse(rows[i][k]);

      /// if not null then is a number in the row and
      /// the previous token should not be a number
      if (temp2 != null) {
        if (previousWasNumber) {
          return {'valid': false, 'error_number': 8, 'error': errors[8]};
        }
        sumFields += temp2;
        previousWasNumber = true;
      }

      /// if it's not a number then it's a piece token check
      /// with a regular expression
      else {
        RegExp checkOM = RegExp(r"^[prnbqkPRNBQK]$");
        if (checkOM.firstMatch(rows[i][k]) == null) {
          return {'valid': false, 'error_number': 9, 'error': errors[9]};
        }
        sumFields += 1;
        previousWasNumber = false;
      }
    }

    if (sumFields != 8) {
      return {'valid': false, 'error_number': 10, 'error': errors[10]};
    }
  }

  /// Everything is ok
  return {'valid': true, 'error_number': 0, 'error': errors[0]};
}