]> git.sesse.net Git - remoteglot/blobdiff - www/js/chess.js
Handle streaming PGNs, like from Lichess (although this might break non-streaming...
[remoteglot] / www / js / chess.js
index 201b9d80f2f4e468f5a569ed08a4f8382feba320..4598758205ed2173b1bf75fe59129ed2b572fed1 100644 (file)
@@ -1,6 +1,5 @@
-'use strict';
 /*
- * Copyright (c) 2014, 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  */
 
-/*! Copyright (c) 2014, Jeff Hlywa (jhlywa@gmail.com)
- *  Released under the BSD license
- *  https://github.com/jhlywa/chess.js/blob/master/LICENSE
+/* @license
+ * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
+ * Released under the BSD license
+ * https://github.com/jhlywa/chess.js/blob/master/LICENSE
  */
 
-/** @constructor */
 var Chess = function(fen) {
 
   /* jshint indent: false */
@@ -69,44 +68,6 @@ var Chess = function(fen) {
     k: [-17, -16, -15,   1,  17, 16, 15,  -1]
   };
 
-  var ATTACKS = [
-    20, 0, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0, 0,20, 0,
-     0,20, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0,20, 0, 0,
-     0, 0,20, 0, 0, 0, 0, 24,  0, 0, 0, 0,20, 0, 0, 0,
-     0, 0, 0,20, 0, 0, 0, 24,  0, 0, 0,20, 0, 0, 0, 0,
-     0, 0, 0, 0,20, 0, 0, 24,  0, 0,20, 0, 0, 0, 0, 0,
-     0, 0, 0, 0, 0,20, 2, 24,  2,20, 0, 0, 0, 0, 0, 0,
-     0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
-    24,24,24,24,24,24,56,  0, 56,24,24,24,24,24,24, 0,
-     0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
-     0, 0, 0, 0, 0,20, 2, 24,  2,20, 0, 0, 0, 0, 0, 0,
-     0, 0, 0, 0,20, 0, 0, 24,  0, 0,20, 0, 0, 0, 0, 0,
-     0, 0, 0,20, 0, 0, 0, 24,  0, 0, 0,20, 0, 0, 0, 0,
-     0, 0,20, 0, 0, 0, 0, 24,  0, 0, 0, 0,20, 0, 0, 0,
-     0,20, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0,20, 0, 0,
-    20, 0, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0, 0,20
-  ];
-
-  var RAYS = [
-     17,  0,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0,  0, 15, 0,
-      0, 17,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0, 15,  0, 0,
-      0,  0, 17,  0,  0,  0,  0, 16,  0,  0,  0,  0, 15,  0,  0, 0,
-      0,  0,  0, 17,  0,  0,  0, 16,  0,  0,  0, 15,  0,  0,  0, 0,
-      0,  0,  0,  0, 17,  0,  0, 16,  0,  0, 15,  0,  0,  0,  0, 0,
-      0,  0,  0,  0,  0, 17,  0, 16,  0, 15,  0,  0,  0,  0,  0, 0,
-      0,  0,  0,  0,  0,  0, 17, 16, 15,  0,  0,  0,  0,  0,  0, 0,
-      1,  1,  1,  1,  1,  1,  1,  0, -1, -1,  -1,-1, -1, -1, -1, 0,
-      0,  0,  0,  0,  0,  0,-15,-16,-17,  0,  0,  0,  0,  0,  0, 0,
-      0,  0,  0,  0,  0,-15,  0,-16,  0,-17,  0,  0,  0,  0,  0, 0,
-      0,  0,  0,  0,-15,  0,  0,-16,  0,  0,-17,  0,  0,  0,  0, 0,
-      0,  0,  0,-15,  0,  0,  0,-16,  0,  0,  0,-17,  0,  0,  0, 0,
-      0,  0,-15,  0,  0,  0,  0,-16,  0,  0,  0,  0,-17,  0,  0, 0,
-      0,-15,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,-17,  0, 0,
-    -15,  0,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,  0,-17
-  ];
-
-  var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };
-
   var FLAGS = {
     NORMAL: 'n',
     CAPTURE: 'c',
@@ -147,15 +108,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;
@@ -194,7 +149,6 @@ var Chess = function(fen) {
     var tokens = fen.split(/\s+/);
     var position = tokens[0];
     var square = 0;
-    var valid = SYMBOLS + '12345678/';
 
     if (!validate_fen(fen).valid) {
       return false;
@@ -218,17 +172,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[i].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.a8 + (black_frc_columns[i].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]];
@@ -240,6 +240,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.',
@@ -253,6 +258,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? */
@@ -277,7 +283,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]};
     }
 
