-'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
/* 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
*/
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;
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;
half_moves = 0;
move_number = 1;
history = [];
- header = {};
+ if (!keep_headers) header = {};
update_setup(generate_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;
return false;
}
- clear();
+ clear(keep_headers);
for (var i = 0; i < position.length; i++) {
var piece = position.charAt(i);
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]];
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.',
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? */
}
/* 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]};
}
}
}
+ 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]};
}
}
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 || '-';
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,
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;
}
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)) {
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);
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);
}
}
}
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) {
} 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;
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 */
push(move);
board[move.to] = board[move.from];
- board[move.from] = null;
+ if (move.from != move.to) {
+ board[move.from] = null;
+ }
/* if ep capture, remove the captured pawn */
if (move.flags & BITS.EP_CAPTURE) {
/* 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] = {type: ROOK, color: us};
+ if(castling_from !== move.to && castling_from !== castling_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] = {type: ROOK, color: us};
+ if(castling_from !== move.to && castling_from !== castling_to)
+ board[castling_from] = null;
}
/* turn off castling */
/* 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;
}
}
/* 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;
}
}
var us = turn;
var them = swap_color(turn);
- board[move.from] = board[move.to];
- board[move.from].type = move.piece; // to undo any promotions
- board[move.to] = null;
+ if (move.from != move.to) {
+ board[move.from] = board[move.to];
+ board[move.from].type = move.piece; // to undo any promotions
+ board[move.to] = null;
+ }
if (move.flags & BITS.CAPTURE) {
board[move.to] = {type: move.captured, color: them};
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;
+ if(castling_from !== move.from && castling_from !== castling_to)
+ 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;
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
****************************************************************************/
/* 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);
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));
}
}
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: "<br />" })
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);
}
},
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) {
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');
header_string = '';
}
- reset();
+ reset();
/* parse PGN header */
var headers = parse_pgn_header(header_string, options);
/* 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;
}
}
}
/* 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+/));
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)
}
}
else {
- move = get_move_obj(move);
+ move = move_from_san(move, sloppy);
if (move == null) {
return false;
} else {
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
* 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) &&