Comparing version 0.1.0 to 0.9.0
436
chess.js
@@ -0,3 +1,4 @@ | ||
'use strict'; | ||
/* | ||
* Copyright (c) 2011, Jeff Hlywa (jhlywa@gmail.com) | ||
* Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com) | ||
* All rights reserved. | ||
@@ -28,4 +29,13 @@ * | ||
/* minified license below */ | ||
/*! Copyright (c) 2015, Jeff Hlywa (jhlywa@gmail.com) | ||
* Released under the BSD license | ||
* https://github.com/jhlywa/chess.js/blob/master/LICENSE | ||
*/ | ||
var Chess = function(fen) { | ||
/* jshint indent: false */ | ||
var BLACK = 'b'; | ||
@@ -160,3 +170,3 @@ var WHITE = 'w'; | ||
*/ | ||
if (typeof fen == 'undefined') { | ||
if (typeof fen === 'undefined') { | ||
load(DEFAULT_POSITION); | ||
@@ -171,3 +181,3 @@ } else { | ||
turn = WHITE; | ||
castling = {w: '', b: ''}; | ||
castling = {w: 0, b: 0}; | ||
ep_square = EMPTY; | ||
@@ -200,3 +210,3 @@ half_moves = 0; | ||
if (piece == '/') { | ||
if (piece === '/') { | ||
square += 8; | ||
@@ -227,3 +237,3 @@ } else if (is_digit(piece)) { | ||
ep_square = (tokens[3] == '-') ? EMPTY : SQUARES[tokens[3]]; | ||
ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]]; | ||
half_moves = parseInt(tokens[4], 10); | ||
@@ -254,3 +264,3 @@ move_number = parseInt(tokens[5], 10); | ||
var tokens = fen.split(/\s+/); | ||
if (tokens.length != 6) { | ||
if (tokens.length !== 6) { | ||
return {valid: false, error_number: 1, error: errors[1]}; | ||
@@ -285,4 +295,4 @@ } | ||
/* 7th criterion: 1st field contains 8 rows? */ | ||
var rows = tokens[0].split("/"); | ||
if (rows.length != 8) { | ||
var rows = tokens[0].split('/'); | ||
if (rows.length !== 8) { | ||
return {valid: false, error_number: 7, error: errors[7]}; | ||
@@ -302,3 +312,3 @@ } | ||
} | ||
sum_fields += parseInt(rows[i][k]); | ||
sum_fields += parseInt(rows[i][k], 10); | ||
previous_was_number = true; | ||
@@ -313,7 +323,7 @@ } else { | ||
} | ||
if (sum_fields != 8) { | ||
if (sum_fields !== 8) { | ||
return {valid: false, error_number: 10, error: errors[10]}; | ||
} | ||
} | ||
/* everything's okay! */ | ||
@@ -338,3 +348,3 @@ return {valid: true, error_number: 0, error: errors[0]}; | ||
fen += (color == WHITE) ? | ||
fen += (color === WHITE) ? | ||
piece.toUpperCase() : piece.toLowerCase(); | ||
@@ -348,3 +358,3 @@ } | ||
if (i != SQUARES.h1) { | ||
if (i !== SQUARES.h1) { | ||
fen += '/'; | ||
@@ -366,5 +376,5 @@ } | ||
cflags = cflags || '-'; | ||
var epflags = (ep_square == EMPTY) ? '-' : algebraic(ep_square); | ||
var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square); | ||
return [fen, turn, cflags, epflags, half_moves, move_number].join(' ') | ||
return [fen, turn, cflags, epflags, half_moves, move_number].join(' '); | ||
} | ||
@@ -374,4 +384,4 @@ | ||
for (var i = 0; i < args.length; i += 2) { | ||
if (typeof args[i] == "string" && | ||
typeof args[i + 1] == "string") { | ||
if (typeof args[i] === 'string' && | ||
typeof args[i + 1] === 'string') { | ||
header[args[i]] = args[i + 1]; | ||
@@ -392,8 +402,8 @@ } | ||
if (fen != DEFAULT_POSITION) { | ||
header["SetUp"] = fen; | ||
header["FEN"] = '1'; | ||
if (fen !== DEFAULT_POSITION) { | ||
header['SetUp'] = '1'; | ||
header['FEN'] = fen; | ||
} else { | ||
delete header["SetUp"]; | ||
delete header["FEN"]; | ||
delete header['SetUp']; | ||
delete header['FEN']; | ||
} | ||
@@ -414,3 +424,3 @@ } | ||
/* check for piece */ | ||
if (SYMBOLS.indexOf(piece.type.toLowerCase()) == -1) { | ||
if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) { | ||
return false; | ||
@@ -425,4 +435,11 @@ } | ||
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) { | ||
if (piece.type === KING) { | ||
kings[piece.color] = sq; | ||
@@ -439,3 +456,3 @@ } | ||
board[SQUARES[square]] = null; | ||
if (piece && piece.type == KING) { | ||
if (piece && piece.type === KING) { | ||
kings[piece.color] = EMPTY; | ||
@@ -449,39 +466,35 @@ } | ||
function generate_moves(settings) { | ||
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)) { | ||
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++) { | ||
var promotion = { | ||
color: turn, | ||
from: from, | ||
to: to, | ||
flags: flags | BITS.PROMOTION, | ||
promotion: pieces[i], | ||
piece: board[from].type | ||
}; | ||
/* add the captured piece */ | ||
if (board[to]) { | ||
promotion.captured = board[to].type; | ||
} | ||
moves.push(promotion); | ||
moves.push(build_move(board, from, to, flags, pieces[i])); | ||
} | ||
} else { | ||
var move = { | ||
color: turn, | ||
from: from, | ||
to: to, | ||
flags: flags, | ||
piece: board[from].type | ||
}; | ||
if (board[to]) { | ||
move.captured = board[to].type; | ||
} | ||
moves.push(move); | ||
moves.push(build_move(board, from, to, flags)); | ||
} | ||
@@ -495,3 +508,22 @@ } | ||
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { | ||
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 */ | ||
@@ -501,7 +533,7 @@ if (i & 0x88) { i += 7; continue; } | ||
var piece = board[i]; | ||
if (piece == null || piece.color != us) { | ||
if (piece == null || piece.color !== us) { | ||
continue; | ||
} | ||
if (piece.type == PAWN) { | ||
if (piece.type === PAWN) { | ||
/* single square, non-capturing */ | ||
@@ -514,3 +546,3 @@ var square = i + PAWN_OFFSETS[us][0]; | ||
var square = i + PAWN_OFFSETS[us][1]; | ||
if (second_rank[us] == rank(i) && board[square] == null) { | ||
if (second_rank[us] === rank(i) && board[square] == null) { | ||
add_move(board, moves, i, square, BITS.BIG_PAWN); | ||
@@ -526,5 +558,5 @@ } | ||
if (board[square] != null && | ||
board[square].color == them) { | ||
board[square].color === them) { | ||
add_move(board, moves, i, square, BITS.CAPTURE); | ||
} else if (square == ep_square) { | ||
} else if (square === ep_square) { | ||
add_move(board, moves, i, ep_square, BITS.EP_CAPTURE); | ||
@@ -545,3 +577,3 @@ } | ||
} else { | ||
if (board[square].color == us) break; | ||
if (board[square].color === us) break; | ||
add_move(board, moves, i, square, BITS.CAPTURE); | ||
@@ -552,3 +584,3 @@ break; | ||
/* break, if knight or king */ | ||
if (piece.type == 'n' || piece.type == 'k') break; | ||
if (piece.type === 'n' || piece.type === 'k') break; | ||
} | ||
@@ -559,42 +591,42 @@ } | ||
/* king-side castling */ | ||
if (castling[us] & BITS.KSIDE_CASTLE) { | ||
var castling_from = kings[us]; | ||
var castling_to = castling_from + 2; | ||
/* 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); | ||
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; | ||
/* 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); | ||
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); | ||
} | ||
} | ||
} | ||
/* if no parameters passed in, assume legal w/ algebraic moves */ | ||
if (typeof settings == 'undefined') { | ||
settings = {legal: true}; | ||
} | ||
/* return all pseudo-legal moves (this includes moves that allow the king | ||
* to be captured | ||
* to be captured) | ||
*/ | ||
if (settings.legal != null && settings.legal == false) { | ||
if (!legal) { | ||
return moves; | ||
@@ -616,3 +648,3 @@ } | ||
/* convert a move from 0x88 coordinates to Standard Algebraic Notation | ||
/* convert a move from 0x88 coordinates to Standard Algebraic Notation | ||
* (SAN) | ||
@@ -630,3 +662,3 @@ */ | ||
if (move.piece != PAWN) { | ||
if (move.piece !== PAWN) { | ||
output += move.piece.toUpperCase() + disambiguator; | ||
@@ -636,3 +668,3 @@ } | ||
if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) { | ||
if (move.piece == PAWN) { | ||
if (move.piece === PAWN) { | ||
output += algebraic(move.from)[0]; | ||
@@ -669,3 +701,3 @@ } | ||
/* if empty square or wrong color */ | ||
if (board[i] == null || board[i].color != color) continue; | ||
if (board[i] == null || board[i].color !== color) continue; | ||
@@ -677,7 +709,7 @@ var piece = board[i]; | ||
if (ATTACKS[index] & (1 << SHIFTS[piece.type])) { | ||
if (piece.type == PAWN) { | ||
if (piece.type === PAWN) { | ||
if (difference > 0) { | ||
if (piece.color == WHITE) return true; | ||
if (piece.color === WHITE) return true; | ||
} else { | ||
if (piece.color == BLACK) return true; | ||
if (piece.color === BLACK) return true; | ||
} | ||
@@ -688,3 +720,3 @@ continue; | ||
/* if the piece is a knight or a king */ | ||
if (piece.type == 'n' || piece.type == 'k') return true; | ||
if (piece.type === 'n' || piece.type === 'k') return true; | ||
@@ -695,3 +727,3 @@ var offset = RAYS[index]; | ||
var blocked = false; | ||
while (j != square) { | ||
while (j !== square) { | ||
if (board[j] != null) { blocked = true; break; } | ||
@@ -717,7 +749,7 @@ j += offset; | ||
function in_checkmate() { | ||
return in_check() && generate_moves().length == 0 | ||
return in_check() && generate_moves().length === 0; | ||
} | ||
function in_stalemate() { | ||
return !in_check() && generate_moves().length == 0 | ||
return !in_check() && generate_moves().length === 0; | ||
} | ||
@@ -727,5 +759,8 @@ | ||
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; } | ||
@@ -735,4 +770,7 @@ | ||
if (piece) { | ||
pieces[piece.type] = (piece.type in pieces) ? | ||
pieces[piece.type] = (piece.type in pieces) ? | ||
pieces[piece.type] + 1 : 1; | ||
if (piece.type === BISHOP) { | ||
bishops.push(sq_color); | ||
} | ||
num_pieces++; | ||
@@ -743,9 +781,17 @@ } | ||
/* k vs. k */ | ||
if (num_pieces == 2) { return true; } | ||
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; } | ||
else if (num_pieces === 3 && (pieces[BISHOP] === 1 || | ||
pieces[KNIGHT] === 1)) { return true; } | ||
/* TODO: kb vs. kb where both bishops are on the same color */ | ||
/* 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; } | ||
} | ||
@@ -813,3 +859,3 @@ return false; | ||
if (move.flags & BITS.EP_CAPTURE) { | ||
if (turn == BLACK) { | ||
if (turn === BLACK) { | ||
board[move.to - 16] = null; | ||
@@ -827,3 +873,3 @@ } else { | ||
/* if we moved the king */ | ||
if (board[move.to].type == KING) { | ||
if (board[move.to].type === KING) { | ||
kings[board[move.to].color] = move.to; | ||
@@ -845,12 +891,11 @@ | ||
/* turn off castling */ | ||
castling[turn] = ''; | ||
castling[us] = ''; | ||
} | ||
/* turn off castling if we move a rook */ | ||
if (castling[turn] != '') { | ||
for (var i = 0, len = ROOKS[turn].length; i < len; i++) { | ||
if (move.from == ROOKS[turn][i].square) { | ||
castling[turn] = | ||
castling[turn] ^= ROOKS[turn][i].flag; | ||
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; | ||
@@ -862,7 +907,7 @@ } | ||
/* turn off castling if we capture a rook */ | ||
if (castling[them] != '') { | ||
if (castling[them]) { | ||
for (var i = 0, len = ROOKS[them].length; i < len; i++) { | ||
if (move.to == ROOKS[them][i].square) { | ||
castling[them] = | ||
castling[them] ^= ROOKS[them][i].flag; | ||
if (move.to === ROOKS[them][i].square && | ||
castling[them] & ROOKS[them][i].flag) { | ||
castling[them] ^= ROOKS[them][i].flag; | ||
break; | ||
@@ -875,3 +920,3 @@ } | ||
if (move.flags & BITS.BIG_PAWN) { | ||
if (turn == 'b') { | ||
if (turn === 'b') { | ||
ep_square = move.to - 16; | ||
@@ -886,3 +931,3 @@ } else { | ||
/* reset the 50 move counter if a pawn is moved or a piece is captured */ | ||
if (move.piece == PAWN) { | ||
if (move.piece === PAWN) { | ||
half_moves = 0; | ||
@@ -895,3 +940,3 @@ } else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) { | ||
if (turn == BLACK) { | ||
if (turn === BLACK) { | ||
move_number++; | ||
@@ -918,3 +963,3 @@ } | ||
board[move.from] = board[move.to]; | ||
board[move.from].type = move.piece // to undo any promotions | ||
board[move.from].type = move.piece; // to undo any promotions | ||
board[move.to] = null; | ||
@@ -926,3 +971,3 @@ | ||
var index; | ||
if (us == BLACK) { | ||
if (us === BLACK) { | ||
index = move.to - 16; | ||
@@ -973,10 +1018,10 @@ } else { | ||
*/ | ||
if (piece == ambig_piece && from != ambig_from && to == ambig_to) { | ||
if (piece === ambig_piece && from !== ambig_from && to === ambig_to) { | ||
ambiguities++; | ||
if (rank(from) == rank(ambig_from)) { | ||
if (rank(from) === rank(ambig_from)) { | ||
same_rank++; | ||
} | ||
if (file(from) == file(ambig_from)) { | ||
if (file(from) === file(ambig_from)) { | ||
same_file++; | ||
@@ -1013,3 +1058,3 @@ } | ||
/* display the rank */ | ||
if (file(i) == 0) { | ||
if (file(i) === 0) { | ||
s += ' ' + '87654321'[rank(i)] + ' |'; | ||
@@ -1024,3 +1069,3 @@ } | ||
var color = board[i].color; | ||
var symbol = (color == WHITE) ? | ||
var symbol = (color === WHITE) ? | ||
piece.toUpperCase() : piece.toLowerCase(); | ||
@@ -1058,7 +1103,7 @@ s += ' ' + symbol + ' '; | ||
function swap_color(c) { | ||
return c == WHITE ? BLACK : WHITE; | ||
return c === WHITE ? BLACK : WHITE; | ||
} | ||
function is_digit(c) { | ||
return '0123456789'.indexOf(c) != -1 | ||
return '0123456789'.indexOf(c) !== -1; | ||
} | ||
@@ -1089,3 +1134,3 @@ | ||
for (var property in obj) { | ||
if (typeof property == 'object') { | ||
if (typeof property === 'object') { | ||
dupe[property] = clone(obj[property]); | ||
@@ -1108,3 +1153,3 @@ } else { | ||
function perft(depth) { | ||
var moves = generate_moves({legal: false}) | ||
var moves = generate_moves({legal: false}); | ||
var nodes = 0; | ||
@@ -1175,3 +1220,3 @@ var color = turn; | ||
var ugly_moves = generate_moves(); | ||
var ugly_moves = generate_moves(options); | ||
var moves = []; | ||
@@ -1181,6 +1226,6 @@ | ||
/* does the user want a full move object (most likely not), or just | ||
/* does the user want a full move object (most likely not), or just | ||
* SAN | ||
*/ | ||
if (typeof options != 'undefined' && 'verbose' in options && | ||
if (typeof options !== 'undefined' && 'verbose' in options && | ||
options.verbose) { | ||
@@ -1243,7 +1288,7 @@ moves.push(make_pretty(ugly_moves[i])); | ||
*/ | ||
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") ? | ||
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; | ||
@@ -1256,5 +1301,5 @@ var result = []; | ||
/* TODO: order of enumerated properties in header object is not | ||
* guaranteed, see ECMA-262 spec (section 12.6.4) | ||
* guaranteed, see ECMA-262 spec (section 12.6.4) | ||
*/ | ||
result.push("[" + i + " \"" + header[i] + "\"]" + newline); | ||
result.push('[' + i + ' \"' + header[i] + '\"]' + newline); | ||
header_exists = true; | ||
@@ -1274,3 +1319,3 @@ } | ||
var moves = []; | ||
var move_string = ""; | ||
var move_string = ''; | ||
var pgn_move_number = 1; | ||
@@ -1283,6 +1328,6 @@ | ||
/* if the position started with black to move, start PGN with 1. ... */ | ||
if (pgn_move_number == 1 && move.color == 'b') { | ||
if (pgn_move_number === 1 && move.color === 'b') { | ||
move_string = '1. ...'; | ||
pgn_move_number++; | ||
} else if (move.color == 'w') { | ||
} else if (move.color === 'w') { | ||
/* store the previous generated move_string if we have one */ | ||
@@ -1294,5 +1339,5 @@ if (move_string.length) { | ||
pgn_move_number++; | ||
} | ||
} | ||
move_string = move_string + " " + move_to_san(move); | ||
move_string = move_string + ' ' + move_to_san(move); | ||
make_move(move); | ||
@@ -1307,3 +1352,3 @@ } | ||
/* is there a result? */ | ||
if (typeof header.Result != 'undefined') { | ||
if (typeof header.Result !== 'undefined') { | ||
moves.push(header.Result); | ||
@@ -1315,4 +1360,4 @@ } | ||
*/ | ||
if (max_width == 0) { | ||
return result.join("") + moves.join(" "); | ||
if (max_width === 0) { | ||
return result.join('') + moves.join(' '); | ||
} | ||
@@ -1324,6 +1369,6 @@ | ||
/* if the current move will push past max_width */ | ||
if (current_width + moves[i].length > max_width && i != 0) { | ||
if (current_width + moves[i].length > max_width && i !== 0) { | ||
/* don't end the line with whitespace */ | ||
if (result[result.length - 1] == " ") { | ||
if (result[result.length - 1] === ' ') { | ||
result.pop(); | ||
@@ -1334,4 +1379,4 @@ } | ||
current_width = 0; | ||
} else if (i != 0) { | ||
result.push(" "); | ||
} else if (i !== 0) { | ||
result.push(' '); | ||
current_width++; | ||
@@ -1343,3 +1388,3 @@ } | ||
return result.join(""); | ||
return result.join(''); | ||
}, | ||
@@ -1349,3 +1394,3 @@ | ||
function mask(str) { | ||
return str.replace(/\n/g, '\\n'); | ||
return str.replace(/\\/g, '\\'); | ||
} | ||
@@ -1357,5 +1402,8 @@ | ||
function move_from_san(move) { | ||
/* strip off any move decorations: e.g Nf3+?! */ | ||
var moveReplaced = move.replace(/[+#?!=]/,''); | ||
var moves = generate_moves(); | ||
for (var i = 0, len = moves.length; i < len; i++) { | ||
if (move == move_to_san(moves[i])) { | ||
if (moveReplaced == | ||
move_to_san(moves[i]).replace(/[+#?!=]/,'')) { | ||
return moves[i]; | ||
@@ -1378,9 +1426,9 @@ } | ||
} | ||
function parse_pgn_header(header, options) { | ||
var newline_char = (typeof options == 'object' && | ||
typeof options.newline_char == 'string') ? | ||
options.newline_char : '\n'; | ||
var newline_char = (typeof options === 'object' && | ||
typeof options.newline_char === 'string') ? | ||
options.newline_char : '\r?\n'; | ||
var header_obj = {}; | ||
var headers = header.split(newline_char); | ||
var headers = header.split(new RegExp(mask(newline_char))); | ||
var key = ''; | ||
@@ -1396,12 +1444,12 @@ var value = ''; | ||
} | ||
return header_obj; | ||
} | ||
var newline_char = (typeof options == 'object' && | ||
typeof options.newline_char == 'string') ? | ||
options.newline_char : '\n'; | ||
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'); | ||
'1.(' + mask(newline_char) + '|.)*$', 'g'); | ||
@@ -1412,8 +1460,8 @@ /* get header part of the PGN file */ | ||
/* no info part given, begins with moves */ | ||
if (header_string[0] != '[') { | ||
if (header_string[0] !== '[') { | ||
header_string = ''; | ||
} | ||
reset(); | ||
/* parse PGN header */ | ||
@@ -1424,3 +1472,3 @@ var headers = parse_pgn_header(header_string, options); | ||
} | ||
/* delete header to get the moves */ | ||
@@ -1431,3 +1479,3 @@ var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' '); | ||
ms = ms.replace(/(\{[^}]+\})+?/g, ''); | ||
/* delete move numbers */ | ||
@@ -1441,3 +1489,3 @@ ms = ms.replace(/\d+\./g, ''); | ||
/* delete empty entries */ | ||
moves = moves.join(",").replace(/,,+/g, ',').split(","); | ||
moves = moves.join(',').replace(/,,+/g, ',').split(','); | ||
var move = ''; | ||
@@ -1447,3 +1495,3 @@ | ||
move = get_move_obj(moves[half_move]); | ||
/* move not possible! (don't clear the board to examine to show the | ||
@@ -1458,7 +1506,7 @@ * latest valid position) | ||
} | ||
/* examine last move */ | ||
move = moves[moves.length - 1]; | ||
if (POSSIBLE_RESULTS.indexOf(move) > -1) { | ||
if (has_keys(header) && typeof header.Result == 'undefined') { | ||
if (has_keys(header) && typeof header.Result === 'undefined') { | ||
set_header(['Result', move]); | ||
@@ -1503,6 +1551,8 @@ } | ||
if (typeof move == 'string') { | ||
if (typeof move === 'string') { | ||
/* convert the move string to a move object */ | ||
/* strip off any move decorations: e.g Nf3+?! */ | ||
var moveReplaced = move.replace(/[+#?!=]/,''); | ||
for (var i = 0, len = moves.length; i < len; i++) { | ||
if (move == move_to_san(moves[i])) { | ||
if (moveReplaced === move_to_san(moves[i]).replace(/[+#?!=]/,'')) { | ||
move_obj = moves[i]; | ||
@@ -1512,9 +1562,9 @@ break; | ||
} | ||
} else if (typeof move == 'object') { | ||
} else if (typeof move === 'object') { | ||
/* 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) && | ||
if (move.from === algebraic(moves[i].from) && | ||
move.to === algebraic(moves[i].to) && | ||
(!('promotion' in moves[i]) || | ||
move.promotion == moves[i].promotion)) { | ||
move.promotion === moves[i].promotion)) { | ||
move_obj = moves[i]; | ||
@@ -1569,3 +1619,3 @@ break; | ||
var sq_0x88 = SQUARES[square]; | ||
return ((rank(sq_0x88) + file(sq_0x88)) % 2 == 0) ? 'light' : 'dark'; | ||
return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark'; | ||
} | ||
@@ -1579,3 +1629,3 @@ | ||
var move_history = []; | ||
var verbose = (typeof options != 'undefined' && 'verbose' in options && | ||
var verbose = (typeof options !== 'undefined' && 'verbose' in options && | ||
options.verbose); | ||
@@ -1600,7 +1650,9 @@ | ||
} | ||
} | ||
}; | ||
}; | ||
/* export Chess object if using node or any other CommonJS compatible | ||
* environment */ | ||
if (typeof exports != 'undefined') exports.Chess = Chess; | ||
if (typeof exports !== 'undefined') exports.Chess = Chess; | ||
/* export Chess object for any RequireJS compatible environment */ | ||
if (typeof define !== 'undefined') define( function () { return Chess; }); |
{ | ||
"name": "chess.js", | ||
"version": "0.1.0", | ||
"version": "0.9.0", | ||
"description": "A Javascript chess library for chess move generation/validation, piece placement/movement, and check/checkmate/draw detection", | ||
"author": "Jeff Hlywa <jhlywa@gmail.com> (https://github.com/jhlywa)", | ||
"contributors": [ | ||
"Steve Bragg (https://github.com/2sb18)", | ||
"E. Azer Koçulu (https://github.com/azer)", | ||
"Falco Nogatz (https://https://github.com/fnogatz)" | ||
], | ||
"keywords": ["chess"], | ||
"main": "chess.js", | ||
"repository" : { | ||
"type" : "git", | ||
"url" : "http://github.com/jhlywa/chess.js.git" | ||
}, | ||
"devDependencies": { | ||
"mocha": "*", | ||
"chai": "*" | ||
} | ||
} |
599
README.md
# chess.js | ||
chess.js is a Javascript chess library that is used for chess move generation/validation, | ||
piece placement/movement, and check/checkmate/stalemate detection - basically everything | ||
but the AI. | ||
chess.js is a Javascript chess library that is used for chess move | ||
generation/validation, piece placement/movement, and check/checkmate/stalemate | ||
detection - basically everything but the AI. | ||
Using chess.js in a browser is straight-forward: | ||
<script type="text/javascript" src="chess.js"></script> | ||
<script type="text/javascript"> | ||
chess.js has been extensively tested in node.js and most modern browsers. | ||
var chess = new Chess(); | ||
... | ||
</script> | ||
Using chess.js in node.js is equally easy: | ||
var ch = require('/chess.js') | ||
var chess = new ch.Chess(); | ||
... | ||
## Example Code | ||
The code below plays a complete game of chess ... randomly. | ||
var sys = require('sys'), | ||
ch = require('./chess'); | ||
```js | ||
var Chess = require('./chess').Chess; | ||
var chess = new Chess(); | ||
var chess = new ch.Chess(); | ||
while (!chess.game_over()) { | ||
var moves = chess.moves(); | ||
var move = moves[Math.floor(Math.random() * moves.length)]; | ||
chess.move(move); | ||
} | ||
console.log(chess.pgn()); | ||
``` | ||
while (!chess.game_over()) { | ||
sys.puts('position: ' + chess.fen()); | ||
var moves = chess.moves(); | ||
var move = moves[Math.floor(Math.random() * moves.length)]; | ||
chess.move(move); | ||
sys.puts('move: ' + move); | ||
} | ||
## Sites Using chess.js | ||
- [The Internet Chess Club (ICC)](http://www.chessclub.com/) | ||
- [lichess](http://lichess.org/tv) | ||
- [Redbull - Battle for the Queen](http://battleforthequeen.redbull.com/) | ||
- [3D Hartwig Chess](http://creativejs.com/2012/12/3d-hartwig-chess/) | ||
- [Scene VR](http://client.scenevr.com/?connect=chess.scenevr.hosting/chess.xml) | ||
- [Multiplayer Chess](http://chessapp.com/) | ||
- [Reti Chess](http://retichess.nodejitsu.com/) | ||
- [Chess Fork](http://www.chessfork.com/) | ||
- [Lozza](http://op12no2.me/posts/1641) | ||
- [angular-chess](http://theborakompanioni.github.io/angular-chess) | ||
Need a user interface? Try Chris Oakman's excellent | ||
[chessboard.js](http://chessboardjs.com) library. See | ||
[chessboard.js - Random vs Random](http://chessboardjs.com/examples#5002) for | ||
an example integration of chess.js with chessboard.js. | ||
## API | ||
@@ -46,7 +48,9 @@ | ||
// board defaults to the starting position when called with no parameters | ||
var chess = new Chess(); | ||
```js | ||
// board defaults to the starting position when called with no parameters | ||
var chess = new Chess(); | ||
// pass in a FEN string to load a particular position | ||
var chess = new Chess('r1k4r/p2nb1p1/2b4p/1p1n1p2/2PP4/3Q1NB1/1P3PPP/R5K1 b - c3 0 19'); | ||
// pass in a FEN string to load a particular position | ||
var chess = new Chess('r1k4r/p2nb1p1/2b4p/1p1n1p2/2PP4/3Q1NB1/1P3PPP/R5K1 b - c3 0 19'); | ||
``` | ||
@@ -56,21 +60,23 @@ ### .ascii() | ||
var chess = new Chess(); | ||
```js | ||
var chess = new Chess(); | ||
// make some moves | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
// make some moves | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
chess.ascii(); | ||
// -> ' +------------------------+ | ||
// 8 | r n b q k b n r | | ||
// 7 | p p p p . p p p | | ||
// 6 | . . . . . . . . | | ||
// 5 | . . . . p . . . | | ||
// 4 | . . . . P P . . | | ||
// 3 | . . . . . . . . | | ||
// 2 | P P P P . . P P | | ||
// 1 | R N B Q K B N R | | ||
// +------------------------+ | ||
// a b c d e f g h' | ||
chess.ascii(); | ||
// -> ' +------------------------+ | ||
// 8 | r n b q k b n r | | ||
// 7 | p p p p . p p p | | ||
// 6 | . . . . . . . . | | ||
// 5 | . . . . p . . . | | ||
// 4 | . . . . P P . . | | ||
// 3 | . . . . . . . . | | ||
// 2 | P P P P . . P P | | ||
// 1 | R N B Q K B N R | | ||
// +------------------------+ | ||
// a b c d e f g h' | ||
``` | ||
@@ -80,5 +86,7 @@ ### .clear() | ||
chess.clear(); | ||
chess.fen(); | ||
// -> '8/8/8/8/8/8/8/8 w - - 0 1' <- empty board | ||
```js | ||
chess.clear(); | ||
chess.fen(); | ||
// -> '8/8/8/8/8/8/8/8 w - - 0 1' <- empty board | ||
``` | ||
@@ -88,26 +96,30 @@ ### .fen() | ||
var chess = new Chess(); | ||
```js | ||
var chess = new Chess(); | ||
// make some moves | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
// make some moves | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq f3 0 2' | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq f3 0 2' | ||
``` | ||
### .game_over() | ||
Returns true or false if the game has ended via checkmate, stalemate, or draw. | ||
### .game_over() | ||
Returns true if the game has ended via checkmate, stalemate, draw, threefold repetition, or insufficient material. Otherwise, returns false. | ||
var chess = new Chess(); | ||
chess.game_over(); | ||
// -> false | ||
```js | ||
var chess = new Chess(); | ||
chess.game_over(); | ||
// -> false | ||
chess.load('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.game_over(); | ||
// -> true (stalemate) | ||
chess.load('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.game_over(); | ||
// -> true (stalemate) | ||
chess.load('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.game_over(); | ||
// -> true (checkmate) | ||
chess.load('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.game_over(); | ||
// -> true (checkmate) | ||
``` | ||
@@ -117,9 +129,11 @@ ### .get(square) | ||
chess.clear(); | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
```js | ||
chess.clear(); | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
chess.get('a5'); | ||
// -> { type: 'p', color: 'b' }, | ||
chess.get('a6'); | ||
// -> null | ||
chess.get('a5'); | ||
// -> { type: 'p', color: 'b' }, | ||
chess.get('a6'); | ||
// -> null | ||
``` | ||
@@ -131,64 +145,76 @@ ### .history([ options ]) | ||
var chess = new Chess(); | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
chess.move('exf4'); | ||
chess.history(); | ||
// -> ['e4', 'e5', 'f4', 'exf4'] | ||
```js | ||
var chess = new Chess(); | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('f4'); | ||
chess.move('exf4'); | ||
chess.history({ verbose: true }); | ||
// -> [{ color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e4' }, | ||
// { color: 'b', from: 'e7', to: 'e5', flags: 'b', piece: 'p', san: 'e5' }, | ||
// { color: 'w', from: 'f2', to: 'f4', flags: 'b', piece: 'p', san: 'f4' }, | ||
// { color: 'b', from: 'e5', to: 'f4', flags: 'c', piece: 'p', captured: 'p', san: 'exf4' }] | ||
chess.history(); | ||
// -> ['e4', 'e5', 'f4', 'exf4'] | ||
### .in_check() | ||
chess.history({ verbose: true }); | ||
// -> [{ color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e4' }, | ||
// { color: 'b', from: 'e7', to: 'e5', flags: 'b', piece: 'p', san: 'e5' }, | ||
// { color: 'w', from: 'f2', to: 'f4', flags: 'b', piece: 'p', san: 'f4' }, | ||
// { color: 'b', from: 'e5', to: 'f4', flags: 'c', piece: 'p', captured: 'p', san: 'exf4' }] | ||
``` | ||
### .in_check() | ||
Returns true or false if the side to move is in check. | ||
var chess = new Chess('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.in_check(); | ||
// -> true | ||
```js | ||
var chess = new Chess('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.in_check(); | ||
// -> true | ||
``` | ||
### .in_checkmate() | ||
### .in_checkmate() | ||
Returns true or false if the side to move has been checkmated. | ||
var chess = new Chess('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.in_checkmate(); | ||
// -> true | ||
```js | ||
var chess = new Chess('rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3'); | ||
chess.in_checkmate(); | ||
// -> true | ||
``` | ||
### .in_draw() | ||
### .in_draw() | ||
Returns true or false if the game is drawn (50-move rule or insufficient material). | ||
var chess = new Chess('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.in_draw(); | ||
// -> true | ||
```js | ||
var chess = new Chess('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.in_draw(); | ||
// -> true | ||
``` | ||
### .in_stalemate() | ||
### .in_stalemate() | ||
Returns true or false if the side to move has been stalemated. | ||
var chess = new Chess('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.in_stalemate(); | ||
// -> true | ||
```js | ||
var chess = new Chess('4k3/4P3/4K3/8/8/8/8/8 b - - 0 78'); | ||
chess.in_stalemate(); | ||
// -> true | ||
``` | ||
### .in_threefold_repetition() | ||
### .in_threefold_repetition() | ||
Returns true or false if the current board position has occurred three or more | ||
times. | ||
var chess = new Chess('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'); | ||
// -> true | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 1st time | ||
chess.in_threefold_repetition(); | ||
// -> false | ||
```js | ||
var chess = new Chess('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'); | ||
// -> true | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 1st time | ||
chess.in_threefold_repetition(); | ||
// -> false | ||
chess.move('Nf3'); chess.move('Nf6') chess.move('Ng1'); chess.move('Ng8'); | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 2nd time | ||
chess.in_threefold_repetition(); | ||
// -> false | ||
chess.move('Nf3'); chess.move('Nf6'); chess.move('Ng1'); chess.move('Ng8'); | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 2nd time | ||
chess.in_threefold_repetition(); | ||
// -> false | ||
chess.move('Nf3'); chess.move('Nf6') chess.move('Ng1'); chess.move('Ng8'); | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 3rd time | ||
chess.in_threefold_repetition(); | ||
// -> true | ||
chess.move('Nf3'); chess.move('Nf6'); chess.move('Ng1'); chess.move('Ng8'); | ||
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq occurs 3rd time | ||
chess.in_threefold_repetition(); | ||
// -> true | ||
``` | ||
@@ -199,16 +225,20 @@ ### .header() | ||
chess.header('White', 'Robert James Fischer'); | ||
chess.header('Black', 'Mikhail Tal'); | ||
```js | ||
chess.header('White', 'Robert James Fischer'); | ||
chess.header('Black', 'Mikhail Tal'); | ||
// or | ||
// or | ||
chess.header('White', 'Morphy', 'Black', 'Anderssen', 'Date', '1858-??-??'); | ||
chess.header('White', 'Morphy', 'Black', 'Anderssen', 'Date', '1858-??-??'); | ||
``` | ||
### .insufficient_material() | ||
### .insufficient_material() | ||
Returns true if the game is drawn due to insufficient material (K vs. K, | ||
K vs. KB, or K vs. KN); otherwise false. | ||
var chess = new Chess('k7/8/n7/8/8/8/8/7K b - - 0 1'); | ||
chess.insufficient_material() | ||
// -> true | ||
```js | ||
var chess = new Chess('k7/8/n7/8/8/8/8/7K b - - 0 1'); | ||
chess.insufficient_material() | ||
// -> true | ||
``` | ||
@@ -219,54 +249,58 @@ ### .load(fen) | ||
var chess = new Chess(); | ||
chess.load('4r3/8/2p2PPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> true | ||
chess.load('4r3/8/X12XPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> false, bad piece X | ||
```js | ||
var chess = new Chess(); | ||
chess.load('4r3/8/2p2PPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> true | ||
chess.load('4r3/8/X12XPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> false, bad piece X | ||
``` | ||
### .load_pgn(pgn, [ options ]) | ||
Load the moves of a game stored in | ||
[Portal Game Notation](http://en.wikipedia.org/wiki/Portable_Game_Notation). | ||
Options is a optional parameter that contains a 'newline_char' denoting the line | ||
delimiter (the default delimiter is '\n'). Returns true if the PGN was parsed | ||
successfully, otherwise false. | ||
Load the moves of a game stored in | ||
[Portable Game Notation](http://en.wikipedia.org/wiki/Portable_Game_Notation). | ||
Options is a optional parameter that contains a 'newline_char' which is a | ||
string representation of a RegExp (and should not be pre-escaped) and defaults | ||
to '\r?\n'). Returns true if the PGN was parsed successfully, otherwise false. | ||
var chess = new Chess(); | ||
pgn = ['[Event "Casual Game"]', | ||
'[Site "Berlin GER"]', | ||
'[Date "1852.??.??"]', | ||
'[EventDate "?"]', | ||
'[Round "?"]', | ||
'[Result "1-0"]', | ||
'[White "Adolf Anderssen"]', | ||
'[Black "Jean Dufresne"]', | ||
'[ECO "C52"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "47"]', | ||
'', | ||
'1.e4 e5 2.Nf3 Nc6 3.Bc4 Bc5 4.b4 Bxb4 5.c3 Ba5 6.d4 exd4 7.O-O', | ||
'd3 8.Qb3 Qf6 9.e5 Qg6 10.Re1 Nge7 11.Ba3 b5 12.Qxb5 Rb8 13.Qa4', | ||
'Bb6 14.Nbd2 Bb7 15.Ne4 Qf5 16.Bxd3 Qh5 17.Nf6+ gxf6 18.exf6', | ||
'Rg8 19.Rad1 Qxf3 20.Rxe7+ Nxe7 21.Qxd7+ Kxd7 22.Bf5+ Ke8', | ||
'23.Bd7+ Kf8 24.Bxe7# 1-0']; | ||
```js | ||
var chess = new Chess(); | ||
pgn = ['[Event "Casual Game"]', | ||
'[Site "Berlin GER"]', | ||
'[Date "1852.??.??"]', | ||
'[EventDate "?"]', | ||
'[Round "?"]', | ||
'[Result "1-0"]', | ||
'[White "Adolf Anderssen"]', | ||
'[Black "Jean Dufresne"]', | ||
'[ECO "C52"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "47"]', | ||
'', | ||
'1.e4 e5 2.Nf3 Nc6 3.Bc4 Bc5 4.b4 Bxb4 5.c3 Ba5 6.d4 exd4 7.O-O', | ||
'd3 8.Qb3 Qf6 9.e5 Qg6 10.Re1 Nge7 11.Ba3 b5 12.Qxb5 Rb8 13.Qa4', | ||
'Bb6 14.Nbd2 Bb7 15.Ne4 Qf5 16.Bxd3 Qh5 17.Nf6+ gxf6 18.exf6', | ||
'Rg8 19.Rad1 Qxf3 20.Rxe7+ Nxe7 21.Qxd7+ Kxd7 22.Bf5+ Ke8', | ||
'23.Bd7+ Kf8 24.Bxe7# 1-0']; | ||
chess.load_pgn(pgn.join('\n')); | ||
// -> true | ||
chess.load_pgn(pgn.join('\n')); | ||
// -> true | ||
chess.fen() | ||
// -> 1r3kr1/pbpBBp1p/1b3P2/8/8/2P2q2/P4PPP/3R2K1 b - - 0 24 | ||
chess.fen() | ||
// -> 1r3kr1/pbpBBp1p/1b3P2/8/8/2P2q2/P4PPP/3R2K1 b - - 0 24 | ||
chess.ascii() | ||
// -> ' +------------------------+ | ||
// 8 | . r . . . k r . | | ||
// 7 | p b p B B p . p | | ||
// 6 | . b . . . P . . | | ||
// 5 | . . . . . . . . | | ||
// 4 | . . . . . . . . | | ||
// 3 | . . P . . q . . | | ||
// 2 | P . . . . P P P | | ||
// 1 | . . . R . . K . | | ||
// +------------------------+ | ||
// a b c d e f g h' | ||
chess.ascii() | ||
// -> ' +------------------------+ | ||
// 8 | . r . . . k r . | | ||
// 7 | p b p B B p . p | | ||
// 6 | . b . . . P . . | | ||
// 5 | . . . . . . . . | | ||
// 4 | . . . . . . . . | | ||
// 3 | . . P . . q . . | | ||
// 2 | P . . . . P P P | | ||
// 1 | . . . R . . K . | | ||
// +------------------------+ | ||
// a b c d e f g h' | ||
``` | ||
@@ -278,12 +312,14 @@ ### .move(move) | ||
var chess = new Chess(); | ||
```js | ||
var chess = new Chess(); | ||
chess.move('e4') | ||
// -> { color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e2' } | ||
chess.move('e4') | ||
// -> { color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e4' } | ||
chess.move('nf6') // SAN is case sensitive!! | ||
// -> null | ||
chess.move('nf6') // SAN is case sensitive!! | ||
// -> null | ||
chess.move('Nf6') | ||
// -> { color: 'b', from: 'g8', to: 'f6', flags: 'n', piece: 'n', san: 'Nf6' } | ||
chess.move('Nf6') | ||
// -> { color: 'b', from: 'g8', to: 'f6', flags: 'n', piece: 'n', san: 'Nf6' } | ||
``` | ||
@@ -293,24 +329,34 @@ Or by passing .move() a move object (only the 'to', 'from', and when necessary | ||
var chess = new Chess(); | ||
```js | ||
var chess = new Chess(); | ||
chess.move({ from: 'g2', to: 'g3' }); | ||
// -> { color: 'w', from: 'g2', to: 'g3', flags: 'n', piece: 'p', san: 'g3' } | ||
chess.move({ from: 'g2', to: 'g3' }); | ||
// -> { color: 'w', from: 'g2', to: 'g3', flags: 'n', piece: 'p', san: 'g3' } | ||
``` | ||
### .moves([ options ]) | ||
Returns a list of all legal moves from the current position. The function be passed a options hash which controls the verbosity of the return values (this may change in the future). | ||
Returns a list of legals moves from the current position. The function takes an optional parameter which controls the single-square move generation and verbosity. | ||
var chess = new Chess(); | ||
chess.moves(); | ||
// -> ['a3', 'a4', 'b3', 'b4', 'c3', 'c4', 'd3', 'd4', 'e3', 'e4', | ||
'f3', 'f4', 'g3', 'g4', 'h3', 'h4', 'Na3', 'Nc3', 'Nf3', 'Nh3'] | ||
```js | ||
var chess = new Chess(); | ||
chess.moves(); | ||
// -> ['a3', 'a4', 'b3', 'b4', 'c3', 'c4', 'd3', 'd4', 'e3', 'e4', | ||
// 'f3', 'f4', 'g3', 'g4', 'h3', 'h4', 'Na3', 'Nc3', 'Nf3', 'Nh3'] | ||
chess.moves({ verbose: true }); | ||
// -> [{ color: 'w', from: 'a2', to: 'a3', | ||
flags: 'n', piece: 'p', san 'a3' | ||
# a captured: key is included when the move is a capture | ||
# a promotion: key is included when the move is a promotion | ||
}, | ||
... | ||
] | ||
chess.moves({square: 'e2'}); | ||
// -> ['e3', 'e4'] | ||
chess.moves({square: 'e9'}); // invalid square | ||
// -> [] | ||
chess.moves({ verbose: true }); | ||
// -> [{ color: 'w', from: 'a2', to: 'a3', | ||
// flags: 'n', piece: 'p', san 'a3' | ||
// # a captured: key is included when the move is a capture | ||
// # a promotion: key is included when the move is a promotion | ||
// }, | ||
// ... | ||
// ] | ||
``` | ||
The _piece_, _captured_, and _promotion_ fields contain the lowercase | ||
@@ -325,3 +371,3 @@ representation of the applicable piece. | ||
- 'c' - a standard capture | ||
- 'p' - a promotion | ||
- 'p' - a promotion | ||
- 'k' - kingside castling | ||
@@ -336,11 +382,13 @@ - 'q' - queenside castling | ||
var chess = new Chess(); | ||
chess.info('White', 'Plunky', 'Black', 'Plinkie'); | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('Nc3'); | ||
chess.move('Nc6'); | ||
```js | ||
var chess = new Chess(); | ||
chess.header('White', 'Plunky', 'Black', 'Plinkie'); | ||
chess.move('e4'); | ||
chess.move('e5'); | ||
chess.move('Nc3'); | ||
chess.move('Nc6'); | ||
chess.pgn({ max_width: 5, newline_char: '<br />' }); | ||
// -> '[White "Plunky"]<br />[Black "Plinkie"]<br /><br />1. e4 e5<br />2. Nc3 Nc6' | ||
chess.pgn({ max_width: 5, newline_char: '<br />' }); | ||
// -> '[White "Plunky"]<br />[Black "Plinkie"]<br /><br />1. e4 e5<br />2. Nc3 Nc6' | ||
``` | ||
@@ -350,27 +398,45 @@ ### .put(piece, square) | ||
{ type: ..., color: ... }. Returns true if piece was successfully placed, | ||
otherwise false. | ||
otherwise the board remains unchanged and false is returned. `put()` will fail | ||
when passed an invalid piece or square, or when two or more kings of the | ||
same color are placed. | ||
chess.clear(); | ||
```js | ||
chess.clear(); | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
// -> true | ||
chess.put({ type: 'k', color: 'w' }, 'h1') // shorthand | ||
// -> true | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
// -> true | ||
chess.put({ type: 'k', color: 'w' }, 'h1') // shorthand | ||
// -> true | ||
chess.fen(); | ||
// -> '8/8/8/p7/8/8/8/7K w - - 0 0' | ||
chess.fen(); | ||
// -> '8/8/8/p7/8/8/8/7K w - - 0 0' | ||
chess.put({ type: 'z', color: 'w' }, 'a1') // invalid piece | ||
// -> false | ||
chess.clear(); | ||
chess.put({ type: 'k', color: 'w' }, 'a1') | ||
// -> true | ||
chess.put({ type: 'k', color: 'w' }, 'h1') // fail - two kings | ||
// -> false | ||
``` | ||
### .remove(square) | ||
Remove and return the piece on _square_. | ||
chess.clear(); | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
chess.put({ type: chess.KING, color: chess.WHITE }, 'h1') // put a white king on h1 | ||
```js | ||
chess.clear(); | ||
chess.put({ type: chess.PAWN, color: chess.BLACK }, 'a5') // put a black pawn on a5 | ||
chess.put({ type: chess.KING, color: chess.WHITE }, 'h1') // put a white king on h1 | ||
chess.remove('a5'); | ||
// -> { type: 'p', color: 'b' }, | ||
chess.remove('h1'); | ||
// -> { type: 'k', color: 'w' }, | ||
chess.remove('e1'); | ||
// -> null | ||
chess.remove('a5'); | ||
// -> { type: 'p', color: 'b' }, | ||
chess.remove('h1'); | ||
// -> { type: 'k', color: 'w' }, | ||
chess.remove('e1'); | ||
// -> null | ||
``` | ||
@@ -383,9 +449,11 @@ ### .reset() | ||
var chess = Chess(); | ||
chess.square_color('h1') | ||
// -> 'light' | ||
chess.square_color('a7') | ||
// -> 'dark' | ||
chess.square_color('bogus square') | ||
// -> null | ||
```js | ||
var chess = Chess(); | ||
chess.square_color('h1') | ||
// -> 'light' | ||
chess.square_color('a7') | ||
// -> 'dark' | ||
chess.square_color('bogus square') | ||
// -> null | ||
``` | ||
@@ -395,24 +463,28 @@ ### .turn() | ||
chess.load('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1') | ||
chess.turn() | ||
// -> 'b' | ||
```js | ||
chess.load('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1') | ||
chess.turn() | ||
// -> 'b' | ||
``` | ||
### .undo() | ||
Takeback the last half-move, returning a move object if successful, otherwise null. | ||
var chess = new Chess(); | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' | ||
chess.move('e4'); | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1' | ||
```js | ||
var chess = new Chess(); | ||
chess.undo(); | ||
// -> { color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e4' } | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1' | ||
chess.undo(); | ||
// -> null | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' | ||
chess.move('e4'); | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1' | ||
chess.undo(); | ||
// -> { color: 'w', from: 'e2', to: 'e4', flags: 'b', piece: 'p', san: 'e4' } | ||
chess.fen(); | ||
// -> 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1' | ||
chess.undo(); | ||
// -> null | ||
``` | ||
### .validate_fen(fen): | ||
@@ -422,36 +494,27 @@ Returns a validation object specifying validity or the errors found within the | ||
chess.validate_fen('2n1r3/p1k2pp1/B1p3b1/P7/5bP1/2N1B3/1P2KP2/2R5 b - - 4 25'); | ||
// -> { valid: true, error_number: 0, error: 'No errors.' } | ||
```js | ||
chess.validate_fen('2n1r3/p1k2pp1/B1p3b1/P7/5bP1/2N1B3/1P2KP2/2R5 b - - 4 25'); | ||
// -> { valid: true, error_number: 0, error: 'No errors.' } | ||
chess.validate_fen('4r3/8/X12XPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> { valid: false, error_number: 9, | ||
// error: '1st field (piece positions) is invalid [invalid piece].' } | ||
chess.validate_fen('4r3/8/X12XPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45'); | ||
// -> { valid: false, error_number: 9, | ||
// error: '1st field (piece positions) is invalid [invalid piece].' } | ||
``` | ||
## MUSIC | ||
## CONTRIBUTORS | ||
Special thanks to the following developers for their patches and contributions: | ||
- [Steve Bragg](https://github.com/2sb18) | ||
- [E. Azer Koçulu](https://github.com/azer) | ||
- [Falco Nogatz](https://github.com/fnogatz) | ||
Musical support provided by: | ||
- [The Grateful Dead](http://www.youtube.com/watch?v=YLzUme1gN8c) | ||
- [The Grateful Dead](https://www.youtube.com/watch?feature=player_detailpage&v=ANF6qanEB7s#t=2999) | ||
- [Umphrey's McGee](http://www.youtube.com/watch?v=jh-1fFWkSdw) | ||
## BUGS | ||
- the en passant square and castling flags aren't adjusted when using the put/remove functions (workaround: use .load() instead) | ||
- The en passant square and castling flags aren't adjusted when using the put/remove functions (workaround: use .load() instead) | ||
## TODO | ||
- add AI (basic alpha-beta search w/ primitive position evaluation) | ||
- add jQuery chessboard widget | ||
- investigate the use of piece lists (this may shave a few cycles off generate_moves() and attacked()) | ||
- Investigate the use of piece lists (this may shave a few cycles off | ||
generate_moves() and attacked()). | ||
- Refactor API to use camelCase - yuck. | ||
- Add more robust FEN validation. |
@@ -1,22 +0,12 @@ | ||
var is_node = true; | ||
try { | ||
is_node = typeof process == 'object' | ||
} catch (e) { | ||
is_node = false; | ||
if (typeof require != "undefined") { | ||
var chai = require('chai'); | ||
var Chess = require('../chess').Chess; | ||
} | ||
var assert = chai.assert; | ||
if (is_node) { | ||
var sys = require('sys'), | ||
ch = require('../chess'); | ||
var log = sys.puts; | ||
var Chess = ch.Chess; | ||
} else { | ||
var log = function(text, newline) { | ||
var console = document.getElementById('console'); | ||
console.innerHTML += text; | ||
if (typeof newline == 'undefined' || newline == true) { | ||
console.innerHTML += '<br />'; | ||
if (!Array.prototype.forEach) { | ||
Array.prototype.forEach = function(fn, scope) { | ||
for(var i = 0, len = this.length; i < len; ++i) { | ||
fn.call(scope, this[i], i, this); | ||
} | ||
@@ -26,7 +16,7 @@ } | ||
function perft_unit_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
suite("Perft", function() { | ||
var perfts = [ | ||
{fen: 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1', | ||
{fen: 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1', | ||
depth: 3, nodes: 97862}, | ||
@@ -41,21 +31,77 @@ {fen: '8/PPP4k/8/8/8/8/4Kppp/8 w - - 0 1', | ||
var total_nodes = 0; | ||
for (var i = 0; i < perfts.length; i++) { | ||
chess.load(perfts[i].fen); | ||
var nodes = chess.perft(perfts[i].depth); | ||
var s = 'Perft Test #' + i + ': ' + perfts[i].fen + ' - ' + nodes + ' : '; | ||
s += (nodes != perfts[i].nodes) ? 'FAILED!' : 'PASSED!'; | ||
total_nodes += nodes; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
perfts.forEach(function(perft) { | ||
var chess = new Chess(); | ||
chess.load(perft.fen); | ||
log('--> Perft Time: ' + diff + ' secs ' + '(' + Math.floor(total_nodes / diff) + ' NPS)'); | ||
log(''); | ||
} | ||
test(perft.fen, function() { | ||
var nodes = chess.perft(perft.depth); | ||
assert(nodes == perft.nodes); | ||
}); | ||
function checkmate_unit_tests() { | ||
}); | ||
}); | ||
suite("Single Square Move Generation", function() { | ||
var positions = [ | ||
{fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', | ||
square: 'e2', verbose: false, moves: ['e3', 'e4']}, | ||
{fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', | ||
square: 'e9', verbose: false, moves: []}, // invalid square | ||
{fen: 'rnbqk1nr/pppp1ppp/4p3/8/1b1P4/2N5/PPP1PPPP/R1BQKBNR w KQkq - 2 3', | ||
square: 'c3', verbose: false, moves: []}, // pinned piece | ||
{fen: '8/k7/8/8/8/8/7p/K7 b - - 0 1', | ||
square: 'h2', verbose: false, moves: ['h1=Q+', 'h1=R+', 'h1=B', 'h1=N']}, // promotion | ||
{fen: 'r1bq1rk1/1pp2ppp/p1np1n2/2b1p3/2B1P3/2NP1N2/PPPBQPPP/R3K2R w KQ - 0 8', | ||
square: 'e1', verbose: false, moves: ['Kf1', 'Kd1', 'O-O', 'O-O-O']}, // castling | ||
{fen: 'r1bq1rk1/1pp2ppp/p1np1n2/2b1p3/2B1P3/2NP1N2/PPPBQPPP/R3K2R w - - 0 8', | ||
square: 'e1', verbose: false, moves: ['Kf1', 'Kd1']}, // no castling | ||
{fen: '8/7K/8/8/1R6/k7/1R1p4/8 b - - 0 1', | ||
square: 'a3', verbose: false, moves: []}, // trapped king | ||
{fen: '8/7K/8/8/1R6/k7/1R1p4/8 b - - 0 1', | ||
square: 'd2', verbose: true, | ||
moves: | ||
[{color:'b', from:'d2', to:'d1', flags:'np', piece:'p', promotion:'q', san:'d1=Q'}, | ||
{color:'b', from:'d2', to:'d1', flags:'np', piece:'p', promotion:'r', san:'d1=R'}, | ||
{color:'b', from:'d2', to:'d1', flags:'np', piece:'p', promotion:'b', san:'d1=B'}, | ||
{color:'b', from:'d2', to:'d1', flags:'np', piece:'p', promotion:'n', san:'d1=N'}] | ||
}, // verbose | ||
{fen: 'rnbqk2r/ppp1pp1p/5n1b/3p2pQ/1P2P3/B1N5/P1PP1PPP/R3KBNR b KQkq - 3 5', | ||
square: 'f1', verbose: true, moves: []}, // issue #30 | ||
]; | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
chess.load(position.fen); | ||
test(position.fen + ' ' + position.square, function() { | ||
var moves = chess.moves({square: position.square, verbose: position.verbose}); | ||
var passed = position.moves.length == moves.length; | ||
for (var j = 0; j < moves.length; j++) { | ||
if (!position.verbose) { | ||
passed = passed && moves[j] == position.moves[j]; | ||
} else { | ||
for (var k in moves[j]) { | ||
passed = passed && moves[j][k] == position.moves[j][k]; | ||
} | ||
} | ||
} | ||
assert(passed); | ||
}); | ||
}); | ||
}); | ||
suite("Checkmate", function() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var checkmates = [ | ||
@@ -65,21 +111,19 @@ '8/5r2/4K1q1/4p3/3k4/8/8/8 w - - 0 7', | ||
'r3k2r/ppp2p1p/2n1p1p1/8/2B2P1q/2NPb1n1/PP4PP/R2Q3K w kq - 0 8', | ||
'8/6R1/pp1r3p/6p1/P3R1Pk/1P4P1/7K/8 b - - 0 4', | ||
] | ||
'8/6R1/pp1r3p/6p1/P3R1Pk/1P4P1/7K/8 b - - 0 4' | ||
]; | ||
for (var i = 0; i < checkmates.length; i++) { | ||
chess.load(checkmates[i]); | ||
var s = 'Checkmate Test #' + i + ': ' + checkmates[i] + ' : '; | ||
s += (chess.in_checkmate()) ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
checkmates.forEach(function(checkmate) { | ||
chess.load(checkmate); | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Checkmate Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
test(checkmate, function() { | ||
assert(chess.in_checkmate()); | ||
}); | ||
}); | ||
function stalemate_unit_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
}); | ||
suite("Stalemate", function() { | ||
var stalemates = [ | ||
@@ -90,17 +134,17 @@ '1R6/8/8/8/8/8/7R/k6K b - - 0 1', | ||
for (var i = 0; i < stalemates.length; i++) { | ||
chess.load(stalemates[i]); | ||
var s = 'Stalemate Test #' + i + ': ' + stalemates[i] + ' : '; | ||
s += (chess.in_stalemate()) ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Stalemate Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
stalemates.forEach(function(stalemate) { | ||
var chess = new Chess(); | ||
chess.load(stalemate); | ||
function insufficient_material_unit_test() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
test(stalemate, function() { | ||
assert(chess.in_stalemate()) | ||
}); | ||
}); | ||
}); | ||
suite("Insufficient Material", function() { | ||
var positions = [ | ||
@@ -112,26 +156,29 @@ {fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', draw: false}, | ||
{fen: '8/2b5/8/8/8/8/8/k6K w - - 0 1', draw: true}, | ||
{fen: '8/b7/3B4/8/8/8/8/k6K w - - 0 1', draw: true}, | ||
{fen: '8/b7/B7/8/8/8/8/k6K w - - 0 1', draw: false}, | ||
{fen: '8/b1B1b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1', draw: true}, | ||
{fen: '8/bB2b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1', draw: false} | ||
]; | ||
for (var i = 0; i < positions.length; i++) { | ||
chess.load(positions[i].fen); | ||
var s = 'Insufficient Material Test #' + i + ': ' + positions[i].fen + ' : '; | ||
if (positions[i].draw) { | ||
s += (chess.insufficient_material() && chess.in_draw()) ? 'PASSED!' : 'FAILED!'; | ||
} else { | ||
s += (!chess.insufficient_material() && !chess.in_draw()) ? 'PASSED!' : 'FAILED!'; | ||
} | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
chess.load(position.fen); | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Insufficient Material Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
test(position.fen, function() { | ||
if (position.draw) { | ||
assert(chess.insufficient_material() && chess.in_draw()); | ||
} else { | ||
assert(!chess.insufficient_material() && !chess.in_draw()); | ||
} | ||
}); | ||
function threefold_repetition_unit_test() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
}); | ||
}); | ||
suite("Threefold Repetition", function() { | ||
var positions = [ | ||
{fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', | ||
{fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', | ||
moves: ['Nf3', 'Nf6', 'Ng1', 'Ng8', 'Nf3', 'Nf6', 'Ng1', 'Ng8']}, | ||
@@ -144,34 +191,30 @@ | ||
for (var i = 0; i < positions.length; i++) { | ||
chess.load(positions[i].fen); | ||
var s = 'Threefold Repetition Test #' + i + ': ' + positions[i].fen + ' : '; | ||
var passed = true; | ||
for (var j = 0; j < positions[i].moves.length; j++) { | ||
if (chess.in_threefold_repetition()) { | ||
passed = false; | ||
break; | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
chess.load(position.fen); | ||
test(position.fen, function() { | ||
var passed = true; | ||
for (var j = 0; j < position.moves.length; j++) { | ||
if (chess.in_threefold_repetition()) { | ||
passed = false; | ||
break; | ||
} | ||
chess.move(position.moves[j]); | ||
} | ||
chess.move(positions[i].moves[j]); | ||
} | ||
if (!passed) { | ||
s += 'FAILED!'; | ||
} else { | ||
s += (chess.in_threefold_repetition() && chess.in_draw()) ? 'PASSED!' : 'FAILED!'; | ||
} | ||
assert(passed && chess.in_threefold_repetition() && chess.in_draw()); | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Threefold Repetition Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
}); | ||
function algebraic_notation_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var passed = true; | ||
}); | ||
}); | ||
suite("Algebraic Notation", function() { | ||
var positions = [ | ||
{fen: '7k/3R4/3p2Q1/6Q1/2N1N3/8/8/3R3K w - - 0 1', | ||
{fen: '7k/3R4/3p2Q1/6Q1/2N1N3/8/8/3R3K w - - 0 1', | ||
moves: ['Rd8#', 'Re7', 'Rf7', 'Rg7', 'Rh7#', 'R7xd6', 'Rc7', 'Rb7', 'Ra7', | ||
@@ -188,3 +231,3 @@ 'Qf7', 'Qe8#', 'Qg7#', 'Qg8#', 'Qh7#', 'Q6h6#', 'Q6h5#', 'Q6f5', | ||
'axb8=N', 'c8=Q+', 'c8=R+', 'c8=B', 'c8=N', 'cxb8=Q+', 'cxb8=R+', | ||
'cxb8=B', 'cxb8=N', 'Ra2', 'Ra3', 'Ra4', 'Ra5', 'Ra6', 'Rb1', | ||
'cxb8=B', 'cxb8=N', 'Ra2', 'Ra3', 'Ra4', 'Ra5', 'Ra6', 'Rb1', | ||
'Rc1', 'Rd1', 'Kd2', 'Ke2', 'Kf2', 'Kf1', 'Kd1', 'Rh2', 'Rh3', | ||
@@ -212,31 +255,32 @@ 'Rh4', 'Rh5', 'Rh6', 'Rh7', 'Rh8+', 'Rg1', 'Rf1+', 'O-O+', | ||
'N4b3', 'Kb8']}, | ||
]; | ||
]; | ||
for (var i = 0; i < positions.length; i++) { | ||
var s = 'Algebraic Notation Test #' + i + ': ' + positions[i].fen + ' : '; | ||
chess.load(positions[i].fen); | ||
var moves = chess.moves(); | ||
if (moves.length != positions[i].moves.length) { | ||
passed = false; | ||
} else { | ||
for (var j = 0; j < moves.length; j++) { | ||
if (positions[i].moves.indexOf(moves[j]) == -1) { | ||
passed = false; | ||
break; | ||
} | ||
} | ||
} | ||
s += passed ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
var passed = true; | ||
chess.load(position.fen); | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Algebraic Notation Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
test(position.fen, function() { | ||
var moves = chess.moves(); | ||
if (moves.length != position.moves.length) { | ||
passed = false; | ||
} else { | ||
for (var j = 0; j < moves.length; j++) { | ||
if (position.moves.indexOf(moves[j]) == -1) { | ||
passed = false; | ||
break; | ||
} | ||
} | ||
} | ||
assert(passed); | ||
}); | ||
function get_put_remove_tests() { | ||
}); | ||
}); | ||
suite("Get/Put/Remove", function() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var passed = true; | ||
@@ -263,76 +307,92 @@ var positions = [ | ||
should_pass: false}, | ||
/* disallow two kings (black) */ | ||
{pieces: {a7: {type: chess.KING, color: chess.BLACK}, | ||
h2: {type: chess.KING, color: chess.WHITE}, | ||
a8: {type: chess.KING, color: chess.BLACK}}, | ||
should_pass: false}, | ||
/* disallow two kings (white) */ | ||
{pieces: {a7: {type: chess.KING, color: chess.BLACK}, | ||
h2: {type: chess.KING, color: chess.WHITE}, | ||
h1: {type: chess.KING, color: chess.WHITE}}, | ||
should_pass: false}, | ||
/* allow two kings if overwriting the exact same square */ | ||
{pieces: {a7: {type: chess.KING, color: chess.BLACK}, | ||
h2: {type: chess.KING, color: chess.WHITE}, | ||
h2: {type: chess.KING, color: chess.WHITE}}, | ||
should_pass: true}, | ||
]; | ||
for (var i = 0; i < positions.length; i++) { | ||
positions.forEach(function(position) { | ||
passed = true; | ||
chess.clear(); | ||
var s = 'Get/Put Test #' + i + ' (' + positions[i].should_pass + '): '; | ||
/* places the pieces */ | ||
for (var square in positions[i].pieces) { | ||
passed &= chess.put(positions[i].pieces[square], square); | ||
} | ||
test("position should pass - " + position.should_pass, function() { | ||
/* iterate over every square to make sure get returns the proper | ||
* piece values/color | ||
*/ | ||
for (var j = 0; j < chess.SQUARES.length; j++) { | ||
var square = chess.SQUARES[j]; | ||
if (!(square in positions[i].pieces)) { | ||
if (chess.get(square)) { | ||
passed = false; | ||
break; | ||
} | ||
} else { | ||
var piece = chess.get(square); | ||
if (!(piece && | ||
piece.type == positions[i].pieces[square].type && | ||
piece.color == positions[i].pieces[square].color)) { | ||
passed = false; | ||
break; | ||
} | ||
/* places the pieces */ | ||
for (var square in position.pieces) { | ||
passed &= chess.put(position.pieces[square], square); | ||
} | ||
} | ||
if (passed) { | ||
/* remove the pieces */ | ||
/* iterate over every square to make sure get returns the proper | ||
* piece values/color | ||
*/ | ||
for (var j = 0; j < chess.SQUARES.length; j++) { | ||
var square = chess.SQUARES[j]; | ||
var piece = chess.remove(square); | ||
if ((!(square in positions[i].pieces)) && piece) { | ||
passed = false; | ||
break; | ||
if (!(square in position.pieces)) { | ||
if (chess.get(square)) { | ||
passed = false; | ||
break; | ||
} | ||
} else { | ||
var piece = chess.get(square); | ||
if (!(piece && | ||
piece.type == position.pieces[square].type && | ||
piece.color == position.pieces[square].color)) { | ||
passed = false; | ||
break; | ||
} | ||
} | ||
} | ||
if (piece && | ||
(positions[i].pieces[square].type != piece.type || | ||
positions[i].pieces[square].color != piece.color)) { | ||
passed = false; | ||
break; | ||
if (passed) { | ||
/* remove the pieces */ | ||
for (var j = 0; j < chess.SQUARES.length; j++) { | ||
var square = chess.SQUARES[j]; | ||
var piece = chess.remove(square); | ||
if ((!(square in position.pieces)) && piece) { | ||
passed = false; | ||
break; | ||
} | ||
if (piece && | ||
(position.pieces[square].type != piece.type || | ||
position.pieces[square].color != piece.color)) { | ||
passed = false; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
/* finally, check for an empty board */ | ||
passed = passed && (chess.fen() == '8/8/8/8/8/8/8/8 w - - 0 1'); | ||
/* finally, check for an empty board */ | ||
passed = passed && (chess.fen() == '8/8/8/8/8/8/8/8 w - - 0 1'); | ||
/* some tests should fail, so make sure we're supposed to pass/fail each | ||
* test | ||
*/ | ||
passed = (passed == positions[i].should_pass); | ||
/* some tests should fail, so make sure we're supposed to pass/fail each | ||
* test | ||
*/ | ||
passed = (passed == position.should_pass); | ||
s += passed ? 'PASSED!' : 'FAILED!'; | ||
assert(passed); | ||
}); | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> Get/Put Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
}); | ||
function fen_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var passed = true; | ||
}); | ||
suite("FEN", function() { | ||
var positions = [ | ||
@@ -354,18 +414,17 @@ {fen: '8/8/8/8/8/8/8/8 w - - 0 1', should_pass: true}, | ||
for (var i = 0; i < positions.length; i++) { | ||
passed = true; | ||
chess.load(positions[i].fen); | ||
var s = 'FEN Test #' + i + ': ' + positions[i].fen + ' (' + positions[i].should_pass + '): '; | ||
passed = (chess.fen() == positions[i].fen == positions[i].should_pass); | ||
s += (passed) ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> FEN Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
function pgn_tests() { | ||
var start = new Date; | ||
test(position.fen + ' (' + position.should_pass + ')', function() { | ||
chess.load(position.fen); | ||
assert(chess.fen() == position.fen == position.should_pass); | ||
}); | ||
}); | ||
}); | ||
suite("PGN", function() { | ||
var passed = true; | ||
@@ -375,8 +434,8 @@ var error_message; | ||
{moves: ['d4', 'd5', 'Nf3', 'Nc6', 'e3', 'e6', 'Bb5', 'g5', 'O-O', 'Qf6', 'Nc3', | ||
'Bd7', 'Bxc6', 'Bxc6', 'Re1', 'O-O-O', 'a4', 'Bb4', 'a5', 'b5', 'axb6', | ||
'axb6', 'Ra8+', 'Kd7', 'Ne5+', 'Kd6', 'Rxd8+', 'Qxd8', 'Nxf7+', 'Ke7', | ||
'Nxd5+', 'Qxd5', 'c3', 'Kxf7', 'Qf3+', 'Qxf3', 'gxf3', 'Bxf3', 'cxb4', | ||
'e5', 'dxe5', 'Ke6', 'b3', 'Kxe5', 'Bb2+', 'Ke4', 'Bxh8', 'Nf6', 'Bxf6', | ||
'h5', 'Bxg5', 'Bg2', 'Kxg2', 'Kf5', 'Bh4', 'Kg4', 'Bg3', 'Kf5', 'e4+', | ||
'Kg4', 'e5', 'h4', 'Bxh4', 'Kxh4', 'e6', 'c5', 'bxc5', 'bxc5', 'e7', 'c4', | ||
'Bd7', 'Bxc6', 'Bxc6', 'Re1', 'O-O-O', 'a4', 'Bb4', 'a5', 'b5', 'axb6', | ||
'axb6', 'Ra8+', 'Kd7', 'Ne5+', 'Kd6', 'Rxd8+', 'Qxd8', 'Nxf7+', 'Ke7', | ||
'Nxd5+', 'Qxd5', 'c3', 'Kxf7', 'Qf3+', 'Qxf3', 'gxf3', 'Bxf3', 'cxb4', | ||
'e5', 'dxe5', 'Ke6', 'b3', 'Kxe5', 'Bb2+', 'Ke4', 'Bxh8', 'Nf6', 'Bxf6', | ||
'h5', 'Bxg5', 'Bg2', 'Kxg2', 'Kf5', 'Bh4', 'Kg4', 'Bg3', 'Kf5', 'e4+', | ||
'Kg4', 'e5', 'h4', 'Bxh4', 'Kxh4', 'e6', 'c5', 'bxc5', 'bxc5', 'e7', 'c4', | ||
'bxc4', 'Kg4', 'e8=Q', 'Kf5', 'Qe5+', 'Kg4', 'Re4#'], | ||
@@ -409,3 +468,3 @@ header: ['White', 'Jeff Hlywa', 'Black', 'Steve Bragg', 'GreatestGameEverPlayed?', 'True'], | ||
max_width:20, | ||
pgn: '[SetUp "r1bqk1nr/pppp1ppp/2n5/4p3/1bB1P3/2P2N2/P2P1PPP/RNBQK2R b KQkq - 0 1"]\n[FEN "1"]\n\n1. ... Ba5 2. O-O d6\n3. d4', | ||
pgn: '[SetUp "1"]\n[FEN "r1bqk1nr/pppp1ppp/2n5/4p3/1bB1P3/2P2N2/P2P1PPP/RNBQK2R b KQkq - 0 1"]\n\n1. ... Ba5 2. O-O d6\n3. d4', | ||
starting_position: 'r1bqk1nr/pppp1ppp/2n5/4p3/1bB1P3/2P2N2/P2P1PPP/RNBQK2R b KQkq - 0 1', | ||
@@ -415,29 +474,179 @@ fen: 'r1bqk1nr/ppp2ppp/2np4/b3p3/2BPP3/2P2N2/P4PPP/RNBQ1RK1 b kq d3 0 3'} | ||
for (var i = 0; i < positions.length; i++) { | ||
var chess = ("starting_position" in positions[i]) ? new Chess(positions[i].starting_position) : new Chess(); | ||
passed = true; | ||
error_message = ""; | ||
for (var j = 0; j < positions[i].moves.length; j++) { | ||
if (chess.move(positions[i].moves[j]) === null) { | ||
error_message = "move() did not accept " + positions[i].moves[j] + " : "; | ||
break; | ||
positions.forEach(function(position, i) { | ||
test(i, function() { | ||
var chess = ("starting_position" in position) ? new Chess(position.starting_position) : new Chess(); | ||
passed = true; | ||
error_message = ""; | ||
for (var j = 0; j < position.moves.length; j++) { | ||
if (chess.move(position.moves[j]) === null) { | ||
error_message = "move() did not accept " + position.moves[j] + " : "; | ||
break; | ||
} | ||
} | ||
} | ||
var s = 'PGN Test #' + i + ': ' + error_message; | ||
chess.header.apply(null, positions[i].header); | ||
var pgn = chess.pgn({max_width:positions[i].max_width, newline_char:positions[i].newline_char}); | ||
var fen = chess.fen(); | ||
passed = pgn === positions[i].pgn && fen === positions[i].fen; | ||
s += (passed) ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish-start) / 1000; | ||
log('--> PGN Time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
function make_move_tests() { | ||
chess.header.apply(null, position.header); | ||
var pgn = chess.pgn({max_width:position.max_width, newline_char:position.newline_char}); | ||
var fen = chess.fen(); | ||
passed = pgn === position.pgn && fen === position.fen; | ||
assert(passed && error_message.length == 0); | ||
}); | ||
}); | ||
}); | ||
suite("Load PGN", function() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var tests = [ | ||
{pgn: [ | ||
'[Event "Reykjavik WCh"]', | ||
'[Site "Reykjavik WCh"]', | ||
'[Date "1972.01.07"]', | ||
'[EventDate "?"]', | ||
'[Round "6"]', | ||
'[Result "1-0"]', | ||
'[White "Robert James Fischer"]', | ||
'[Black "Boris Spassky"]', | ||
'[ECO "D59"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "81"]', | ||
'', | ||
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6', | ||
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6', | ||
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7', | ||
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7', | ||
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5', | ||
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7', | ||
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8', | ||
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0'], | ||
expect: true | ||
}, | ||
{fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17', | ||
pgn: [ | ||
'[Event "Paris"]', | ||
'[Site "Paris"]', | ||
'[Date "1858.??.??"]', | ||
'[EventDate "?"]', | ||
'[Round "?"]', | ||
'[Result "1-0"]', | ||
'[White "Paul Morphy"]', | ||
'[Black "Duke Karl / Count Isouard"]', | ||
'[ECO "C41"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "33"]', | ||
'', | ||
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 {This is a weak move', | ||
'already.--Fischer} 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Bc4 Nf6 7.Qb3 Qe7', | ||
'8.Nc3 c6 9.Bg5 {Black is in what\'s like a zugzwang position', | ||
'here. He can\'t develop the [Queen\'s] knight because the pawn', | ||
'is hanging, the bishop is blocked because of the', | ||
'Queen.--Fischer} b5 10.Nxb5 cxb5 11.Bxb5+ Nbd7 12.O-O-O Rd8', | ||
'13.Rxd7 Rxd7 14.Rd1 Qe6 15.Bxd7+ Nxd7 16.Qb8+ Nxb8 17.Rd8# 1-0'], | ||
expect: true}, | ||
{pgn: [ | ||
'1. e4 e5 2. f4 exf4 3. Nf3 g5 4. h4 g4 5. Ne5 Nf6 6. Nxg4 Nxe4', | ||
'7. d3 Ng3 8. Bxf4 Nxh1 9. Qe2+ Qe7 10. Nf6+ Kd8 11. Bxc7+ Kxc7', | ||
'12. Nd5+ Kd8 13. Nxe7 Bxe7 14. Qg4 d6 15. Qf4 Rg8 16. Qxf7 Bxh4+', | ||
'17. Kd2 Re8 18. Na3 Na6 19. Qh5 Bf6 20. Qxh1 Bxb2 21. Qh4+ Kd7', | ||
'22. Rb1 Bxa3 23. Qa4+'], | ||
expect: true}, | ||
/* regression test - broken PGN parser ended up here: | ||
* fen = rnbqk2r/pp1p1ppp/4pn2/1N6/1bPN4/8/PP2PPPP/R1BQKB1R b KQkq - 2 6 */ | ||
{pgn: ['1. d4 Nf6 2. c4 e6 3. Nf3 c5 4. Nc3 cxd4 5. Nxd4 Bb4 6. Nb5'], | ||
fen: 'rnbqk2r/pp1p1ppp/4pn2/1N6/1bP5/2N5/PP2PPPP/R1BQKB1R b KQkq - 2 6', | ||
expect: true}, | ||
{pgn: ['1. e4 Qxd7 1/2-1/2'], | ||
expect: false}, | ||
]; | ||
var newline_chars = ['\n', '<br />', '\r\n', 'BLAH']; | ||
tests.forEach(function(t, i) { | ||
newline_chars.forEach(function(newline, j) { | ||
test(i + String.fromCharCode(97 + j), function() { | ||
var result = chess.load_pgn(t.pgn.join(newline), { newline_char: newline }); | ||
var should_pass = t.expect; | ||
/* some tests are expected to fail */ | ||
if (should_pass) { | ||
/* some PGN's tests contain comments which are stripped during parsing, | ||
* so we'll need compare the results of the load against a FEN string | ||
* (instead of the reconstructed PGN [e.g. test.pgn.join(newline)]) | ||
*/ | ||
if ('fen' in t) { | ||
assert(result && chess.fen() == t.fen); | ||
} else { | ||
assert(result && chess.pgn({ max_width: 65, newline_char: newline }) == t.pgn.join(newline)); | ||
} | ||
} else { | ||
/* this test should fail, so make sure it does */ | ||
assert(result == should_pass); | ||
} | ||
}); | ||
}); | ||
}); | ||
// special case dirty file containing a mix of \n and \r\n | ||
test('dirty pgn', function() { | ||
var pgn = | ||
'[Event "Reykjavik WCh"]\n' + | ||
'[Site "Reykjavik WCh"]\n' + | ||
'[Date "1972.01.07"]\n' + | ||
'[EventDate "?"]\n' + | ||
'[Round "6"]\n' + | ||
'[Result "1-0"]\n' + | ||
'[White "Robert James Fischer"]\r\n' + | ||
'[Black "Boris Spassky"]\n' + | ||
'[ECO "D59"]\n' + | ||
'[WhiteElo "?"]\n' + | ||
'[BlackElo "?"]\n' + | ||
'[PlyCount "81"]\n' + | ||
'\r\n' + | ||
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6\n' + | ||
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6\n' + | ||
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7\n' + | ||
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7\r\n' + | ||
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5\n' + | ||
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7\n' + | ||
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8\n' + | ||
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0\n'; | ||
var result = chess.load_pgn(pgn, { newline_char: '\r?\n' }); | ||
assert(result); | ||
assert(chess.load_pgn(pgn)); | ||
assert(chess.pgn().match(/^\[\[/) === null); | ||
}); | ||
}); | ||
suite('Pawn promotion without equal sign', function() { | ||
test('load_pgn', function() { | ||
var chess = new Chess(); | ||
assert(chess.load_pgn('1. d4 c5 2. d5 b6 3. Nc3 e6 4. dxe6 g6 5. exf7+ Ke7 6. fxg8Q')); | ||
assert.deepEqual(chess.history(), ['d4', 'c5', 'd5', 'b6', 'Nc3', 'e6', 'dxe6', 'g6', 'exf7+', 'Ke7', 'fxg8=Q']); | ||
}); | ||
test('move', function() { | ||
var chess = new Chess(); | ||
assert(chess.load_pgn('1. d4 c5 2. d5 b6 3. Nc3 e6 4. dxe6 g6 5. exf7+ Ke7')); | ||
assert.isNotNull(chess.move('fxg8Q')); | ||
assert.deepEqual(chess.history(), ['d4', 'c5', 'd5', 'b6', 'Nc3', 'e6', 'dxe6', 'g6', 'exf7+', 'Ke7', 'fxg8=Q']); | ||
}); | ||
}); | ||
suite("Make Move", function() { | ||
var positions = [ | ||
@@ -458,27 +667,33 @@ {fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', | ||
move: 'fxe6', | ||
next: 'rnbqkbnr/pp3ppp/2ppP3/8/4P3/8/PPPP2PP/RNBQKBNR b KQkq - 0 1'} | ||
next: 'rnbqkbnr/pp3ppp/2ppP3/8/4P3/8/PPPP2PP/RNBQKBNR b KQkq - 0 1', | ||
captured: 'p'}, | ||
{fen: 'rnbqkbnr/pppp2pp/8/4p3/4Pp2/2PP4/PP3PPP/RNBQKBNR b KQkq e3 0 1', | ||
legal: true, | ||
move: 'fxe3', | ||
next: 'rnbqkbnr/pppp2pp/8/4p3/8/2PPp3/PP3PPP/RNBQKBNR w KQkq - 0 2', | ||
captured: 'p'} | ||
]; | ||
for (var i = 0; i < positions.length; i++) { | ||
chess.load(positions[i].fen); | ||
var s = 'make move test #' + i + ': ' + positions[i].fen + | ||
' (' + positions[i].move + ' ' + positions[i].legal + ') : '; | ||
var result = chess.move(positions[i].move); | ||
if (positions[i].legal) { | ||
s += (result && chess.fen() == positions[i].next) ? 'PASSED!' : 'FAILED!'; | ||
} else { | ||
s += (!result) ? 'PASSED!' : 'FAILED!'; | ||
} | ||
positions.forEach(function(position) { | ||
var chess = new Chess(); | ||
chess.load(position.fen); | ||
test(position.fen + ' (' + position.move + ' ' + position.legal + ')', function() { | ||
var result = chess.move(position.move); | ||
if (position.legal) { | ||
assert(result | ||
&& chess.fen() == position.next | ||
&& result.captured == position.captured); | ||
} else { | ||
assert(!result); | ||
} | ||
}); | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> make move time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
}); | ||
function validate_fen_tests() { | ||
}); | ||
suite("Validate FEN", function() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var positions = [ | ||
@@ -609,108 +824,15 @@ {fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNRw KQkq - 0 1', error_number: 1}, | ||
for (var i = 0; i < positions.length; i++) { | ||
var s = 'validate fen test #' + i + ': ' + positions[i].fen + | ||
' (valid: ' + (positions[i].error_number == 0) + ') : '; | ||
var result = chess.validate_fen(positions[i].fen); | ||
s += (result.error_number == positions[i].error_number) ? | ||
'PASSED!' : 'FAILED! (' + result.error_number + ')'; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> validate fen time: ' + diff + ' secs'); | ||
log(''); | ||
positions.forEach(function(position) { | ||
test(position.fen + ' (valid: ' + (position.error_number == 0) + ')', function() { | ||
var result = chess.validate_fen(position.fen); | ||
assert(result.error_number == position.error_number, result.error_number); | ||
}); | ||
} | ||
}); | ||
}); | ||
function load_pgn_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var tests = [ | ||
{pgn: [ | ||
'[Event "Reykjavik WCh"]', | ||
'[Site "Reykjavik WCh"]', | ||
'[Date "1972.01.07"]', | ||
'[EventDate "?"]', | ||
'[Round "6"]', | ||
'[Result "1-0"]', | ||
'[White "Robert James Fischer"]', | ||
'[Black "Boris Spassky"]', | ||
'[ECO "D59"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "81"]', | ||
'', | ||
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6', | ||
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6', | ||
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7', | ||
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7', | ||
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5', | ||
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7', | ||
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8', | ||
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0'] | ||
}, | ||
{fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17', | ||
pgn: [ | ||
'[Event "Paris"]', | ||
'[Site "Paris"]', | ||
'[Date "1858.??.??"]', | ||
'[EventDate "?"]', | ||
'[Round "?"]', | ||
'[Result "1-0"]', | ||
'[White "Paul Morphy"]', | ||
'[Black "Duke Karl / Count Isouard"]', | ||
'[ECO "C41"]', | ||
'[WhiteElo "?"]', | ||
'[BlackElo "?"]', | ||
'[PlyCount "33"]', | ||
'', | ||
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 {This is a weak move', | ||
'already.--Fischer} 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Bc4 Nf6 7.Qb3 Qe7', | ||
'8.Nc3 c6 9.Bg5 {Black is in what\'s like a zugzwang position', | ||
'here. He can\'t develop the [Queen\'s] knight because the pawn', | ||
'is hanging, the bishop is blocked because of the', | ||
'Queen.--Fischer} b5 10.Nxb5 cxb5 11.Bxb5+ Nbd7 12.O-O-O Rd8', | ||
'13.Rxd7 Rxd7 14.Rd1 Qe6 15.Bxd7+ Nxd7 16.Qb8+ Nxb8 17.Rd8# 1-0']}, | ||
{pgn: [ | ||
'1. e4 e5 2. f4 exf4 3. Nf3 g5 4. h4 g4 5. Ne5 Nf6 6. Nxg4 Nxe4', | ||
'7. d3 Ng3 8. Bxf4 Nxh1 9. Qe2+ Qe7 10. Nf6+ Kd8 11. Bxc7+ Kxc7', | ||
'12. Nd5+ Kd8 13. Nxe7 Bxe7 14. Qg4 d6 15. Qf4 Rg8 16. Qxf7 Bxh4+', | ||
'17. Kd2 Re8 18. Na3 Na6 19. Qh5 Bf6 20. Qxh1 Bxb2 21. Qh4+ Kd7', | ||
'22. Rb1 Bxa3 23. Qa4+']}, | ||
]; | ||
suite("History", function() { | ||
var newline_chars = ['\n', '<br />', '\n\r', 'BLAH']; | ||
for (var i = 0; i < tests.length; i++) { | ||
for (var j = 0; j < newline_chars.length; j++) { | ||
var newline = newline_chars[j]; | ||
var s = 'load pgn test #' + i + String.fromCharCode(97 + j) + ': '; | ||
var result = chess.load_pgn(tests[i].pgn.join(newline), { newline_char: newline }); | ||
/* some PGN's tests contain comments which are stripped during parsing, | ||
* so we'll need compare the results of the load against a FEN string | ||
* (instead of the reconstructed PGN [e.g. tests[i].pgn.join(newline)]) | ||
*/ | ||
if ('fen' in tests[i]) { | ||
s += (result && chess.fen() == tests[i].fen) ? 'PASSED!' : 'FAILED'; | ||
} else { | ||
s += (result && chess.pgn({ max_width: 65, newline_char: newline }) == | ||
tests[i].pgn.join(newline)) ? 'PASSED!' : 'FAILED!'; | ||
} | ||
log(s); | ||
} | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> validate fen time: ' + diff + ' secs'); | ||
log(''); | ||
} | ||
function history_tests() { | ||
var chess = new Chess(); | ||
var start = new Date; | ||
var tests = [ | ||
@@ -814,79 +936,55 @@ {verbose: false, | ||
for (var i = 0; i < tests.length; i++) { | ||
tests.forEach(function(t, i) { | ||
var passed = true; | ||
var s = 'history test #' + i + ': '; | ||
chess.reset(); | ||
for (var j = 0; j < tests[i].moves.length; j++) { | ||
chess.move(tests[i].moves[j]) | ||
} | ||
var history = chess.history({verbose: tests[i].verbose}); | ||
if (tests[i].fen != chess.fen()) { | ||
passed = false; | ||
} else if (history.length != tests[i].moves.length) { | ||
passed = false; | ||
} else { | ||
for (var j = 0; j < tests[i].moves.length; j++) { | ||
if (!tests[i].verbose) { | ||
if (history[j] != tests[i].moves[j]) { | ||
passed = false; | ||
break; | ||
} | ||
} else { | ||
for (var key in history[j]) { | ||
if (history[j][key] != tests[i].moves[j][key]) { | ||
test(i, function() { | ||
chess.reset(); | ||
for (var j = 0; j < t.moves.length; j++) { | ||
chess.move(t.moves[j]) | ||
} | ||
var history = chess.history({verbose: t.verbose}); | ||
if (t.fen != chess.fen()) { | ||
passed = false; | ||
} else if (history.length != t.moves.length) { | ||
passed = false; | ||
} else { | ||
for (var j = 0; j < t.moves.length; j++) { | ||
if (!t.verbose) { | ||
if (history[j] != t.moves[j]) { | ||
passed = false; | ||
break; | ||
} | ||
} else { | ||
for (var key in history[j]) { | ||
if (history[j][key] != t.moves[j][key]) { | ||
passed = false; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
assert(passed); | ||
}); | ||
s += passed ? 'PASSED!' : 'FAILED!'; | ||
log(s); | ||
} | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('--> history test time: ' + diff + ' secs'); | ||
log(''); | ||
}); | ||
}); | ||
} | ||
suite('Regression Tests', function() { | ||
// Github Issue #32 reported by AlgoTrader | ||
test('Issue #32 - castling flag reappearing', function() { | ||
var chess = new Chess('b3k2r/5p2/4p3/1p5p/6p1/2PR2P1/BP3qNP/6QK b k - 2 28'); | ||
chess.move({from:'a8', to:'g2'}); | ||
assert(chess.fen() == '4k2r/5p2/4p3/1p5p/6p1/2PR2P1/BP3qbP/6QK w k - 0 29'); | ||
}); | ||
function run_unit_tests() { | ||
var start = new Date; | ||
if (!is_node) { | ||
var console = document.getElementById('console'); | ||
if (console == null) { | ||
alert('Can\'t locate console. Aborting.'); | ||
return | ||
} | ||
} | ||
perft_unit_tests(); | ||
checkmate_unit_tests(); | ||
stalemate_unit_tests(); | ||
insufficient_material_unit_test(); | ||
threefold_repetition_unit_test(); | ||
algebraic_notation_tests(); | ||
get_put_remove_tests(); | ||
fen_tests(); | ||
pgn_tests(); | ||
load_pgn_tests(); | ||
make_move_tests(); | ||
validate_fen_tests(); | ||
history_tests(); | ||
var finish = new Date; | ||
var diff = (finish - start) / 1000; | ||
log('Total Time: ' + diff + ' secs'); | ||
} | ||
if (is_node) { | ||
run_unit_tests(); | ||
} | ||
test('Issue #58 - placing more than one king', function() { | ||
var chess = new Chess('N3k3/8/8/8/8/8/5b2/4K3 w - - 0 1'); | ||
assert(chess.put({type: 'k', color: 'w'}, 'a1') == false); | ||
chess.put({type: 'q', color: 'w'}, 'a1'); | ||
chess.remove('a1'); | ||
assert(chess.moves().join(' ') == 'Kd2 Ke2 Kxf2 Kf1 Kd1'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
348610
14
0
10102
505
0
2
11
3