@@ -318,6 +325,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]};
   }
@@ -356,10 +368,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 || '-';
@@ -447,7 +488,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,
@@ -461,7 +502,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;
@@ -470,7 +513,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)) {
@@ -479,8 +522,33 @@ 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 = [];
@@ -523,15 +591,15 @@ var Chess = function(fen) {
             add_move(board, moves, i, square, BITS.NORMAL);
 
           /* double square */
-          square = i + PAWN_OFFSETS[us][1];
+          var square = i + PAWN_OFFSETS[us][1];
           if (second_rank[us] === rank(i) && board[square] == null) {
             add_move(board, moves, i, square, BITS.BIG_PAWN);
           }
         }
 
         /* pawn captures */
-        for (var j = 2; j < 4; j++) {
-          square = i + PAWN_OFFSETS[us][j];
+        for (j = 2; j < 4; j++) {
+          var square = i + PAWN_OFFSETS[us][j];
           if (square & 0x88) continue;
 
           if (board[square] != null &&
@@ -571,36 +639,33 @@ 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);
         }
       }
     }
 
+    return possibly_filter_moves(moves, us, legal);
+  }
+
+  function possibly_filter_moves(moves, us, legal) {
     /* return all pseudo-legal moves (this includes moves that allow the king
      * to be captured)
      */
@@ -621,10 +686,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) {
@@ -632,7 +739,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;
@@ -665,41 +772,77 @@ 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 */
-      if (i & 0x88) { i += 7; continue; }
+    // Check for attacks by the king.
+    if (Math.abs(rank(kings[color]) - rank(square)) <= 1 &&
+        Math.abs(file(kings[color]) - file(square)) <= 1) {
+      return true;
+    }
 
-      /* if empty square or wrong color */
-      if (board[i] == null || board[i].color !== color) continue;
+    // Check for attacks by knights.
+    for (const offset of PIECE_OFFSETS[KNIGHT]) {
+      let knight_sq = square + offset;
+      if (knight_sq & 0x88) continue;
 
-      var piece = board[i];
-      var difference = i - square;
-      var index = difference + 119;
+      if (board[knight_sq] != null &&
+          board[knight_sq].type === KNIGHT &&
+          board[knight_sq].color === color) {
+        return true;
+      }
+    }
 
-      if (ATTACKS[index] & (1 << SHIFTS[piece.type])) {
-        if (piece.type === PAWN) {
-          if (difference > 0) {
-            if (piece.color === WHITE) return true;
-          } else {
-            if (piece.color === BLACK) return true;
+    // Check for attacks by pawns.
+    const p1sq = square - PAWN_OFFSETS[color][2];
+    const p2sq = square - PAWN_OFFSETS[color][3];
+    if (!(p1sq & 0x88) &&
+        board[p1sq] != null &&
+        board[p1sq].type === PAWN &&
+        board[p1sq].color === color) {
+      return true;
+    }
+    if (!(p2sq & 0x88) &&
+        board[p2sq] != null &&
+        board[p2sq].type === PAWN &&
+        board[p2sq].color === color) {
+      return true;
+    }
+
+    // Check for attacks by rooks (where queens count as rooks).
+    for (const offset of PIECE_OFFSETS[ROOK]) {
+      let rook_sq = square;
+      while (true) {
+        rook_sq += offset;
+        if (rook_sq & 0x88) break;
+
+        if (board[rook_sq] != null) {
+          if ((board[rook_sq].type === ROOK || board[rook_sq].type === QUEEN) &&
+              board[rook_sq].color === color) {
+            return true;
           }
-          continue;
+          break;
         }
+      }
+    }
 
-        /* if the piece is a knight or a king */
-        if (piece.type === 'n' || piece.type === 'k') return true;
-
-        var offset = RAYS[index];
-        var j = i + offset;
-
-        var blocked = false;
-        while (j !== square) {
-          if (board[j] != null) { blocked = true; break; }
-          j += offset;
+    // And similarly for attacks by bishops (where queens count as bishops).
+    for (const offset of PIECE_OFFSETS[BISHOP]) {
+      let bishop_sq = square;
+      while (true) {
+        bishop_sq += offset;
+        if (bishop_sq & 0x88) break;
+
+        if (board[bishop_sq] != null) {
+          if ((board[bishop_sq].type === BISHOP || board[bishop_sq].type === QUEEN) &&
+              board[bishop_sq].color === color) {
+            return true;
+          }
+          break;
         }
-
-        if (!blocked) return true;
       }
     }
 
@@ -817,7 +960,9 @@ var Chess = function(fen) {
     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) {
@@ -840,14 +985,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] = {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 */
@@ -856,10 +1003,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;
         }
       }
@@ -867,10 +1014,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;
         }
       }
@@ -917,9 +1064,11 @@ var Chess = function(fen) {
     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};
@@ -937,28 +1086,41 @@ 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] = {type: ROOK, color: us};
+      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 from = move.from;
     var to = move.to;
     var piece = move.piece;
 
+    if (piece === 'p' || piece === 'k') {
+       // Pawn or king moves are never ambiguous.
+       return '';
+    }
+
+    let moves = find_attacking_moves(move.to, piece, move.color);
+    if (moves.length <= 1) {
+       // There can be no ambiguity, so don't bother checking legality
+       // (we assume the move has already been found legal).
+       return '';
+    }
+
+    moves = possibly_filter_moves(moves, move.color, !sloppy);
+
     var ambiguities = 0;
     var same_rank = 0;
     var same_file = 0;
@@ -1006,6 +1168,42 @@ var Chess = function(fen) {
     return '';
   }
 
+  // Find all pseudolegal moves featuring the given piece moving to
+  // the given square (using symmetry of all non-pawn-or-castle moves,
+  // we simply generate moves backwards). Does not support pawns.
+  // Assumes there's not already a piece of our own color
+  // on the destination square.
+  function find_attacking_moves(to, piece, us) {
+    let moves = [];
+
+    function add_move(board, moves, from, to, flags, rook_sq) {
+      moves.push(build_move(board, from, to, flags, undefined, rook_sq));
+    }
+    for (let offset of PIECE_OFFSETS[piece]) {
+      var square = to;
+
+      while (true) {
+        square += offset;
+        if (square & 0x88) break;
+
+        if (board[square] != null) {
+          if (board[square].color !== us || board[square].type !== piece) break;
+          if (board[to] == null) {
+            add_move(board, moves, square, to, BITS.NORMAL);
+          } else {
+            add_move(board, moves, square, to, BITS.CAPTURE);
+          }
+          break;
+        }
+
+        /* break if knight or king */
+        if (piece === 'n' || piece === 'k') break;
+      }
+    }
+
+    return moves;
+  }
+
   function ascii() {
     var s = '   +------------------------+\n';
     for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
@@ -1036,6 +1234,61 @@ 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];
+      }
+    }
+
+    let moves;
+    let piece_matches = clean_move.match(/^([NBRQK])x?([a-h][1-8])$/);
+    if (piece_matches) {
+      // Only look for moves by the given piece to the given square.
+      let to = SQUARES[piece_matches[2]];
+      if (board[to] != null && board[to].color === turn) {
+        // Cannot capture our own piece.
+        return null;
+      }
+      moves = find_attacking_moves(to, piece_matches[1].toLowerCase(), turn);
+      // Legal moves only.
+      moves = possibly_filter_moves(moves, turn, true);
+    } else {
+      // Fallback (also used for pawns): Any (legal) moves.
+      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
    ****************************************************************************/
@@ -1063,7 +1316,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);
 
