load_pgn method
Load the moves of a game stored in Portable Game Notation.
options
is an optional parameter that contains a 'newline_char'
which is a string representation of a RegExp (and should not be pre-escaped)
and defaults to '\r?\n').
Returns true
if the PGN was parsed successfully, otherwise false
.
Implementation
bool load_pgn(String? pgn, [Map? options]) {
String mask(str) {
return str.replaceAll(RegExp(r'\\'), '\\');
}
/* convert a move from Standard Algebraic Notation (SAN) to 0x88
* coordinates
*/
Move? move_from_san(move) {
final moves = generate_moves();
for (var i = 0, len = moves.length; i < len; i++) {
/* strip off any trailing move decorations: e.g Nf3+?! */
if (move.replaceAll(RegExp(r'[+#?!=]+$'), '') ==
move_to_san(moves[i]).replaceAll(RegExp(r'[+#?!=]+$'), '')) {
return moves[i];
}
}
return null;
}
Move? get_move_obj(move) {
return move_from_san(trim(move));
}
/*has_keys(object) {
bool has_keys = false;
for (var key in object) {
has_keys = true;
}
return has_keys;
}*/
Map<String, String> parse_pgn_header(header, [Map? options]) {
final newline_char =
(options != null && options.containsKey('newline_char'))
? options['newline_char']
: '\r?\n';
final header_obj = <String, String>{};
final headers = header.split(RegExp(newline_char));
var key = '';
var value = '';
for (var i = 0; i < headers.length; i++) {
var keyMatch = RegExp(r'^\[([A-Z][A-Za-z]*)\s.*\]$');
var temp = keyMatch.firstMatch(headers[i]);
if (temp != null) {
key = temp[1]!;
}
//print(key);
var valueMatch = RegExp(r'^\[[A-Za-z]+\s"(.*)"\]$');
temp = valueMatch.firstMatch(headers[i]);
if (temp != null) {
value = temp[1]!;
}
//print(value);
if (trim(key).isNotEmpty) {
header_obj[key] = value;
}
}
return header_obj;
}
final newline_char =
(options != null && options.containsKey('newline_char'))
? options['newline_char']
: '\r?\n';
//var regex = new RegExp(r'^(\[.*\]).*' + r'1\.'); //+ r"1\."); //+ mask(newline_char));
final indexOfMoveStart = pgn!.indexOf(RegExp(newline_char + r'\d+\.{1,3}'));
/* get header part of the PGN file */
String? header_string;
if (indexOfMoveStart != -1) {
header_string = pgn.substring(0, indexOfMoveStart).trim();
}
/* no info part given, begins with moves */
if (header_string == null || header_string[0] != '[') {
header_string = '';
}
/* parse PGN header */
final headers = parse_pgn_header(header_string, options);
if (headers.containsKey('FEN')) {
load(headers['FEN']!);
} else {
reset();
}
for (var key in headers.keys) {
set_header([key, headers[key]]);
}
/* delete header to get the moves */
var ms = pgn
.replaceAll(header_string, '')
.replaceAll(RegExp(mask(newline_char)), ' ');
/* delete comments */
ms = ms.replaceAll(RegExp(r'({[^}]+\})+?'), '');
/* delete move numbers */
ms = ms.replaceAll(RegExp(r'\d+\.{1,3}'), '');
/* delete recursive annotation variations */
RegExp regExp = RegExp(r'(\([^\(\)]+\))+?');
var variations = regExp.allMatches(ms).toList();
while (variations.isNotEmpty) {
ms = ms.replaceAll(regExp, '');
variations = regExp.allMatches(ms).toList();
}
/* trim and get array of moves */
var moves = trim(ms).split(RegExp(r'\s+'));
/* delete empty entries */
moves = moves.join(',').replaceAll(RegExp(r',,+'), ',').split(',');
var move;
for (var half_move = 0; half_move < moves.length - 1; half_move++) {
move = get_move_obj(moves[half_move]);
/* move not possible! (don't clear the board to examine to show the
* latest valid position)
*/
if (move == null) {
return false;
} else {
make_move(move);
}
}
/* examine last move */
move = moves[moves.length - 1];
if (POSSIBLE_RESULTS.contains(move)) {
if (!header.containsKey('Result')) {
set_header(['Result', move]);
}
} else {
final moveObj = get_move_obj(move);
if (moveObj == null) {
return false;
} else {
make_move(moveObj);
}
}
return true;
}