Skip to content

Instantly share code, notes, and snippets.

@lolzballs
Last active March 2, 2019 02:42
Show Gist options
  • Save lolzballs/8a91a7809939ea8b92112736651d6e28 to your computer and use it in GitHub Desktop.
Save lolzballs/8a91a7809939ea8b92112736651d6e28 to your computer and use it in GitHub Desktop.
double
/*
* Copyright (c) 2017, Jeff Hlywa ([email protected])
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*----------------------------------------------------------------------------*/
/* minified license below */
/* @license
* Copyright (c) 2017, Jeff Hlywa ([email protected])
* Released under the BSD license
* https://github.com/jhlywa/chess.js/blob/master/LICENSE
*/
var Chess = function(fen) {
/* jshint indent: false */
var BLACK = 'b';
var WHITE = 'w';
var EMPTY = -1;
var PAWN = 'p';
var KNIGHT = 'n';
var BISHOP = 'b';
var ROOK = 'r';
var QUEEN = 'q';
var KING = 'k';
var SYMBOLS = 'pnbrqkPNBRQK';
var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*'];
var PAWN_OFFSETS = {
b: [16, 32, 17, 15],
w: [-16, -32, -17, -15]
};
var PIECE_OFFSETS = {
n: [-18, -33, -31, -14, 18, 33, 31, 14],
b: [-17, -15, 17, 15],
r: [-16, 1, 16, -1],
q: [-17, -16, -15, 1, 17, 16, 15, -1],
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',
BIG_PAWN: 'b',
EP_CAPTURE: 'e',
PROMOTION: 'p',
KSIDE_CASTLE: 'k',
QSIDE_CASTLE: 'q'
};
var BITS = {
NORMAL: 1,
CAPTURE: 2,
BIG_PAWN: 4,
EP_CAPTURE: 8,
PROMOTION: 16,
KSIDE_CASTLE: 32,
QSIDE_CASTLE: 64
};
var RANK_1 = 7;
var RANK_2 = 6;
var RANK_3 = 5;
var RANK_4 = 4;
var RANK_5 = 3;
var RANK_6 = 2;
var RANK_7 = 1;
var RANK_8 = 0;
var SQUARES = {
a8: 0, b8: 1, c8: 2, d8: 3, e8: 4, f8: 5, g8: 6, h8: 7,
a7: 16, b7: 17, c7: 18, d7: 19, e7: 20, f7: 21, g7: 22, h7: 23,
a6: 32, b6: 33, c6: 34, d6: 35, e6: 36, f6: 37, g6: 38, h6: 39,
a5: 48, b5: 49, c5: 50, d5: 51, e5: 52, f5: 53, g5: 54, h5: 55,
a4: 64, b4: 65, c4: 66, d4: 67, e4: 68, f4: 69, g4: 70, h4: 71,
a3: 80, b3: 81, c3: 82, d3: 83, e3: 84, f3: 85, g3: 86, h3: 87,
a2: 96, b2: 97, c2: 98, d2: 99, e2: 100, f2: 101, g2: 102, h2: 103,
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 turn = WHITE;
var castling = {w: 96, b: 96};
var ep_square = EMPTY;
var half_moves = 0;
var move_number = 1;
var history = [];
var header = {};
/* if the user passes in a fen string, load it, else default to
* starting position
*/
if (typeof fen === 'undefined') {
load(DEFAULT_POSITION);
} else {
load(fen);
}
function clear() {
board = new Array(128);
kings = {w: EMPTY, b: EMPTY};
turn = WHITE;
castling = {w: 0, b: 0};
ep_square = EMPTY;
half_moves = 0;
move_number = 1;
history = [];
header = {};
update_setup(generate_fen());
}
function reset() {
load(DEFAULT_POSITION);
}
function load(fen) {
var tokens = fen.split(/\s+/);
var position = tokens[0];
var square = 0;
if (!validate_fen(fen).valid) {
return false;
}
clear();
for (var i = 0; i < position.length; i++) {
var piece = position.charAt(i);
if (piece === '/') {
square += 8;
} else if (is_digit(piece)) {
square += parseInt(piece, 10);
} else {
var color = (piece < 'a') ? WHITE : BLACK;
put({type: piece.toLowerCase(), color: color}, algebraic(square));
square++;
}
}
turn = tokens[1];
if (tokens[2].indexOf('K') > -1) {
castling.w |= BITS.KSIDE_CASTLE;
}
if (tokens[2].indexOf('Q') > -1) {
castling.w |= BITS.QSIDE_CASTLE;
}
if (tokens[2].indexOf('k') > -1) {
castling.b |= BITS.KSIDE_CASTLE;
}
if (tokens[2].indexOf('q') > -1) {
castling.b |= BITS.QSIDE_CASTLE;
}
ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
half_moves = parseInt(tokens[4], 10);
move_number = parseInt(tokens[5], 10);
update_setup(generate_fen());
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.',
1: 'FEN string must contain six space-delimited fields.',
2: '6th field (move number) must be a positive integer.',
3: '5th field (half move counter) must be a non-negative integer.',
4: '4th field (en-passant square) is invalid.',
5: '3rd field (castling availability) is invalid.',
6: '2nd field (side to move) is invalid.',
7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.',
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? */
var tokens = fen.split(/\s+/);
if (tokens.length !== 6) {
return {valid: false, error_number: 1, error: errors[1]};
}
/* 2nd criterion: move number field is a integer value > 0? */
if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) {
return {valid: false, error_number: 2, error: errors[2]};
}
/* 3rd criterion: half move counter is an integer >= 0? */
if (isNaN(tokens[4]) || (parseInt(tokens[4], 10) < 0)) {
return {valid: false, error_number: 3, error: errors[3]};
}
/* 4th criterion: 4th field is a valid e.p.-string? */
if (!/^(-|[abcdefgh][36])$/.test(tokens[3])) {
return {valid: false, error_number: 4, error: errors[4]};
}
/* 5th criterion: 3th field is a valid castle-string? */
if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) {
return {valid: false, error_number: 5, error: errors[5]};
}
/* 6th criterion: 2nd field is "w" (white) or "b" (black)? */
if (!/^(w|b)$/.test(tokens[1])) {
return {valid: false, error_number: 6, error: errors[6]};
}
/* 7th criterion: 1st field contains 8 rows? */
var rows = tokens[0].split('/');
if (rows.length !== 8) {
return {valid: false, error_number: 7, error: errors[7]};
}
/* 8th criterion: every row is valid? */
for (var i = 0; i < rows.length; i++) {
/* check for right sum of fields AND not two numbers in succession */
var sum_fields = 0;
var previous_was_number = false;
for (var k = 0; k < rows[i].length; k++) {
if (!isNaN(rows[i][k])) {
if (previous_was_number) {
return {valid: false, error_number: 8, error: errors[8]};
}
sum_fields += parseInt(rows[i][k], 10);
previous_was_number = true;
} else {
if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
return {valid: false, error_number: 9, error: errors[9]};
}
sum_fields += 1;
previous_was_number = false;
}
}
if (sum_fields !== 8) {
return {valid: false, error_number: 10, error: errors[10]};
}
}
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]};
}
function generate_fen() {
var empty = 0;
var fen = '';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
if (board[i] == null) {
empty++;
} else {
if (empty > 0) {
fen += empty;
empty = 0;
}
var color = board[i].color;
var piece = board[i].type;
fen += (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
}
if ((i + 1) & 0x88) {
if (empty > 0) {
fen += empty;
}
if (i !== SQUARES.h1) {
fen += '/';
}
empty = 0;
i += 8;
}
}
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'; }
/* do we have an empty castling flag? */
cflags = cflags || '-';
var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square);
return [fen, turn, cflags, epflags, half_moves, move_number].join(' ');
}
function set_header(args) {
for (var i = 0; i < args.length; i += 2) {
if (typeof args[i] === 'string' &&
typeof args[i + 1] === 'string') {
header[args[i]] = args[i + 1];
}
}
return header;
}
/* called when the initial board setup is changed with put() or remove().
* modifies the SetUp and FEN properties of the header object. if the FEN is
* equal to the default position, the SetUp and FEN are deleted
* the setup is only updated if history.length is zero, ie moves haven't been
* made.
*/
function update_setup(fen) {
if (history.length > 0) return;
if (fen !== DEFAULT_POSITION) {
header['SetUp'] = '1';
header['FEN'] = fen;
} else {
delete header['SetUp'];
delete header['FEN'];
}
}
function get(square) {
var piece = board[SQUARES[square]];
return (piece) ? {type: piece.type, color: piece.color} : null;
}
function put(piece, square) {
/* check for valid piece object */
if (!('type' in piece && 'color' in piece)) {
return false;
}
/* check for piece */
if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) {
return false;
}
/* check for valid square */
if (!(square in SQUARES)) {
return false;
}
var sq = SQUARES[square];
/* don't let the user place more than one king */
if (piece.type == KING &&
!(kings[piece.color] == EMPTY || kings[piece.color] == sq)) {
return false;
}
board[sq] = {type: piece.type, color: piece.color};
if (piece.type === KING) {
kings[piece.color] = sq;
}
update_setup(generate_fen());
return true;
}
function remove(square) {
var piece = get(square);
board[SQUARES[square]] = null;
if (piece && piece.type === KING) {
kings[piece.color] = EMPTY;
}
update_setup(generate_fen());
return piece;
}
function build_move(board, from, to, flags, promotion) {
var move = {
color: turn,
from: from,
to: to,
flags: flags,
piece: board[from].type
};
if (promotion) {
move.flags |= BITS.PROMOTION;
move.promotion = promotion;
}
if (board[to]) {
move.captured = board[to].type;
} else if (flags & BITS.EP_CAPTURE) {
move.captured = PAWN;
}
return move;
}
function generate_moves(options) {
function add_move(board, moves, from, to, flags) {
/* if pawn promotion */
if (board[from].type === PAWN &&
(rank(to) === RANK_8 || rank(to) === RANK_1)) {
var pieces = [QUEEN, ROOK, BISHOP, KNIGHT];
for (var i = 0, len = pieces.length; i < len; i++) {
moves.push(build_move(board, from, to, flags, pieces[i]));
}
} else {
moves.push(build_move(board, from, to, flags));
}
}
var moves = [];
var us = turn;
var them = swap_color(us);
var second_rank = {b: RANK_7, w: RANK_2};
var first_sq = SQUARES.a8;
var last_sq = SQUARES.h1;
var single_square = false;
/* do we want legal moves? */
var legal = (typeof options !== 'undefined' && 'legal' in options) ?
options.legal : true;
/* are we generating moves for a single square? */
if (typeof options !== 'undefined' && 'square' in options) {
if (options.square in SQUARES) {
first_sq = last_sq = SQUARES[options.square];
single_square = true;
} else {
/* invalid square */
return [];
}
}
for (var i = first_sq; i <= last_sq; i++) {
/* did we run off the end of the board */
if (i & 0x88) { i += 7; continue; }
var piece = board[i];
if (piece == null || piece.color !== us) {
continue;
}
if (piece.type === PAWN) {
/* single square, non-capturing */
var square = i + PAWN_OFFSETS[us][0];
if (board[square] == null) {
add_move(board, moves, i, square, BITS.NORMAL);
}
/* pawn captures */
for (j = 2; j < 4; j++) {
var square = i + PAWN_OFFSETS[us][j];
if (square & 0x88) continue;
if (board[square] != null &&
board[square].color === them) {
add_move(board, moves, i, square, BITS.CAPTURE);
} else if (square === ep_square) {
add_move(board, moves, i, ep_square, BITS.EP_CAPTURE);
}
}
} else {
for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) {
var offset = PIECE_OFFSETS[piece.type][j];
var square = i;
while (true) {
square += offset;
if (square & 0x88) break;
if (board[square] == null) {
add_move(board, moves, i, square, BITS.NORMAL);
} else {
if (board[square].color === us) break;
add_move(board, moves, i, square, BITS.CAPTURE);
break;
}
/* break, if knight or king */
if (piece.type === 'n' || piece.type === 'k') break;
}
}
}
}
/* check for castling if: a) we're generating all moves, or b) we're doing
* single square move generation on the king's square
*/
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);
}
}
/* 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);
}
}
}
/* return all pseudo-legal moves (this includes moves that allow the king
* to be captured)
*/
if (!legal) {
return moves;
}
/* filter out illegal moves */
var legal_moves = [];
for (var i = 0, len = moves.length; i < len; i++) {
make_move(moves[i]);
if (!king_attacked(us)) {
legal_moves.push(moves[i]);
}
undo_move();
}
return legal_moves;
}
/* 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, sloppy) {
var output = '';
if (move.flags & BITS.KSIDE_CASTLE) {
output = 'O-O';
} else if (move.flags & BITS.QSIDE_CASTLE) {
output = 'O-O-O';
} else {
var disambiguator = get_disambiguator(move, sloppy);
if (move.piece !== PAWN) {
output += move.piece.toUpperCase() + disambiguator;
}
if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
if (move.piece === PAWN) {
output += algebraic(move.from)[0];
}
output += 'x';
}
output += algebraic(move.to);
if (move.flags & BITS.PROMOTION) {
output += '=' + move.promotion.toUpperCase();
}
}
make_move(move);
if (in_check()) {
if (in_checkmate()) {
output += '#';
} else {
output += '+';
}
}
undo_move();
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; }
/* if empty square or wrong color */
if (board[i] == null || board[i].color !== color) continue;
var piece = board[i];
var difference = i - square;
var index = difference + 119;
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;
}
continue;
}
/* 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;
}
if (!blocked) return true;
}
}
return false;
}
function king_attacked(color) {
return attacked(swap_color(color), kings[color]);
}
function in_check() {
return king_attacked(turn);
}
function in_checkmate() {
return in_check() && generate_moves().length === 0;
}
function in_stalemate() {
return !in_check() && generate_moves().length === 0;
}
function insufficient_material() {
var pieces = {};
var bishops = [];
var num_pieces = 0;
var sq_color = 0;
for (var i = SQUARES.a8; i<= SQUARES.h1; i++) {
sq_color = (sq_color + 1) % 2;
if (i & 0x88) { i += 7; continue; }
var piece = board[i];
if (piece) {
pieces[piece.type] = (piece.type in pieces) ?
pieces[piece.type] + 1 : 1;
if (piece.type === BISHOP) {
bishops.push(sq_color);
}
num_pieces++;
}
}
/* k vs. k */
if (num_pieces === 2) { return true; }
/* k vs. kn .... or .... k vs. kb */
else if (num_pieces === 3 && (pieces[BISHOP] === 1 ||
pieces[KNIGHT] === 1)) { return true; }
/* kb vs. kb where any number of bishops are all on the same color */
else if (num_pieces === pieces[BISHOP] + 2) {
var sum = 0;
var len = bishops.length;
for (var i = 0; i < len; i++) {
sum += bishops[i];
}
if (sum === 0 || sum === len) { return true; }
}
return false;
}
function in_threefold_repetition() {
/* TODO: while this function is fine for casual use, a better
* implementation would use a Zobrist key (instead of FEN). the
* Zobrist key would be maintained in the make_move/undo_move functions,
* avoiding the costly that we do below.
*/
var moves = [];
var positions = {};
var repetition = false;
while (true) {
var move = undo_move();
if (!move) break;
moves.push(move);
}
while (true) {
/* remove the last two fields in the FEN string, they're not needed
* when checking for draw by rep */
var fen = generate_fen().split(' ').slice(0,4).join(' ');
/* has the position occurred three or move times */
positions[fen] = (fen in positions) ? positions[fen] + 1 : 1;
if (positions[fen] >= 3) {
repetition = true;
}
if (!moves.length) {
break;
}
make_move(moves.pop());
}
return repetition;
}
function push(move) {
history.push({
move: move,
kings: {b: kings.b, w: kings.w},
turn: turn,
castling: {b: castling.b, w: castling.w},
ep_square: ep_square,
half_moves: half_moves,
move_number: move_number
});
}
function make_move(move) {
var us = turn;
var them = swap_color(us);
push(move);
board[move.to] = board[move.from];
board[move.from] = null;
/* if ep capture, remove the captured pawn */
if (move.flags & BITS.EP_CAPTURE) {
if (turn === BLACK) {
board[move.to - 16] = null;
} else {
board[move.to + 16] = null;
}
}
/* if pawn promotion, replace with new piece */
if (move.flags & BITS.PROMOTION) {
board[move.to] = {type: move.promotion, color: us};
}
/* if we moved the king */
if (board[move.to].type === KING) {
kings[board[move.to].color] = move.to;
/* 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;
} 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;
}
/* turn off castling */
castling[us] = '';
}
/* 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;
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;
break;
}
}
}
/* if big pawn move, update the en passant square */
if (move.flags & BITS.BIG_PAWN) {
if (turn === 'b') {
ep_square = move.to - 16;
} else {
ep_square = move.to + 16;
}
} else {
ep_square = EMPTY;
}
/* reset the 50 move counter if a pawn is moved or a piece is captured */
if (move.piece === PAWN) {
half_moves = 0;
} else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
half_moves = 0;
} else {
half_moves++;
}
if (turn === BLACK) {
move_number++;
}
turn = swap_color(turn);
}
function undo_move() {
var old = history.pop();
if (old == null) { return null; }
var move = old.move;
kings = old.kings;
turn = old.turn;
castling = old.castling;
ep_square = old.ep_square;
half_moves = old.half_moves;
move_number = old.move_number;
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.flags & BITS.CAPTURE) {
board[move.to] = {type: move.captured, color: them};
} else if (move.flags & BITS.EP_CAPTURE) {
var index;
if (us === BLACK) {
index = move.to - 16;
} else {
index = move.to + 16;
}
board[index] = {type: PAWN, 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_from = move.to - 1;
} else if (move.flags & BITS.QSIDE_CASTLE) {
castling_to = move.to - 2;
castling_from = move.to + 1;
}
board[castling_to] = board[castling_from];
board[castling_from] = null;
}
return move;
}
/* this function is used to uniquely identify ambiguous moves */
function get_disambiguator(move, sloppy) {
var moves = generate_moves({legal: !sloppy});
var from = move.from;
var to = move.to;
var piece = move.piece;
var ambiguities = 0;
var same_rank = 0;
var same_file = 0;
for (var i = 0, len = moves.length; i < len; i++) {
var ambig_from = moves[i].from;
var ambig_to = moves[i].to;
var ambig_piece = moves[i].piece;
/* if a move of the same piece type ends on the same to square, we'll
* need to add a disambiguator to the algebraic notation
*/
if (piece === ambig_piece && from !== ambig_from && to === ambig_to) {
ambiguities++;
if (rank(from) === rank(ambig_from)) {
same_rank++;
}
if (file(from) === file(ambig_from)) {
same_file++;
}
}
}
if (ambiguities > 0) {
/* if there exists a similar moving piece on the same rank and file as
* the move in question, use the square as the disambiguator
*/
if (same_rank > 0 && same_file > 0) {
return algebraic(from);
}
/* if the moving piece rests on the same file, use the rank symbol as the
* disambiguator
*/
else if (same_file > 0) {
return algebraic(from).charAt(1);
}
/* else use the file symbol */
else {
return algebraic(from).charAt(0);
}
}
return '';
}
function ascii() {
var s = ' +------------------------+\n';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
/* display the rank */
if (file(i) === 0) {
s += ' ' + '87654321'[rank(i)] + ' |';
}
/* empty piece */
if (board[i] == null) {
s += ' . ';
} else {
var piece = board[i].type;
var color = board[i].color;
var symbol = (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
s += ' ' + symbol + ' ';
}
if ((i + 1) & 0x88) {
s += '|\n';
i += 8;
}
}
s += ' +------------------------+\n';
s += ' a b c d e f g h\n';
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];
}
}
var 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
****************************************************************************/
function rank(i) {
return i >> 4;
}
function file(i) {
return i & 15;
}
function algebraic(i){
var f = file(i), r = rank(i);
return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
}
function swap_color(c) {
return c === WHITE ? BLACK : WHITE;
}
function is_digit(c) {
return '0123456789'.indexOf(c) !== -1;
}
/* pretty = external move object */
function make_pretty(ugly_move) {
var move = clone(ugly_move);
move.san = move_to_san(move, false);
move.to = algebraic(move.to);
move.from = algebraic(move.from);
var flags = '';
for (var flag in BITS) {
if (BITS[flag] & move.flags) {
flags += FLAGS[flag];
}
}
move.flags = flags;
return move;
}
function clone(obj) {
var dupe = (obj instanceof Array) ? [] : {};
for (var property in obj) {
if (typeof property === 'object') {
dupe[property] = clone(obj[property]);
} else {
dupe[property] = obj[property];
}
}
return dupe;
}
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
/*****************************************************************************
* DEBUGGING UTILITIES
****************************************************************************/
function perft(depth) {
var moves = generate_moves({legal: false});
var nodes = 0;
var color = turn;
for (var i = 0, len = moves.length; i < len; i++) {
make_move(moves[i]);
if (!king_attacked(color)) {
if (depth - 1 > 0) {
var child_nodes = perft(depth - 1);
nodes += child_nodes;
} else {
nodes++;
}
}
undo_move();
}
return nodes;
}
return {
/***************************************************************************
* PUBLIC CONSTANTS (is there a better way to do this?)
**************************************************************************/
WHITE: WHITE,
BLACK: BLACK,
PAWN: PAWN,
KNIGHT: KNIGHT,
BISHOP: BISHOP,
ROOK: ROOK,
QUEEN: QUEEN,
KING: KING,
SQUARES: (function() {
/* from the ECMA-262 spec (section 12.6.4):
* "The mechanics of enumerating the properties ... is
* implementation dependent"
* so: for (var sq in SQUARES) { keys.push(sq); } might not be
* ordered correctly
*/
var keys = [];
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
if (i & 0x88) { i += 7; continue; }
keys.push(algebraic(i));
}
return keys;
})(),
FLAGS: FLAGS,
/***************************************************************************
* PUBLIC API
**************************************************************************/
load: function(fen) {
return load(fen);
},
reset: function() {
return reset();
},
moves: function(options) {
/* The internal representation of a chess move is in 0x88 format, and
* not meant to be human-readable. The code below converts the 0x88
* square coordinates to algebraic coordinates. It also prunes an
* unnecessary move keys resulting from a verbose call.
*/
var ugly_moves = generate_moves(options);
var moves = [];
for (var i = 0, len = ugly_moves.length; i < len; i++) {
/* does the user want a full move object (most likely not), or just
* SAN
*/
if (typeof options !== 'undefined' && 'verbose' in options &&
options.verbose) {
moves.push(make_pretty(ugly_moves[i]));
} else {
moves.push(move_to_san(ugly_moves[i], false));
}
}
return moves;
},
ugly_moves: function(options) {
var ugly_moves = generate_moves(options);
return ugly_moves;
},
in_check: function() {
return in_check();
},
in_checkmate: function() {
return in_checkmate();
},
in_stalemate: function() {
return in_stalemate();
},
in_draw: function() {
return half_moves >= 100 ||
in_stalemate() ||
insufficient_material() ||
in_threefold_repetition();
},
insufficient_material: function() {
return insufficient_material();
},
in_threefold_repetition: function() {
return in_threefold_repetition();
},
game_over: function() {
return half_moves >= 100 ||
in_checkmate() ||
in_stalemate() ||
insufficient_material() ||
in_threefold_repetition();
},
validate_fen: function(fen) {
return validate_fen(fen);
},
fen: function() {
return generate_fen();
},
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']))) {
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, options) {
/* The move function can be called with in the following parameters:
*
* .move('Nxb7') <- where 'move' is a case-sensitive SAN string
*
* .move({ from: 'h7', <- where the 'move' is a move object (additional
* to :'h8', fields are ignored)
* 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;
if (typeof move === 'string') {
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) &&
move.to === algebraic(moves[i].to) &&
(!('promotion' in moves[i]) ||
move.promotion === moves[i].promotion)) {
move_obj = moves[i];
break;
}
}
}
/* failed to find move */
if (!move_obj) {
return null;
}
/* need to make a copy of move because we can't generate SAN after the
* move is made
*/
var pretty_move = make_pretty(move_obj);
make_move(move_obj);
return pretty_move;
},
ugly_move: function(move_obj, options) {
var pretty_move = make_pretty(move_obj);
make_move(move_obj);
return pretty_move;
},
undo: function() {
var move = undo_move();
return (move) ? make_pretty(move) : null;
},
clear: function() {
return clear();
},
put: function(piece, square) {
return put(piece, square);
},
get: function(square) {
return get(square);
},
remove: function(square) {
return remove(square);
},
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';
}
return null;
},
history: function(options) {
var reversed_history = [];
var move_history = [];
var verbose = (typeof options !== 'undefined' && 'verbose' in options &&
options.verbose);
while (history.length > 0) {
reversed_history.push(undo_move());
}
while (reversed_history.length > 0) {
var move = reversed_history.pop();
if (verbose) {
move_history.push(make_pretty(move));
} else {
move_history.push(move_to_san(move));
}
make_move(move);
}
return move_history;
}
};
};
/* export Chess object if using node or any other CommonJS compatible
* environment */
if (typeof exports !== 'undefined') exports.Chess = Chess;
/* export Chess object for any RequireJS compatible environment */
if (typeof define !== 'undefined') define( function () { return Chess; });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment