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