3 * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
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.
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.
27 *----------------------------------------------------------------------------*/
29 /* minified license below */
32 * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
33 * Released under the BSD license
34 * https://github.com/jhlywa/chess.js/blob/master/LICENSE
37 var Chess = function(fen) {
39 /* jshint indent: false */
53 var SYMBOLS = 'pnbrqkPNBRQK';
55 var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
57 var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*'];
61 w: [-16, -32, -17, -15]
65 n: [-18, -33, -31, -14, 18, 33, 31, 14],
66 b: [-17, -15, 17, 15],
68 q: [-17, -16, -15, 1, 17, 16, 15, -1],
69 k: [-17, -16, -15, 1, 17, 16, 15, -1]
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
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
108 var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };
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
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}]
157 var board = new Array(128);
158 var kings = {w: EMPTY, b: EMPTY};
160 var castling = {w: 0, b: 0};
161 var ep_square = EMPTY;
167 /* if the user passes in a fen string, load it, else default to
170 if (typeof fen === 'undefined') {
171 load(DEFAULT_POSITION);
177 board = new Array(128);
178 kings = {w: EMPTY, b: EMPTY};
180 castling = {w: 0, b: 0};
186 update_setup(generate_fen());
190 load(DEFAULT_POSITION);
194 var tokens = fen.split(/\s+/);
195 var position = tokens[0];
198 if (!validate_fen(fen).valid) {
204 for (var i = 0; i < position.length; i++) {
205 var piece = position.charAt(i);
209 } else if (is_digit(piece)) {
210 square += parseInt(piece, 10);
212 var color = (piece < 'a') ? WHITE : BLACK;
213 put({type: piece.toLowerCase(), color: color}, algebraic(square));
220 if (tokens[2].indexOf('K') > -1) {
221 castling.w |= BITS.KSIDE_CASTLE;
223 if (tokens[2].indexOf('Q') > -1) {
224 castling.w |= BITS.QSIDE_CASTLE;
226 if (tokens[2].indexOf('k') > -1) {
227 castling.b |= BITS.KSIDE_CASTLE;
229 if (tokens[2].indexOf('q') > -1) {
230 castling.b |= BITS.QSIDE_CASTLE;
233 ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
234 half_moves = parseInt(tokens[4], 10);
235 move_number = parseInt(tokens[5], 10);
237 update_setup(generate_fen());
242 function validate_fen(fen) {
245 1: 'FEN string must contain six space-delimited fields.',
246 2: '6th field (move number) must be a positive integer.',
247 3: '5th field (half move counter) must be a non-negative integer.',
248 4: '4th field (en-passant square) is invalid.',
249 5: '3rd field (castling availability) is invalid.',
250 6: '2nd field (side to move) is invalid.',
251 7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.',
252 8: '1st field (piece positions) is invalid [consecutive numbers].',
253 9: '1st field (piece positions) is invalid [invalid piece].',
254 10: '1st field (piece positions) is invalid [row too large].',
257 /* 1st criterion: 6 space-seperated fields? */
258 var tokens = fen.split(/\s+/);
259 if (tokens.length !== 6) {
260 return {valid: false, error_number: 1, error: errors[1]};
263 /* 2nd criterion: move number field is a integer value > 0? */
264 if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) {
265 return {valid: false, error_number: 2, error: errors[2]};
268 /* 3rd criterion: half move counter is an integer >= 0? */
269 if (isNaN(tokens[4]) || (parseInt(tokens[4], 10) < 0)) {
270 return {valid: false, error_number: 3, error: errors[3]};
273 /* 4th criterion: 4th field is a valid e.p.-string? */
274 if (!/^(-|[abcdefgh][36])$/.test(tokens[3])) {
275 return {valid: false, error_number: 4, error: errors[4]};
278 /* 5th criterion: 3th field is a valid castle-string? */
279 if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) {
280 return {valid: false, error_number: 5, error: errors[5]};
283 /* 6th criterion: 2nd field is "w" (white) or "b" (black)? */
284 if (!/^(w|b)$/.test(tokens[1])) {
285 return {valid: false, error_number: 6, error: errors[6]};
288 /* 7th criterion: 1st field contains 8 rows? */
289 var rows = tokens[0].split('/');
290 if (rows.length !== 8) {
291 return {valid: false, error_number: 7, error: errors[7]};
294 /* 8th criterion: every row is valid? */
295 for (var i = 0; i < rows.length; i++) {
296 /* check for right sum of fields AND not two numbers in succession */
298 var previous_was_number = false;
300 for (var k = 0; k < rows[i].length; k++) {
301 if (!isNaN(rows[i][k])) {
302 if (previous_was_number) {
303 return {valid: false, error_number: 8, error: errors[8]};
305 sum_fields += parseInt(rows[i][k], 10);
306 previous_was_number = true;
308 if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
309 return {valid: false, error_number: 9, error: errors[9]};
312 previous_was_number = false;
315 if (sum_fields !== 8) {
316 return {valid: false, error_number: 10, error: errors[10]};
320 /* everything's okay! */
321 return {valid: true, error_number: 0, error: errors[0]};
324 function generate_fen() {
328 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
329 if (board[i] == null) {
336 var color = board[i].color;
337 var piece = board[i].type;
339 fen += (color === WHITE) ?
340 piece.toUpperCase() : piece.toLowerCase();
343 if ((i + 1) & 0x88) {
348 if (i !== SQUARES.h1) {
358 if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; }
359 if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; }
360 if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; }
361 if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; }
363 /* do we have an empty castling flag? */
364 cflags = cflags || '-';
365 var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square);
367 return [fen, turn, cflags, epflags, half_moves, move_number].join(' ');
370 function set_header(args) {
371 for (var i = 0; i < args.length; i += 2) {
372 if (typeof args[i] === 'string' &&
373 typeof args[i + 1] === 'string') {
374 header[args[i]] = args[i + 1];
380 /* called when the initial board setup is changed with put() or remove().
381 * modifies the SetUp and FEN properties of the header object. if the FEN is
382 * equal to the default position, the SetUp and FEN are deleted
383 * the setup is only updated if history.length is zero, ie moves haven't been
386 function update_setup(fen) {
387 if (history.length > 0) return;
389 if (fen !== DEFAULT_POSITION) {
390 header['SetUp'] = '1';
393 delete header['SetUp'];
394 delete header['FEN'];
398 function get(square) {
399 var piece = board[SQUARES[square]];
400 return (piece) ? {type: piece.type, color: piece.color} : null;
403 function put(piece, square) {
404 /* check for valid piece object */
405 if (!('type' in piece && 'color' in piece)) {
409 /* check for piece */
410 if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) {
414 /* check for valid square */
415 if (!(square in SQUARES)) {
419 var sq = SQUARES[square];
421 /* don't let the user place more than one king */
422 if (piece.type == KING &&
423 !(kings[piece.color] == EMPTY || kings[piece.color] == sq)) {
427 board[sq] = {type: piece.type, color: piece.color};
428 if (piece.type === KING) {
429 kings[piece.color] = sq;
432 update_setup(generate_fen());
437 function remove(square) {
438 var piece = get(square);
439 board[SQUARES[square]] = null;
440 if (piece && piece.type === KING) {
441 kings[piece.color] = EMPTY;
444 update_setup(generate_fen());
449 function build_move(board, from, to, flags, promotion) {
455 piece: board[from].type
459 move.flags |= BITS.PROMOTION;
460 move.promotion = promotion;
464 move.captured = board[to].type;
465 } else if (flags & BITS.EP_CAPTURE) {
466 move.captured = PAWN;
471 function generate_moves(options) {
472 function add_move(board, moves, from, to, flags) {
473 /* if pawn promotion */
474 if (board[from].type === PAWN &&
475 (rank(to) === RANK_8 || rank(to) === RANK_1)) {
476 var pieces = [QUEEN, ROOK, BISHOP, KNIGHT];
477 for (var i = 0, len = pieces.length; i < len; i++) {
478 moves.push(build_move(board, from, to, flags, pieces[i]));
481 moves.push(build_move(board, from, to, flags));
487 var them = swap_color(us);
488 var second_rank = {b: RANK_7, w: RANK_2};
490 var first_sq = SQUARES.a8;
491 var last_sq = SQUARES.h1;
492 var single_square = false;
494 /* do we want legal moves? */
495 var legal = (typeof options !== 'undefined' && 'legal' in options) ?
496 options.legal : true;
498 /* are we generating moves for a single square? */
499 if (typeof options !== 'undefined' && 'square' in options) {
500 if (options.square in SQUARES) {
501 first_sq = last_sq = SQUARES[options.square];
502 single_square = true;
509 for (var i = first_sq; i <= last_sq; i++) {
510 /* did we run off the end of the board */
511 if (i & 0x88) { i += 7; continue; }
513 var piece = board[i];
514 if (piece == null || piece.color !== us) {
518 if (piece.type === PAWN) {
519 /* single square, non-capturing */
520 var square = i + PAWN_OFFSETS[us][0];
521 if (board[square] == null) {
522 add_move(board, moves, i, square, BITS.NORMAL);
525 var square = i + PAWN_OFFSETS[us][1];
526 if (second_rank[us] === rank(i) && board[square] == null) {
527 add_move(board, moves, i, square, BITS.BIG_PAWN);
532 for (j = 2; j < 4; j++) {
533 var square = i + PAWN_OFFSETS[us][j];
534 if (square & 0x88) continue;
536 if (board[square] != null &&
537 board[square].color === them) {
538 add_move(board, moves, i, square, BITS.CAPTURE);
539 } else if (square === ep_square) {
540 add_move(board, moves, i, ep_square, BITS.EP_CAPTURE);
544 for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) {
545 var offset = PIECE_OFFSETS[piece.type][j];
550 if (square & 0x88) break;
552 if (board[square] == null) {
553 add_move(board, moves, i, square, BITS.NORMAL);
555 if (board[square].color === us) break;
556 add_move(board, moves, i, square, BITS.CAPTURE);
560 /* break, if knight or king */
561 if (piece.type === 'n' || piece.type === 'k') break;
567 /* check for castling if: a) we're generating all moves, or b) we're doing
568 * single square move generation on the king's square
570 if ((!single_square) || last_sq === kings[us]) {
571 /* king-side castling */
572 if (castling[us] & BITS.KSIDE_CASTLE) {
573 var castling_from = kings[us];
574 var castling_to = castling_from + 2;
576 if (board[castling_from + 1] == null &&
577 board[castling_to] == null &&
578 !attacked(them, kings[us]) &&
579 !attacked(them, castling_from + 1) &&
580 !attacked(them, castling_to)) {
581 add_move(board, moves, kings[us] , castling_to,
586 /* queen-side castling */
587 if (castling[us] & BITS.QSIDE_CASTLE) {
588 var castling_from = kings[us];
589 var castling_to = castling_from - 2;
591 if (board[castling_from - 1] == null &&
592 board[castling_from - 2] == null &&
593 board[castling_from - 3] == null &&
594 !attacked(them, kings[us]) &&
595 !attacked(them, castling_from - 1) &&
596 !attacked(them, castling_to)) {
597 add_move(board, moves, kings[us], castling_to,
603 /* return all pseudo-legal moves (this includes moves that allow the king
610 /* filter out illegal moves */
611 var legal_moves = [];
612 for (var i = 0, len = moves.length; i < len; i++) {
614 if (!king_attacked(us)) {
615 legal_moves.push(moves[i]);
623 /* convert a move from 0x88 coordinates to Standard Algebraic Notation
626 function move_to_san(move) {
629 if (move.flags & BITS.KSIDE_CASTLE) {
631 } else if (move.flags & BITS.QSIDE_CASTLE) {
634 var disambiguator = get_disambiguator(move);
636 if (move.piece !== PAWN) {
637 output += move.piece.toUpperCase() + disambiguator;
640 if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
641 if (move.piece === PAWN) {
642 output += algebraic(move.from)[0];
647 output += algebraic(move.to);
649 if (move.flags & BITS.PROMOTION) {
650 output += '=' + move.promotion.toUpperCase();
656 if (in_checkmate()) {
667 function attacked(color, square) {
668 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
669 /* did we run off the end of the board */
670 if (i & 0x88) { i += 7; continue; }
672 /* if empty square or wrong color */
673 if (board[i] == null || board[i].color !== color) continue;
675 var piece = board[i];
676 var difference = i - square;
677 var index = difference + 119;
679 if (ATTACKS[index] & (1 << SHIFTS[piece.type])) {
680 if (piece.type === PAWN) {
681 if (difference > 0) {
682 if (piece.color === WHITE) return true;
684 if (piece.color === BLACK) return true;
689 /* if the piece is a knight or a king */
690 if (piece.type === 'n' || piece.type === 'k') return true;
692 var offset = RAYS[index];
696 while (j !== square) {
697 if (board[j] != null) { blocked = true; break; }
701 if (!blocked) return true;
708 function king_attacked(color) {
709 return attacked(swap_color(color), kings[color]);
712 function in_check() {
713 return king_attacked(turn);
716 function in_checkmate() {
717 return in_check() && generate_moves().length === 0;
720 function in_stalemate() {
721 return !in_check() && generate_moves().length === 0;
724 function insufficient_material() {
730 for (var i = SQUARES.a8; i<= SQUARES.h1; i++) {
731 sq_color = (sq_color + 1) % 2;
732 if (i & 0x88) { i += 7; continue; }
734 var piece = board[i];
736 pieces[piece.type] = (piece.type in pieces) ?
737 pieces[piece.type] + 1 : 1;
738 if (piece.type === BISHOP) {
739 bishops.push(sq_color);
746 if (num_pieces === 2) { return true; }
748 /* k vs. kn .... or .... k vs. kb */
749 else if (num_pieces === 3 && (pieces[BISHOP] === 1 ||
750 pieces[KNIGHT] === 1)) { return true; }
752 /* kb vs. kb where any number of bishops are all on the same color */
753 else if (num_pieces === pieces[BISHOP] + 2) {
755 var len = bishops.length;
756 for (var i = 0; i < len; i++) {
759 if (sum === 0 || sum === len) { return true; }
765 function in_threefold_repetition() {
766 /* TODO: while this function is fine for casual use, a better
767 * implementation would use a Zobrist key (instead of FEN). the
768 * Zobrist key would be maintained in the make_move/undo_move functions,
769 * avoiding the costly that we do below.
773 var repetition = false;
776 var move = undo_move();
782 /* remove the last two fields in the FEN string, they're not needed
783 * when checking for draw by rep */
784 var fen = generate_fen().split(' ').slice(0,4).join(' ');
786 /* has the position occurred three or move times */
787 positions[fen] = (fen in positions) ? positions[fen] + 1 : 1;
788 if (positions[fen] >= 3) {
795 make_move(moves.pop());
801 function push(move) {
804 kings: {b: kings.b, w: kings.w},
806 castling: {b: castling.b, w: castling.w},
807 ep_square: ep_square,
808 half_moves: half_moves,
809 move_number: move_number
813 function make_move(move) {
815 var them = swap_color(us);
818 board[move.to] = board[move.from];
819 board[move.from] = null;
821 /* if ep capture, remove the captured pawn */
822 if (move.flags & BITS.EP_CAPTURE) {
823 if (turn === BLACK) {
824 board[move.to - 16] = null;
826 board[move.to + 16] = null;
830 /* if pawn promotion, replace with new piece */
831 if (move.flags & BITS.PROMOTION) {
832 board[move.to] = {type: move.promotion, color: us};
835 /* if we moved the king */
836 if (board[move.to].type === KING) {
837 kings[board[move.to].color] = move.to;
839 /* if we castled, move the rook next to the king */
840 if (move.flags & BITS.KSIDE_CASTLE) {
841 var castling_to = move.to - 1;
842 var castling_from = move.to + 1;
843 board[castling_to] = board[castling_from];
844 board[castling_from] = null;
845 } else if (move.flags & BITS.QSIDE_CASTLE) {
846 var castling_to = move.to + 1;
847 var castling_from = move.to - 2;
848 board[castling_to] = board[castling_from];
849 board[castling_from] = null;
852 /* turn off castling */
856 /* turn off castling if we move a rook */
858 for (var i = 0, len = ROOKS[us].length; i < len; i++) {
859 if (move.from === ROOKS[us][i].square &&
860 castling[us] & ROOKS[us][i].flag) {
861 castling[us] ^= ROOKS[us][i].flag;
867 /* turn off castling if we capture a rook */
868 if (castling[them]) {
869 for (var i = 0, len = ROOKS[them].length; i < len; i++) {
870 if (move.to === ROOKS[them][i].square &&
871 castling[them] & ROOKS[them][i].flag) {
872 castling[them] ^= ROOKS[them][i].flag;
878 /* if big pawn move, update the en passant square */
879 if (move.flags & BITS.BIG_PAWN) {
881 ep_square = move.to - 16;
883 ep_square = move.to + 16;
889 /* reset the 50 move counter if a pawn is moved or a piece is captured */
890 if (move.piece === PAWN) {
892 } else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
898 if (turn === BLACK) {
901 turn = swap_color(turn);
904 function undo_move() {
905 var old = history.pop();
906 if (old == null) { return null; }
911 castling = old.castling;
912 ep_square = old.ep_square;
913 half_moves = old.half_moves;
914 move_number = old.move_number;
917 var them = swap_color(turn);
919 board[move.from] = board[move.to];
920 board[move.from].type = move.piece; // to undo any promotions
921 board[move.to] = null;
923 if (move.flags & BITS.CAPTURE) {
924 board[move.to] = {type: move.captured, color: them};
925 } else if (move.flags & BITS.EP_CAPTURE) {
928 index = move.to - 16;
930 index = move.to + 16;
932 board[index] = {type: PAWN, color: them};
936 if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) {
937 var castling_to, castling_from;
938 if (move.flags & BITS.KSIDE_CASTLE) {
939 castling_to = move.to + 1;
940 castling_from = move.to - 1;
941 } else if (move.flags & BITS.QSIDE_CASTLE) {
942 castling_to = move.to - 2;
943 castling_from = move.to + 1;
946 board[castling_to] = board[castling_from];
947 board[castling_from] = null;
953 /* this function is used to uniquely identify ambiguous moves */
954 function get_disambiguator(move) {
955 var moves = generate_moves();
957 var from = move.from;
959 var piece = move.piece;
965 for (var i = 0, len = moves.length; i < len; i++) {
966 var ambig_from = moves[i].from;
967 var ambig_to = moves[i].to;
968 var ambig_piece = moves[i].piece;
970 /* if a move of the same piece type ends on the same to square, we'll
971 * need to add a disambiguator to the algebraic notation
973 if (piece === ambig_piece && from !== ambig_from && to === ambig_to) {
976 if (rank(from) === rank(ambig_from)) {
980 if (file(from) === file(ambig_from)) {
986 if (ambiguities > 0) {
987 /* if there exists a similar moving piece on the same rank and file as
988 * the move in question, use the square as the disambiguator
990 if (same_rank > 0 && same_file > 0) {
991 return algebraic(from);
993 /* if the moving piece rests on the same file, use the rank symbol as the
996 else if (same_file > 0) {
997 return algebraic(from).charAt(1);
999 /* else use the file symbol */
1001 return algebraic(from).charAt(0);
1009 var s = ' +------------------------+\n';
1010 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1011 /* display the rank */
1012 if (file(i) === 0) {
1013 s += ' ' + '87654321'[rank(i)] + ' |';
1017 if (board[i] == null) {
1020 var piece = board[i].type;
1021 var color = board[i].color;
1022 var symbol = (color === WHITE) ?
1023 piece.toUpperCase() : piece.toLowerCase();
1024 s += ' ' + symbol + ' ';
1027 if ((i + 1) & 0x88) {
1032 s += ' +------------------------+\n';
1033 s += ' a b c d e f g h\n';
1038 /*****************************************************************************
1040 ****************************************************************************/
1049 function algebraic(i){
1050 var f = file(i), r = rank(i);
1051 return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
1054 function swap_color(c) {
1055 return c === WHITE ? BLACK : WHITE;
1058 function is_digit(c) {
1059 return '0123456789'.indexOf(c) !== -1;
1062 /* pretty = external move object */
1063 function make_pretty(ugly_move) {
1064 var move = clone(ugly_move);
1065 move.san = move_to_san(move);
1066 move.to = algebraic(move.to);
1067 move.from = algebraic(move.from);
1071 for (var flag in BITS) {
1072 if (BITS[flag] & move.flags) {
1073 flags += FLAGS[flag];
1081 function clone(obj) {
1082 var dupe = (obj instanceof Array) ? [] : {};
1084 for (var property in obj) {
1085 if (typeof property === 'object') {
1086 dupe[property] = clone(obj[property]);
1088 dupe[property] = obj[property];
1095 function trim(str) {
1096 return str.replace(/^\s+|\s+$/g, '');
1099 /*****************************************************************************
1100 * DEBUGGING UTILITIES
1101 ****************************************************************************/
1102 function perft(depth) {
1103 var moves = generate_moves({legal: false});
1107 for (var i = 0, len = moves.length; i < len; i++) {
1108 make_move(moves[i]);
1109 if (!king_attacked(color)) {
1110 if (depth - 1 > 0) {
1111 var child_nodes = perft(depth - 1);
1112 nodes += child_nodes;
1124 /***************************************************************************
1125 * PUBLIC CONSTANTS (is there a better way to do this?)
1126 **************************************************************************/
1135 SQUARES: (function() {
1136 /* from the ECMA-262 spec (section 12.6.4):
1137 * "The mechanics of enumerating the properties ... is
1138 * implementation dependent"
1139 * so: for (var sq in SQUARES) { keys.push(sq); } might not be
1143 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1144 if (i & 0x88) { i += 7; continue; }
1145 keys.push(algebraic(i));
1151 /***************************************************************************
1153 **************************************************************************/
1154 load: function(fen) {
1162 moves: function(options) {
1163 /* The internal representation of a chess move is in 0x88 format, and
1164 * not meant to be human-readable. The code below converts the 0x88
1165 * square coordinates to algebraic coordinates. It also prunes an
1166 * unnecessary move keys resulting from a verbose call.
1169 var ugly_moves = generate_moves(options);
1172 for (var i = 0, len = ugly_moves.length; i < len; i++) {
1174 /* does the user want a full move object (most likely not), or just
1177 if (typeof options !== 'undefined' && 'verbose' in options &&
1179 moves.push(make_pretty(ugly_moves[i]));
1181 moves.push(move_to_san(ugly_moves[i]));
1188 in_check: function() {
1192 in_checkmate: function() {
1193 return in_checkmate();
1196 in_stalemate: function() {
1197 return in_stalemate();
1200 in_draw: function() {
1201 return half_moves >= 100 ||
1203 insufficient_material() ||
1204 in_threefold_repetition();
1207 insufficient_material: function() {
1208 return insufficient_material();
1211 in_threefold_repetition: function() {
1212 return in_threefold_repetition();
1215 game_over: function() {
1216 return half_moves >= 100 ||
1219 insufficient_material() ||
1220 in_threefold_repetition();
1223 validate_fen: function(fen) {
1224 return validate_fen(fen);
1228 return generate_fen();
1231 pgn: function(options) {
1232 /* using the specification from http://www.chessclub.com/help/PGN-spec
1233 * example for html usage: .pgn({ max_width: 72, newline_char: "<br />" })
1235 var newline = (typeof options === 'object' &&
1236 typeof options.newline_char === 'string') ?
1237 options.newline_char : '\n';
1238 var max_width = (typeof options === 'object' &&
1239 typeof options.max_width === 'number') ?
1240 options.max_width : 0;
1242 var header_exists = false;
1244 /* add the PGN header headerrmation */
1245 for (var i in header) {
1246 /* TODO: order of enumerated properties in header object is not
1247 * guaranteed, see ECMA-262 spec (section 12.6.4)
1249 result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
1250 header_exists = true;
1253 if (header_exists && history.length) {
1254 result.push(newline);
1257 /* pop all of history onto reversed_history */
1258 var reversed_history = [];
1259 while (history.length > 0) {
1260 reversed_history.push(undo_move());
1264 var move_string = '';
1265 var pgn_move_number = 1;
1267 /* build the list of moves. a move_string looks like: "3. e3 e6" */
1268 while (reversed_history.length > 0) {
1269 var move = reversed_history.pop();
1271 /* if the position started with black to move, start PGN with 1. ... */
1272 if (pgn_move_number === 1 && move.color === 'b') {
1273 move_string = '1. ...';
1275 } else if (move.color === 'w') {
1276 /* store the previous generated move_string if we have one */
1277 if (move_string.length) {
1278 moves.push(move_string);
1280 move_string = pgn_move_number + '.';
1284 move_string = move_string + ' ' + move_to_san(move);
1288 /* are there any other leftover moves? */
1289 if (move_string.length) {
1290 moves.push(move_string);
1293 /* is there a result? */
1294 if (typeof header.Result !== 'undefined') {
1295 moves.push(header.Result);
1298 /* history should be back to what is was before we started generating PGN,
1299 * so join together moves
1301 if (max_width === 0) {
1302 return result.join('') + moves.join(' ');
1305 /* wrap the PGN output at max_width */
1306 var current_width = 0;
1307 for (var i = 0; i < moves.length; i++) {
1308 /* if the current move will push past max_width */
1309 if (current_width + moves[i].length > max_width && i !== 0) {
1311 /* don't end the line with whitespace */
1312 if (result[result.length - 1] === ' ') {
1316 result.push(newline);
1318 } else if (i !== 0) {
1322 result.push(moves[i]);
1323 current_width += moves[i].length;
1326 return result.join('');
1329 load_pgn: function(pgn, options) {
1330 function mask(str) {
1331 return str.replace(/\\/g, '\\');
1334 /* convert a move from Standard Algebraic Notation (SAN) to 0x88
1337 function move_from_san(move) {
1338 /* strip off any move decorations: e.g Nf3+?! */
1339 var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
1340 var moves = generate_moves();
1341 for (var i = 0, len = moves.length; i < len; i++) {
1342 if (move_replaced ===
1343 move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
1350 function get_move_obj(move) {
1351 return move_from_san(trim(move));
1354 function has_keys(object) {
1355 var has_keys = false;
1356 for (var key in object) {
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)));
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;
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');
1389 /* get header part of the PGN file */
1390 var header_string = pgn.replace(regex, '$1');
1392 /* no info part given, begins with moves */
1393 if (header_string[0] !== '[') {
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]]);
1405 /* load the starting position indicated by [Setup '1'] and
1407 if (headers['SetUp'] === '1') {
1408 if (!(('FEN' in headers) && load(headers['FEN']))) {
1413 /* delete header to get the moves */
1414 var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
1416 /* delete comments */
1417 ms = ms.replace(/(\{[^}]+\})+?/g, '');
1419 /* delete recursive annotation variations */
1420 var rav_regex = /(\([^\(\)]+\))+?/g
1421 while (rav_regex.test(ms)) {
1422 ms = ms.replace(rav_regex, '');
1425 /* delete move numbers */
1426 ms = ms.replace(/\d+\./g, '');
1428 /* delete ... indicating black to move */
1429 ms = ms.replace(/\.\.\./g, '');
1431 /* trim and get array of moves */
1432 var moves = trim(ms).split(new RegExp(/\s+/));
1434 /* delete empty entries */
1435 moves = moves.join(',').replace(/,,+/g, ',').split(',');
1438 for (var half_move = 0; half_move < moves.length - 1; half_move++) {
1439 move = get_move_obj(moves[half_move]);
1441 /* move not possible! (don't clear the board to examine to show the
1442 * latest valid position)
1451 /* examine last move */
1452 move = moves[moves.length - 1];
1453 if (POSSIBLE_RESULTS.indexOf(move) > -1) {
1454 if (has_keys(header) && typeof header.Result === 'undefined') {
1455 set_header(['Result', move]);
1459 move = get_move_obj(move);
1469 header: function() {
1470 return set_header(arguments);
1481 move: function(move) {
1482 /* The move function can be called with in the following parameters:
1484 * .move('Nxb7') <- where 'move' is a case-sensitive SAN string
1486 * .move({ from: 'h7', <- where the 'move' is a move object (additional
1487 * to :'h8', fields are ignored)
1491 var move_obj = null;
1492 var moves = generate_moves();
1494 if (typeof move === 'string') {
1495 /* convert the move string to a move object */
1496 /* strip off any move decorations: e.g Nf3+?! */
1497 var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
1498 for (var i = 0, len = moves.length; i < len; i++) {
1499 if (move_replaced ===
1500 move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
1501 move_obj = moves[i];
1505 } else if (typeof move === 'object') {
1506 /* convert the pretty move object to an ugly move object */
1507 for (var i = 0, len = moves.length; i < len; i++) {
1508 if (move.from === algebraic(moves[i].from) &&
1509 move.to === algebraic(moves[i].to) &&
1510 (!('promotion' in moves[i]) ||
1511 move.promotion === moves[i].promotion)) {
1512 move_obj = moves[i];
1518 /* failed to find move */
1523 /* need to make a copy of move because we can't generate SAN after the
1526 var pretty_move = make_pretty(move_obj);
1528 make_move(move_obj);
1534 var move = undo_move();
1535 return (move) ? make_pretty(move) : null;
1542 put: function(piece, square) {
1543 return put(piece, square);
1546 get: function(square) {
1550 remove: function(square) {
1551 return remove(square);
1554 perft: function(depth) {
1555 return perft(depth);
1558 square_color: function(square) {
1559 if (square in SQUARES) {
1560 var sq_0x88 = SQUARES[square];
1561 return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
1567 history: function(options) {
1568 var reversed_history = [];
1569 var move_history = [];
1570 var verbose = (typeof options !== 'undefined' && 'verbose' in options &&
1573 while (history.length > 0) {
1574 reversed_history.push(undo_move());
1577 while (reversed_history.length > 0) {
1578 var move = reversed_history.pop();
1580 move_history.push(make_pretty(move));
1582 move_history.push(move_to_san(move));
1587 return move_history;
1593 /* export Chess object if using node or any other CommonJS compatible
1595 if (typeof exports !== 'undefined') exports.Chess = Chess;
1596 /* export Chess object for any RequireJS compatible environment */
1597 if (typeof define !== 'undefined') define( function () { return Chess; });