@@ -1156,9 +1409,9 @@ var Chess = function(fen) {
       return load(fen);
     },
 
-    reset: function() {
-      return reset();
-    },
+    // reset: function() {
+    //   return reset();
+    // },
 
     moves: function(options) {
       /* The internal representation of a chess move is in 0x88 format, and
@@ -1179,39 +1432,39 @@ 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));
         }
       }
 
       return moves;
     },
 
-    in_check: function() {
-      return in_check();
-    },
+    // in_check: function() {
+    //   return in_check();
+    // },
 
-    in_checkmate: function() {
-      return in_checkmate();
-    },
+    // in_checkmate: function() {
+    //   return in_checkmate();
+    // },
 
-    in_stalemate: function() {
-      return in_stalemate();
-    },
+    // in_stalemate: function() {
+    //   return in_stalemate();
+    // },
 
-    in_draw: function() {
-      return half_moves >= 100 ||
-             in_stalemate() ||
-             insufficient_material() ||
-             in_threefold_repetition();
-    },
+    // in_draw: function() {
+    //   return half_moves >= 100 ||
+    //          in_stalemate() ||
+    //          insufficient_material() ||
+    //          in_threefold_repetition();
+    // },
 
-    insufficient_material: function() {
-      return insufficient_material();
-    },
+    // insufficient_material: function() {
+    //   return insufficient_material();
+    // },
 
-    in_threefold_repetition: function() {
-      return in_threefold_repetition();
-    },
+    // in_threefold_repetition: function() {
+    //   return in_threefold_repetition();
+    // },
 
     game_over: function() {
       return half_moves >= 100 ||
@@ -1221,248 +1474,269 @@ var Chess = function(fen) {
              in_threefold_repetition();
     },
 
-    validate_fen: function(fen) {
-      return validate_fen(fen);
-    },
+    // validate_fen: function(fen) {
+    //   return validate_fen(fen);
+    // },
 
     fen: function() {
       return generate_fen();
     },
 
-    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 newline = (typeof options === 'object' &&
-                     typeof options.newline_char === 'string') ?
-                     options.newline_char : '\n';
-      var max_width = (typeof options === 'object' &&
-                       typeof options.max_width === 'number') ?
-                       options.max_width : 0;
-      var result = [];
-      var header_exists = false;
-
-      /* add the PGN header headerrmation */
-      for (var i in header) {
-        /* TODO: order of enumerated properties in header object is not
-         * guaranteed, see ECMA-262 spec (section 12.6.4)
-         */
-        result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
-        header_exists = true;
-      }
-
-      if (header_exists && history.length) {
-        result.push(newline);
-      }
-
-      /* pop all of history onto reversed_history */
-      var reversed_history = [];
-      while (history.length > 0) {
-        reversed_history.push(undo_move());
-      }
-
-      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++;
-        } 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_string + ' ' + move_to_san(move);
-        make_move(move);
-      }
-
-      /* are there any other leftover moves? */
-      if (move_string.length) {
-        moves.push(move_string);
-      }
-
-      /* is there a result? */
-      if (typeof header.Result !== 'undefined') {
-        moves.push(header.Result);
-      }
-
-      /* history should be back to what is was before we started generating PGN,
-       * so join together moves
-       */
-      if (max_width === 0) {
-        return result.join('') + moves.join(' ');
-      }
-
-      /* wrap the PGN output at max_width */
-      var current_width = 0;
-      for (var i = 0; i < moves.length; i++) {
-        /* if the current move will push past max_width */
-        if (current_width + moves[i].length > max_width && i !== 0) {
-
-          /* don't end the line with whitespace */
-          if (result[result.length - 1] === ' ') {
-            result.pop();
-          }
-
-          result.push(newline);
-          current_width = 0;
-        } else if (i !== 0) {
-          result.push(' ');
-          current_width++;
-        }
-        result.push(moves[i]);
-        current_width += moves[i].length;
-      }
-
-      return result.join('');
-    },
-
-    load_pgn: function(pgn, options) {
-      function mask(str) {
-        return str.replace(/\\/g, '\\');
-      }
-
-      /* convert a move from Standard Algebraic Notation (SAN) to 0x88
-       * coordinates
-      */
-      function move_from_san(move) {
-        var moves = generate_moves();
-        for (var i = 0, len = moves.length; i < len; i++) {
-          /* strip off any trailing move decorations: e.g Nf3+?! */
-          if (move.replace(/[+#?!=]+$/,'') ==
-              move_to_san(moves[i]).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 has_keys;
-      }
-
-      function parse_pgn_header(header, options) {
-        var newline_char = (typeof options === 'object' &&
-                            typeof options.newline_char === 'string') ?
-                            options.newline_char : '\r?\n';
-        var header_obj = {};
-        var headers = header.split(new RegExp(mask(newline_char)));
-        var key = '';
-        var value = '';
-
-        for (var i = 0; i < headers.length; i++) {
-          key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
-          value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
-          if (trim(key).length > 0) {
-            header_obj[key] = value;
-          }
-        }
-
-        return header_obj;
-      }
-
-      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');
-
-      /* get header part of the PGN file */
-      var header_string = pgn.replace(regex, '$1');
-
-      /* no info part given, begins with moves */
-      if (header_string[0] !== '[') {
-        header_string = '';
-      }
-
-     reset();
-
-      /* parse PGN header */
-      var headers = parse_pgn_header(header_string, options);
-      for (var key in headers) {
-        set_header([key, headers[key]]);
-      }
-
-      /* delete header to get the moves */
-      var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
-
-      /* delete comments */
-      ms = ms.replace(/(\{[^}]+\})+?/g, '');
-
-      /* delete move numbers */
-      ms = ms.replace(/\d+\./g, '');
-
-
-      /* trim and get array of moves */
-      var moves = trim(ms).split(new RegExp(/\s+/));
-
-      /* delete empty entries */
-      moves = moves.join(',').replace(/,,+/g, ',').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.indexOf(move) > -1) {
-        if (has_keys(header) && typeof header.Result === 'undefined') {
-          set_header(['Result', move]);
-        }
-      }
-      else {
-        move = get_move_obj(move);
-        if (move == null) {
-          return false;
-        } else {
-          make_move(move);
-        }
-      }
-      return true;
-    },
-
-    header: function() {
-      return set_header(arguments);
-    },
-
-    ascii: function() {
-      return ascii();
-    },
+    // 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 newline = (typeof options === 'object' &&
+    //                  typeof options.newline_char === 'string') ?
+    //                  options.newline_char : '\n';
+    //   var max_width = (typeof options === 'object' &&
+    //                    typeof options.max_width === 'number') ?
+    //                    options.max_width : 0;
+    //   var result = [];
+    //   var header_exists = false;
+
+    //   /* add the PGN header headerrmation */
+    //   for (var i in header) {
+    //     /* TODO: order of enumerated properties in header object is not
+    //      * guaranteed, see ECMA-262 spec (section 12.6.4)
+    //      */
+    //     result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
+    //     header_exists = true;
+    //   }
+
+    //   if (header_exists && history.length) {
+    //     result.push(newline);
+    //   }
+
+    //   /* pop all of history onto reversed_history */
+    //   var reversed_history = [];
+    //   while (history.length > 0) {
+    //     reversed_history.push(undo_move());
+    //   }
+
+    //   var moves = [];
+    //   var move_string = '';
+
+    //   /* 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 (!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 = move_number + '.';
+    //     }
+
+    //     move_string = move_string + ' ' + move_to_san(move, false);
+    //     make_move(move);
+    //   }
+
+    //   /* are there any other leftover moves? */
+    //   if (move_string.length) {
+    //     moves.push(move_string);
+    //   }
+
+    //   /* is there a result? */
+    //   if (typeof header.Result !== 'undefined') {
+    //     moves.push(header.Result);
+    //   }
+
+    //   /* history should be back to what is was before we started generating PGN,
+    //    * so join together moves
+    //    */
+    //   if (max_width === 0) {
+    //     return result.join('') + moves.join(' ');
+    //   }
+
+    //   /* wrap the PGN output at max_width */
+    //   var current_width = 0;
+    //   for (var i = 0; i < moves.length; i++) {
+    //     /* if the current move will push past max_width */
+    //     if (current_width + moves[i].length > max_width && i !== 0) {
+
+    //       /* don't end the line with whitespace */
+    //       if (result[result.length - 1] === ' ') {
+    //         result.pop();
+    //       }
+
+    //       result.push(newline);
+    //       current_width = 0;
+    //     } else if (i !== 0) {
+    //       result.push(' ');
+    //       current_width++;
+    //     }
+    //     result.push(moves[i]);
+    //     current_width += moves[i].length;
+    //   }
+
+    //   return result.join('');
+    // },
+
+    // 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, '\\');
+    //   }
+
+    //   function has_keys(object) {
+    //     for (var key in object) {
+    //       return true;
+    //     }
+    //     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 header_obj = {};
+    //     var headers = header.split(new RegExp(mask(newline_char)));
+    //     var key = '';
+    //     var value = '';
+
+    //     for (var i = 0; i < headers.length; i++) {
+    //       key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
+    //       value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
+    //       if (trim(key).length > 0) {
+    //         header_obj[key] = value;
+    //       }
+    //     }
+
+    //     return header_obj;
+    //   }
+
+    //   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');
+
+    //   /* get header part of the PGN file */
+    //   var header_string = pgn.replace(regex, '$1');
+
+    //   /* no info part given, begins with moves */
+    //   if (header_string[0] !== '[') {
+    //     header_string = '';
+    //   }
+
+    //   reset();
+
+    //   /* parse PGN header */
+    //   var headers = parse_pgn_header(header_string, options);
+    //   for (var key in headers) {
+    //     set_header([key, headers[key]]);
+    //   }
+
+    //   /* load the starting position indicated by [Setup '1'] and
+    //   * [FEN position] */
+    //   if (headers['SetUp'] === '1') {
+    //       if (!(('FEN' in headers) && load(headers['FEN'], true ))) { // second argument to load: don't clear the headers
+    //         return false;
+    //       }
+    //   }
+
+    //   /* delete header to get the moves */
+    //   var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
+
+    //   /* delete comments */
+    //   ms = ms.replace(/(\{[^}]+\})+?/g, '');
+
+    //   /* delete recursive annotation variations */
+    //   var rav_regex = /(\([^\(\)]+\))+?/g
+    //   while (rav_regex.test(ms)) {
+    //     ms = ms.replace(rav_regex, '');
+    //   }
+
+    //   /* delete move numbers */
+    //   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+/));
+
+    //   /* delete empty entries */
+    //   moves = moves.join(',').replace(/,,+/g, ',').split(',');
+    //   var move = '';
+
+    //   for (var half_move = 0; half_move < moves.length - 1; 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)
+    //      */
+    //     if (move == null) {
+    //       return false;
+    //     } else {
+    //       make_move(move);
+    //     }
+    //   }
+
+    //   /* examine last move */
+    //   move = moves[moves.length - 1];
+    //   if (POSSIBLE_RESULTS.indexOf(move) > -1) {
+    //     if (has_keys(header) && typeof header.Result === 'undefined') {
+    //       set_header(['Result', move]);
+    //     }
+    //   }
+    //   else {
+    //     move = move_from_san(move, sloppy);
+    //     if (move == null) {
+    //       return false;
+    //     } else {
+    //       make_move(move);
+    //     }
+    //   }
+    //   return true;
+    // },
+
+    // header: function() {
+    //   return set_header(arguments);
+    // },
+
+    // ascii: function() {
+    //   return ascii();
+    // },
 
     turn: function() {
       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
@@ -1472,18 +1746,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 */
-        for (var i = 0, len = moves.length; i < len; i++) {
-          if (move === move_to_san(moves[i])) {
-            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) &&
@@ -1520,30 +1795,30 @@ var Chess = function(fen) {
       return clear();
     },
 
-    put: function(piece, square) {
-      return put(piece, square);
-    },
+    // put: function(piece, square) {
+    //   return put(piece, square);
+    // },
 
-    get: function(square) {
-      return get(square);
-    },
+    // get: function(square) {
+    //   return get(square);
+    // },
 
-    remove: function(square) {
-      return remove(square);
-    },
+    // remove: function(square) {
+    //   return remove(square);
+    // },
 
-    perft: function(depth) {
-      return perft(depth);
-    },
+    // perft: function(depth) {
+    //   return perft(depth);
+    // },
 
-    square_color: function(square) {
-      if (square in SQUARES) {
-        var sq_0x88 = SQUARES[square];
-        return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
-      }
+    // square_color: function(square) {
+    //   if (square in SQUARES) {
+    //     var sq_0x88 = SQUARES[square];
+    //     return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
+    //   }
 
-      return null;
-    },
+    //   return null;
+    // },
 
     history: function(options) {
       var reversed_history = [];
@@ -1570,3 +1845,9 @@ var Chess = function(fen) {
 
   };
 };
+
+/* export Chess object if using node or any other CommonJS compatible
+ * environment */
+if (typeof exports !== 'undefined') exports.Chess = Chess;
+/* export Chess object for any RequireJS compatible environment */
+if (typeof define !== 'undefined') define( function () { return Chess;  });