]> git.sesse.net Git - remoteglot/blob - www/js/chess.js
Add a SCORE_NONE, and fix a typo.
[remoteglot] / www / js / chess.js
1 'use strict';
2 /*
3  * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice,
10  *    this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions and the following disclaimer in the documentation
13  *    and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  *
27  *----------------------------------------------------------------------------*/
28
29 /* minified license below  */
30
31 /* @license
32  * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
33  * Released under the BSD license
34  * https://github.com/jhlywa/chess.js/blob/master/LICENSE
35  */
36
37 var Chess = function(fen) {
38
39   /* jshint indent: false */
40
41   var BLACK = 'b';
42   var WHITE = 'w';
43
44   var EMPTY = -1;
45
46   var PAWN = 'p';
47   var KNIGHT = 'n';
48   var BISHOP = 'b';
49   var ROOK = 'r';
50   var QUEEN = 'q';
51   var KING = 'k';
52
53   var SYMBOLS = 'pnbrqkPNBRQK';
54
55   var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
56
57   var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*'];
58
59   var PAWN_OFFSETS = {
60     b: [16, 32, 17, 15],
61     w: [-16, -32, -17, -15]
62   };
63
64   var PIECE_OFFSETS = {
65     n: [-18, -33, -31, -14,  18, 33, 31,  14],
66     b: [-17, -15,  17,  15],
67     r: [-16,   1,  16,  -1],
68     q: [-17, -16, -15,   1,  17, 16, 15,  -1],
69     k: [-17, -16, -15,   1,  17, 16, 15,  -1]
70   };
71
72   var ATTACKS = [
73     20, 0, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0, 0,20, 0,
74      0,20, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0,20, 0, 0,
75      0, 0,20, 0, 0, 0, 0, 24,  0, 0, 0, 0,20, 0, 0, 0,
76      0, 0, 0,20, 0, 0, 0, 24,  0, 0, 0,20, 0, 0, 0, 0,
77      0, 0, 0, 0,20, 0, 0, 24,  0, 0,20, 0, 0, 0, 0, 0,
78      0, 0, 0, 0, 0,20, 2, 24,  2,20, 0, 0, 0, 0, 0, 0,
79      0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
80     24,24,24,24,24,24,56,  0, 56,24,24,24,24,24,24, 0,
81      0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
82      0, 0, 0, 0, 0,20, 2, 24,  2,20, 0, 0, 0, 0, 0, 0,
83      0, 0, 0, 0,20, 0, 0, 24,  0, 0,20, 0, 0, 0, 0, 0,
84      0, 0, 0,20, 0, 0, 0, 24,  0, 0, 0,20, 0, 0, 0, 0,
85      0, 0,20, 0, 0, 0, 0, 24,  0, 0, 0, 0,20, 0, 0, 0,
86      0,20, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0,20, 0, 0,
87     20, 0, 0, 0, 0, 0, 0, 24,  0, 0, 0, 0, 0, 0,20
88   ];
89
90   var RAYS = [
91      17,  0,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0,  0, 15, 0,
92       0, 17,  0,  0,  0,  0,  0, 16,  0,  0,  0,  0,  0, 15,  0, 0,
93       0,  0, 17,  0,  0,  0,  0, 16,  0,  0,  0,  0, 15,  0,  0, 0,
94       0,  0,  0, 17,  0,  0,  0, 16,  0,  0,  0, 15,  0,  0,  0, 0,
95       0,  0,  0,  0, 17,  0,  0, 16,  0,  0, 15,  0,  0,  0,  0, 0,
96       0,  0,  0,  0,  0, 17,  0, 16,  0, 15,  0,  0,  0,  0,  0, 0,
97       0,  0,  0,  0,  0,  0, 17, 16, 15,  0,  0,  0,  0,  0,  0, 0,
98       1,  1,  1,  1,  1,  1,  1,  0, -1, -1,  -1,-1, -1, -1, -1, 0,
99       0,  0,  0,  0,  0,  0,-15,-16,-17,  0,  0,  0,  0,  0,  0, 0,
100       0,  0,  0,  0,  0,-15,  0,-16,  0,-17,  0,  0,  0,  0,  0, 0,
101       0,  0,  0,  0,-15,  0,  0,-16,  0,  0,-17,  0,  0,  0,  0, 0,
102       0,  0,  0,-15,  0,  0,  0,-16,  0,  0,  0,-17,  0,  0,  0, 0,
103       0,  0,-15,  0,  0,  0,  0,-16,  0,  0,  0,  0,-17,  0,  0, 0,
104       0,-15,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,-17,  0, 0,
105     -15,  0,  0,  0,  0,  0,  0,-16,  0,  0,  0,  0,  0,  0,-17
106   ];
107
108   var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };
109
110   var FLAGS = {
111     NORMAL: 'n',
112     CAPTURE: 'c',
113     BIG_PAWN: 'b',
114     EP_CAPTURE: 'e',
115     PROMOTION: 'p',
116     KSIDE_CASTLE: 'k',
117     QSIDE_CASTLE: 'q'
118   };
119
120   var BITS = {
121     NORMAL: 1,
122     CAPTURE: 2,
123     BIG_PAWN: 4,
124     EP_CAPTURE: 8,
125     PROMOTION: 16,
126     KSIDE_CASTLE: 32,
127     QSIDE_CASTLE: 64
128   };
129
130   var RANK_1 = 7;
131   var RANK_2 = 6;
132   var RANK_3 = 5;
133   var RANK_4 = 4;
134   var RANK_5 = 3;
135   var RANK_6 = 2;
136   var RANK_7 = 1;
137   var RANK_8 = 0;
138
139   var SQUARES = {
140     a8:   0, b8:   1, c8:   2, d8:   3, e8:   4, f8:   5, g8:   6, h8:   7,
141     a7:  16, b7:  17, c7:  18, d7:  19, e7:  20, f7:  21, g7:  22, h7:  23,
142     a6:  32, b6:  33, c6:  34, d6:  35, e6:  36, f6:  37, g6:  38, h6:  39,
143     a5:  48, b5:  49, c5:  50, d5:  51, e5:  52, f5:  53, g5:  54, h5:  55,
144     a4:  64, b4:  65, c4:  66, d4:  67, e4:  68, f4:  69, g4:  70, h4:  71,
145     a3:  80, b3:  81, c3:  82, d3:  83, e3:  84, f3:  85, g3:  86, h3:  87,
146     a2:  96, b2:  97, c2:  98, d2:  99, e2: 100, f2: 101, g2: 102, h2: 103,
147     a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119
148   };
149
150   var ROOKS = {
151     w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE},
152         {square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}],
153     b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE},
154         {square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}]
155   };
156
157   var board = new Array(128);
158   var kings = {w: EMPTY, b: EMPTY};
159   var turn = WHITE;
160   var castling = {w: 0, b: 0};
161   var ep_square = EMPTY;
162   var half_moves = 0;
163   var move_number = 1;
164   var history = [];
165   var header = {};
166
167   /* if the user passes in a fen string, load it, else default to
168    * starting position
169    */
170   if (typeof fen === 'undefined') {
171     load(DEFAULT_POSITION);
172   } else {
173     load(fen);
174   }
175
176   function clear() {
177     board = new Array(128);
178     kings = {w: EMPTY, b: EMPTY};
179     turn = WHITE;
180     castling = {w: 0, b: 0};
181     ep_square = EMPTY;
182     half_moves = 0;
183     move_number = 1;
184     history = [];
185     header = {};
186     update_setup(generate_fen());
187   }
188
189   function reset() {
190     load(DEFAULT_POSITION);
191   }
192
193   function load(fen) {
194     var tokens = fen.split(/\s+/);
195     var position = tokens[0];
196     var square = 0;
197
198     if (!validate_fen(fen).valid) {
199       return false;
200     }
201
202     clear();
203
204     for (var i = 0; i < position.length; i++) {
205       var piece = position.charAt(i);
206
207       if (piece === '/') {
208         square += 8;
209       } else if (is_digit(piece)) {
210         square += parseInt(piece, 10);
211       } else {
212         var color = (piece < 'a') ? WHITE : BLACK;
213         put({type: piece.toLowerCase(), color: color}, algebraic(square));
214         square++;
215       }
216     }
217
218     turn = tokens[1];
219
220     if (tokens[2].indexOf('K') > -1) {
221       castling.w |= BITS.KSIDE_CASTLE;
222     }
223     if (tokens[2].indexOf('Q') > -1) {
224       castling.w |= BITS.QSIDE_CASTLE;
225     }
226     if (tokens[2].indexOf('k') > -1) {
227       castling.b |= BITS.KSIDE_CASTLE;
228     }
229     if (tokens[2].indexOf('q') > -1) {
230       castling.b |= BITS.QSIDE_CASTLE;
231     }
232
233     ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
234     half_moves = parseInt(tokens[4], 10);
235     move_number = parseInt(tokens[5], 10);
236
237     update_setup(generate_fen());
238
239     return true;
240   }
241
242   function validate_fen(fen) {
243     var errors = {
244        0: 'No errors.',
245        1: 'FEN string must contain six space-delimited fields.',
246        2: '6th field (move number) must be a positive integer.',
247        3: '5th field (half move counter) must be a non-negative integer.',
248        4: '4th field (en-passant square) is invalid.',
249        5: '3rd field (castling availability) is invalid.',
250        6: '2nd field (side to move) is invalid.',
251        7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.',
252        8: '1st field (piece positions) is invalid [consecutive numbers].',
253        9: '1st field (piece positions) is invalid [invalid piece].',
254       10: '1st field (piece positions) is invalid [row too large].',
255     };
256
257     /* 1st criterion: 6 space-seperated fields? */
258     var tokens = fen.split(/\s+/);
259     if (tokens.length !== 6) {
260       return {valid: false, error_number: 1, error: errors[1]};
261     }
262
263     /* 2nd criterion: move number field is a integer value > 0? */
264     if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) {
265       return {valid: false, error_number: 2, error: errors[2]};
266     }
267
268     /* 3rd criterion: half move counter is an integer >= 0? */
269     if (isNaN(tokens[4]) || (parseInt(tokens[4], 10) < 0)) {
270       return {valid: false, error_number: 3, error: errors[3]};
271     }
272
273     /* 4th criterion: 4th field is a valid e.p.-string? */
274     if (!/^(-|[abcdefgh][36])$/.test(tokens[3])) {
275       return {valid: false, error_number: 4, error: errors[4]};
276     }
277
278     /* 5th criterion: 3th field is a valid castle-string? */
279     if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) {
280       return {valid: false, error_number: 5, error: errors[5]};
281     }
282
283     /* 6th criterion: 2nd field is "w" (white) or "b" (black)? */
284     if (!/^(w|b)$/.test(tokens[1])) {
285       return {valid: false, error_number: 6, error: errors[6]};
286     }
287
288     /* 7th criterion: 1st field contains 8 rows? */
289     var rows = tokens[0].split('/');
290     if (rows.length !== 8) {
291       return {valid: false, error_number: 7, error: errors[7]};
292     }
293
294     /* 8th criterion: every row is valid? */
295     for (var i = 0; i < rows.length; i++) {
296       /* check for right sum of fields AND not two numbers in succession */
297       var sum_fields = 0;
298       var previous_was_number = false;
299
300       for (var k = 0; k < rows[i].length; k++) {
301         if (!isNaN(rows[i][k])) {
302           if (previous_was_number) {
303             return {valid: false, error_number: 8, error: errors[8]};
304           }
305           sum_fields += parseInt(rows[i][k], 10);
306           previous_was_number = true;
307         } else {
308           if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
309             return {valid: false, error_number: 9, error: errors[9]};
310           }
311           sum_fields += 1;
312           previous_was_number = false;
313         }
314       }
315       if (sum_fields !== 8) {
316         return {valid: false, error_number: 10, error: errors[10]};
317       }
318     }
319
320     /* everything's okay! */
321     return {valid: true, error_number: 0, error: errors[0]};
322   }
323
324   function generate_fen() {
325     var empty = 0;
326     var fen = '';
327
328     for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
329       if (board[i] == null) {
330         empty++;
331       } else {
332         if (empty > 0) {
333           fen += empty;
334           empty = 0;
335         }
336         var color = board[i].color;
337         var piece = board[i].type;
338
339         fen += (color === WHITE) ?
340                  piece.toUpperCase() : piece.toLowerCase();
341       }
342
343       if ((i + 1) & 0x88) {
344         if (empty > 0) {
345           fen += empty;
346         }
347
348         if (i !== SQUARES.h1) {
349           fen += '/';
350         }
351
352         empty = 0;
353         i += 8;
354       }
355     }
356
357     var cflags = '';
358     if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; }
359     if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; }
360     if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; }
361     if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; }
362
363     /* do we have an empty castling flag? */
364     cflags = cflags || '-';
365     var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square);
366
367     return [fen, turn, cflags, epflags, half_moves, move_number].join(' ');
368   }
369
370   function set_header(args) {
371     for (var i = 0; i < args.length; i += 2) {
372       if (typeof args[i] === 'string' &&
373           typeof args[i + 1] === 'string') {
374         header[args[i]] = args[i + 1];
375       }
376     }
377     return header;
378   }
379
380   /* called when the initial board setup is changed with put() or remove().
381    * modifies the SetUp and FEN properties of the header object.  if the FEN is
382    * equal to the default position, the SetUp and FEN are deleted
383    * the setup is only updated if history.length is zero, ie moves haven't been
384    * made.
385    */
386   function update_setup(fen) {
387     if (history.length > 0) return;
388
389     if (fen !== DEFAULT_POSITION) {
390       header['SetUp'] = '1';
391       header['FEN'] = fen;
392     } else {
393       delete header['SetUp'];
394       delete header['FEN'];
395     }
396   }
397
398   function get(square) {
399     var piece = board[SQUARES[square]];
400     return (piece) ? {type: piece.type, color: piece.color} : null;
401   }
402
403   function put(piece, square) {
404     /* check for valid piece object */
405     if (!('type' in piece && 'color' in piece)) {
406       return false;
407     }
408
409     /* check for piece */
410     if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) {
411       return false;
412     }
413
414     /* check for valid square */
415     if (!(square in SQUARES)) {
416       return false;
417     }
418
419     var sq = SQUARES[square];
420
421     /* don't let the user place more than one king */
422     if (piece.type == KING &&
423         !(kings[piece.color] == EMPTY || kings[piece.color] == sq)) {
424       return false;
425     }
426
427     board[sq] = {type: piece.type, color: piece.color};
428     if (piece.type === KING) {
429       kings[piece.color] = sq;
430     }
431
432     update_setup(generate_fen());
433
434     return true;
435   }
436
437   function remove(square) {
438     var piece = get(square);
439     board[SQUARES[square]] = null;
440     if (piece && piece.type === KING) {
441       kings[piece.color] = EMPTY;
442     }
443
444     update_setup(generate_fen());
445
446     return piece;
447   }
448
449   function build_move(board, from, to, flags, promotion) {
450     var move = {
451       color: turn,
452       from: from,
453       to: to,
454       flags: flags,
455       piece: board[from].type
456     };
457
458     if (promotion) {
459       move.flags |= BITS.PROMOTION;
460       move.promotion = promotion;
461     }
462
463     if (board[to]) {
464       move.captured = board[to].type;
465     } else if (flags & BITS.EP_CAPTURE) {
466         move.captured = PAWN;
467     }
468     return move;
469   }
470
471   function generate_moves(options) {
472     function add_move(board, moves, from, to, flags) {
473       /* if pawn promotion */
474       if (board[from].type === PAWN &&
475          (rank(to) === RANK_8 || rank(to) === RANK_1)) {
476           var pieces = [QUEEN, ROOK, BISHOP, KNIGHT];
477           for (var i = 0, len = pieces.length; i < len; i++) {
478             moves.push(build_move(board, from, to, flags, pieces[i]));
479           }
480       } else {
481        moves.push(build_move(board, from, to, flags));
482       }
483     }
484
485     var moves = [];
486     var us = turn;
487     var them = swap_color(us);
488     var second_rank = {b: RANK_7, w: RANK_2};
489
490     var first_sq = SQUARES.a8;
491     var last_sq = SQUARES.h1;
492     var single_square = false;
493
494     /* do we want legal moves? */
495     var legal = (typeof options !== 'undefined' && 'legal' in options) ?
496                 options.legal : true;
497
498     /* are we generating moves for a single square? */
499     if (typeof options !== 'undefined' && 'square' in options) {
500       if (options.square in SQUARES) {
501         first_sq = last_sq = SQUARES[options.square];
502         single_square = true;
503       } else {
504         /* invalid square */
505         return [];
506       }
507     }
508
509     for (var i = first_sq; i <= last_sq; i++) {
510       /* did we run off the end of the board */
511       if (i & 0x88) { i += 7; continue; }
512
513       var piece = board[i];
514       if (piece == null || piece.color !== us) {
515         continue;
516       }
517
518       if (piece.type === PAWN) {
519         /* single square, non-capturing */
520         var square = i + PAWN_OFFSETS[us][0];
521         if (board[square] == null) {
522             add_move(board, moves, i, square, BITS.NORMAL);
523
524           /* double square */
525           var square = i + PAWN_OFFSETS[us][1];
526           if (second_rank[us] === rank(i) && board[square] == null) {
527             add_move(board, moves, i, square, BITS.BIG_PAWN);
528           }
529         }
530
531         /* pawn captures */
532         for (j = 2; j < 4; j++) {
533           var square = i + PAWN_OFFSETS[us][j];
534           if (square & 0x88) continue;
535
536           if (board[square] != null &&
537               board[square].color === them) {
538               add_move(board, moves, i, square, BITS.CAPTURE);
539           } else if (square === ep_square) {
540               add_move(board, moves, i, ep_square, BITS.EP_CAPTURE);
541           }
542         }
543       } else {
544         for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) {
545           var offset = PIECE_OFFSETS[piece.type][j];
546           var square = i;
547
548           while (true) {
549             square += offset;
550             if (square & 0x88) break;
551
552             if (board[square] == null) {
553               add_move(board, moves, i, square, BITS.NORMAL);
554             } else {
555               if (board[square].color === us) break;
556               add_move(board, moves, i, square, BITS.CAPTURE);
557               break;
558             }
559
560             /* break, if knight or king */
561             if (piece.type === 'n' || piece.type === 'k') break;
562           }
563         }
564       }
565     }
566
567     /* check for castling if: a) we're generating all moves, or b) we're doing
568      * single square move generation on the king's square
569      */
570     if ((!single_square) || last_sq === kings[us]) {
571       /* king-side castling */
572       if (castling[us] & BITS.KSIDE_CASTLE) {
573         var castling_from = kings[us];
574         var castling_to = castling_from + 2;
575
576         if (board[castling_from + 1] == null &&
577             board[castling_to]       == null &&
578             !attacked(them, kings[us]) &&
579             !attacked(them, castling_from + 1) &&
580             !attacked(them, castling_to)) {
581           add_move(board, moves, kings[us] , castling_to,
582                    BITS.KSIDE_CASTLE);
583         }
584       }
585
586       /* queen-side castling */
587       if (castling[us] & BITS.QSIDE_CASTLE) {
588         var castling_from = kings[us];
589         var castling_to = castling_from - 2;
590
591         if (board[castling_from - 1] == null &&
592             board[castling_from - 2] == null &&
593             board[castling_from - 3] == null &&
594             !attacked(them, kings[us]) &&
595             !attacked(them, castling_from - 1) &&
596             !attacked(them, castling_to)) {
597           add_move(board, moves, kings[us], castling_to,
598                    BITS.QSIDE_CASTLE);
599         }
600       }
601     }
602
603     /* return all pseudo-legal moves (this includes moves that allow the king
604      * to be captured)
605      */
606     if (!legal) {
607       return moves;
608     }
609
610     /* filter out illegal moves */
611     var legal_moves = [];
612     for (var i = 0, len = moves.length; i < len; i++) {
613       make_move(moves[i]);
614       if (!king_attacked(us)) {
615         legal_moves.push(moves[i]);
616       }
617       undo_move();
618     }
619
620     return legal_moves;
621   }
622
623   /* convert a move from 0x88 coordinates to Standard Algebraic Notation
624    * (SAN)
625    */
626   function move_to_san(move) {
627     var output = '';
628
629     if (move.flags & BITS.KSIDE_CASTLE) {
630       output = 'O-O';
631     } else if (move.flags & BITS.QSIDE_CASTLE) {
632       output = 'O-O-O';
633     } else {
634       var disambiguator = get_disambiguator(move);
635
636       if (move.piece !== PAWN) {
637         output += move.piece.toUpperCase() + disambiguator;
638       }
639
640       if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
641         if (move.piece === PAWN) {
642           output += algebraic(move.from)[0];
643         }
644         output += 'x';
645       }
646
647       output += algebraic(move.to);
648
649       if (move.flags & BITS.PROMOTION) {
650         output += '=' + move.promotion.toUpperCase();
651       }
652     }
653
654     make_move(move);
655     if (in_check()) {
656       if (in_checkmate()) {
657         output += '#';
658       } else {
659         output += '+';
660       }
661     }
662     undo_move();
663
664     return output;
665   }
666
667   function attacked(color, square) {
668     for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
669       /* did we run off the end of the board */
670       if (i & 0x88) { i += 7; continue; }
671
672       /* if empty square or wrong color */
673       if (board[i] == null || board[i].color !== color) continue;
674
675       var piece = board[i];
676       var difference = i - square;
677       var index = difference + 119;
678
679       if (ATTACKS[index] & (1 << SHIFTS[piece.type])) {
680         if (piece.type === PAWN) {
681           if (difference > 0) {
682             if (piece.color === WHITE) return true;
683           } else {
684             if (piece.color === BLACK) return true;
685           }
686           continue;
687         }
688
689         /* if the piece is a knight or a king */
690         if (piece.type === 'n' || piece.type === 'k') return true;
691
692         var offset = RAYS[index];
693         var j = i + offset;
694
695         var blocked = false;
696         while (j !== square) {
697           if (board[j] != null) { blocked = true; break; }
698           j += offset;
699         }
700
701         if (!blocked) return true;
702       }
703     }
704
705     return false;
706   }
707
708   function king_attacked(color) {
709     return attacked(swap_color(color), kings[color]);
710   }
711
712   function in_check() {
713     return king_attacked(turn);
714   }
715
716   function in_checkmate() {
717     return in_check() && generate_moves().length === 0;
718   }
719
720   function in_stalemate() {
721     return !in_check() && generate_moves().length === 0;
722   }
723
724   function insufficient_material() {
725     var pieces = {};
726     var bishops = [];
727     var num_pieces = 0;
728     var sq_color = 0;
729
730     for (var i = SQUARES.a8; i<= SQUARES.h1; i++) {
731       sq_color = (sq_color + 1) % 2;
732       if (i & 0x88) { i += 7; continue; }
733
734       var piece = board[i];
735       if (piece) {
736         pieces[piece.type] = (piece.type in pieces) ?
737                               pieces[piece.type] + 1 : 1;
738         if (piece.type === BISHOP) {
739           bishops.push(sq_color);
740         }
741         num_pieces++;
742       }
743     }
744
745     /* k vs. k */
746     if (num_pieces === 2) { return true; }
747
748     /* k vs. kn .... or .... k vs. kb */
749     else if (num_pieces === 3 && (pieces[BISHOP] === 1 ||
750                                  pieces[KNIGHT] === 1)) { return true; }
751
752     /* kb vs. kb where any number of bishops are all on the same color */
753     else if (num_pieces === pieces[BISHOP] + 2) {
754       var sum = 0;
755       var len = bishops.length;
756       for (var i = 0; i < len; i++) {
757         sum += bishops[i];
758       }
759       if (sum === 0 || sum === len) { return true; }
760     }
761
762     return false;
763   }
764
765   function in_threefold_repetition() {
766     /* TODO: while this function is fine for casual use, a better
767      * implementation would use a Zobrist key (instead of FEN). the
768      * Zobrist key would be maintained in the make_move/undo_move functions,
769      * avoiding the costly that we do below.
770      */
771     var moves = [];
772     var positions = {};
773     var repetition = false;
774
775     while (true) {
776       var move = undo_move();
777       if (!move) break;
778       moves.push(move);
779     }
780
781     while (true) {
782       /* remove the last two fields in the FEN string, they're not needed
783        * when checking for draw by rep */
784       var fen = generate_fen().split(' ').slice(0,4).join(' ');
785
786       /* has the position occurred three or move times */
787       positions[fen] = (fen in positions) ? positions[fen] + 1 : 1;
788       if (positions[fen] >= 3) {
789         repetition = true;
790       }
791
792       if (!moves.length) {
793         break;
794       }
795       make_move(moves.pop());
796     }
797
798     return repetition;
799   }
800
801   function push(move) {
802     history.push({
803       move: move,
804       kings: {b: kings.b, w: kings.w},
805       turn: turn,
806       castling: {b: castling.b, w: castling.w},
807       ep_square: ep_square,
808       half_moves: half_moves,
809       move_number: move_number
810     });
811   }
812
813   function make_move(move) {
814     var us = turn;
815     var them = swap_color(us);
816     push(move);
817
818     board[move.to] = board[move.from];
819     board[move.from] = null;
820
821     /* if ep capture, remove the captured pawn */
822     if (move.flags & BITS.EP_CAPTURE) {
823       if (turn === BLACK) {
824         board[move.to - 16] = null;
825       } else {
826         board[move.to + 16] = null;
827       }
828     }
829
830     /* if pawn promotion, replace with new piece */
831     if (move.flags & BITS.PROMOTION) {
832       board[move.to] = {type: move.promotion, color: us};
833     }
834
835     /* if we moved the king */
836     if (board[move.to].type === KING) {
837       kings[board[move.to].color] = move.to;
838
839       /* if we castled, move the rook next to the king */
840       if (move.flags & BITS.KSIDE_CASTLE) {
841         var castling_to = move.to - 1;
842         var castling_from = move.to + 1;
843         board[castling_to] = board[castling_from];
844         board[castling_from] = null;
845       } else if (move.flags & BITS.QSIDE_CASTLE) {
846         var castling_to = move.to + 1;
847         var castling_from = move.to - 2;
848         board[castling_to] = board[castling_from];
849         board[castling_from] = null;
850       }
851
852       /* turn off castling */
853       castling[us] = '';
854     }
855
856     /* turn off castling if we move a rook */
857     if (castling[us]) {
858       for (var i = 0, len = ROOKS[us].length; i < len; i++) {
859         if (move.from === ROOKS[us][i].square &&
860             castling[us] & ROOKS[us][i].flag) {
861           castling[us] ^= ROOKS[us][i].flag;
862           break;
863         }
864       }
865     }
866
867     /* turn off castling if we capture a rook */
868     if (castling[them]) {
869       for (var i = 0, len = ROOKS[them].length; i < len; i++) {
870         if (move.to === ROOKS[them][i].square &&
871             castling[them] & ROOKS[them][i].flag) {
872           castling[them] ^= ROOKS[them][i].flag;
873           break;
874         }
875       }
876     }
877
878     /* if big pawn move, update the en passant square */
879     if (move.flags & BITS.BIG_PAWN) {
880       if (turn === 'b') {
881         ep_square = move.to - 16;
882       } else {
883         ep_square = move.to + 16;
884       }
885     } else {
886       ep_square = EMPTY;
887     }
888
889     /* reset the 50 move counter if a pawn is moved or a piece is captured */
890     if (move.piece === PAWN) {
891       half_moves = 0;
892     } else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
893       half_moves = 0;
894     } else {
895       half_moves++;
896     }
897
898     if (turn === BLACK) {
899       move_number++;
900     }
901     turn = swap_color(turn);
902   }
903
904   function undo_move() {
905     var old = history.pop();
906     if (old == null) { return null; }
907
908     var move = old.move;
909     kings = old.kings;
910     turn = old.turn;
911     castling = old.castling;
912     ep_square = old.ep_square;
913     half_moves = old.half_moves;
914     move_number = old.move_number;
915
916     var us = turn;
917     var them = swap_color(turn);
918
919     board[move.from] = board[move.to];
920     board[move.from].type = move.piece;  // to undo any promotions
921     board[move.to] = null;
922
923     if (move.flags & BITS.CAPTURE) {
924       board[move.to] = {type: move.captured, color: them};
925     } else if (move.flags & BITS.EP_CAPTURE) {
926       var index;
927       if (us === BLACK) {
928         index = move.to - 16;
929       } else {
930         index = move.to + 16;
931       }
932       board[index] = {type: PAWN, color: them};
933     }
934
935
936     if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) {
937       var castling_to, castling_from;
938       if (move.flags & BITS.KSIDE_CASTLE) {
939         castling_to = move.to + 1;
940         castling_from = move.to - 1;
941       } else if (move.flags & BITS.QSIDE_CASTLE) {
942         castling_to = move.to - 2;
943         castling_from = move.to + 1;
944       }
945
946       board[castling_to] = board[castling_from];
947       board[castling_from] = null;
948     }
949
950     return move;
951   }
952
953   /* this function is used to uniquely identify ambiguous moves */
954   function get_disambiguator(move) {
955     var moves = generate_moves();
956
957     var from = move.from;
958     var to = move.to;
959     var piece = move.piece;
960
961     var ambiguities = 0;
962     var same_rank = 0;
963     var same_file = 0;
964
965     for (var i = 0, len = moves.length; i < len; i++) {
966       var ambig_from = moves[i].from;
967       var ambig_to = moves[i].to;
968       var ambig_piece = moves[i].piece;
969
970       /* if a move of the same piece type ends on the same to square, we'll
971        * need to add a disambiguator to the algebraic notation
972        */
973       if (piece === ambig_piece && from !== ambig_from && to === ambig_to) {
974         ambiguities++;
975
976         if (rank(from) === rank(ambig_from)) {
977           same_rank++;
978         }
979
980         if (file(from) === file(ambig_from)) {
981           same_file++;
982         }
983       }
984     }
985
986     if (ambiguities > 0) {
987       /* if there exists a similar moving piece on the same rank and file as
988        * the move in question, use the square as the disambiguator
989        */
990       if (same_rank > 0 && same_file > 0) {
991         return algebraic(from);
992       }
993       /* if the moving piece rests on the same file, use the rank symbol as the
994        * disambiguator
995        */
996       else if (same_file > 0) {
997         return algebraic(from).charAt(1);
998       }
999       /* else use the file symbol */
1000       else {
1001         return algebraic(from).charAt(0);
1002       }
1003     }
1004
1005     return '';
1006   }
1007
1008   function ascii() {
1009     var s = '   +------------------------+\n';
1010     for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1011       /* display the rank */
1012       if (file(i) === 0) {
1013         s += ' ' + '87654321'[rank(i)] + ' |';
1014       }
1015
1016       /* empty piece */
1017       if (board[i] == null) {
1018         s += ' . ';
1019       } else {
1020         var piece = board[i].type;
1021         var color = board[i].color;
1022         var symbol = (color === WHITE) ?
1023                      piece.toUpperCase() : piece.toLowerCase();
1024         s += ' ' + symbol + ' ';
1025       }
1026
1027       if ((i + 1) & 0x88) {
1028         s += '|\n';
1029         i += 8;
1030       }
1031     }
1032     s += '   +------------------------+\n';
1033     s += '     a  b  c  d  e  f  g  h\n';
1034
1035     return s;
1036   }
1037
1038   /*****************************************************************************
1039    * UTILITY FUNCTIONS
1040    ****************************************************************************/
1041   function rank(i) {
1042     return i >> 4;
1043   }
1044
1045   function file(i) {
1046     return i & 15;
1047   }
1048
1049   function algebraic(i){
1050     var f = file(i), r = rank(i);
1051     return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
1052   }
1053
1054   function swap_color(c) {
1055     return c === WHITE ? BLACK : WHITE;
1056   }
1057
1058   function is_digit(c) {
1059     return '0123456789'.indexOf(c) !== -1;
1060   }
1061
1062   /* pretty = external move object */
1063   function make_pretty(ugly_move) {
1064     var move = clone(ugly_move);
1065     move.san = move_to_san(move);
1066     move.to = algebraic(move.to);
1067     move.from = algebraic(move.from);
1068
1069     var flags = '';
1070
1071     for (var flag in BITS) {
1072       if (BITS[flag] & move.flags) {
1073         flags += FLAGS[flag];
1074       }
1075     }
1076     move.flags = flags;
1077
1078     return move;
1079   }
1080
1081   function clone(obj) {
1082     var dupe = (obj instanceof Array) ? [] : {};
1083
1084     for (var property in obj) {
1085       if (typeof property === 'object') {
1086         dupe[property] = clone(obj[property]);
1087       } else {
1088         dupe[property] = obj[property];
1089       }
1090     }
1091
1092     return dupe;
1093   }
1094
1095   function trim(str) {
1096     return str.replace(/^\s+|\s+$/g, '');
1097   }
1098
1099   /*****************************************************************************
1100    * DEBUGGING UTILITIES
1101    ****************************************************************************/
1102   function perft(depth) {
1103     var moves = generate_moves({legal: false});
1104     var nodes = 0;
1105     var color = turn;
1106
1107     for (var i = 0, len = moves.length; i < len; i++) {
1108       make_move(moves[i]);
1109       if (!king_attacked(color)) {
1110         if (depth - 1 > 0) {
1111           var child_nodes = perft(depth - 1);
1112           nodes += child_nodes;
1113         } else {
1114           nodes++;
1115         }
1116       }
1117       undo_move();
1118     }
1119
1120     return nodes;
1121   }
1122
1123   return {
1124     /***************************************************************************
1125      * PUBLIC CONSTANTS (is there a better way to do this?)
1126      **************************************************************************/
1127     WHITE: WHITE,
1128     BLACK: BLACK,
1129     PAWN: PAWN,
1130     KNIGHT: KNIGHT,
1131     BISHOP: BISHOP,
1132     ROOK: ROOK,
1133     QUEEN: QUEEN,
1134     KING: KING,
1135     SQUARES: (function() {
1136                 /* from the ECMA-262 spec (section 12.6.4):
1137                  * "The mechanics of enumerating the properties ... is
1138                  * implementation dependent"
1139                  * so: for (var sq in SQUARES) { keys.push(sq); } might not be
1140                  * ordered correctly
1141                  */
1142                 var keys = [];
1143                 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1144                   if (i & 0x88) { i += 7; continue; }
1145                   keys.push(algebraic(i));
1146                 }
1147                 return keys;
1148               })(),
1149     FLAGS: FLAGS,
1150
1151     /***************************************************************************
1152      * PUBLIC API
1153      **************************************************************************/
1154     load: function(fen) {
1155       return load(fen);
1156     },
1157
1158     reset: function() {
1159       return reset();
1160     },
1161
1162     moves: function(options) {
1163       /* The internal representation of a chess move is in 0x88 format, and
1164        * not meant to be human-readable.  The code below converts the 0x88
1165        * square coordinates to algebraic coordinates.  It also prunes an
1166        * unnecessary move keys resulting from a verbose call.
1167        */
1168
1169       var ugly_moves = generate_moves(options);
1170       var moves = [];
1171
1172       for (var i = 0, len = ugly_moves.length; i < len; i++) {
1173
1174         /* does the user want a full move object (most likely not), or just
1175          * SAN
1176          */
1177         if (typeof options !== 'undefined' && 'verbose' in options &&
1178             options.verbose) {
1179           moves.push(make_pretty(ugly_moves[i]));
1180         } else {
1181           moves.push(move_to_san(ugly_moves[i]));
1182         }
1183       }
1184
1185       return moves;
1186     },
1187
1188     in_check: function() {
1189       return in_check();
1190     },
1191
1192     in_checkmate: function() {
1193       return in_checkmate();
1194     },
1195
1196     in_stalemate: function() {
1197       return in_stalemate();
1198     },
1199
1200     in_draw: function() {
1201       return half_moves >= 100 ||
1202              in_stalemate() ||
1203              insufficient_material() ||
1204              in_threefold_repetition();
1205     },
1206
1207     insufficient_material: function() {
1208       return insufficient_material();
1209     },
1210
1211     in_threefold_repetition: function() {
1212       return in_threefold_repetition();
1213     },
1214
1215     game_over: function() {
1216       return half_moves >= 100 ||
1217              in_checkmate() ||
1218              in_stalemate() ||
1219              insufficient_material() ||
1220              in_threefold_repetition();
1221     },
1222
1223     validate_fen: function(fen) {
1224       return validate_fen(fen);
1225     },
1226
1227     fen: function() {
1228       return generate_fen();
1229     },
1230
1231     pgn: function(options) {
1232       /* using the specification from http://www.chessclub.com/help/PGN-spec
1233        * example for html usage: .pgn({ max_width: 72, newline_char: "<br />" })
1234        */
1235       var newline = (typeof options === 'object' &&
1236                      typeof options.newline_char === 'string') ?
1237                      options.newline_char : '\n';
1238       var max_width = (typeof options === 'object' &&
1239                        typeof options.max_width === 'number') ?
1240                        options.max_width : 0;
1241       var result = [];
1242       var header_exists = false;
1243
1244       /* add the PGN header headerrmation */
1245       for (var i in header) {
1246         /* TODO: order of enumerated properties in header object is not
1247          * guaranteed, see ECMA-262 spec (section 12.6.4)
1248          */
1249         result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
1250         header_exists = true;
1251       }
1252
1253       if (header_exists && history.length) {
1254         result.push(newline);
1255       }
1256
1257       /* pop all of history onto reversed_history */
1258       var reversed_history = [];
1259       while (history.length > 0) {
1260         reversed_history.push(undo_move());
1261       }
1262
1263       var moves = [];
1264       var move_string = '';
1265       var pgn_move_number = 1;
1266
1267       /* build the list of moves.  a move_string looks like: "3. e3 e6" */
1268       while (reversed_history.length > 0) {
1269         var move = reversed_history.pop();
1270
1271         /* if the position started with black to move, start PGN with 1. ... */
1272         if (pgn_move_number === 1 && move.color === 'b') {
1273           move_string = '1. ...';
1274           pgn_move_number++;
1275         } else if (move.color === 'w') {
1276           /* store the previous generated move_string if we have one */
1277           if (move_string.length) {
1278             moves.push(move_string);
1279           }
1280           move_string = pgn_move_number + '.';
1281           pgn_move_number++;
1282         }
1283
1284         move_string = move_string + ' ' + move_to_san(move);
1285         make_move(move);
1286       }
1287
1288       /* are there any other leftover moves? */
1289       if (move_string.length) {
1290         moves.push(move_string);
1291       }
1292
1293       /* is there a result? */
1294       if (typeof header.Result !== 'undefined') {
1295         moves.push(header.Result);
1296       }
1297
1298       /* history should be back to what is was before we started generating PGN,
1299        * so join together moves
1300        */
1301       if (max_width === 0) {
1302         return result.join('') + moves.join(' ');
1303       }
1304
1305       /* wrap the PGN output at max_width */
1306       var current_width = 0;
1307       for (var i = 0; i < moves.length; i++) {
1308         /* if the current move will push past max_width */
1309         if (current_width + moves[i].length > max_width && i !== 0) {
1310
1311           /* don't end the line with whitespace */
1312           if (result[result.length - 1] === ' ') {
1313             result.pop();
1314           }
1315
1316           result.push(newline);
1317           current_width = 0;
1318         } else if (i !== 0) {
1319           result.push(' ');
1320           current_width++;
1321         }
1322         result.push(moves[i]);
1323         current_width += moves[i].length;
1324       }
1325
1326       return result.join('');
1327     },
1328
1329     load_pgn: function(pgn, options) {
1330       function mask(str) {
1331         return str.replace(/\\/g, '\\');
1332       }
1333
1334       /* convert a move from Standard Algebraic Notation (SAN) to 0x88
1335        * coordinates
1336       */
1337       function move_from_san(move) {
1338         /* strip off any move decorations: e.g Nf3+?! */
1339         var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
1340         var moves = generate_moves();
1341         for (var i = 0, len = moves.length; i < len; i++) {
1342           if (move_replaced ===
1343               move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
1344             return moves[i];
1345           }
1346         }
1347         return null;
1348       }
1349
1350       function get_move_obj(move) {
1351         return move_from_san(trim(move));
1352       }
1353
1354       function has_keys(object) {
1355         var has_keys = false;
1356         for (var key in object) {
1357           has_keys = true;
1358         }
1359         return has_keys;
1360       }
1361
1362       function parse_pgn_header(header, options) {
1363         var newline_char = (typeof options === 'object' &&
1364                             typeof options.newline_char === 'string') ?
1365                             options.newline_char : '\r?\n';
1366         var header_obj = {};
1367         var headers = header.split(new RegExp(mask(newline_char)));
1368         var key = '';
1369         var value = '';
1370
1371         for (var i = 0; i < headers.length; i++) {
1372           key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
1373           value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
1374           if (trim(key).length > 0) {
1375             header_obj[key] = value;
1376           }
1377         }
1378
1379         return header_obj;
1380       }
1381
1382       var newline_char = (typeof options === 'object' &&
1383                           typeof options.newline_char === 'string') ?
1384                           options.newline_char : '\r?\n';
1385         var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' +
1386                                '(' + mask(newline_char) + ')*' +
1387                                '1.(' + mask(newline_char) + '|.)*$', 'g');
1388
1389       /* get header part of the PGN file */
1390       var header_string = pgn.replace(regex, '$1');
1391
1392       /* no info part given, begins with moves */
1393       if (header_string[0] !== '[') {
1394         header_string = '';
1395       }
1396
1397      reset();
1398
1399       /* parse PGN header */
1400       var headers = parse_pgn_header(header_string, options);
1401       for (var key in headers) {
1402         set_header([key, headers[key]]);
1403       }
1404
1405       /* load the starting position indicated by [Setup '1'] and
1406       * [FEN position] */
1407       if (headers['SetUp'] === '1') {
1408           if (!(('FEN' in headers) && load(headers['FEN']))) {
1409             return false;
1410           }
1411       }
1412
1413       /* delete header to get the moves */
1414       var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
1415
1416       /* delete comments */
1417       ms = ms.replace(/(\{[^}]+\})+?/g, '');
1418
1419       /* delete recursive annotation variations */
1420       var rav_regex = /(\([^\(\)]+\))+?/g
1421       while (rav_regex.test(ms)) {
1422         ms = ms.replace(rav_regex, '');
1423       }
1424
1425       /* delete move numbers */
1426       ms = ms.replace(/\d+\./g, '');
1427
1428       /* delete ... indicating black to move */
1429       ms = ms.replace(/\.\.\./g, '');
1430
1431       /* trim and get array of moves */
1432       var moves = trim(ms).split(new RegExp(/\s+/));
1433
1434       /* delete empty entries */
1435       moves = moves.join(',').replace(/,,+/g, ',').split(',');
1436       var move = '';
1437
1438       for (var half_move = 0; half_move < moves.length - 1; half_move++) {
1439         move = get_move_obj(moves[half_move]);
1440
1441         /* move not possible! (don't clear the board to examine to show the
1442          * latest valid position)
1443          */
1444         if (move == null) {
1445           return false;
1446         } else {
1447           make_move(move);
1448         }
1449       }
1450
1451       /* examine last move */
1452       move = moves[moves.length - 1];
1453       if (POSSIBLE_RESULTS.indexOf(move) > -1) {
1454         if (has_keys(header) && typeof header.Result === 'undefined') {
1455           set_header(['Result', move]);
1456         }
1457       }
1458       else {
1459         move = get_move_obj(move);
1460         if (move == null) {
1461           return false;
1462         } else {
1463           make_move(move);
1464         }
1465       }
1466       return true;
1467     },
1468
1469     header: function() {
1470       return set_header(arguments);
1471     },
1472
1473     ascii: function() {
1474       return ascii();
1475     },
1476
1477     turn: function() {
1478       return turn;
1479     },
1480
1481     move: function(move) {
1482       /* The move function can be called with in the following parameters:
1483        *
1484        * .move('Nxb7')      <- where 'move' is a case-sensitive SAN string
1485        *
1486        * .move({ from: 'h7', <- where the 'move' is a move object (additional
1487        *         to :'h8',      fields are ignored)
1488        *         promotion: 'q',
1489        *      })
1490        */
1491       var move_obj = null;
1492       var moves = generate_moves();
1493
1494       if (typeof move === 'string') {
1495         /* convert the move string to a move object */
1496         /* strip off any move decorations: e.g Nf3+?! */
1497         var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
1498         for (var i = 0, len = moves.length; i < len; i++) {
1499           if (move_replaced ===
1500               move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
1501             move_obj = moves[i];
1502             break;
1503           }
1504         }
1505       } else if (typeof move === 'object') {
1506         /* convert the pretty move object to an ugly move object */
1507         for (var i = 0, len = moves.length; i < len; i++) {
1508           if (move.from === algebraic(moves[i].from) &&
1509               move.to === algebraic(moves[i].to) &&
1510               (!('promotion' in moves[i]) ||
1511               move.promotion === moves[i].promotion)) {
1512             move_obj = moves[i];
1513             break;
1514           }
1515         }
1516       }
1517
1518       /* failed to find move */
1519       if (!move_obj) {
1520         return null;
1521       }
1522
1523       /* need to make a copy of move because we can't generate SAN after the
1524        * move is made
1525        */
1526       var pretty_move = make_pretty(move_obj);
1527
1528       make_move(move_obj);
1529
1530       return pretty_move;
1531     },
1532
1533     undo: function() {
1534       var move = undo_move();
1535       return (move) ? make_pretty(move) : null;
1536     },
1537
1538     clear: function() {
1539       return clear();
1540     },
1541
1542     put: function(piece, square) {
1543       return put(piece, square);
1544     },
1545
1546     get: function(square) {
1547       return get(square);
1548     },
1549
1550     remove: function(square) {
1551       return remove(square);
1552     },
1553
1554     perft: function(depth) {
1555       return perft(depth);
1556     },
1557
1558     square_color: function(square) {
1559       if (square in SQUARES) {
1560         var sq_0x88 = SQUARES[square];
1561         return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
1562       }
1563
1564       return null;
1565     },
1566
1567     history: function(options) {
1568       var reversed_history = [];
1569       var move_history = [];
1570       var verbose = (typeof options !== 'undefined' && 'verbose' in options &&
1571                      options.verbose);
1572
1573       while (history.length > 0) {
1574         reversed_history.push(undo_move());
1575       }
1576
1577       while (reversed_history.length > 0) {
1578         var move = reversed_history.pop();
1579         if (verbose) {
1580           move_history.push(make_pretty(move));
1581         } else {
1582           move_history.push(move_to_san(move));
1583         }
1584         make_move(move);
1585       }
1586
1587       return move_history;
1588     }
1589
1590   };
1591 };
1592
1593 /* export Chess object if using node or any other CommonJS compatible
1594  * environment */
1595 if (typeof exports !== 'undefined') exports.Chess = Chess;
1596 /* export Chess object for any RequireJS compatible environment */
1597 if (typeof define !== 'undefined') define( function () { return Chess;  });