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