From 486fa19a98409f87248fcc0b687e7515597769d1 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 14 Jan 2018 11:38:04 +0100 Subject: [PATCH] Upgrade Chess.js to a version that supports Chess960 castling. --- www/index.html | 2 +- www/js/chess.js | 438 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 321 insertions(+), 119 deletions(-) diff --git a/www/index.html b/www/index.html index 9631994..2c113e7 100644 --- a/www/index.html +++ b/www/index.html @@ -78,7 +78,7 @@ - + diff --git a/www/js/chess.js b/www/js/chess.js index 079d7d7..569d7c8 100644 --- a/www/js/chess.js +++ b/www/js/chess.js @@ -1,6 +1,5 @@ -'use strict'; /* - * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com) + * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +28,7 @@ /* minified license below */ /* @license - * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com) + * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com) * Released under the BSD license * https://github.com/jhlywa/chess.js/blob/master/LICENSE */ @@ -147,15 +146,9 @@ var Chess = function(fen) { a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119 }; - var ROOKS = { - w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE}, - {square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}], - b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE}, - {square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}] - }; - var board = new Array(128); var kings = {w: EMPTY, b: EMPTY}; + var rooks = {w: [], b: []}; var turn = WHITE; var castling = {w: 0, b: 0}; var ep_square = EMPTY; @@ -173,7 +166,11 @@ var Chess = function(fen) { load(fen); } - function clear() { + function clear(keep_headers) { + if (typeof keep_headers === 'undefined') { + keep_headers = false; + } + board = new Array(128); kings = {w: EMPTY, b: EMPTY}; turn = WHITE; @@ -182,7 +179,7 @@ var Chess = function(fen) { half_moves = 0; move_number = 1; history = []; - header = {}; + if (!keep_headers) header = {}; update_setup(generate_fen()); } @@ -190,7 +187,11 @@ var Chess = function(fen) { load(DEFAULT_POSITION); } - function load(fen) { + function load(fen, keep_headers) { + if (typeof keep_headers === 'undefined') { + keep_headers = false; + } + var tokens = fen.split(/\s+/); var position = tokens[0]; var square = 0; @@ -199,7 +200,7 @@ var Chess = function(fen) { return false; } - clear(); + clear(keep_headers); for (var i = 0; i < position.length; i++) { var piece = position.charAt(i); @@ -217,17 +218,63 @@ var Chess = function(fen) { turn = tokens[1]; + rooks = {w: [], b: []}; + if (tokens[2].indexOf('K') > -1) { castling.w |= BITS.KSIDE_CASTLE; + for (var sq = SQUARES.h1; sq >= SQUARES.c1; --sq) { + if (is_rook(board[sq], WHITE)) { + rooks[WHITE].push({square: sq, flag: BITS.KSIDE_CASTLE}); + break; + } + } } if (tokens[2].indexOf('Q') > -1) { castling.w |= BITS.QSIDE_CASTLE; + for (var sq = SQUARES.a1; sq <= SQUARES.g1; ++sq) { + if (is_rook(board[sq], WHITE)) { + rooks[WHITE].push({square: sq, flag: BITS.QSIDE_CASTLE}); + break; + } + } + } + var white_frc_columns = tokens[2].match(/[A-H]/g); + var i, flag; + if (white_frc_columns !== null) { + for (i = 0; i < white_frc_columns.length; ++i) { + var sq = SQUARES.a1 + (white_frc_columns[0].charCodeAt(0) - "A".charCodeAt(0)); + flag = sq < kings[WHITE] ? BITS.QSIDE_CASTLE : BITS.KSIDE_CASTLE; + castling.w |= flag; + rooks[WHITE].push({square: sq, flag: flag}); + } } + if (tokens[2].indexOf('k') > -1) { castling.b |= BITS.KSIDE_CASTLE; + for (var sq = SQUARES.h8; sq >= SQUARES.c8; --sq) { + if (is_rook(board[sq], BLACK)) { + rooks[BLACK].push({square: sq, flag: BITS.KSIDE_CASTLE}); + break; + } + } } if (tokens[2].indexOf('q') > -1) { castling.b |= BITS.QSIDE_CASTLE; + for (var sq = SQUARES.a8; sq <= SQUARES.g8; ++sq) { + if (is_rook(board[sq], BLACK)) { + rooks[BLACK].push({square: sq, flag: BITS.QSIDE_CASTLE}); + break; + } + } + } + var black_frc_columns = tokens[2].match(/[a-h]/g); + if (black_frc_columns !== null) { + for (i = 0; i < black_frc_columns.length; ++i) { + var sq = SQUARES.a1 + (black_frc_columns[0].charCodeAt(0) - "A".charCodeAt(0)); + flag = sq < kings[BLACK] ? BITS.QSIDE_CASTLE : BITS.KSIDE_CASTLE; + castling.b |= flag; + rooks[BLACK].push({square: sq, flag: flag}); + } } ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]]; @@ -239,6 +286,11 @@ var Chess = function(fen) { return true; } + /* TODO: this function is pretty much crap - it validates structure but + * completely ignores content (e.g. doesn't verify that each side has a king) + * ... we should rewrite this, and ditch the silly error_number field while + * we're at it + */ function validate_fen(fen) { var errors = { 0: 'No errors.', @@ -252,6 +304,7 @@ var Chess = function(fen) { 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].', + 11: 'Illegal en-passant square', }; /* 1st criterion: 6 space-seperated fields? */ @@ -276,7 +329,8 @@ var Chess = function(fen) { } /* 5th criterion: 3th field is a valid castle-string? */ - if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) { + if( !/^[C-HK]?[A-FQ]?[c-hk]?[a-fq]?$/.test(tokens[2]) && + tokens[2] !== '-') { return {valid: false, error_number: 5, error: errors[5]}; } @@ -317,6 +371,11 @@ var Chess = function(fen) { } } + if ((tokens[3][1] == '3' && tokens[1] == 'w') || + (tokens[3][1] == '6' && tokens[1] == 'b')) { + return {valid: false, error_number: 11, error: errors[11]}; + } + /* everything's okay! */ return {valid: true, error_number: 0, error: errors[0]}; } @@ -355,10 +414,39 @@ var Chess = function(fen) { } var cflags = ''; - if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; } - if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; } - if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; } - if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; } + var sq; + if (castling[WHITE] & BITS.KSIDE_CASTLE) { + sq = search_rook(board, WHITE, BITS.KSIDE_CASTLE); + if (is_outermost_rook(board, WHITE, BITS.KSIDE_CASTLE, sq)) { + cflags += 'K'; + } else { + cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1); + } + } + if (castling[WHITE] & BITS.QSIDE_CASTLE) { + sq = search_rook(board, WHITE, BITS.QSIDE_CASTLE); + if (is_outermost_rook(board, WHITE, BITS.QSIDE_CASTLE, sq)) { + cflags += 'Q'; + } else { + cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1); + } + } + if (castling[BLACK] & BITS.KSIDE_CASTLE) { + sq = search_rook(board, BLACK, BITS.KSIDE_CASTLE); + if (is_outermost_rook(board, BLACK, BITS.KSIDE_CASTLE, sq)) { + cflags += 'k'; + } else { + cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1); + } + } + if (castling[BLACK] & BITS.QSIDE_CASTLE) { + sq = search_rook(board, BLACK, BITS.QSIDE_CASTLE); + if (is_outermost_rook(board, BLACK, BITS.QSIDE_CASTLE, sq)) { + cflags += 'q'; + } else { + cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1); + } + } /* do we have an empty castling flag? */ cflags = cflags || '-'; @@ -446,7 +534,7 @@ var Chess = function(fen) { return piece; } - function build_move(board, from, to, flags, promotion) { + function build_move(board, from, to, flags, promotion, rook_sq) { var move = { color: turn, from: from, @@ -460,7 +548,9 @@ var Chess = function(fen) { move.promotion = promotion; } - if (board[to]) { + if (flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) { + move.rook_sq = rook_sq; // remember the position of the rook + } else if (board[to]) { move.captured = board[to].type; } else if (flags & BITS.EP_CAPTURE) { move.captured = PAWN; @@ -469,7 +559,7 @@ var Chess = function(fen) { } function generate_moves(options) { - function add_move(board, moves, from, to, flags) { + function add_move(board, moves, from, to, flags, rook_sq) { /* if pawn promotion */ if (board[from].type === PAWN && (rank(to) === RANK_8 || rank(to) === RANK_1)) { @@ -478,10 +568,35 @@ var Chess = function(fen) { moves.push(build_move(board, from, to, flags, pieces[i])); } } else { - moves.push(build_move(board, from, to, flags)); + moves.push(build_move(board, from, to, flags, undefined, rook_sq)); } } + function check_castle(board, king_from, king_to, rook_from, rook_to, them) { + var sq; + + // Check that no pieces are standing between the king and its destination + // square, and also between the rook and its destination square. + var king_left = Math.min(king_from, king_to); + var king_right = Math.max(king_from, king_to); + var left = Math.min(king_left, Math.min(rook_from, rook_to)); + var right = Math.max(king_right, Math.max(rook_from, rook_to)); + for (sq = left; sq <= right; ++sq) { + if (sq != king_from && sq != rook_from && board[sq]) { + return false; + } + } + + // Check that none of the squares on the king's way are under attack. + for (sq = king_left; sq <= king_right; ++sq) { + if (attacked(them, sq)) { + return false; + } + } + + return true; + } + var moves = []; var us = turn; var them = swap_color(us); @@ -570,32 +685,25 @@ var Chess = function(fen) { if ((!single_square) || last_sq === kings[us]) { /* king-side castling */ if (castling[us] & BITS.KSIDE_CASTLE) { - var castling_from = kings[us]; - var castling_to = castling_from + 2; - - if (board[castling_from + 1] == null && - board[castling_to] == null && - !attacked(them, kings[us]) && - !attacked(them, castling_from + 1) && - !attacked(them, castling_to)) { - add_move(board, moves, kings[us] , castling_to, - BITS.KSIDE_CASTLE); + var king_from = kings[us]; + var king_to = us === WHITE ? SQUARES.g1 : SQUARES.g8; + var rook_from = search_rook(board, us, BITS.KSIDE_CASTLE); + var rook_to = king_to - 1; + + if (check_castle(board, king_from, king_to, rook_from, rook_to, them)) { + add_move(board, moves, king_from, king_to, BITS.KSIDE_CASTLE, rook_from); } } /* queen-side castling */ if (castling[us] & BITS.QSIDE_CASTLE) { - var castling_from = kings[us]; - var castling_to = castling_from - 2; - - if (board[castling_from - 1] == null && - board[castling_from - 2] == null && - board[castling_from - 3] == null && - !attacked(them, kings[us]) && - !attacked(them, castling_from - 1) && - !attacked(them, castling_to)) { - add_move(board, moves, kings[us], castling_to, - BITS.QSIDE_CASTLE); + var king_from = kings[us]; + var king_to = us === WHITE ? SQUARES.c1 : SQUARES.c8; + var rook_from = search_rook(board, us, BITS.QSIDE_CASTLE); + var rook_to = king_to + 1; + + if (check_castle(board, king_from, king_to, rook_from, rook_to, them)) { + add_move(board, moves, king_from, king_to, BITS.QSIDE_CASTLE, rook_from); } } } @@ -620,10 +728,52 @@ var Chess = function(fen) { return legal_moves; } + function is_rook(piece, color) { + return (typeof piece !== 'undefined' && piece !== null && + piece.type === ROOK && piece.color == color); + } + + function search_rook(board, us, flag) { + for (var i = 0, len = rooks[us].length; i < len; i++) { + if (flag & rooks[us][i].flag) { + return rooks[us][i].square; + } + } + return null; + } + + function is_outermost_rook(board, us, flag, sq) { + var end_sq; + if (flag == BITS.KSIDE_CASTLE) { + var end_sq = (us == WHITE) ? SQUARES.h1 : SQUARES.h8; + while (++sq <= end_sq) { + if (is_rook(board[sq], us)) { + return false; + } + } + } else { + var end_sq = (us == WHITE) ? SQUARES.a1 : SQUARES.a8; + while (--sq >= end_sq) { + if (is_rook(board[sq], us)) { + return false; + } + } + } + return true; + } + /* convert a move from 0x88 coordinates to Standard Algebraic Notation * (SAN) + * + * @param {boolean} sloppy Use the sloppy SAN generator to work around over + * disambiguation bugs in Fritz and Chessbase. See below: + * + * r1bqkbnr/ppp2ppp/2n5/1B1pP3/4P3/8/PPPP2PP/RNBQK1NR b KQkq - 2 4 + * 4. ... Nge7 is overly disambiguated because the knight on c6 is pinned + * 4. ... Ne7 is technically the valid SAN */ - function move_to_san(move) { + function move_to_san(move, sloppy) { + var output = ''; if (move.flags & BITS.KSIDE_CASTLE) { @@ -631,7 +781,7 @@ var Chess = function(fen) { } else if (move.flags & BITS.QSIDE_CASTLE) { output = 'O-O-O'; } else { - var disambiguator = get_disambiguator(move); + var disambiguator = get_disambiguator(move, sloppy); if (move.piece !== PAWN) { output += move.piece.toUpperCase() + disambiguator; @@ -664,6 +814,11 @@ var Chess = function(fen) { return output; } + // parses all of the decorators out of a SAN string + function stripped_san(move) { + return move.replace(/=/,'').replace(/[+#]?[?!]*$/,''); + } + function attacked(color, square) { for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { /* did we run off the end of the board */ @@ -813,6 +968,7 @@ var Chess = function(fen) { function make_move(move) { var us = turn; var them = swap_color(us); + var old_to = board[move.to]; push(move); board[move.to] = board[move.from]; @@ -839,14 +995,16 @@ var Chess = function(fen) { /* if we castled, move the rook next to the king */ if (move.flags & BITS.KSIDE_CASTLE) { var castling_to = move.to - 1; - var castling_from = move.to + 1; - board[castling_to] = board[castling_from]; - board[castling_from] = null; + var castling_from = move.rook_sq; + board[castling_to] = old_to===null ? board[castling_from] : old_to; + if(castling_from !== move.to) + board[castling_from] = null; } else if (move.flags & BITS.QSIDE_CASTLE) { var castling_to = move.to + 1; - var castling_from = move.to - 2; - board[castling_to] = board[castling_from]; - board[castling_from] = null; + var castling_from = move.rook_sq; + board[castling_to] = old_to===null ? board[castling_from] : old_to; + if(castling_from !== move.to) + board[castling_from] = null; } /* turn off castling */ @@ -855,10 +1013,10 @@ var Chess = function(fen) { /* turn off castling if we move a rook */ if (castling[us]) { - for (var i = 0, len = ROOKS[us].length; i < len; i++) { - if (move.from === ROOKS[us][i].square && - castling[us] & ROOKS[us][i].flag) { - castling[us] ^= ROOKS[us][i].flag; + for (var i = 0, len = rooks[us].length; i < len; i++) { + if (move.from === rooks[us][i].square && + castling[us] & rooks[us][i].flag) { + castling[us] ^= rooks[us][i].flag; break; } } @@ -866,10 +1024,10 @@ var Chess = function(fen) { /* turn off castling if we capture a rook */ if (castling[them]) { - for (var i = 0, len = ROOKS[them].length; i < len; i++) { - if (move.to === ROOKS[them][i].square && - castling[them] & ROOKS[them][i].flag) { - castling[them] ^= ROOKS[them][i].flag; + for (var i = 0, len = rooks[them].length; i < len; i++) { + if (move.to === rooks[them][i].square && + castling[them] & rooks[them][i].flag) { + castling[them] ^= rooks[them][i].flag; break; } } @@ -916,6 +1074,8 @@ var Chess = function(fen) { var us = turn; var them = swap_color(turn); + var old_from = board[move.from]; + board[move.from] = board[move.to]; board[move.from].type = move.piece; // to undo any promotions board[move.to] = null; @@ -936,23 +1096,24 @@ var Chess = function(fen) { if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) { var castling_to, castling_from; if (move.flags & BITS.KSIDE_CASTLE) { - castling_to = move.to + 1; + castling_to = move.rook_sq; castling_from = move.to - 1; } else if (move.flags & BITS.QSIDE_CASTLE) { - castling_to = move.to - 2; + castling_to = move.rook_sq; castling_from = move.to + 1; } - board[castling_to] = board[castling_from]; - board[castling_from] = null; + board[castling_to] = old_from===null ? board[castling_from] : old_from; + if(castling_from !== move.from) + board[castling_from] = null; } return move; } /* this function is used to uniquely identify ambiguous moves */ - function get_disambiguator(move) { - var moves = generate_moves(); + function get_disambiguator(move, sloppy) { + var moves = generate_moves({legal: !sloppy}); var from = move.from; var to = move.to; @@ -1035,6 +1196,45 @@ var Chess = function(fen) { return s; } + // convert a move from Standard Algebraic Notation (SAN) to 0x88 coordinates + function move_from_san(move, sloppy) { + // strip off any move decorations: e.g Nf3+?! + var clean_move = stripped_san(move); + + // if we're using the sloppy parser run a regex to grab piece, to, and from + // this should parse invalid SAN like: Pe2-e4, Rc1c4, Qf3xf7 + if (sloppy) { + var matches = clean_move.match(/([pnbrqkPNBRQK])?([a-h][1-8])x?-?([a-h][1-8])([qrbnQRBN])?/); + if (matches) { + var piece = matches[1]; + var from = matches[2]; + var to = matches[3]; + var promotion = matches[4]; + } + } + + var moves = generate_moves(); + for (var i = 0, len = moves.length; i < len; i++) { + // try the strict parser first, then the sloppy parser if requested + // by the user + if ((clean_move === stripped_san(move_to_san(moves[i]))) || + (sloppy && clean_move === stripped_san(move_to_san(moves[i], true)))) { + return moves[i]; + } else { + if (matches && + (!piece || piece.toLowerCase() == moves[i].piece) && + SQUARES[from] == moves[i].from && + SQUARES[to] == moves[i].to && + (!promotion || promotion.toLowerCase() == moves[i].promotion)) { + return moves[i]; + } + } + } + + return null; + } + + /***************************************************************************** * UTILITY FUNCTIONS ****************************************************************************/ @@ -1062,7 +1262,7 @@ var Chess = function(fen) { /* pretty = external move object */ function make_pretty(ugly_move) { var move = clone(ugly_move); - move.san = move_to_san(move); + move.san = move_to_san(move, false); move.to = algebraic(move.to); move.from = algebraic(move.from); @@ -1178,7 +1378,7 @@ var Chess = function(fen) { options.verbose) { moves.push(make_pretty(ugly_moves[i])); } else { - moves.push(move_to_san(ugly_moves[i])); + moves.push(move_to_san(ugly_moves[i], false)); } } @@ -1228,6 +1428,26 @@ var Chess = function(fen) { return generate_fen(); }, + board: function() { + var output = [], + row = []; + + for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { + if (board[i] == null) { + row.push(null) + } else { + row.push({type: board[i].type, color: board[i].color}) + } + if ((i + 1) & 0x88) { + output.push(row); + row = [] + i += 8; + } + } + + return output; + }, + pgn: function(options) { /* using the specification from http://www.chessclub.com/help/PGN-spec * example for html usage: .pgn({ max_width: 72, newline_char: "
" }) @@ -1262,26 +1482,23 @@ var Chess = function(fen) { var moves = []; var move_string = ''; - var pgn_move_number = 1; /* build the list of moves. a move_string looks like: "3. e3 e6" */ while (reversed_history.length > 0) { var move = reversed_history.pop(); /* if the position started with black to move, start PGN with 1. ... */ - if (pgn_move_number === 1 && move.color === 'b') { - move_string = '1. ...'; - pgn_move_number++; + if (!history.length && move.color === 'b') { + move_string = move_number + '. ...'; } else if (move.color === 'w') { /* store the previous generated move_string if we have one */ if (move_string.length) { moves.push(move_string); } - move_string = pgn_move_number + '.'; - pgn_move_number++; + move_string = move_number + '.'; } - move_string = move_string + ' ' + move_to_san(move); + move_string = move_string + ' ' + move_to_san(move, false); make_move(move); } @@ -1327,36 +1544,20 @@ var Chess = function(fen) { }, load_pgn: function(pgn, options) { + // allow the user to specify the sloppy move parser to work around over + // disambiguation bugs in Fritz and Chessbase + var sloppy = (typeof options !== 'undefined' && 'sloppy' in options) ? + options.sloppy : false; + function mask(str) { return str.replace(/\\/g, '\\'); } - /* convert a move from Standard Algebraic Notation (SAN) to 0x88 - * coordinates - */ - function move_from_san(move) { - /* strip off any move decorations: e.g Nf3+?! */ - var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,''); - var moves = generate_moves(); - for (var i = 0, len = moves.length; i < len; i++) { - if (move_replaced === - move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) { - return moves[i]; - } - } - return null; - } - - function get_move_obj(move) { - return move_from_san(trim(move)); - } - function has_keys(object) { - var has_keys = false; for (var key in object) { - has_keys = true; + return true; } - return has_keys; + return false; } function parse_pgn_header(header, options) { @@ -1382,9 +1583,9 @@ var Chess = function(fen) { var newline_char = (typeof options === 'object' && typeof options.newline_char === 'string') ? options.newline_char : '\r?\n'; - var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' + - '(' + mask(newline_char) + ')*' + - '1.(' + mask(newline_char) + '|.)*$', 'g'); + var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' + + '(' + mask(newline_char) + ')*' + + '1.(' + mask(newline_char) + '|.)*$', 'g'); /* get header part of the PGN file */ var header_string = pgn.replace(regex, '$1'); @@ -1394,7 +1595,7 @@ var Chess = function(fen) { header_string = ''; } - reset(); + reset(); /* parse PGN header */ var headers = parse_pgn_header(header_string, options); @@ -1405,7 +1606,7 @@ var Chess = function(fen) { /* load the starting position indicated by [Setup '1'] and * [FEN position] */ if (headers['SetUp'] === '1') { - if (!(('FEN' in headers) && load(headers['FEN']))) { + if (!(('FEN' in headers) && load(headers['FEN'], true ))) { // second argument to load: don't clear the headers return false; } } @@ -1423,11 +1624,14 @@ var Chess = function(fen) { } /* delete move numbers */ - ms = ms.replace(/\d+\./g, ''); + ms = ms.replace(/\d+\.(\.\.)?/g, ''); /* delete ... indicating black to move */ ms = ms.replace(/\.\.\./g, ''); + /* delete numeric annotation glyphs */ + ms = ms.replace(/\$\d+/g, ''); + /* trim and get array of moves */ var moves = trim(ms).split(new RegExp(/\s+/)); @@ -1436,7 +1640,7 @@ var Chess = function(fen) { var move = ''; for (var half_move = 0; half_move < moves.length - 1; half_move++) { - move = get_move_obj(moves[half_move]); + move = move_from_san(moves[half_move], sloppy); /* move not possible! (don't clear the board to examine to show the * latest valid position) @@ -1456,7 +1660,7 @@ var Chess = function(fen) { } } else { - move = get_move_obj(move); + move = move_from_san(move, sloppy); if (move == null) { return false; } else { @@ -1478,7 +1682,7 @@ var Chess = function(fen) { return turn; }, - move: function(move) { + move: function(move, options) { /* The move function can be called with in the following parameters: * * .move('Nxb7') <- where 'move' is a case-sensitive SAN string @@ -1488,21 +1692,19 @@ var Chess = function(fen) { * promotion: 'q', * }) */ + + // allow the user to specify the sloppy move parser to work around over + // disambiguation bugs in Fritz and Chessbase + var sloppy = (typeof options !== 'undefined' && 'sloppy' in options) ? + options.sloppy : false; + var move_obj = null; - var moves = generate_moves(); if (typeof move === 'string') { - /* convert the move string to a move object */ - /* strip off any move decorations: e.g Nf3+?! */ - var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,''); - for (var i = 0, len = moves.length; i < len; i++) { - if (move_replaced === - move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) { - move_obj = moves[i]; - break; - } - } + move_obj = move_from_san(move, sloppy); } else if (typeof move === 'object') { + var moves = generate_moves(); + /* convert the pretty move object to an ugly move object */ for (var i = 0, len = moves.length; i < len; i++) { if (move.from === algebraic(moves[i].from) && -- 2.39.2