-'use strict';
/*
- * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
+ * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
/* minified license below */
/* @license
- * Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com)
+ * Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
* Released under the BSD license
* https://github.com/jhlywa/chess.js/blob/master/LICENSE
*/
k: [-17, -16, -15, 1, 17, 16, 15, -1]
};
- var ATTACKS = [
- 20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20, 0,
- 0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
- 0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
- 0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
- 0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
- 24,24,24,24,24,24,56, 0, 56,24,24,24,24,24,24, 0,
- 0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
- 0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
- 0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
- 0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
- 20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20
- ];
-
- var RAYS = [
- 17, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 15, 0,
- 0, 17, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 15, 0, 0,
- 0, 0, 17, 0, 0, 0, 0, 16, 0, 0, 0, 0, 15, 0, 0, 0,
- 0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 0,
- 0, 0, 0, 0, 17, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 17, 0, 16, 0, 15, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 17, 16, 15, 0, 0, 0, 0, 0, 0, 0,
- 1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1,-1, -1, -1, -1, 0,
- 0, 0, 0, 0, 0, 0,-15,-16,-17, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,-15, 0,-16, 0,-17, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,-15, 0, 0,-16, 0, 0,-17, 0, 0, 0, 0, 0,
- 0, 0, 0,-15, 0, 0, 0,-16, 0, 0, 0,-17, 0, 0, 0, 0,
- 0, 0,-15, 0, 0, 0, 0,-16, 0, 0, 0, 0,-17, 0, 0, 0,
- 0,-15, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0,-17, 0, 0,
- -15, 0, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0, 0,-17
- ];
-
- var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };
-
var FLAGS = {
NORMAL: 'n',
CAPTURE: 'c',
a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119
};
- var ROOKS = {
- w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE},
- {square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}],
- b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE},
- {square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}]
- };
-
var board = new Array(128);
var kings = {w: EMPTY, b: EMPTY};
+ var rooks = {w: [], b: []};
var turn = WHITE;
var castling = {w: 0, b: 0};
var ep_square = EMPTY;
turn = tokens[1];
+ rooks = {w: [], b: []};
+
if (tokens[2].indexOf('K') > -1) {
castling.w |= BITS.KSIDE_CASTLE;
+ for (var sq = SQUARES.h1; sq >= SQUARES.c1; --sq) {
+ if (is_rook(board[sq], WHITE)) {
+ rooks[WHITE].push({square: sq, flag: BITS.KSIDE_CASTLE});
+ break;
+ }
+ }
}
if (tokens[2].indexOf('Q') > -1) {
castling.w |= BITS.QSIDE_CASTLE;
+ for (var sq = SQUARES.a1; sq <= SQUARES.g1; ++sq) {
+ if (is_rook(board[sq], WHITE)) {
+ rooks[WHITE].push({square: sq, flag: BITS.QSIDE_CASTLE});
+ break;
+ }
+ }
+ }
+ var white_frc_columns = tokens[2].match(/[A-H]/g);
+ var i, flag;
+ if (white_frc_columns !== null) {
+ for (i = 0; i < white_frc_columns.length; ++i) {
+ var sq = SQUARES.a1 + (white_frc_columns[i].charCodeAt(0) - "A".charCodeAt(0));
+ flag = sq < kings[WHITE] ? BITS.QSIDE_CASTLE : BITS.KSIDE_CASTLE;
+ castling.w |= flag;
+ rooks[WHITE].push({square: sq, flag: flag});
+ }
}
+
if (tokens[2].indexOf('k') > -1) {
castling.b |= BITS.KSIDE_CASTLE;
+ for (var sq = SQUARES.h8; sq >= SQUARES.c8; --sq) {
+ if (is_rook(board[sq], BLACK)) {
+ rooks[BLACK].push({square: sq, flag: BITS.KSIDE_CASTLE});
+ break;
+ }
+ }
}
if (tokens[2].indexOf('q') > -1) {
castling.b |= BITS.QSIDE_CASTLE;
+ for (var sq = SQUARES.a8; sq <= SQUARES.g8; ++sq) {
+ if (is_rook(board[sq], BLACK)) {
+ rooks[BLACK].push({square: sq, flag: BITS.QSIDE_CASTLE});
+ break;
+ }
+ }
+ }
+ var black_frc_columns = tokens[2].match(/[a-h]/g);
+ if (black_frc_columns !== null) {
+ for (i = 0; i < black_frc_columns.length; ++i) {
+ var sq = SQUARES.a8 + (black_frc_columns[i].charCodeAt(0) - "a".charCodeAt(0));
+ flag = sq < kings[BLACK] ? BITS.QSIDE_CASTLE : BITS.KSIDE_CASTLE;
+ castling.b |= flag;
+ rooks[BLACK].push({square: sq, flag: flag});
+ }
}
ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
return true;
}
+ /* TODO: this function is pretty much crap - it validates structure but
+ * completely ignores content (e.g. doesn't verify that each side has a king)
+ * ... we should rewrite this, and ditch the silly error_number field while
+ * we're at it
+ */
function validate_fen(fen) {
var errors = {
0: 'No errors.',
8: '1st field (piece positions) is invalid [consecutive numbers].',
9: '1st field (piece positions) is invalid [invalid piece].',
10: '1st field (piece positions) is invalid [row too large].',
+ 11: 'Illegal en-passant square',
};
/* 1st criterion: 6 space-seperated fields? */
}
/* 5th criterion: 3th field is a valid castle-string? */
- if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) {
+ if( !/^[C-HK]?[A-FQ]?[c-hk]?[a-fq]?$/.test(tokens[2]) &&
+ tokens[2] !== '-') {
return {valid: false, error_number: 5, error: errors[5]};
}
}
}
+ if ((tokens[3][1] == '3' && tokens[1] == 'w') ||
+ (tokens[3][1] == '6' && tokens[1] == 'b')) {
+ return {valid: false, error_number: 11, error: errors[11]};
+ }
+
/* everything's okay! */
return {valid: true, error_number: 0, error: errors[0]};
}
}
var cflags = '';
- if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; }
- if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; }
- if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; }
- if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; }
+ var sq;
+ if (castling[WHITE] & BITS.KSIDE_CASTLE) {
+ sq = search_rook(board, WHITE, BITS.KSIDE_CASTLE);
+ if (is_outermost_rook(board, WHITE, BITS.KSIDE_CASTLE, sq)) {
+ cflags += 'K';
+ } else {
+ cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1);
+ }
+ }
+ if (castling[WHITE] & BITS.QSIDE_CASTLE) {
+ sq = search_rook(board, WHITE, BITS.QSIDE_CASTLE);
+ if (is_outermost_rook(board, WHITE, BITS.QSIDE_CASTLE, sq)) {
+ cflags += 'Q';
+ } else {
+ cflags += 'ABCDEFGH'.substring(file(sq), file(sq) + 1);
+ }
+ }
+ if (castling[BLACK] & BITS.KSIDE_CASTLE) {
+ sq = search_rook(board, BLACK, BITS.KSIDE_CASTLE);
+ if (is_outermost_rook(board, BLACK, BITS.KSIDE_CASTLE, sq)) {
+ cflags += 'k';
+ } else {
+ cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1);
+ }
+ }
+ if (castling[BLACK] & BITS.QSIDE_CASTLE) {
+ sq = search_rook(board, BLACK, BITS.QSIDE_CASTLE);
+ if (is_outermost_rook(board, BLACK, BITS.QSIDE_CASTLE, sq)) {
+ cflags += 'q';
+ } else {
+ cflags += 'abcdefgh'.substring(file(sq), file(sq) + 1);
+ }
+ }
/* do we have an empty castling flag? */
cflags = cflags || '-';
return piece;
}
- function build_move(board, from, to, flags, promotion) {
+ function build_move(board, from, to, flags, promotion, rook_sq) {
var move = {
color: turn,
from: from,
move.promotion = promotion;
}
- if (board[to]) {
+ if (flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) {
+ move.rook_sq = rook_sq; // remember the position of the rook
+ } else if (board[to]) {
move.captured = board[to].type;
} else if (flags & BITS.EP_CAPTURE) {
move.captured = PAWN;
}
function generate_moves(options) {
- function add_move(board, moves, from, to, flags) {
+ function add_move(board, moves, from, to, flags, rook_sq) {
/* if pawn promotion */
if (board[from].type === PAWN &&
(rank(to) === RANK_8 || rank(to) === RANK_1)) {
moves.push(build_move(board, from, to, flags, pieces[i]));
}
} else {
- moves.push(build_move(board, from, to, flags));
+ moves.push(build_move(board, from, to, flags, undefined, rook_sq));
+ }
+ }
+
+ function check_castle(board, king_from, king_to, rook_from, rook_to, them) {
+ var sq;
+
+ // Check that no pieces are standing between the king and its destination
+ // square, and also between the rook and its destination square.
+ var king_left = Math.min(king_from, king_to);
+ var king_right = Math.max(king_from, king_to);
+ var left = Math.min(king_left, Math.min(rook_from, rook_to));
+ var right = Math.max(king_right, Math.max(rook_from, rook_to));
+ for (sq = left; sq <= right; ++sq) {
+ if (sq != king_from && sq != rook_from && board[sq]) {
+ return false;
+ }
+ }
+
+ // Check that none of the squares on the king's way are under attack.
+ for (sq = king_left; sq <= king_right; ++sq) {
+ if (attacked(them, sq)) {
+ return false;
+ }
}
+
+ return true;
}
var moves = [];
if ((!single_square) || last_sq === kings[us]) {
/* king-side castling */
if (castling[us] & BITS.KSIDE_CASTLE) {
- var castling_from = kings[us];
- var castling_to = castling_from + 2;
-
- if (board[castling_from + 1] == null &&
- board[castling_to] == null &&
- !attacked(them, kings[us]) &&
- !attacked(them, castling_from + 1) &&
- !attacked(them, castling_to)) {
- add_move(board, moves, kings[us] , castling_to,
- BITS.KSIDE_CASTLE);
+ var king_from = kings[us];
+ var king_to = us === WHITE ? SQUARES.g1 : SQUARES.g8;
+ var rook_from = search_rook(board, us, BITS.KSIDE_CASTLE);
+ var rook_to = king_to - 1;
+
+ if (check_castle(board, king_from, king_to, rook_from, rook_to, them)) {
+ add_move(board, moves, king_from, king_to, BITS.KSIDE_CASTLE, rook_from);
}
}
/* queen-side castling */
if (castling[us] & BITS.QSIDE_CASTLE) {
- var castling_from = kings[us];
- var castling_to = castling_from - 2;
-
- if (board[castling_from - 1] == null &&
- board[castling_from - 2] == null &&
- board[castling_from - 3] == null &&
- !attacked(them, kings[us]) &&
- !attacked(them, castling_from - 1) &&
- !attacked(them, castling_to)) {
- add_move(board, moves, kings[us], castling_to,
- BITS.QSIDE_CASTLE);
+ var king_from = kings[us];
+ var king_to = us === WHITE ? SQUARES.c1 : SQUARES.c8;
+ var rook_from = search_rook(board, us, BITS.QSIDE_CASTLE);
+ var rook_to = king_to + 1;
+
+ if (check_castle(board, king_from, king_to, rook_from, rook_to, them)) {
+ add_move(board, moves, king_from, king_to, BITS.QSIDE_CASTLE, rook_from);
}
}
}
+ return possibly_filter_moves(moves, us, legal);
+ }
+
+ function possibly_filter_moves(moves, us, legal) {
/* return all pseudo-legal moves (this includes moves that allow the king
* to be captured)
*/
return legal_moves;
}
+ function is_rook(piece, color) {
+ return (typeof piece !== 'undefined' && piece !== null &&
+ piece.type === ROOK && piece.color == color);
+ }
+
+ function search_rook(board, us, flag) {
+ for (var i = 0, len = rooks[us].length; i < len; i++) {
+ if (flag & rooks[us][i].flag) {
+ return rooks[us][i].square;
+ }
+ }
+ return null;
+ }
+
+ function is_outermost_rook(board, us, flag, sq) {
+ var end_sq;
+ if (flag == BITS.KSIDE_CASTLE) {
+ var end_sq = (us == WHITE) ? SQUARES.h1 : SQUARES.h8;
+ while (++sq <= end_sq) {
+ if (is_rook(board[sq], us)) {
+ return false;
+ }
+ }
+ } else {
+ var end_sq = (us == WHITE) ? SQUARES.a1 : SQUARES.a8;
+ while (--sq >= end_sq) {
+ if (is_rook(board[sq], us)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
/* convert a move from 0x88 coordinates to Standard Algebraic Notation
* (SAN)
+ *
+ * @param {boolean} sloppy Use the sloppy SAN generator to work around over
+ * disambiguation bugs in Fritz and Chessbase. See below:
+ *
+ * r1bqkbnr/ppp2ppp/2n5/1B1pP3/4P3/8/PPPP2PP/RNBQK1NR b KQkq - 2 4
+ * 4. ... Nge7 is overly disambiguated because the knight on c6 is pinned
+ * 4. ... Ne7 is technically the valid SAN
*/
- function move_to_san(move) {
+ function move_to_san(move, sloppy) {
+
var output = '';
if (move.flags & BITS.KSIDE_CASTLE) {
} else if (move.flags & BITS.QSIDE_CASTLE) {
output = 'O-O-O';
} else {
- var disambiguator = get_disambiguator(move);
+ var disambiguator = get_disambiguator(move, sloppy);
if (move.piece !== PAWN) {
output += move.piece.toUpperCase() + disambiguator;
return output;
}
+ // parses all of the decorators out of a SAN string
+ function stripped_san(move) {
+ return move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
+ }
+
function attacked(color, square) {
- for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
- /* did we run off the end of the board */
- if (i & 0x88) { i += 7; continue; }
+ // Check for attacks by the king.
+ if (Math.abs(rank(kings[color]) - rank(square)) <= 1 &&
+ Math.abs(file(kings[color]) - file(square)) <= 1) {
+ return true;
+ }
- /* if empty square or wrong color */
- if (board[i] == null || board[i].color !== color) continue;
+ // Check for attacks by knights.
+ for (const offset of PIECE_OFFSETS[KNIGHT]) {
+ let knight_sq = square + offset;
+ if (knight_sq & 0x88) continue;
- var piece = board[i];
- var difference = i - square;
- var index = difference + 119;
+ if (board[knight_sq] != null &&
+ board[knight_sq].type === KNIGHT &&
+ board[knight_sq].color === color) {
+ return true;
+ }
+ }
- if (ATTACKS[index] & (1 << SHIFTS[piece.type])) {
- if (piece.type === PAWN) {
- if (difference > 0) {
- if (piece.color === WHITE) return true;
- } else {
- if (piece.color === BLACK) return true;
+ // Check for attacks by pawns.
+ const p1sq = square - PAWN_OFFSETS[color][2];
+ const p2sq = square - PAWN_OFFSETS[color][3];
+ if (!(p1sq & 0x88) &&
+ board[p1sq] != null &&
+ board[p1sq].type === PAWN &&
+ board[p1sq].color === color) {
+ return true;
+ }
+ if (!(p2sq & 0x88) &&
+ board[p2sq] != null &&
+ board[p2sq].type === PAWN &&
+ board[p2sq].color === color) {
+ return true;
+ }
+
+ // Check for attacks by rooks (where queens count as rooks).
+ for (const offset of PIECE_OFFSETS[ROOK]) {
+ let rook_sq = square;
+ while (true) {
+ rook_sq += offset;
+ if (rook_sq & 0x88) break;
+
+ if (board[rook_sq] != null) {
+ if ((board[rook_sq].type === ROOK || board[rook_sq].type === QUEEN) &&
+ board[rook_sq].color === color) {
+ return true;
}
- continue;
+ break;
}
+ }
+ }
- /* if the piece is a knight or a king */
- if (piece.type === 'n' || piece.type === 'k') return true;
-
- var offset = RAYS[index];
- var j = i + offset;
-
- var blocked = false;
- while (j !== square) {
- if (board[j] != null) { blocked = true; break; }
- j += offset;
+ // And similarly for attacks by bishops (where queens count as bishops).
+ for (const offset of PIECE_OFFSETS[BISHOP]) {
+ let bishop_sq = square;
+ while (true) {
+ bishop_sq += offset;
+ if (bishop_sq & 0x88) break;
+
+ if (board[bishop_sq] != null) {
+ if ((board[bishop_sq].type === BISHOP || board[bishop_sq].type === QUEEN) &&
+ board[bishop_sq].color === color) {
+ return true;
+ }
+ break;
}
-
- if (!blocked) return true;
}
}
push(move);
board[move.to] = board[move.from];
- board[move.from] = null;
+ if (move.from != move.to) {
+ board[move.from] = null;
+ }
/* if ep capture, remove the captured pawn */
if (move.flags & BITS.EP_CAPTURE) {
/* if we castled, move the rook next to the king */
if (move.flags & BITS.KSIDE_CASTLE) {
var castling_to = move.to - 1;
- var castling_from = move.to + 1;
- board[castling_to] = board[castling_from];
- board[castling_from] = null;
+ var castling_from = move.rook_sq;
+ board[castling_to] = {type: ROOK, color: us};
+ if(castling_from !== move.to && castling_from !== castling_to)
+ board[castling_from] = null;
} else if (move.flags & BITS.QSIDE_CASTLE) {
var castling_to = move.to + 1;
- var castling_from = move.to - 2;
- board[castling_to] = board[castling_from];
- board[castling_from] = null;
+ var castling_from = move.rook_sq;
+ board[castling_to] = {type: ROOK, color: us};
+ if(castling_from !== move.to && castling_from !== castling_to)
+ board[castling_from] = null;
}
/* turn off castling */
/* turn off castling if we move a rook */
if (castling[us]) {
- for (var i = 0, len = ROOKS[us].length; i < len; i++) {
- if (move.from === ROOKS[us][i].square &&
- castling[us] & ROOKS[us][i].flag) {
- castling[us] ^= ROOKS[us][i].flag;
+ for (var i = 0, len = rooks[us].length; i < len; i++) {
+ if (move.from === rooks[us][i].square &&
+ castling[us] & rooks[us][i].flag) {
+ castling[us] ^= rooks[us][i].flag;
break;
}
}
/* turn off castling if we capture a rook */
if (castling[them]) {
- for (var i = 0, len = ROOKS[them].length; i < len; i++) {
- if (move.to === ROOKS[them][i].square &&
- castling[them] & ROOKS[them][i].flag) {
- castling[them] ^= ROOKS[them][i].flag;
+ for (var i = 0, len = rooks[them].length; i < len; i++) {
+ if (move.to === rooks[them][i].square &&
+ castling[them] & rooks[them][i].flag) {
+ castling[them] ^= rooks[them][i].flag;
break;
}
}
var us = turn;
var them = swap_color(turn);
- board[move.from] = board[move.to];
- board[move.from].type = move.piece; // to undo any promotions
- board[move.to] = null;
+ if (move.from != move.to) {
+ board[move.from] = board[move.to];
+ board[move.from].type = move.piece; // to undo any promotions
+ board[move.to] = null;
+ }
if (move.flags & BITS.CAPTURE) {
board[move.to] = {type: move.captured, color: them};
if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) {
var castling_to, castling_from;
if (move.flags & BITS.KSIDE_CASTLE) {
- castling_to = move.to + 1;
+ castling_to = move.rook_sq;
castling_from = move.to - 1;
} else if (move.flags & BITS.QSIDE_CASTLE) {
- castling_to = move.to - 2;
+ castling_to = move.rook_sq;
castling_from = move.to + 1;
}
- board[castling_to] = board[castling_from];
- board[castling_from] = null;
+ board[castling_to] = {type: ROOK, color: us};
+ if(castling_from !== move.from && castling_from !== castling_to)
+ board[castling_from] = null;
}
return move;
}
/* this function is used to uniquely identify ambiguous moves */
- function get_disambiguator(move) {
- var moves = generate_moves();
-
+ function get_disambiguator(move, sloppy) {
var from = move.from;
var to = move.to;
var piece = move.piece;
+ if (piece === 'p' || piece === 'k') {
+ // Pawn or king moves are never ambiguous.
+ return '';
+ }
+
+ let moves = find_attacking_moves(move.to, piece, move.color);
+ if (moves.length <= 1) {
+ // There can be no ambiguity, so don't bother checking legality
+ // (we assume the move has already been found legal).
+ return '';
+ }
+
+ moves = possibly_filter_moves(moves, move.color, !sloppy);
+
var ambiguities = 0;
var same_rank = 0;
var same_file = 0;
return '';
}
+ // Find all pseudolegal moves featuring the given piece moving to
+ // the given square (using symmetry of all non-pawn-or-castle moves,
+ // we simply generate moves backwards). Does not support pawns.
+ // Assumes there's not already a piece of our own color
+ // on the destination square.
+ function find_attacking_moves(to, piece, us) {
+ let moves = [];
+
+ function add_move(board, moves, from, to, flags, rook_sq) {
+ moves.push(build_move(board, from, to, flags, undefined, rook_sq));
+ }
+ for (let offset of PIECE_OFFSETS[piece]) {
+ var square = to;
+
+ while (true) {
+ square += offset;
+ if (square & 0x88) break;
+
+ if (board[square] != null) {
+ if (board[square].color !== us || board[square].type !== piece) break;
+ if (board[to] == null) {
+ add_move(board, moves, square, to, BITS.NORMAL);
+ } else {
+ add_move(board, moves, square, to, BITS.CAPTURE);
+ }
+ break;
+ }
+
+ /* break if knight or king */
+ if (piece === 'n' || piece === 'k') break;
+ }
+ }
+
+ return moves;
+ }
+
function ascii() {
var s = ' +------------------------+\n';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
return s;
}
+ // convert a move from Standard Algebraic Notation (SAN) to 0x88 coordinates
+ function move_from_san(move, sloppy) {
+ // strip off any move decorations: e.g Nf3+?!
+ var clean_move = stripped_san(move);
+
+ // if we're using the sloppy parser run a regex to grab piece, to, and from
+ // this should parse invalid SAN like: Pe2-e4, Rc1c4, Qf3xf7
+ if (sloppy) {
+ var matches = clean_move.match(/([pnbrqkPNBRQK])?([a-h][1-8])x?-?([a-h][1-8])([qrbnQRBN])?/);
+ if (matches) {
+ var piece = matches[1];
+ var from = matches[2];
+ var to = matches[3];
+ var promotion = matches[4];
+ }
+ }
+
+ let moves;
+ let piece_matches = clean_move.match(/^([NBRQK])x?([a-h][1-8])$/);
+ if (piece_matches) {
+ // Only look for moves by the given piece to the given square.
+ let to = SQUARES[piece_matches[2]];
+ if (board[to] != null && board[to].color === turn) {
+ // Cannot capture our own piece.
+ return null;
+ }
+ moves = find_attacking_moves(to, piece_matches[1].toLowerCase(), turn);
+ // Legal moves only.
+ moves = possibly_filter_moves(moves, turn, true);
+ } else {
+ // Fallback (also used for pawns): Any (legal) moves.
+ moves = generate_moves();
+ }
+
+ for (var i = 0, len = moves.length; i < len; i++) {
+ // try the strict parser first, then the sloppy parser if requested
+ // by the user
+ if ((clean_move === stripped_san(move_to_san(moves[i]))) ||
+ (sloppy && clean_move === stripped_san(move_to_san(moves[i], true)))) {
+ return moves[i];
+ } else {
+ if (matches &&
+ (!piece || piece.toLowerCase() == moves[i].piece) &&
+ SQUARES[from] == moves[i].from &&
+ SQUARES[to] == moves[i].to &&
+ (!promotion || promotion.toLowerCase() == moves[i].promotion)) {
+ return moves[i];
+ }
+ }
+ }
+
+ return null;
+ }
+
+
/*****************************************************************************
* UTILITY FUNCTIONS
****************************************************************************/
/* pretty = external move object */
function make_pretty(ugly_move) {
var move = clone(ugly_move);
- move.san = move_to_san(move);
+ move.san = move_to_san(move, false);
move.to = algebraic(move.to);
move.from = algebraic(move.from);
return load(fen);
},
- reset: function() {
- return reset();
- },
+ // reset: function() {
+ // return reset();
+ // },
moves: function(options) {
/* The internal representation of a chess move is in 0x88 format, and
options.verbose) {
moves.push(make_pretty(ugly_moves[i]));
} else {
- moves.push(move_to_san(ugly_moves[i]));
+ moves.push(move_to_san(ugly_moves[i], false));
}
}
return moves;
},
- in_check: function() {
- return in_check();
- },
+ // in_check: function() {
+ // return in_check();
+ // },
- in_checkmate: function() {
- return in_checkmate();
- },
+ // in_checkmate: function() {
+ // return in_checkmate();
+ // },
- in_stalemate: function() {
- return in_stalemate();
- },
+ // in_stalemate: function() {
+ // return in_stalemate();
+ // },
- in_draw: function() {
- return half_moves >= 100 ||
- in_stalemate() ||
- insufficient_material() ||
- in_threefold_repetition();
- },
+ // in_draw: function() {
+ // return half_moves >= 100 ||
+ // in_stalemate() ||
+ // insufficient_material() ||
+ // in_threefold_repetition();
+ // },
- insufficient_material: function() {
- return insufficient_material();
- },
+ // insufficient_material: function() {
+ // return insufficient_material();
+ // },
- in_threefold_repetition: function() {
- return in_threefold_repetition();
- },
+ // in_threefold_repetition: function() {
+ // return in_threefold_repetition();
+ // },
game_over: function() {
return half_moves >= 100 ||
in_threefold_repetition();
},
- validate_fen: function(fen) {
- return validate_fen(fen);
- },
+ // validate_fen: function(fen) {
+ // return validate_fen(fen);
+ // },
fen: function() {
return generate_fen();
},
- pgn: function(options) {
- /* using the specification from http://www.chessclub.com/help/PGN-spec
- * example for html usage: .pgn({ max_width: 72, newline_char: "<br />" })
- */
- var newline = (typeof options === 'object' &&
- typeof options.newline_char === 'string') ?
- options.newline_char : '\n';
- var max_width = (typeof options === 'object' &&
- typeof options.max_width === 'number') ?
- options.max_width : 0;
- var result = [];
- var header_exists = false;
-
- /* add the PGN header headerrmation */
- for (var i in header) {
- /* TODO: order of enumerated properties in header object is not
- * guaranteed, see ECMA-262 spec (section 12.6.4)
- */
- result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
- header_exists = true;
- }
-
- if (header_exists && history.length) {
- result.push(newline);
- }
-
- /* pop all of history onto reversed_history */
- var reversed_history = [];
- while (history.length > 0) {
- reversed_history.push(undo_move());
- }
-
- var moves = [];
- var move_string = '';
- var pgn_move_number = 1;
-
- /* build the list of moves. a move_string looks like: "3. e3 e6" */
- while (reversed_history.length > 0) {
- var move = reversed_history.pop();
-
- /* if the position started with black to move, start PGN with 1. ... */
- if (pgn_move_number === 1 && move.color === 'b') {
- move_string = '1. ...';
- pgn_move_number++;
- } else if (move.color === 'w') {
- /* store the previous generated move_string if we have one */
- if (move_string.length) {
- moves.push(move_string);
- }
- move_string = pgn_move_number + '.';
- pgn_move_number++;
- }
-
- move_string = move_string + ' ' + move_to_san(move);
- make_move(move);
- }
-
- /* are there any other leftover moves? */
- if (move_string.length) {
- moves.push(move_string);
- }
-
- /* is there a result? */
- if (typeof header.Result !== 'undefined') {
- moves.push(header.Result);
- }
-
- /* history should be back to what is was before we started generating PGN,
- * so join together moves
- */
- if (max_width === 0) {
- return result.join('') + moves.join(' ');
- }
-
- /* wrap the PGN output at max_width */
- var current_width = 0;
- for (var i = 0; i < moves.length; i++) {
- /* if the current move will push past max_width */
- if (current_width + moves[i].length > max_width && i !== 0) {
-
- /* don't end the line with whitespace */
- if (result[result.length - 1] === ' ') {
- result.pop();
- }
-
- result.push(newline);
- current_width = 0;
- } else if (i !== 0) {
- result.push(' ');
- current_width++;
- }
- result.push(moves[i]);
- current_width += moves[i].length;
- }
-
- return result.join('');
- },
-
- load_pgn: function(pgn, options) {
- function mask(str) {
- return str.replace(/\\/g, '\\');
- }
-
- /* convert a move from Standard Algebraic Notation (SAN) to 0x88
- * coordinates
- */
- function move_from_san(move) {
- /* strip off any move decorations: e.g Nf3+?! */
- var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
- var moves = generate_moves();
- for (var i = 0, len = moves.length; i < len; i++) {
- if (move_replaced ===
- move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
- return moves[i];
- }
- }
- return null;
- }
-
- function get_move_obj(move) {
- return move_from_san(trim(move));
- }
-
- function has_keys(object) {
- var has_keys = false;
- for (var key in object) {
- has_keys = true;
- }
- return has_keys;
- }
-
- function parse_pgn_header(header, options) {
- var newline_char = (typeof options === 'object' &&
- typeof options.newline_char === 'string') ?
- options.newline_char : '\r?\n';
- var header_obj = {};
- var headers = header.split(new RegExp(mask(newline_char)));
- var key = '';
- var value = '';
-
- for (var i = 0; i < headers.length; i++) {
- key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
- value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
- if (trim(key).length > 0) {
- header_obj[key] = value;
- }
- }
-
- return header_obj;
- }
-
- var newline_char = (typeof options === 'object' &&
- typeof options.newline_char === 'string') ?
- options.newline_char : '\r?\n';
- var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' +
- '(' + mask(newline_char) + ')*' +
- '1.(' + mask(newline_char) + '|.)*$', 'g');
-
- /* get header part of the PGN file */
- var header_string = pgn.replace(regex, '$1');
-
- /* no info part given, begins with moves */
- if (header_string[0] !== '[') {
- header_string = '';
- }
-
- reset();
-
- /* parse PGN header */
- var headers = parse_pgn_header(header_string, options);
- for (var key in headers) {
- set_header([key, headers[key]]);
- }
-
- /* load the starting position indicated by [Setup '1'] and
- * [FEN position] */
- if (headers['SetUp'] === '1') {
- if (!(('FEN' in headers) && load(headers['FEN']))) {
- return false;
- }
- }
-
- /* delete header to get the moves */
- var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
-
- /* delete comments */
- ms = ms.replace(/(\{[^}]+\})+?/g, '');
-
- /* delete recursive annotation variations */
- var rav_regex = /(\([^\(\)]+\))+?/g
- while (rav_regex.test(ms)) {
- ms = ms.replace(rav_regex, '');
- }
-
- /* delete move numbers */
- ms = ms.replace(/\d+\./g, '');
-
- /* delete ... indicating black to move */
- ms = ms.replace(/\.\.\./g, '');
-
- /* trim and get array of moves */
- var moves = trim(ms).split(new RegExp(/\s+/));
-
- /* delete empty entries */
- moves = moves.join(',').replace(/,,+/g, ',').split(',');
- var move = '';
-
- for (var half_move = 0; half_move < moves.length - 1; half_move++) {
- move = get_move_obj(moves[half_move]);
-
- /* move not possible! (don't clear the board to examine to show the
- * latest valid position)
- */
- if (move == null) {
- return false;
- } else {
- make_move(move);
- }
- }
-
- /* examine last move */
- move = moves[moves.length - 1];
- if (POSSIBLE_RESULTS.indexOf(move) > -1) {
- if (has_keys(header) && typeof header.Result === 'undefined') {
- set_header(['Result', move]);
- }
- }
- else {
- move = get_move_obj(move);
- if (move == null) {
- return false;
- } else {
- make_move(move);
- }
- }
- return true;
- },
-
- header: function() {
- return set_header(arguments);
- },
-
- ascii: function() {
- return ascii();
- },
+ // board: function() {
+ // var output = [],
+ // row = [];
+
+ // for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
+ // if (board[i] == null) {
+ // row.push(null)
+ // } else {
+ // row.push({type: board[i].type, color: board[i].color})
+ // }
+ // if ((i + 1) & 0x88) {
+ // output.push(row);
+ // row = []
+ // i += 8;
+ // }
+ // }
+
+ // return output;
+ // },
+
+ // pgn: function(options) {
+ // /* using the specification from http://www.chessclub.com/help/PGN-spec
+ // * example for html usage: .pgn({ max_width: 72, newline_char: "<br />" })
+ // */
+ // var newline = (typeof options === 'object' &&
+ // typeof options.newline_char === 'string') ?
+ // options.newline_char : '\n';
+ // var max_width = (typeof options === 'object' &&
+ // typeof options.max_width === 'number') ?
+ // options.max_width : 0;
+ // var result = [];
+ // var header_exists = false;
+
+ // /* add the PGN header headerrmation */
+ // for (var i in header) {
+ // /* TODO: order of enumerated properties in header object is not
+ // * guaranteed, see ECMA-262 spec (section 12.6.4)
+ // */
+ // result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
+ // header_exists = true;
+ // }
+
+ // if (header_exists && history.length) {
+ // result.push(newline);
+ // }
+
+ // /* pop all of history onto reversed_history */
+ // var reversed_history = [];
+ // while (history.length > 0) {
+ // reversed_history.push(undo_move());
+ // }
+
+ // var moves = [];
+ // var move_string = '';
+
+ // /* build the list of moves. a move_string looks like: "3. e3 e6" */
+ // while (reversed_history.length > 0) {
+ // var move = reversed_history.pop();
+
+ // /* if the position started with black to move, start PGN with 1. ... */
+ // if (!history.length && move.color === 'b') {
+ // move_string = move_number + '. ...';
+ // } else if (move.color === 'w') {
+ // /* store the previous generated move_string if we have one */
+ // if (move_string.length) {
+ // moves.push(move_string);
+ // }
+ // move_string = move_number + '.';
+ // }
+
+ // move_string = move_string + ' ' + move_to_san(move, false);
+ // make_move(move);
+ // }
+
+ // /* are there any other leftover moves? */
+ // if (move_string.length) {
+ // moves.push(move_string);
+ // }
+
+ // /* is there a result? */
+ // if (typeof header.Result !== 'undefined') {
+ // moves.push(header.Result);
+ // }
+
+ // /* history should be back to what is was before we started generating PGN,
+ // * so join together moves
+ // */
+ // if (max_width === 0) {
+ // return result.join('') + moves.join(' ');
+ // }
+
+ // /* wrap the PGN output at max_width */
+ // var current_width = 0;
+ // for (var i = 0; i < moves.length; i++) {
+ // /* if the current move will push past max_width */
+ // if (current_width + moves[i].length > max_width && i !== 0) {
+
+ // /* don't end the line with whitespace */
+ // if (result[result.length - 1] === ' ') {
+ // result.pop();
+ // }
+
+ // result.push(newline);
+ // current_width = 0;
+ // } else if (i !== 0) {
+ // result.push(' ');
+ // current_width++;
+ // }
+ // result.push(moves[i]);
+ // current_width += moves[i].length;
+ // }
+
+ // return result.join('');
+ // },
+
+ // load_pgn: function(pgn, options) {
+ // // allow the user to specify the sloppy move parser to work around over
+ // // disambiguation bugs in Fritz and Chessbase
+ // var sloppy = (typeof options !== 'undefined' && 'sloppy' in options) ?
+ // options.sloppy : false;
+
+ // function mask(str) {
+ // return str.replace(/\\/g, '\\');
+ // }
+
+ // function has_keys(object) {
+ // for (var key in object) {
+ // return true;
+ // }
+ // return false;
+ // }
+
+ // function parse_pgn_header(header, options) {
+ // var newline_char = (typeof options === 'object' &&
+ // typeof options.newline_char === 'string') ?
+ // options.newline_char : '\r?\n';
+ // var header_obj = {};
+ // var headers = header.split(new RegExp(mask(newline_char)));
+ // var key = '';
+ // var value = '';
+
+ // for (var i = 0; i < headers.length; i++) {
+ // key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
+ // value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
+ // if (trim(key).length > 0) {
+ // header_obj[key] = value;
+ // }
+ // }
+
+ // return header_obj;
+ // }
+
+ // var newline_char = (typeof options === 'object' &&
+ // typeof options.newline_char === 'string') ?
+ // options.newline_char : '\r?\n';
+ // var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' +
+ // '(' + mask(newline_char) + ')*' +
+ // '1.(' + mask(newline_char) + '|.)*$', 'g');
+
+ // /* get header part of the PGN file */
+ // var header_string = pgn.replace(regex, '$1');
+
+ // /* no info part given, begins with moves */
+ // if (header_string[0] !== '[') {
+ // header_string = '';
+ // }
+
+ // reset();
+
+ // /* parse PGN header */
+ // var headers = parse_pgn_header(header_string, options);
+ // for (var key in headers) {
+ // set_header([key, headers[key]]);
+ // }
+
+ // /* load the starting position indicated by [Setup '1'] and
+ // * [FEN position] */
+ // if (headers['SetUp'] === '1') {
+ // if (!(('FEN' in headers) && load(headers['FEN'], true ))) { // second argument to load: don't clear the headers
+ // return false;
+ // }
+ // }
+
+ // /* delete header to get the moves */
+ // var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');
+
+ // /* delete comments */
+ // ms = ms.replace(/(\{[^}]+\})+?/g, '');
+
+ // /* delete recursive annotation variations */
+ // var rav_regex = /(\([^\(\)]+\))+?/g
+ // while (rav_regex.test(ms)) {
+ // ms = ms.replace(rav_regex, '');
+ // }
+
+ // /* delete move numbers */
+ // ms = ms.replace(/\d+\.(\.\.)?/g, '');
+
+ // /* delete ... indicating black to move */
+ // ms = ms.replace(/\.\.\./g, '');
+
+ // /* delete numeric annotation glyphs */
+ // ms = ms.replace(/\$\d+/g, '');
+
+ // /* trim and get array of moves */
+ // var moves = trim(ms).split(new RegExp(/\s+/));
+
+ // /* delete empty entries */
+ // moves = moves.join(',').replace(/,,+/g, ',').split(',');
+ // var move = '';
+
+ // for (var half_move = 0; half_move < moves.length - 1; half_move++) {
+ // move = move_from_san(moves[half_move], sloppy);
+
+ // /* move not possible! (don't clear the board to examine to show the
+ // * latest valid position)
+ // */
+ // if (move == null) {
+ // return false;
+ // } else {
+ // make_move(move);
+ // }
+ // }
+
+ // /* examine last move */
+ // move = moves[moves.length - 1];
+ // if (POSSIBLE_RESULTS.indexOf(move) > -1) {
+ // if (has_keys(header) && typeof header.Result === 'undefined') {
+ // set_header(['Result', move]);
+ // }
+ // }
+ // else {
+ // move = move_from_san(move, sloppy);
+ // if (move == null) {
+ // return false;
+ // } else {
+ // make_move(move);
+ // }
+ // }
+ // return true;
+ // },
+
+ // header: function() {
+ // return set_header(arguments);
+ // },
+
+ // ascii: function() {
+ // return ascii();
+ // },
turn: function() {
return turn;
},
- move: function(move) {
+ move: function(move, options) {
/* The move function can be called with in the following parameters:
*
* .move('Nxb7') <- where 'move' is a case-sensitive SAN string
* promotion: 'q',
* })
*/
+
+ // allow the user to specify the sloppy move parser to work around over
+ // disambiguation bugs in Fritz and Chessbase
+ var sloppy = (typeof options !== 'undefined' && 'sloppy' in options) ?
+ options.sloppy : false;
+
var move_obj = null;
- var moves = generate_moves();
if (typeof move === 'string') {
- /* convert the move string to a move object */
- /* strip off any move decorations: e.g Nf3+?! */
- var move_replaced = move.replace(/=/,'').replace(/[+#]?[?!]*$/,'');
- for (var i = 0, len = moves.length; i < len; i++) {
- if (move_replaced ===
- move_to_san(moves[i]).replace(/=/,'').replace(/[+#]?[?!]*$/,'')) {
- move_obj = moves[i];
- break;
- }
- }
+ move_obj = move_from_san(move, sloppy);
} else if (typeof move === 'object') {
+ var moves = generate_moves();
+
/* convert the pretty move object to an ugly move object */
for (var i = 0, len = moves.length; i < len; i++) {
if (move.from === algebraic(moves[i].from) &&
return clear();
},
- put: function(piece, square) {
- return put(piece, square);
- },
+ // put: function(piece, square) {
+ // return put(piece, square);
+ // },
- get: function(square) {
- return get(square);
- },
+ // get: function(square) {
+ // return get(square);
+ // },
- remove: function(square) {
- return remove(square);
- },
+ // remove: function(square) {
+ // return remove(square);
+ // },
- perft: function(depth) {
- return perft(depth);
- },
+ // perft: function(depth) {
+ // return perft(depth);
+ // },
- square_color: function(square) {
- if (square in SQUARES) {
- var sq_0x88 = SQUARES[square];
- return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
- }
+ // square_color: function(square) {
+ // if (square in SQUARES) {
+ // var sq_0x88 = SQUARES[square];
+ // return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark';
+ // }
- return null;
- },
+ // return null;
+ // },
history: function(options) {
var reversed_history = [];