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