2 * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
26 *----------------------------------------------------------------------------*/
28 /* minified license below */
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
36 var Chess = function(fen) {
38 /* jshint indent: false */
52 var SYMBOLS = 'pnbrqkPNBRQK';
54 var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
56 var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*'];
60 w: [-16, -32, -17, -15]
64 n: [-18, -33, -31, -14, 18, 33, 31, 14],
65 b: [-17, -15, 17, 15],
67 q: [-17, -16, -15, 1, 17, 16, 15, -1],
68 k: [-17, -16, -15, 1, 17, 16, 15, -1]
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
111 var board = new Array(128);
112 var kings = {w: EMPTY, b: EMPTY};
113 var rooks = {w: [], b: []};
115 var castling = {w: 0, b: 0};
116 var ep_square = EMPTY;
122 /* if the user passes in a fen string, load it, else default to
125 if (typeof fen === 'undefined') {
126 load(DEFAULT_POSITION);
132 board = new Array(128);
133 kings = {w: EMPTY, b: EMPTY};
135 castling = {w: 0, b: 0};
141 update_setup(generate_fen());
145 load(DEFAULT_POSITION);
149 var tokens = fen.split(/\s+/);
150 var position = tokens[0];
153 if (!validate_fen(fen).valid) {
159 for (var i = 0; i < position.length; i++) {
160 var piece = position.charAt(i);
164 } else if (is_digit(piece)) {
165 square += parseInt(piece, 10);
167 var color = (piece < 'a') ? WHITE : BLACK;
168 put({type: piece.toLowerCase(), color: color}, algebraic(square));
175 rooks = {w: [], b: []};
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});
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});
195 var white_frc_columns = tokens[2].match(/[A-H]/g);
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;
202 rooks[WHITE].push({square: sq, flag: flag});
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});
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});
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;
230 rooks[BLACK].push({square: sq, flag: flag});
234 ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
235 half_moves = parseInt(tokens[4], 10);
236 move_number = parseInt(tokens[5], 10);
238 update_setup(generate_fen());
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
248 function validate_fen(fen) {
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',
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]};
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]};
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]};
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]};
285 /* 5th criterion: 3th field is a valid castle-string? */
286 if( !/^[C-HK]?[A-FQ]?[c-hk]?[a-fq]?$/.test(tokens[2]) &&
288 return {valid: false, error_number: 5, error: errors[5]};
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]};
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]};
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 */
306 var previous_was_number = false;
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]};
313 sum_fields += parseInt(rows[i][k], 10);
314 previous_was_number = true;
316 if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
317 return {valid: false, error_number: 9, error: errors[9]};
320 previous_was_number = false;
323 if (sum_fields !== 8) {
324 return {valid: false, error_number: 10, error: errors[10]};
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]};
333 /* everything's okay! */
334 return {valid: true, error_number: 0, error: errors[0]};
337 function generate_fen() {
341 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
342 if (board[i] == null) {
349 var color = board[i].color;
350 var piece = board[i].type;
352 fen += (color === WHITE) ?
353 piece.toUpperCase() : piece.toLowerCase();
356 if ((i + 1) & 0x88) {
361 if (i !== SQUARES.h1) {
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)) {
377 cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1);
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)) {
385 cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1);
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)) {
393 cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1);
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)) {
401 cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1);
405 /* do we have an empty castling flag? */
406 cflags = cflags || '-';
407 var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square);
409 return [fen, turn, cflags, epflags, half_moves, move_number].join(' ');
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];
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
428 function update_setup(fen) {
429 if (history.length > 0) return;
431 if (fen !== DEFAULT_POSITION) {
432 header['SetUp'] = '1';
435 delete header['SetUp'];
436 delete header['FEN'];
440 function get(square) {
441 var piece = board[SQUARES[square]];
442 return (piece) ? {type: piece.type, color: piece.color} : null;
445 function put(piece, square) {
446 /* check for valid piece object */
447 if (!('type' in piece && 'color' in piece)) {
451 /* check for piece */
452 if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) {
456 /* check for valid square */
457 if (!(square in SQUARES)) {
461 var sq = SQUARES[square];
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)) {
469 board[sq] = {type: piece.type, color: piece.color};
470 if (piece.type === KING) {
471 kings[piece.color] = sq;
474 update_setup(generate_fen());
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;
486 update_setup(generate_fen());
491 function build_move(board, from, to, flags, promotion, rook_sq) {
497 piece: board[from].type
501 move.flags |= BITS.PROMOTION;
502 move.promotion = promotion;
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;
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]));
525 moves.push(build_move(board, from, to, flags, undefined, rook_sq));
529 function check_castle(board, king_from, king_to, rook_from, rook_to, them) {
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]) {
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)) {
556 var them = swap_color(us);
557 var second_rank = {b: RANK_7, w: RANK_2};
559 var first_sq = SQUARES.a8;
560 var last_sq = SQUARES.h1;
561 var single_square = false;
563 /* do we want legal moves? */
564 var legal = (typeof options !== 'undefined' && 'legal' in options) ?
565 options.legal : true;
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;
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; }
582 var piece = board[i];
583 if (piece == null || piece.color !== us) {
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);
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);
601 for (j = 2; j < 4; j++) {
602 var square = i + PAWN_OFFSETS[us][j];
603 if (square & 0x88) continue;
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);
613 for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) {
614 var offset = PIECE_OFFSETS[piece.type][j];
619 if (square & 0x88) break;
621 if (board[square] == null) {
622 add_move(board, moves, i, square, BITS.NORMAL);
624 if (board[square].color === us) break;
625 add_move(board, moves, i, square, BITS.CAPTURE);
629 /* break, if knight or king */
630 if (piece.type === 'n' || piece.type === 'k') break;
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
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;
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);
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;
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);
665 return possibly_filter_moves(moves, us, legal);
668 function possibly_filter_moves(moves, us, legal) {
669 /* return all pseudo-legal moves (this includes moves that allow the king
676 /* filter out illegal moves */
677 var legal_moves = [];
678 for (var i = 0, len = moves.length; i < len; i++) {
680 if (!king_attacked(us)) {
681 legal_moves.push(moves[i]);
689 function is_rook(piece, color) {
690 return (typeof piece !== 'undefined' && piece !== null &&
691 piece.type === ROOK && piece.color == color);
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;
703 function is_outermost_rook(board, us, flag, 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)) {
713 var end_sq = (us == WHITE) ? SQUARES.a1 : SQUARES.a8;
714 while (--sq >= end_sq) {
715 if (is_rook(board[sq], us)) {
723 /* convert a move from 0x88 coordinates to Standard Algebraic Notation
726 * @param {boolean} sloppy Use the sloppy SAN generator to work around over
727 * disambiguation bugs in Fritz and Chessbase. See below:
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
733 function move_to_san(move, sloppy) {
737 if (move.flags & BITS.KSIDE_CASTLE) {
739 } else if (move.flags & BITS.QSIDE_CASTLE) {
742 var disambiguator = get_disambiguator(move, sloppy);
744 if (move.piece !== PAWN) {
745 output += move.piece.toUpperCase() + disambiguator;
748 if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
749 if (move.piece === PAWN) {
750 output += algebraic(move.from)[0];
755 output += algebraic(move.to);
757 if (move.flags & BITS.PROMOTION) {
758 output += '=' + move.promotion.toUpperCase();
764 if (in_checkmate()) {
775 // parses all of the decorators out of a SAN string
776 function stripped_san(move) {
777 return move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
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) {
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;
792 if (board[knight_sq] != null &&
793 board[knight_sq].type === KNIGHT &&
794 board[knight_sq].color === color) {
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) {
808 if (!(p2sq & 0x88) &&
809 board[p2sq] != null &&
810 board[p2sq].type === PAWN &&
811 board[p2sq].color === color) {
815 // Check for attacks by rooks (where queens count as rooks).
816 for (const offset of PIECE_OFFSETS[ROOK]) {
817 let rook_sq = square;
820 if (rook_sq & 0x88) break;
822 if (board[rook_sq] != null) {
823 if ((board[rook_sq].type === ROOK || board[rook_sq].type === QUEEN) &&
824 board[rook_sq].color === color) {
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;
837 if (bishop_sq & 0x88) break;
839 if (board[bishop_sq] != null) {
840 if ((board[bishop_sq].type === BISHOP || board[bishop_sq].type === QUEEN) &&
841 board[bishop_sq].color === color) {
852 function king_attacked(color) {
853 return attacked(swap_color(color), kings[color]);
856 function in_check() {
857 return king_attacked(turn);
860 function in_checkmate() {
861 return in_check() && generate_moves().length === 0;
864 function in_stalemate() {
865 return !in_check() && generate_moves().length === 0;
868 function insufficient_material() {
874 for (var i = SQUARES.a8; i<= SQUARES.h1; i++) {
875 sq_color = (sq_color + 1) % 2;
876 if (i & 0x88) { i += 7; continue; }
878 var piece = board[i];
880 pieces[piece.type] = (piece.type in pieces) ?
881 pieces[piece.type] + 1 : 1;
882 if (piece.type === BISHOP) {
883 bishops.push(sq_color);
890 if (num_pieces === 2) { return true; }
892 /* k vs. kn .... or .... k vs. kb */
893 else if (num_pieces === 3 && (pieces[BISHOP] === 1 ||
894 pieces[KNIGHT] === 1)) { return true; }
896 /* kb vs. kb where any number of bishops are all on the same color */
897 else if (num_pieces === pieces[BISHOP] + 2) {
899 var len = bishops.length;
900 for (var i = 0; i < len; i++) {
903 if (sum === 0 || sum === len) { return true; }
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.
917 var repetition = false;
920 var move = undo_move();
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(' ');
930 /* has the position occurred three or move times */
931 positions[fen] = (fen in positions) ? positions[fen] + 1 : 1;
932 if (positions[fen] >= 3) {
939 make_move(moves.pop());
945 function push(move) {
948 kings: {b: kings.b, w: kings.w},
950 castling: {b: castling.b, w: castling.w},
951 ep_square: ep_square,
952 half_moves: half_moves,
953 move_number: move_number
957 function make_move(move) {
959 var them = swap_color(us);
962 board[move.to] = board[move.from];
963 if (move.from != move.to) {
964 board[move.from] = null;
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;
972 board[move.to + 16] = null;
976 /* if pawn promotion, replace with new piece */
977 if (move.flags & BITS.PROMOTION) {
978 board[move.to] = {type: move.promotion, color: us};
981 /* if we moved the king */
982 if (board[move.to].type === KING) {
983 kings[board[move.to].color] = move.to;
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;
1000 /* turn off castling */
1004 /* turn off castling if we move a rook */
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;
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;
1026 /* if big pawn move, update the en passant square */
1027 if (move.flags & BITS.BIG_PAWN) {
1029 ep_square = move.to - 16;
1031 ep_square = move.to + 16;
1037 /* reset the 50 move counter if a pawn is moved or a piece is captured */
1038 if (move.piece === PAWN) {
1040 } else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
1046 if (turn === BLACK) {
1049 turn = swap_color(turn);
1052 function undo_move() {
1053 var old = history.pop();
1054 if (old == null) { return null; }
1056 var move = old.move;
1059 castling = old.castling;
1060 ep_square = old.ep_square;
1061 half_moves = old.half_moves;
1062 move_number = old.move_number;
1065 var them = swap_color(turn);
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;
1073 if (move.flags & BITS.CAPTURE) {
1074 board[move.to] = {type: move.captured, color: them};
1075 } else if (move.flags & BITS.EP_CAPTURE) {
1078 index = move.to - 16;
1080 index = move.to + 16;
1082 board[index] = {type: PAWN, color: them};
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;
1096 board[castling_to] = {type: ROOK, color: us};
1097 if(castling_from !== move.from && castling_from !== castling_to)
1098 board[castling_from] = null;
1104 /* this function is used to uniquely identify ambiguous moves */
1105 function get_disambiguator(move, sloppy) {
1106 var from = move.from;
1108 var piece = move.piece;
1110 if (piece === 'p' || piece === 'k') {
1111 // Pawn or king moves are never ambiguous.
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).
1122 moves = possibly_filter_moves(moves, move.color, !sloppy);
1124 var ambiguities = 0;
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;
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
1136 if (piece === ambig_piece && from !== ambig_from && to === ambig_to) {
1139 if (rank(from) === rank(ambig_from)) {
1143 if (file(from) === file(ambig_from)) {
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
1153 if (same_rank > 0 && same_file > 0) {
1154 return algebraic(from);
1156 /* if the moving piece rests on the same file, use the rank symbol as the
1159 else if (same_file > 0) {
1160 return algebraic(from).charAt(1);
1162 /* else use the file symbol */
1164 return algebraic(from).charAt(0);
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) {
1179 function add_move(board, moves, from, to, flags, rook_sq) {
1180 moves.push(build_move(board, from, to, flags, undefined, rook_sq));
1182 for (let offset of PIECE_OFFSETS[piece]) {
1187 if (square & 0x88) break;
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);
1194 add_move(board, moves, square, to, BITS.CAPTURE);
1199 /* break if knight or king */
1200 if (piece === 'n' || piece === 'k') break;
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)] + ' |';
1216 if (board[i] == null) {
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 + ' ';
1226 if ((i + 1) & 0x88) {
1231 s += ' +------------------------+\n';
1232 s += ' a b c d e f g h\n';
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);
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
1245 var matches = clean_move.match(/([pnbrqkPNBRQK])?([a-h][1-8])x?-?([a-h][1-8])([qrbnQRBN])?/);
1247 var piece = matches[1];
1248 var from = matches[2];
1249 var to = matches[3];
1250 var promotion = matches[4];
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.
1263 moves = find_attacking_moves(to, piece_matches[1].toLowerCase(), turn);
1264 // Legal moves only.
1265 moves = possibly_filter_moves(moves, turn, true);
1267 // Fallback (also used for pawns): Any (legal) moves.
1268 moves = generate_moves();
1271 for (var i = 0, len = moves.length; i < len; i++) {
1272 // try the strict parser first, then the sloppy parser if requested
1274 if ((clean_move === stripped_san(move_to_san(moves[i]))) ||
1275 (sloppy && clean_move === stripped_san(move_to_san(moves[i], true)))) {
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)) {
1292 /*****************************************************************************
1294 ****************************************************************************/
1303 function algebraic(i){
1304 var f = file(i), r = rank(i);
1305 return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
1308 function swap_color(c) {
1309 return c === WHITE ? BLACK : WHITE;
1312 function is_digit(c) {
1313 return '0123456789'.indexOf(c) !== -1;
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);
1325 for (var flag in BITS) {
1326 if (BITS[flag] & move.flags) {
1327 flags += FLAGS[flag];
1335 function clone(obj) {
1336 var dupe = (obj instanceof Array) ? [] : {};
1338 for (var property in obj) {
1339 if (typeof property === 'object') {
1340 dupe[property] = clone(obj[property]);
1342 dupe[property] = obj[property];
1349 function trim(str) {
1350 return str.replace(/^\s+|\s+$/g, '');
1353 /*****************************************************************************
1354 * DEBUGGING UTILITIES
1355 ****************************************************************************/
1356 function perft(depth) {
1357 var moves = generate_moves({legal: false});
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;
1378 /***************************************************************************
1379 * PUBLIC CONSTANTS (is there a better way to do this?)
1380 **************************************************************************/
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
1397 for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1398 if (i & 0x88) { i += 7; continue; }
1399 keys.push(algebraic(i));
1405 /***************************************************************************
1407 **************************************************************************/
1408 load: function(fen) {
1412 // reset: function() {
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.
1423 var ugly_moves = generate_moves(options);
1426 for (var i = 0, len = ugly_moves.length; i < len; i++) {
1428 /* does the user want a full move object (most likely not), or just
1431 if (typeof options !== 'undefined' && 'verbose' in options &&
1433 moves.push(make_pretty(ugly_moves[i]));
1435 moves.push(move_to_san(ugly_moves[i], false));
1442 // in_check: function() {
1443 // return in_check();
1446 // in_checkmate: function() {
1447 // return in_checkmate();
1450 // in_stalemate: function() {
1451 // return in_stalemate();
1454 // in_draw: function() {
1455 // return half_moves >= 100 ||
1456 // in_stalemate() ||
1457 // insufficient_material() ||
1458 // in_threefold_repetition();
1461 // insufficient_material: function() {
1462 // return insufficient_material();
1465 // in_threefold_repetition: function() {
1466 // return in_threefold_repetition();
1469 game_over: function() {
1470 return half_moves >= 100 ||
1473 insufficient_material() ||
1474 in_threefold_repetition();
1477 // validate_fen: function(fen) {
1478 // return validate_fen(fen);
1482 return generate_fen();
1485 // board: function() {
1489 // for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
1490 // if (board[i] == null) {
1493 // row.push({type: board[i].type, color: board[i].color})
1495 // if ((i + 1) & 0x88) {
1496 // output.push(row);
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 />" })
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;
1516 // var header_exists = false;
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)
1523 // result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
1524 // header_exists = true;
1527 // if (header_exists && history.length) {
1528 // result.push(newline);
1531 // /* pop all of history onto reversed_history */
1532 // var reversed_history = [];
1533 // while (history.length > 0) {
1534 // reversed_history.push(undo_move());
1538 // var move_string = '';
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();
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);
1552 // move_string = move_number + '.';
1555 // move_string = move_string + ' ' + move_to_san(move, false);
1559 // /* are there any other leftover moves? */
1560 // if (move_string.length) {
1561 // moves.push(move_string);
1564 // /* is there a result? */
1565 // if (typeof header.Result !== 'undefined') {
1566 // moves.push(header.Result);
1569 // /* history should be back to what is was before we started generating PGN,
1570 // * so join together moves
1572 // if (max_width === 0) {
1573 // return result.join('') + moves.join(' ');
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) {
1582 // /* don't end the line with whitespace */
1583 // if (result[result.length - 1] === ' ') {
1587 // result.push(newline);
1588 // current_width = 0;
1589 // } else if (i !== 0) {
1590 // result.push(' ');
1593 // result.push(moves[i]);
1594 // current_width += moves[i].length;
1597 // return result.join('');
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;
1606 // function mask(str) {
1607 // return str.replace(/\\/g, '\\');
1610 // function has_keys(object) {
1611 // for (var key in object) {
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)));
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;
1634 // return header_obj;
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');
1644 // /* get header part of the PGN file */
1645 // var header_string = pgn.replace(regex, '$1');
1647 // /* no info part given, begins with moves */
1648 // if (header_string[0] !== '[') {
1649 // header_string = '';
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]]);
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
1668 // /* delete header to get the moves */
1669 // var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
1671 // /* delete comments */
1672 // ms = ms.replace(/(\{[^}]+\})+?/g, '');
1674 // /* delete recursive annotation variations */
1675 // var rav_regex = /(\([^\(\)]+\))+?/g
1676 // while (rav_regex.test(ms)) {
1677 // ms = ms.replace(rav_regex, '');
1680 // /* delete move numbers */
1681 // ms = ms.replace(/\d+\.(\.\.)?/g, '');
1683 // /* delete ... indicating black to move */
1684 // ms = ms.replace(/\.\.\./g, '');
1686 // /* delete numeric annotation glyphs */
1687 // ms = ms.replace(/\$\d+/g, '');
1689 // /* trim and get array of moves */
1690 // var moves = trim(ms).split(new RegExp(/\s+/));
1692 // /* delete empty entries */
1693 // moves = moves.join(',').replace(/,,+/g, ',').split(',');
1696 // for (var half_move = 0; half_move < moves.length - 1; half_move++) {
1697 // move = move_from_san(moves[half_move], sloppy);
1699 // /* move not possible! (don't clear the board to examine to show the
1700 // * latest valid position)
1702 // if (move == null) {
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]);
1717 // move = move_from_san(move, sloppy);
1718 // if (move == null) {
1727 // header: function() {
1728 // return set_header(arguments);
1731 // ascii: function() {
1739 move: function(move, options) {
1740 /* The move function can be called with in the following parameters:
1742 * .move('Nxb7') <- where 'move' is a case-sensitive SAN string
1744 * .move({ from: 'h7', <- where the 'move' is a move object (additional
1745 * to :'h8', fields are ignored)
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;
1755 var move_obj = null;
1757 if (typeof move === 'string') {
1758 move_obj = move_from_san(move, sloppy);
1759 } else if (typeof move === 'object') {
1760 var moves = generate_moves();
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];
1774 /* failed to find move */
1779 /* need to make a copy of move because we can't generate SAN after the
1782 var pretty_move = make_pretty(move_obj);
1784 make_move(move_obj);
1790 var move = undo_move();
1791 return (move) ? make_pretty(move) : null;
1798 // put: function(piece, square) {
1799 // return put(piece, square);
1802 // get: function(square) {
1803 // return get(square);
1806 // remove: function(square) {
1807 // return remove(square);
1810 // perft: function(depth) {
1811 // return perft(depth);
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';
1823 history: function(options) {
1824 var reversed_history = [];
1825 var move_history = [];
1826 var verbose = (typeof options !== 'undefined' && 'verbose' in options &&
1829 while (history.length > 0) {
1830 reversed_history.push(undo_move());
1833 while (reversed_history.length > 0) {
1834 var move = reversed_history.pop();
1836 move_history.push(make_pretty(move));
1838 move_history.push(move_to_san(move));
1843 return move_history;
1849 /* export Chess object if using node or any other CommonJS compatible
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; });