Last active
December 22, 2021 14:38
-
-
Save Mon-Ouie/e69a0cb2e2c8ed369b176483696e240e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2021, 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. | |
* | |
*----------------------------------------------------------------------------*/ | |
var Chess = function(fen) { | |
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] | |
} | |
// prettier-ignore | |
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 | |
]; | |
// prettier-ignore | |
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 | |
// prettier-ignore | |
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: 0, b: 0 } | |
var ep_square = EMPTY | |
var half_moves = 0 | |
var move_number = 1 | |
var history = [] | |
var header = {} | |
var comments = {} | |
/* 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(keep_headers) { | |
if (typeof keep_headers === 'undefined') { | |
keep_headers = false | |
} | |
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 = [] | |
if (!keep_headers) header = {} | |
comments = {} | |
update_setup(generate_fen()) | |
} | |
function prune_comments() { | |
var reversed_history = []; | |
var current_comments = {}; | |
var copy_comment = function(fen) { | |
if (fen in comments) { | |
current_comments[fen] = comments[fen]; | |
} | |
}; | |
while (history.length > 0) { | |
reversed_history.push(undo_move()); | |
} | |
copy_comment(generate_fen()); | |
while (reversed_history.length > 0) { | |
make_move(reversed_history.pop()); | |
copy_comment(generate_fen()); | |
} | |
comments = current_comments; | |
} | |
function reset() { | |
load(DEFAULT_POSITION) | |
} | |
function load(fen, keep_headers) { | |
if (typeof keep_headers === 'undefined') { | |
keep_headers = false | |
} | |
var tokens = fen.split(/\s+/) | |
var position = tokens[0] | |
var square = 0 | |
if (!validate_fen(fen).valid) { | |
return false | |
} | |
clear(keep_headers) | |
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) | |
/* double square */ | |
var square = i + PAWN_OFFSETS[us][1] | |
if (second_rank[us] === rank(i) && board[square] == null) { | |
add_move(board, moves, i, square, BITS.BIG_PAWN) | |
} | |
} | |
/* 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 | |
} else if ( | |
/* k vs. kn .... or .... k vs. kb */ | |
num_pieces === 3 && | |
(pieces[BISHOP] === 1 || pieces[KNIGHT] === 1) | |
) { | |
return true | |
} else if (num_pieces === pieces[BISHOP] + 2) { | |
/* kb vs. kb where any number of bishops are all on the same color */ | |
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) | |
} else if (same_file > 0) { | |
/* if the moving piece rests on the same file, use the rank symbol as the | |
* disambiguator | |
*/ | |
return algebraic(from).charAt(1) | |
} else { | |
/* else use the file symbol */ | |
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 | |
}, | |
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) | |
} | |
var append_comment = function(move_string) { | |
var comment = comments[generate_fen()] | |
if (typeof comment !== 'undefined') { | |
var delimiter = move_string.length > 0 ? ' ' : ''; | |
move_string = `${move_string}${delimiter}{${comment}}` | |
} | |
return move_string | |
} | |
/* pop all of history onto reversed_history */ | |
var reversed_history = [] | |
while (history.length > 0) { | |
reversed_history.push(undo_move()) | |
} | |
var moves = [] | |
var move_string = '' | |
/* special case of a commented starting position with no moves */ | |
if (reversed_history.length === 0) { | |
moves.push(append_comment('')) | |
} | |
/* build the list of moves. a move_string looks like: "3. e3 e6" */ | |
while (reversed_history.length > 0) { | |
move_string = append_comment(move_string) | |
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(append_comment(move_string)) | |
} | |
/* is there a result? */ | |
if (typeof header.Result !== 'undefined') { | |
moves.push(header.Result) | |
} | |
/* history should be back to what it was before we started generating PGN, | |
* so join together moves | |
*/ | |
if (max_width === 0) { | |
return result.join('') + moves.join(' ') | |
} | |
var strip = function() { | |
if (result.length > 0 && result[result.length - 1] === ' ') { | |
result.pop(); | |
return true; | |
} | |
return false; | |
}; | |
/* NB: this does not preserve comment whitespace. */ | |
var wrap_comment = function(width, move) { | |
for (var token of move.split(' ')) { | |
if (!token) { | |
continue; | |
} | |
if (width + token.length > max_width) { | |
while (strip()) { | |
width--; | |
} | |
result.push(newline); | |
width = 0; | |
} | |
result.push(token); | |
width += token.length; | |
result.push(' '); | |
width++; | |
} | |
if (strip()) { | |
width--; | |
} | |
return width; | |
}; | |
/* wrap the PGN output at max_width */ | |
var current_width = 0 | |
for (var i = 0; i < moves.length; i++) { | |
if (current_width + moves[i].length > max_width) { | |
if (moves[i].includes('{')) { | |
current_width = wrap_comment(current_width, moves[i]); | |
continue; | |
} | |
} | |
/* 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' | |
// RegExp to split header. Takes advantage of the fact that header and movetext | |
// will always have a blank line between them (ie, two newline_char's). | |
// With default newline_char, will equal: /^(\[((?:\r?\n)|.)*\])(?:\r?\n){2}/ | |
var header_regex = new RegExp( | |
'^(\\[((?:' + | |
mask(newline_char) + | |
')|.)*\\])' + | |
'(?:' + | |
mask(newline_char) + | |
'){2}' | |
) | |
// If no header given, begin with moves. | |
var header_string = header_regex.test(pgn) | |
? header_regex.exec(pgn)[1] | |
: '' | |
// Put the board in the starting position | |
reset() | |
/* parse PGN header */ | |
var headers = parse_pgn_header(header_string, options) | |
for (var key in headers) { | |
set_header([key, headers[key]]) | |
} | |
/* load the starting position indicated by [Setup '1'] and | |
* [FEN position] */ | |
if (headers['SetUp'] === '1') { | |
if (!('FEN' in headers && load(headers['FEN'], true))) { | |
// second argument to load: don't clear the headers | |
return false | |
} | |
} | |
/* NB: the regexes below that delete move numbers, recursive | |
* annotations, and numeric annotation glyphs may also match | |
* text in comments. To prevent this, we transform comments | |
* by hex-encoding them in place and decoding them again after | |
* the other tokens have been deleted. | |
* | |
* While the spec states that PGN files should be ASCII encoded, | |
* we use {en,de}codeURIComponent here to support arbitrary UTF8 | |
* as a convenience for modern users */ | |
var to_hex = function(string) { | |
return Array | |
.from(string) | |
.map(function(c) { | |
/* encodeURI doesn't transform most ASCII characters, | |
* so we handle these ourselves */ | |
return c.charCodeAt(0) < 128 | |
? c.charCodeAt(0).toString(16) | |
: encodeURIComponent(c).replace(/\%/g, '').toLowerCase() | |
}) | |
.join('') | |
} | |
var from_hex = function(string) { | |
return string.length == 0 | |
? '' | |
: decodeURIComponent('%' + string.match(/.{1,2}/g).join('%')) | |
} | |
var encode_comment = function(string) { | |
string = string.replace(new RegExp(mask(newline_char), 'g'), ' ') | |
return `{${to_hex(string.slice(1, string.length - 1))}}` | |
} | |
var decode_comment = function(string) { | |
if (string.startsWith('{') && string.endsWith('}')) { | |
return from_hex(string.slice(1, string.length - 1)) | |
} | |
} | |
/* delete header to get the moves */ | |
var ms = pgn | |
.replace(header_string, '') | |
.replace( | |
/* encode comments so they don't get deleted below */ | |
new RegExp(`(\{[^}]*\})+?|;([^${mask(newline_char)}]*)`, 'g'), | |
function(match, bracket, semicolon) { | |
return bracket !== undefined | |
? encode_comment(bracket) | |
: ' ' + encode_comment(`{${semicolon.slice(1)}}`) | |
} | |
) | |
.replace(new RegExp(mask(newline_char), '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++) { | |
var comment = decode_comment(moves[half_move]) | |
if (comment !== undefined) { | |
comments[generate_fen()] = comment | |
continue | |
} | |
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) | |
} | |
} | |
comment = decode_comment(moves[moves.length - 1]) | |
if (comment !== undefined) { | |
comments[generate_fen()] = comment | |
moves.pop() | |
} | |
/* 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 | |
}, | |
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 | |
}, | |
get_comment: function() { | |
return comments[generate_fen()]; | |
}, | |
set_comment: function(comment) { | |
comments[generate_fen()] = comment.replace('{', '[').replace('}', ']'); | |
}, | |
delete_comment: function() { | |
var comment = comments[generate_fen()]; | |
delete comments[generate_fen()]; | |
return comment; | |
}, | |
get_comments: function() { | |
prune_comments(); | |
return Object.keys(comments).map(function(fen) { | |
return {fen: fen, comment: comments[fen]}; | |
}); | |
}, | |
delete_comments: function() { | |
prune_comments(); | |
return Object.keys(comments) | |
.map(function(fen) { | |
var comment = comments[fen]; | |
delete comments[fen]; | |
return {fen: fen, comment: comment}; | |
}); | |
} | |
} | |
} | |
/* 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 | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.cg-wrap { | |
box-sizing: content-box; | |
position: relative; | |
display: block; | |
} | |
cg-container { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
display: block; | |
top: 0; | |
} | |
cg-board { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
line-height: 0; | |
background-size: cover; | |
cursor: pointer; | |
} | |
cg-board square { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 12.5%; | |
height: 12.5%; | |
pointer-events: none; | |
} | |
cg-board square.move-dest { | |
pointer-events: auto; | |
} | |
cg-board square.last-move { | |
will-change: transform; | |
} | |
.cg-wrap piece { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 12.5%; | |
height: 12.5%; | |
background-size: cover; | |
z-index: 2; | |
will-change: transform; | |
pointer-events: none; | |
} | |
cg-board piece.dragging { | |
cursor: move; | |
/* !important to override z-index from 3D piece inline style */ | |
z-index: 11 !important; | |
} | |
piece.anim { | |
z-index: 8; | |
} | |
piece.fading { | |
z-index: 1; | |
opacity: 0.5; | |
} | |
.cg-wrap piece.ghost { | |
opacity: 0.3; | |
} | |
.cg-wrap piece svg { | |
overflow: hidden; | |
position: relative; | |
top: 0px; | |
left: 0px; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
z-index: 2; | |
opacity: 0.6; | |
} | |
.cg-wrap piece svg image { | |
opacity: 0.5; | |
} | |
.cg-wrap .cg-shapes, | |
.cg-wrap .cg-custom-svgs { | |
overflow: hidden; | |
position: absolute; | |
top: 0px; | |
left: 0px; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
} | |
.cg-wrap .cg-shapes { | |
opacity: 0.6; | |
z-index: 2; | |
} | |
.cg-wrap .cg-custom-svgs { | |
/* over piece.anim = 8, but under piece.dragging = 11 */ | |
z-index: 9; | |
overflow: visible; | |
} | |
.cg-wrap .cg-custom-svgs svg { | |
overflow: visible; | |
} | |
.cg-wrap coords { | |
position: absolute; | |
display: flex; | |
pointer-events: none; | |
opacity: 0.8; | |
font-family: sans-serif; | |
font-size: 9px; | |
} | |
.cg-wrap coords.ranks { | |
left: 4px; | |
top: -20px; | |
flex-flow: column-reverse; | |
height: 100%; | |
width: 12px; | |
} | |
.cg-wrap coords.ranks.black { | |
flex-flow: column; | |
} | |
.cg-wrap coords.files { | |
bottom: -4px; | |
left: 24px; | |
flex-flow: row; | |
width: 100%; | |
height: 16px; | |
text-transform: uppercase; | |
text-align: center; | |
} | |
.cg-wrap coords.files.black { | |
flex-flow: row-reverse; | |
} | |
.cg-wrap coords coord { | |
flex: 1 1 auto; | |
} | |
.cg-wrap coords.ranks coord { | |
transform: translateY(39%); | |
} | |
/** Colored board squares as an embedded SVG */ | |
cg-board { | |
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICAgIHZpZXdCb3g9IjAgMCA4IDgiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CjxnIGlkPSJhIj4KICA8ZyBpZD0iYiI+CiAgICA8ZyBpZD0iYyI+CiAgICAgIDxnIGlkPSJkIj4KICAgICAgICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjZGVlM2U2IiBpZD0iZSIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iMSIgaHJlZj0iI2UiIHg6aHJlZj0iI2UiLz4KICAgICAgICA8cmVjdCB5PSIxIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjOGNhMmFkIiBpZD0iZiIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iLTEiIGhyZWY9IiNmIiB4OmhyZWY9IiNmIi8+CiAgICAgIDwvZz4KICAgICAgPHVzZSB4PSIyIiBocmVmPSIjZCIgeDpocmVmPSIjZCIvPgogICAgPC9nPgogICAgPHVzZSB4PSI0IiBocmVmPSIjYyIgeDpocmVmPSIjYyIvPgogIDwvZz4KICA8dXNlIHk9IjIiIGhyZWY9IiNiIiB4OmhyZWY9IiNiIi8+CjwvZz4KPHVzZSB5PSI0IiBocmVmPSIjYSIgeDpocmVmPSIjYSIvPgo8L3N2Zz4K'); | |
} | |
/** Interactive board square colors */ | |
cg-board square.move-dest { | |
background: radial-gradient(rgba(20, 85, 30, 0.5) 22%, #208530 0, rgba(0, 0, 0, 0.3) 0, rgba(0, 0, 0, 0) 0); | |
} | |
cg-board square.premove-dest { | |
background: radial-gradient(rgba(20, 30, 85, 0.5) 22%, #203085 0, rgba(0, 0, 0, 0.3) 0, rgba(0, 0, 0, 0) 0); | |
} | |
cg-board square.oc.move-dest { | |
background: radial-gradient(transparent 0%, transparent 80%, rgba(20, 85, 0, 0.3) 80%); | |
} | |
cg-board square.oc.premove-dest { | |
background: radial-gradient(transparent 0%, transparent 80%, rgba(20, 30, 85, 0.2) 80%); | |
} | |
cg-board square.move-dest:hover { | |
background: rgba(20, 85, 30, 0.3); | |
} | |
cg-board square.premove-dest:hover { | |
background: rgba(20, 30, 85, 0.2); | |
} | |
cg-board square.last-move { | |
background-color: rgba(155, 199, 0, 0.41); | |
} | |
cg-board square.selected { | |
background-color: rgba(20, 85, 30, 0.5); | |
} | |
cg-board square.check { | |
background: radial-gradient( | |
ellipse at center, | |
rgba(255, 0, 0, 1) 0%, | |
rgba(231, 0, 0, 1) 25%, | |
rgba(169, 0, 0, 0) 89%, | |
rgba(158, 0, 0, 0) 100% | |
); | |
} | |
cg-board square.current-premove { | |
background-color: rgba(20, 30, 85, 0.5); | |
} | |
/** Alternating colors in rank/file labels */ | |
.cg-wrap.orientation-white coords.ranks coord:nth-child(2n), | |
.cg-wrap.orientation-white coords.files coord:nth-child(2n), | |
.cg-wrap.orientation-black coords.ranks coord:nth-child(2n + 1), | |
.cg-wrap.orientation-black coords.files coord:nth-child(2n + 1) { | |
color: #b58863; | |
} | |
.cg-wrap.orientation-black coords.ranks coord:nth-child(2n), | |
.cg-wrap.orientation-black coords.files coord:nth-child(2n), | |
.cg-wrap.orientation-white coords.ranks coord:nth-child(2n + 1), | |
.cg-wrap.orientation-white coords.files coord:nth-child(2n + 1) { | |
color: #f0d9b5; | |
} | |
/** Embedded SVGs for all chess pieces */ | |
.cg-wrap piece.pawn.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PHBhdGggZD0iTTIyLjUgOWMtMi4yMSAwLTQgMS43OS00IDQgMCAuODkuMjkgMS43MS43OCAyLjM4QzE3LjMzIDE2LjUgMTYgMTguNTkgMTYgMjFjMCAyLjAzLjk0IDMuODQgMi40MSA1LjAzLTMgMS4wNi03LjQxIDUuNTUtNy40MSAxMy40N2gyM2MwLTcuOTItNC40MS0xMi40MS03LjQxLTEzLjQ3IDEuNDctMS4xOSAyLjQxLTMgMi40MS01LjAzIDAtMi40MS0xLjMzLTQuNS0zLjI4LTUuNjIuNDktLjY3Ljc4LTEuNDkuNzgtMi4zOCAwLTIuMjEtMS43OS00LTQtNHoiIGZpbGw9IiNmZmYiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.bishop.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxnIGZpbGw9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJidXR0Ij48cGF0aCBkPSJNOSAzNmMzLjM5LS45NyAxMC4xMS40MyAxMy41LTIgMy4zOSAyLjQzIDEwLjExIDEuMDMgMTMuNSAyIDAgMCAxLjY1LjU0IDMgMi0uNjguOTctMS42NS45OS0zIC41LTMuMzktLjk3LTEwLjExLjQ2LTEzLjUtMS0zLjM5IDEuNDYtMTAuMTEuMDMtMTMuNSAxLTEuMzU0LjQ5LTIuMzIzLjQ3LTMtLjUgMS4zNTQtMS45NCAzLTIgMy0yeiIvPjxwYXRoIGQ9Ik0xNSAzMmMyLjUgMi41IDEyLjUgMi41IDE1IDAgLjUtMS41IDAtMiAwLTIgMC0yLjUtMi41LTQtMi41LTQgNS41LTEuNSA2LTExLjUtNS0xNS41LTExIDQtMTAuNSAxNC01IDE1LjUgMCAwLTIuNSAxLjUtMi41IDQgMCAwLS41LjUgMCAyeiIvPjxwYXRoIGQ9Ik0yNSA4YTIuNSAyLjUgMCAxIDEtNSAwIDIuNSAyLjUgMCAxIDEgNSAweiIvPjwvZz48cGF0aCBkPSJNMTcuNSAyNmgxME0xNSAzMGgxNW0tNy41LTE0LjV2NU0yMCAxOGg1IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PC9nPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.knight.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMiAxMGMxMC41IDEgMTYuNSA4IDE2IDI5SDE1YzAtOSAxMC02LjUgOC0yMSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0yNCAxOGMuMzggMi45MS01LjU1IDcuMzctOCA5LTMgMi0yLjgyIDQuMzQtNSA0LTEuMDQyLS45NCAxLjQxLTMuMDQgMC0zLTEgMCAuMTkgMS4yMy0xIDItMSAwLTQuMDAzIDEtNC00IDAtMiA2LTEyIDYtMTJzMS44OS0xLjkgMi0zLjVjLS43My0uOTk0LS41LTItLjUtMyAxLTEgMyAyLjUgMyAyLjVoMnMuNzgtMS45OTIgMi41LTNjMSAwIDEgMyAxIDMiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNOS41IDI1LjVhLjUuNSAwIDEgMS0xIDAgLjUuNSAwIDEgMSAxIDB6bTUuNDMzLTkuNzVhLjUgMS41IDMwIDEgMS0uODY2LS41LjUgMS41IDMwIDEgMSAuODY2LjV6IiBmaWxsPSIjMDAwIi8+PC9nPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.rook.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik05IDM5aDI3di0zSDl2M3ptMy0zdi00aDIxdjRIMTJ6bS0xLTIyVjloNHYyaDVWOWg1djJoNVY5aDR2NSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMzQgMTRsLTMgM0gxNGwtMy0zIi8+PHBhdGggZD0iTTMxIDE3djEyLjVIMTRWMTciIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTMxIDI5LjVsMS41IDIuNWgtMjBsMS41LTIuNSIvPjxwYXRoIGQ9Ik0xMSAxNGgyMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjwvZz48L3N2Zz4='); | |
} | |
.cg-wrap piece.queen.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04IDEyYTIgMiAwIDEgMS00IDAgMiAyIDAgMSAxIDQgMHptMTYuNS00LjVhMiAyIDAgMSAxLTQgMCAyIDIgMCAxIDEgNCAwek00MSAxMmEyIDIgMCAxIDEtNCAwIDIgMiAwIDEgMSA0IDB6TTE2IDguNWEyIDIgMCAxIDEtNCAwIDIgMiAwIDEgMSA0IDB6TTMzIDlhMiAyIDAgMSAxLTQgMCAyIDIgMCAxIDEgNCAweiIvPjxwYXRoIGQ9Ik05IDI2YzguNS0xLjUgMjEtMS41IDI3IDBsMi0xMi03IDExVjExbC01LjUgMTMuNS0zLTE1LTMgMTUtNS41LTE0VjI1TDcgMTRsMiAxMnoiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTkgMjZjMCAyIDEuNSAyIDIuNSA0IDEgMS41IDEgMSAuNSAzLjUtMS41IDEtMS41IDIuNS0xLjUgMi41LTEuNSAxLjUuNSAyLjUuNSAyLjUgNi41IDEgMTYuNSAxIDIzIDAgMCAwIDEuNS0xIDAtMi41IDAgMCAuNS0xLjUtMS0yLjUtLjUtMi41LS41LTIgLjUtMy41IDEtMiAyLjUtMiAyLjUtNC04LjUtMS41LTE4LjUtMS41LTI3IDB6IiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0xMS41IDMwYzMuNS0xIDE4LjUtMSAyMiAwTTEyIDMzLjVjNi0xIDE1LTEgMjEgMCIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4='); | |
} | |
.cg-wrap piece.king.white { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMi41IDExLjYzVjZNMjAgOGg1IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIyLjUgMjVzNC41LTcuNSAzLTEwLjVjMCAwLTEtMi41LTMtMi41cy0zIDIuNS0zIDIuNWMtMS41IDMgMyAxMC41IDMgMTAuNSIgZmlsbD0iI2ZmZiIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48cGF0aCBkPSJNMTEuNSAzN2M1LjUgMy41IDE1LjUgMy41IDIxIDB2LTdzOS00LjUgNi0xMC41Yy00LTYuNS0xMy41LTMuNS0xNiA0VjI3di0zLjVjLTMuNS03LjUtMTMtMTAuNS0xNi00LTMgNiA1IDEwIDUgMTBWMzd6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTExLjUgMzBjNS41LTMgMTUuNS0zIDIxIDBtLTIxIDMuNWM1LjUtMyAxNS41LTMgMjEgMG0tMjEgMy41YzUuNS0zIDE1LjUtMyAyMSAwIi8+PC9nPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.pawn.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PHBhdGggZD0iTTIyLjUgOWMtMi4yMSAwLTQgMS43OS00IDQgMCAuODkuMjkgMS43MS43OCAyLjM4QzE3LjMzIDE2LjUgMTYgMTguNTkgMTYgMjFjMCAyLjAzLjk0IDMuODQgMi40MSA1LjAzLTMgMS4wNi03LjQxIDUuNTUtNy40MSAxMy40N2gyM2MwLTcuOTItNC40MS0xMi40MS03LjQxLTEzLjQ3IDEuNDctMS4xOSAyLjQxLTMgMi40MS01LjAzIDAtMi40MS0xLjMzLTQuNS0zLjI4LTUuNjIuNDktLjY3Ljc4LTEuNDkuNzgtMi4zOCAwLTIuMjEtMS43OS00LTQtNHoiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.bishop.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxnIGZpbGw9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJidXR0Ij48cGF0aCBkPSJNOSAzNmMzLjM5LS45NyAxMC4xMS40MyAxMy41LTIgMy4zOSAyLjQzIDEwLjExIDEuMDMgMTMuNSAyIDAgMCAxLjY1LjU0IDMgMi0uNjguOTctMS42NS45OS0zIC41LTMuMzktLjk3LTEwLjExLjQ2LTEzLjUtMS0zLjM5IDEuNDYtMTAuMTEuMDMtMTMuNSAxLTEuMzU0LjQ5LTIuMzIzLjQ3LTMtLjUgMS4zNTQtMS45NCAzLTIgMy0yeiIvPjxwYXRoIGQ9Ik0xNSAzMmMyLjUgMi41IDEyLjUgMi41IDE1IDAgLjUtMS41IDAtMiAwLTIgMC0yLjUtMi41LTQtMi41LTQgNS41LTEuNSA2LTExLjUtNS0xNS41LTExIDQtMTAuNSAxNC01IDE1LjUgMCAwLTIuNSAxLjUtMi41IDQgMCAwLS41LjUgMCAyeiIvPjxwYXRoIGQ9Ik0yNSA4YTIuNSAyLjUgMCAxIDEtNSAwIDIuNSAyLjUgMCAxIDEgNSAweiIvPjwvZz48cGF0aCBkPSJNMTcuNSAyNmgxME0xNSAzMGgxNW0tNy41LTE0LjV2NU0yMCAxOGg1IiBzdHJva2U9IiNlY2VjZWMiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48L2c+PC9zdmc+'); | |
} | |
.cg-wrap piece.knight.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMiAxMGMxMC41IDEgMTYuNSA4IDE2IDI5SDE1YzAtOSAxMC02LjUgOC0yMSIgZmlsbD0iIzAwMCIvPjxwYXRoIGQ9Ik0yNCAxOGMuMzggMi45MS01LjU1IDcuMzctOCA5LTMgMi0yLjgyIDQuMzQtNSA0LTEuMDQyLS45NCAxLjQxLTMuMDQgMC0zLTEgMCAuMTkgMS4yMy0xIDItMSAwLTQuMDAzIDEtNC00IDAtMiA2LTEyIDYtMTJzMS44OS0xLjkgMi0zLjVjLS43My0uOTk0LS41LTItLjUtMyAxLTEgMyAyLjUgMyAyLjVoMnMuNzgtMS45OTIgMi41LTNjMSAwIDEgMyAxIDMiIGZpbGw9IiMwMDAiLz48cGF0aCBkPSJNOS41IDI1LjVhLjUuNSAwIDEgMS0xIDAgLjUuNSAwIDEgMSAxIDB6bTUuNDMzLTkuNzVhLjUgMS41IDMwIDEgMS0uODY2LS41LjUgMS41IDMwIDEgMSAuODY2LjV6IiBmaWxsPSIjZWNlY2VjIiBzdHJva2U9IiNlY2VjZWMiLz48cGF0aCBkPSJNMjQuNTUgMTAuNGwtLjQ1IDEuNDUuNS4xNWMzLjE1IDEgNS42NSAyLjQ5IDcuOSA2Ljc1UzM1Ljc1IDI5LjA2IDM1LjI1IDM5bC0uMDUuNWgyLjI1bC4wNS0uNWMuNS0xMC4wNi0uODgtMTYuODUtMy4yNS0yMS4zNC0yLjM3LTQuNDktNS43OS02LjY0LTkuMTktNy4xNmwtLjUxLS4xeiIgZmlsbD0iI2VjZWNlYyIgc3Ryb2tlPSJub25lIi8+PC9nPjwvc3ZnPg=='); | |
} | |
.cg-wrap piece.rook.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik05IDM5aDI3di0zSDl2M3ptMy41LTdsMS41LTIuNWgxN2wxLjUgMi41aC0yMHptLS41IDR2LTRoMjF2NEgxMnoiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTE0IDI5LjV2LTEzaDE3djEzSDE0eiIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48cGF0aCBkPSJNMTQgMTYuNUwxMSAxNGgyM2wtMyAyLjVIMTR6TTExIDE0VjloNHYyaDVWOWg1djJoNVY5aDR2NUgxMXoiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTEyIDM1LjVoMjFtLTIwLTRoMTltLTE4LTJoMTdtLTE3LTEzaDE3TTExIDE0aDIzIiBmaWxsPSJub25lIiBzdHJva2U9IiNlY2VjZWMiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjwvZz48L3N2Zz4='); | |
} | |
.cg-wrap piece.queen.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxnIHN0cm9rZT0ibm9uZSI+PGNpcmNsZSBjeD0iNiIgY3k9IjEyIiByPSIyLjc1Ii8+PGNpcmNsZSBjeD0iMTQiIGN5PSI5IiByPSIyLjc1Ii8+PGNpcmNsZSBjeD0iMjIuNSIgY3k9IjgiIHI9IjIuNzUiLz48Y2lyY2xlIGN4PSIzMSIgY3k9IjkiIHI9IjIuNzUiLz48Y2lyY2xlIGN4PSIzOSIgY3k9IjEyIiByPSIyLjc1Ii8+PC9nPjxwYXRoIGQ9Ik05IDI2YzguNS0xLjUgMjEtMS41IDI3IDBsMi41LTEyLjVMMzEgMjVsLS4zLTE0LjEtNS4yIDEzLjYtMy0xNC41LTMgMTQuNS01LjItMTMuNkwxNCAyNSA2LjUgMTMuNSA5IDI2eiIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNOSAyNmMwIDIgMS41IDIgMi41IDQgMSAxLjUgMSAxIC41IDMuNS0xLjUgMS0xLjUgMi41LTEuNSAyLjUtMS41IDEuNS41IDIuNS41IDIuNSA2LjUgMSAxNi41IDEgMjMgMCAwIDAgMS41LTEgMC0yLjUgMCAwIC41LTEuNS0xLTIuNS0uNS0yLjUtLjUtMiAuNS0zLjUgMS0yIDIuNS0yIDIuNS00LTguNS0xLjUtMTguNS0xLjUtMjcgMHoiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTExIDM4LjVhMzUgMzUgMSAwIDAgMjMgMCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMTEgMjlhMzUgMzUgMSAwIDEgMjMgMG0tMjEuNSAyLjVoMjBtLTIxIDNhMzUgMzUgMSAwIDAgMjIgMG0tMjMgM2EzNSAzNSAxIDAgMCAyNCAwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlY2VjZWMiLz48L2c+PC9zdmc+'); | |
} | |
.cg-wrap piece.king.black { | |
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMi41IDExLjYzVjYiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48cGF0aCBkPSJNMjIuNSAyNXM0LjUtNy41IDMtMTAuNWMwIDAtMS0yLjUtMy0yLjVzLTMgMi41LTMgMi41Yy0xLjUgMyAzIDEwLjUgMyAxMC41IiBmaWxsPSIjMDAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0xMS41IDM3YzUuNSAzLjUgMTUuNSAzLjUgMjEgMHYtN3M5LTQuNSA2LTEwLjVjLTQtNi41LTEzLjUtMy41LTE2IDRWMjd2LTMuNWMtMy41LTcuNS0xMy0xMC41LTE2LTQtMyA2IDUgMTAgNSAxMFYzN3oiIGZpbGw9IiMwMDAiLz48cGF0aCBkPSJNMjAgOGg1IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTMyIDI5LjVzOC41LTQgNi4wMy05LjY1QzM0LjE1IDE0IDI1IDE4IDIyLjUgMjQuNWwuMDEgMi4xLS4wMS0yLjFDMjAgMTggOS45MDYgMTQgNi45OTcgMTkuODVjLTIuNDk3IDUuNjUgNC44NTMgOSA0Ljg1MyA5IiBzdHJva2U9IiNlY2VjZWMiLz48cGF0aCBkPSJNMTEuNSAzMGM1LjUtMyAxNS41LTMgMjEgMG0tMjEgMy41YzUuNS0zIDE1LjUtMyAyMSAwbS0yMSAzLjVjNS41LTMgMTUuNS0zIDIxIDAiIHN0cm9rZT0iI2VjZWNlYyIvPjwvZz48L3N2Zz4='); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Chessground;(()=>{"use strict";var e={d:(t,o)=>{for(var n in o)e.o(o,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:o[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};(()=>{e.r(t),e.d(t,{Chessground:()=>Ve});const o=["white","black"],n=["a","b","c","d","e","f","g","h"],r=["1","2","3","4","5","6","7","8"],s=[...r].reverse(),i=Array.prototype.concat(...n.map((e=>r.map((t=>e+t))))),c=e=>i[8*e[0]+e[1]],a=e=>[e.charCodeAt(0)-97,e.charCodeAt(1)-49],l=i.map(a),d=()=>{let e;return{start(){e=performance.now()},cancel(){e=void 0},stop(){if(!e)return 0;const t=performance.now()-e;return e=void 0,t}}},u=e=>"white"===e?"black":"white",p=(e,t)=>{const o=e[0]-t[0],n=e[1]-t[1];return o*o+n*n},f=(e,t)=>e.role===t.role&&e.color===t.color,g=e=>(t,o)=>[(o?t[0]:7-t[0])*e.width/8,(o?7-t[1]:t[1])*e.height/8],h=(e,t)=>{e.style.transform=`translate(${t[0]}px,${t[1]}px)`},m=(e,t)=>{e.style.visibility=t?"visible":"hidden"},v=e=>{var t;return e.clientX||0===e.clientX?[e.clientX,e.clientY]:(null===(t=e.targetTouches)||void 0===t?void 0:t[0])?[e.targetTouches[0].clientX,e.targetTouches[0].clientY]:void 0},b=e=>2===e.buttons||2===e.button,w=(e,t)=>{const o=document.createElement(e);return t&&(o.className=t),o};function y(e,t,o){const n=a(e);return t||(n[0]=7-n[0],n[1]=7-n[1]),[o.left+o.width*n[0]/8+o.width/16,o.top+o.height*(7-n[1])/8+o.height/16]}function k(e,t){return Math.abs(e-t)}const C=(e,t,o,n)=>{const r=k(e,o),s=k(t,n);return 1===r&&2===s||2===r&&1===s},M=(e,t,o,n)=>k(e,o)===k(t,n),S=(e,t,o,n)=>e===o||t===n,P=(e,t,o,n)=>M(e,t,o,n)||S(e,t,o,n);function x(e,t,o){const n=e.get(t);if(!n)return[];const r=a(t),s=n.role,i="pawn"===s?(d=n.color,(e,t,o,n)=>k(e,o)<2&&("white"===d?n===t+1||t<=1&&n===t+2&&e===o:n===t-1||t>=6&&n===t-2&&e===o)):"knight"===s?C:"bishop"===s?M:"rook"===s?S:"queen"===s?P:function(e,t,o){return(n,r,s,i)=>k(n,s)<2&&k(r,i)<2||o&&r===i&&r===("white"===e?0:7)&&(4===n&&(2===s&&t.includes(0)||6===s&&t.includes(7))||t.includes(s))}(n.color,function(e,t){const o="white"===t?"1":"8",n=[];for(const[r,s]of e)r[1]===o&&s.color===t&&"rook"===s.role&&n.push(a(r)[0]);return n}(e,n.color),o);var d;return l.filter((e=>(r[0]!==e[0]||r[1]!==e[1])&&i(r[0],r[1],e[0],e[1]))).map(c)}function A(e,...t){e&&setTimeout((()=>e(...t)),1)}function K(e){e.premovable.current&&(e.premovable.current=void 0,A(e.premovable.events.unset))}function T(e){const t=e.predroppable;t.current&&(t.current=void 0,A(t.events.unset))}function q(e,t,o){const n=e.pieces.get(t),r=e.pieces.get(o);if(t===o||!n)return!1;const s=r&&r.color!==n.color?r:void 0;return o===e.selected&&W(e),A(e.events.move,t,o,s),function(e,t,o){if(!e.autoCastle)return!1;const n=e.pieces.get(t);if(!n||"king"!==n.role)return!1;const r=a(t),s=a(o);if(0!==r[1]&&7!==r[1]||r[1]!==s[1])return!1;4!==r[0]||e.pieces.has(o)||(6===s[0]?o=c([7,s[1]]):2===s[0]&&(o=c([0,s[1]])));const i=e.pieces.get(o);return!(!i||i.color!==n.color||"rook"!==i.role||(e.pieces.delete(t),e.pieces.delete(o),r[0]<s[0]?(e.pieces.set(c([6,s[1]]),n),e.pieces.set(c([5,s[1]]),i)):(e.pieces.set(c([2,s[1]]),n),e.pieces.set(c([3,s[1]]),i)),0))}(e,t,o)||(e.pieces.set(o,n),e.pieces.delete(t)),e.lastMove=[t,o],e.check=void 0,A(e.events.change),s||!0}function L(e,t,o,n){if(e.pieces.has(o)){if(!n)return!1;e.pieces.delete(o)}return A(e.events.dropNewPiece,t,o),e.pieces.set(o,t),e.lastMove=[o],e.check=void 0,A(e.events.change),e.movable.dests=void 0,e.turnColor=u(e.turnColor),!0}function O(e,t,o){const n=q(e,t,o);return n&&(e.movable.dests=void 0,e.turnColor=u(e.turnColor),e.animation.current=void 0),n}function N(e,t,o){if(R(e,t,o)){const n=O(e,t,o);if(n){const r=e.hold.stop();W(e);const s={premove:!1,ctrlKey:e.stats.ctrlKey,holdTime:r};return!0!==n&&(s.captured=n),A(e.movable.events.after,t,o,s),!0}}else if(function(e,t,o){return t!==o&&j(e,t)&&x(e.pieces,t,e.premovable.castle).includes(o)}(e,t,o))return function(e,t,o,n){T(e),e.premovable.current=[t,o],A(e.premovable.events.set,t,o,n)}(e,t,o,{ctrlKey:e.stats.ctrlKey}),W(e),!0;return W(e),!1}function D(e,t,o,n){const r=e.pieces.get(t);r&&(function(e,t,o){const n=e.pieces.get(t);return!(!n||t!==o&&e.pieces.has(o)||"both"!==e.movable.color&&(e.movable.color!==n.color||e.turnColor!==n.color))}(e,t,o)||n)?(e.pieces.delete(t),L(e,r,o,n),A(e.movable.events.afterNewPiece,r.role,o,{premove:!1,predrop:!1})):r&&function(e,t,o){const n=e.pieces.get(t),r=e.pieces.get(o);return!!n&&(!r||r.color!==e.movable.color)&&e.predroppable.enabled&&("pawn"!==n.role||"1"!==o[1]&&"8"!==o[1])&&e.movable.color===n.color&&e.turnColor!==n.color}(e,t,o)?function(e,t,o){K(e),e.predroppable.current={role:t,key:o},A(e.predroppable.events.set,t,o)}(e,r.role,o):(K(e),T(e)),e.pieces.delete(t),W(e)}function $(e,t,o){if(A(e.events.select,t),e.selected){if(e.selected===t&&!e.draggable.enabled)return W(e),void e.hold.cancel();if((e.selectable.enabled||o)&&e.selected!==t&&N(e,e.selected,t))return void(e.stats.dragged=!1)}(H(e,t)||j(e,t))&&(E(e,t),e.hold.start())}function E(e,t){e.selected=t,j(e,t)?e.premovable.dests=x(e.pieces,t,e.premovable.castle):e.premovable.dests=void 0}function W(e){e.selected=void 0,e.premovable.dests=void 0,e.hold.cancel()}function H(e,t){const o=e.pieces.get(t);return!!o&&("both"===e.movable.color||e.movable.color===o.color&&e.turnColor===o.color)}function R(e,t,o){var n,r;return t!==o&&H(e,t)&&(e.movable.free||!!(null===(r=null===(n=e.movable.dests)||void 0===n?void 0:n.get(t))||void 0===r?void 0:r.includes(o)))}function j(e,t){const o=e.pieces.get(t);return!!o&&e.premovable.enabled&&e.movable.color===o.color&&e.turnColor!==o.color}function B(e){const t=e.premovable.current;if(!t)return!1;const o=t[0],n=t[1];let r=!1;if(R(e,o,n)){const t=O(e,o,n);if(t){const s={premove:!0};!0!==t&&(s.captured=t),A(e.movable.events.after,o,n,s),r=!0}}return K(e),r}function F(e){K(e),T(e),W(e)}function z(e){e.movable.color=e.movable.dests=e.animation.current=void 0,F(e)}function I(e,t,o){let n=Math.floor(8*(e[0]-o.left)/o.width);t||(n=7-n);let r=7-Math.floor(8*(e[1]-o.top)/o.height);return t||(r=7-r),n>=0&&n<8&&r>=0&&r<8?c([n,r]):void 0}function V(e){return"white"===e.orientation}const G="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",U={p:"pawn",r:"rook",n:"knight",b:"bishop",q:"queen",k:"king"},X={pawn:"p",rook:"r",knight:"n",bishop:"b",queen:"q",king:"k"};function Y(e){"start"===e&&(e=G);const t=new Map;let o=7,n=0;for(const r of e)switch(r){case" ":return t;case"/":if(--o,o<0)return t;n=0;break;case"~":{const e=t.get(c([n,o]));e&&(e.promoted=!0);break}default:{const e=r.charCodeAt(0);if(e<57)n+=e-48;else{const e=r.toLowerCase();t.set(c([n,o]),{role:U[e],color:r===e?"black":"white"}),++n}}}return t}function Z(e,t){t.animation&&(_(e.animation,t.animation),(e.animation.duration||0)<70&&(e.animation.enabled=!1))}function Q(e,t){var o,n;if((null===(o=t.movable)||void 0===o?void 0:o.dests)&&(e.movable.dests=void 0),(null===(n=t.drawable)||void 0===n?void 0:n.autoShapes)&&(e.drawable.autoShapes=[]),_(e,t),t.fen&&(e.pieces=Y(t.fen),e.drawable.shapes=[]),"check"in t&&function(e,t){if(e.check=void 0,!0===t&&(t=e.turnColor),t)for(const[o,n]of e.pieces)"king"===n.role&&n.color===t&&(e.check=o)}(e,t.check||!1),"lastMove"in t&&!t.lastMove?e.lastMove=void 0:t.lastMove&&(e.lastMove=t.lastMove),e.selected&&E(e,e.selected),Z(e,t),!e.movable.rookCastle&&e.movable.dests){const t="white"===e.movable.color?"1":"8",o="e"+t,n=e.movable.dests.get(o),r=e.pieces.get(o);if(!n||!r||"king"!==r.role)return;e.movable.dests.set(o,n.filter((e=>!(e==="a"+t&&n.includes("c"+t)||e==="h"+t&&n.includes("g"+t)))))}}function _(e,t){for(const o in t)J(e[o])&&J(t[o])?_(e[o],t[o]):e[o]=t[o]}function J(e){return"object"==typeof e}function ee(e,t){return t.animation.enabled?function(e,t){const o=new Map(t.pieces),n=e(t),r=function(e,t){const o=new Map,n=[],r=new Map,s=[],c=[],a=new Map;let l,d,u;for(const[t,o]of e)a.set(t,oe(t,o));for(const e of i)l=t.pieces.get(e),d=a.get(e),l?d?f(l,d.piece)||(s.push(d),c.push(oe(e,l))):c.push(oe(e,l)):d&&s.push(d);for(const e of c)d=ne(e,s.filter((t=>f(e.piece,t.piece)))),d&&(u=[d.pos[0]-e.pos[0],d.pos[1]-e.pos[1]],o.set(e.key,u.concat(u)),n.push(d.key));for(const e of s)n.includes(e.key)||r.set(e.key,e.piece);return{anims:o,fadings:r}}(o,t);if(r.anims.size||r.fadings.size){const e=t.animation.current&&t.animation.current.start;t.animation.current={start:performance.now(),frequency:1/t.animation.duration,plan:r},e||re(t,performance.now())}else t.dom.redraw();return n}(e,t):te(e,t)}function te(e,t){const o=e(t);return t.dom.redraw(),o}function oe(e,t){return{key:e,pos:a(e),piece:t}}function ne(e,t){return t.sort(((t,o)=>p(e.pos,t.pos)-p(e.pos,o.pos)))[0]}function re(e,t){const o=e.animation.current;if(void 0===o)return void(e.dom.destroyed||e.dom.redrawNow());const n=1-(t-o.start)*o.frequency;if(n<=0)e.animation.current=void 0,e.dom.redrawNow();else{const t=(r=n)<.5?4*r*r*r:(r-1)*(2*r-2)*(2*r-2)+1;for(const e of o.plan.anims.values())e[2]=e[0]*t,e[3]=e[1]*t;e.dom.redrawNow(!0),requestAnimationFrame(((t=performance.now())=>re(e,t)))}var r}const se=["green","red","blue","yellow"];function ie(e,t){if(t.touches&&t.touches.length>1)return;t.stopPropagation(),t.preventDefault(),t.ctrlKey?W(e):F(e);const o=v(t),n=I(o,V(e),e.dom.bounds());n&&(e.drawable.current={orig:n,pos:o,brush:ue(t),snapToValidMove:e.drawable.defaultSnapToValidMove},ce(e))}function ce(e){requestAnimationFrame((()=>{const t=e.drawable.current;if(t){const o=I(t.pos,V(e),e.dom.bounds());o||(t.snapToValidMove=!1);const n=t.snapToValidMove?function(e,t,o,n){const r=a(e),s=l.filter((e=>P(r[0],r[1],e[0],e[1])||C(r[0],r[1],e[0],e[1]))),i=s.map((e=>y(c(e),o,n))).map((e=>p(t,e))),[,d]=i.reduce(((e,t,o)=>e[0]<t?e:[t,o]),[i[0],0]);return c(s[d])}(t.orig,t.pos,V(e),e.dom.bounds()):o;n!==t.mouseSq&&(t.mouseSq=n,t.dest=n!==t.orig?n:void 0,e.dom.redrawNow()),ce(e)}}))}function ae(e,t){e.drawable.current&&(e.drawable.current.pos=v(t))}function le(e){const t=e.drawable.current;t&&(t.mouseSq&&function(e,t){const o=e=>e.orig===t.orig&&e.dest===t.dest,n=e.shapes.find(o);n&&(e.shapes=e.shapes.filter((e=>!o(e)))),n&&n.brush===t.brush||e.shapes.push(t),pe(e)}(e.drawable,t),de(e))}function de(e){e.drawable.current&&(e.drawable.current=void 0,e.dom.redraw())}function ue(e){var t;const o=(e.shiftKey||e.ctrlKey)&&b(e),n=e.altKey||e.metaKey||(null===(t=e.getModifierState)||void 0===t?void 0:t.call(e,"AltGraph"));return se[(o?1:0)+(n?2:0)]}function pe(e){e.onChange&&e.onChange(e.shapes)}function fe(e){requestAnimationFrame((()=>{var t;const o=e.draggable.current;if(!o)return;(null===(t=e.animation.current)||void 0===t?void 0:t.plan.anims.has(o.orig))&&(e.animation.current=void 0);const n=e.pieces.get(o.orig);if(n&&f(n,o.piece)){if(!o.started&&p(o.pos,o.origPos)>=Math.pow(e.draggable.distance,2)&&(o.started=!0),o.started){if("function"==typeof o.element){const e=o.element();if(!e)return;e.cgDragging=!0,e.classList.add("dragging"),o.element=e}const t=e.dom.bounds();h(o.element,[o.pos[0]-t.left-t.width/16,o.pos[1]-t.top-t.height/16]),o.keyHasChanged||(o.keyHasChanged=o.orig!==I(o.pos,V(e),t))}}else me(e);fe(e)}))}function ge(e,t){e.draggable.current&&(!t.touches||t.touches.length<2)&&(e.draggable.current.pos=v(t))}function he(e,t){const o=e.draggable.current;if(!o)return;if("touchend"===t.type&&!1!==t.cancelable&&t.preventDefault(),"touchend"===t.type&&o.originTarget!==t.target&&!o.newPiece)return void(e.draggable.current=void 0);K(e),T(e);const n=I(v(t)||o.pos,V(e),e.dom.bounds());n&&o.started&&o.orig!==n?o.newPiece?D(e,o.orig,n,o.force):(e.stats.ctrlKey=t.ctrlKey,N(e,o.orig,n)&&(e.stats.dragged=!0)):o.newPiece?e.pieces.delete(o.orig):e.draggable.deleteOnDropOff&&!n&&(e.pieces.delete(o.orig),A(e.events.change)),(o.orig!==o.previouslySelected&&!o.keyHasChanged||o.orig!==n&&n)&&e.selectable.enabled||W(e),ve(e),e.draggable.current=void 0,e.dom.redraw()}function me(e){const t=e.draggable.current;t&&(t.newPiece&&e.pieces.delete(t.orig),e.draggable.current=void 0,W(e),ve(e),e.dom.redraw())}function ve(e){const t=e.dom.elements;t.ghost&&m(t.ghost,!1)}function be(e,t){let o=e.dom.elements.board.firstChild;for(;o;){if(o.cgKey===t&&"PIECE"===o.tagName)return o;o=o.nextSibling}}function we(e,t){e.exploding&&(t?e.exploding.stage=t:e.exploding=void 0,e.dom.redraw())}function ye(e){return document.createElementNS("http://www.w3.org/2000/svg",e)}function ke(e,t,o,n,r){const s=e.dom.bounds(),i=new Map,c=[];for(const e of t)i.set(e.hash,!1);let a,l=r.firstChild;for(;l;)a=l.getAttribute("cgHash"),i.has(a)?i.set(a,!0):c.push(l),l=l.nextSibling;for(const e of c)r.removeChild(e);for(const c of t)i.get(c.hash)||r.appendChild(Pe(e,c,o,n,s))}function Ce({orig:e,dest:t,brush:o,piece:n,modifiers:r,customSvg:s},i,c,a){return[a.width,a.height,c,e,t,o,t&&(i.get(t)||0)>1,n&&Me(n),r&&(l=r,""+(l.lineWidth||"")),s&&Se(s)].filter((e=>e)).join(",");var l}function Me(e){return[e.color,e.role,e.scale].filter((e=>e)).join(",")}function Se(e){let t=0;for(let o=0;o<e.length;o++)t=(t<<5)-t+e.charCodeAt(o)>>>0;return"custom-"+t.toString()}function Pe(e,{shape:t,current:o,hash:n},r,s,i){let c;if(t.customSvg){const o=Ke(a(t.orig),e.orientation);c=function(e,t,o){const[n,r]=Oe(t,o),s=Ae(ye("g"),{transform:`translate(${n},${r})`}),i=Ae(ye("svg"),{width:1,height:1,viewBox:"0 0 100 100"});return s.appendChild(i),i.innerHTML=e,s}(t.customSvg,o,i)}else if(t.piece)c=function(e,t,o,n){const r=Oe(t,n),s=o.color[0]+("knight"===o.role?"n":o.role[0]).toUpperCase();return Ae(ye("image"),{className:`${o.role} ${o.color}`,x:r[0]-.5,y:r[1]-.5,width:1,height:1,href:e+s+".svg",transform:`scale(${o.scale||1})`,"transform-origin":`${r[0]} ${r[1]}`})}(e.drawable.pieces.baseUrl,Ke(a(t.orig),e.orientation),t.piece,i);else{const n=Ke(a(t.orig),e.orientation);if(t.dest){let l=r[t.brush];t.modifiers&&(l=Te(l,t.modifiers)),c=function(e,t,o,n,r,s){const i=function(e){return(e?20:10)/64}(r&&!n),c=Oe(t,s),a=Oe(o,s),l=a[0]-c[0],d=a[1]-c[1],u=Math.atan2(d,l),p=Math.cos(u)*i,f=Math.sin(u)*i;return Ae(ye("line"),{stroke:e.color,"stroke-width":qe(e,n),"stroke-linecap":"round","marker-end":"url(#arrowhead-"+e.key+")",opacity:Le(e,n),x1:c[0],y1:c[1],x2:a[0]-p,y2:a[1]-f})}(l,n,Ke(a(t.dest),e.orientation),o,(s.get(t.dest)||0)>1,i)}else c=function(e,t,o,n){const r=Oe(t,n),s=[3/64,4/64],i=(n.width+n.height)/(4*Math.max(n.width,n.height));return Ae(ye("circle"),{stroke:e.color,"stroke-width":s[o?0:1],fill:"none",opacity:Le(e,o),cx:r[0],cy:r[1],r:i-s[1]/2})}(r[t.brush],n,o,i)}return c.setAttribute("cgHash",n),c}function xe(e){const t=Ae(ye("marker"),{id:"arrowhead-"+e.key,orient:"auto",markerWidth:4,markerHeight:8,refX:2.05,refY:2.01});return t.appendChild(Ae(ye("path"),{d:"M0,0 V4 L3,2 Z",fill:e.color})),t.setAttribute("cgKey",e.key),t}function Ae(e,t){for(const o in t)e.setAttribute(o,t[o]);return e}function Ke(e,t){return"white"===t?e:[7-e[0],7-e[1]]}function Te(e,t){return{color:e.color,opacity:Math.round(10*e.opacity)/10,lineWidth:Math.round(t.lineWidth||e.lineWidth),key:[e.key,t.lineWidth].filter((e=>e)).join("")}}function qe(e,t){return(e.lineWidth||10)*(t?.85:1)/64}function Le(e,t){return(e.opacity||1)*(t?.9:1)}function Oe(e,t){const o=Math.min(1,t.width/t.height),n=Math.min(1,t.height/t.width);return[(e[0]-3.5)*o,(3.5-e[1])*n]}function Ne(e,t){const o=w("coords",t);let n;for(const t of e)n=w("coord"),n.textContent=t,o.appendChild(n);return o}function De(e,t){const o=e.dom.elements.board;if("ResizeObserver"in window&&new ResizeObserver(t).observe(e.dom.elements.wrap),e.viewOnly)return;const n=function(e){return t=>{e.draggable.current?me(e):e.drawable.current?de(e):t.shiftKey||b(t)?e.drawable.enabled&&ie(e,t):e.viewOnly||(e.dropmode.active?function(e,t){if(!e.dropmode.active)return;K(e),T(e);const o=e.dropmode.piece;if(o){e.pieces.set("a0",o);const n=v(t),r=n&&I(n,V(e),e.dom.bounds());r&&D(e,"a0",r)}e.dom.redraw()}(e,t):function(e,t){if(!t.isTrusted||void 0!==t.button&&0!==t.button)return;if(t.touches&&t.touches.length>1)return;const o=e.dom.bounds(),n=v(t),r=I(n,V(e),o);if(!r)return;const s=e.pieces.get(r),i=e.selected;var c;i||!e.drawable.enabled||!e.drawable.eraseOnClick&&s&&s.color===e.turnColor||(c=e).drawable.shapes.length&&(c.drawable.shapes=[],c.dom.redraw(),pe(c.drawable)),!1!==t.cancelable&&(!t.touches||e.blockTouchScroll||s||i||function(e,t){const o=V(e),n=e.dom.bounds(),r=Math.pow(n.width/8,2);for(const s of e.pieces.keys()){const e=y(s,o,n);if(p(e,t)<=r)return!0}return!1}(e,n))&&t.preventDefault();const l=!!e.premovable.current,d=!!e.predroppable.current;e.stats.ctrlKey=t.ctrlKey,e.selected&&R(e,e.selected,r)?ee((e=>$(e,r)),e):$(e,r);const u=e.selected===r,f=be(e,r);if(s&&f&&u&&function(e,t){const o=e.pieces.get(t);return!!o&&e.draggable.enabled&&("both"===e.movable.color||e.movable.color===o.color&&(e.turnColor===o.color||e.premovable.enabled))}(e,r)){e.draggable.current={orig:r,piece:s,origPos:n,pos:n,started:e.draggable.autoDistance&&e.stats.dragged,element:f,previouslySelected:i,originTarget:t.target,keyHasChanged:!1},f.cgDragging=!0,f.classList.add("dragging");const c=e.dom.elements.ghost;c&&(c.className=`ghost ${s.color} ${s.role}`,h(c,g(o)(a(r),V(e))),m(c,!0)),fe(e)}else l&&K(e),d&&T(e);e.dom.redraw()}(e,t))}}(e);o.addEventListener("touchstart",n,{passive:!1}),o.addEventListener("mousedown",n,{passive:!1}),(e.disableContextMenu||e.drawable.enabled)&&o.addEventListener("contextmenu",(e=>e.preventDefault()))}function $e(e,t,o,n){return e.addEventListener(t,o,n),()=>e.removeEventListener(t,o,n)}function Ee(e,t,o){return n=>{e.drawable.current?e.drawable.enabled&&o(e,n):e.viewOnly||t(e,n)}}function We(e){const t=e.dom.elements.wrap.getBoundingClientRect(),o=e.dom.elements.container,n=t.height/t.width,r=8*Math.floor(t.width*window.devicePixelRatio/8)/window.devicePixelRatio,s=r*n;o.style.width=r+"px",o.style.height=s+"px",e.dom.bounds.clear(),e.addDimensionsCssVars&&(document.documentElement.style.setProperty("--cg-width",r+"px"),document.documentElement.style.setProperty("--cg-height",s+"px"))}function He(e){return"PIECE"===e.tagName}function Re(e){return"SQUARE"===e.tagName}function je(e,t){for(const o of t)e.dom.elements.board.removeChild(o)}function Be(e,t){const o=e[1];return`${t?10-o:3+o}`}function Fe(e){return`${e.color} ${e.role}`}function ze(e,t,o){const n=e.get(t);n?e.set(t,`${n} ${o}`):e.set(t,o)}function Ie(e,t,o){const n=e.get(t);n?n.push(o):e.set(t,[o])}function Ve(e,t){const i={pieces:Y(G),orientation:"white",turnColor:"white",coordinates:!0,autoCastle:!0,viewOnly:!1,disableContextMenu:!1,addPieceZIndex:!1,addDimensionsCssVars:!1,blockTouchScroll:!1,pieceKey:!1,highlight:{lastMove:!0,check:!0},animation:{enabled:!0,duration:200},movable:{free:!0,color:"both",showDests:!0,events:{},rookCastle:!0},premovable:{enabled:!0,showDests:!0,castle:!0,events:{}},predroppable:{enabled:!1,events:{}},draggable:{enabled:!0,distance:3,autoDistance:!0,showGhost:!0,deleteOnDropOff:!1},dropmode:{active:!1},selectable:{enabled:!0},stats:{dragged:!("ontouchstart"in window)},events:{},drawable:{enabled:!0,visible:!0,defaultSnapToValidMove:!0,eraseOnClick:!0,shapes:[],autoShapes:[],brushes:{green:{key:"g",color:"#15781B",opacity:1,lineWidth:10},red:{key:"r",color:"#882020",opacity:1,lineWidth:10},blue:{key:"b",color:"#003088",opacity:1,lineWidth:10},yellow:{key:"y",color:"#e68f00",opacity:1,lineWidth:10},paleBlue:{key:"pb",color:"#003088",opacity:.4,lineWidth:15},paleGreen:{key:"pg",color:"#15781B",opacity:.4,lineWidth:15},paleRed:{key:"pr",color:"#882020",opacity:.4,lineWidth:15},paleGrey:{key:"pgr",color:"#4a4a4a",opacity:.35,lineWidth:15}},pieces:{baseUrl:"https://lichess1.org/assets/piece/cburnett/"},prevSvgHash:""},hold:d()};function c(){const t="dom"in i?i.dom.unbind:void 0,s=function(e,t){e.innerHTML="",e.classList.add("cg-wrap");for(const n of o)e.classList.toggle("orientation-"+n,t.orientation===n);e.classList.toggle("manipulable",!t.viewOnly);const s=w("cg-container");e.appendChild(s);const i=w("cg-board");let c,a,l;if(s.appendChild(i),t.drawable.visible&&(c=Ae(ye("svg"),{class:"cg-shapes",viewBox:"-4 -4 8 8",preserveAspectRatio:"xMidYMid slice"}),c.appendChild(ye("defs")),c.appendChild(ye("g")),a=Ae(ye("svg"),{class:"cg-custom-svgs",viewBox:"-3.5 -3.5 8 8",preserveAspectRatio:"xMidYMid slice"}),a.appendChild(ye("g")),s.appendChild(c),s.appendChild(a)),t.coordinates){const e="black"===t.orientation?" black":"";s.appendChild(Ne(r,"ranks"+e)),s.appendChild(Ne(n,"files"+e))}return t.draggable.showGhost&&(l=w("piece","ghost"),m(l,!1),s.appendChild(l)),{board:i,container:s,wrap:e,ghost:l,svg:c,customSvg:a}}(e,i),c=function(e){let t;const o=()=>(void 0===t&&(t=s.board.getBoundingClientRect()),t);return o.clear=()=>{t=void 0},o}(),l=e=>{(function(e){const t=V(e),o=g(e.dom.bounds()),n=e.dom.elements.board,r=e.pieces,s=e.animation.current,i=s?s.plan.anims:new Map,c=s?s.plan.fadings:new Map,l=e.draggable.current,d=function(e){var t;const o=new Map;if(e.lastMove&&e.highlight.lastMove)for(const t of e.lastMove)ze(o,t,"last-move");if(e.check&&e.highlight.check&&ze(o,e.check,"check"),e.selected&&(ze(o,e.selected,"selected"),e.movable.showDests)){const n=null===(t=e.movable.dests)||void 0===t?void 0:t.get(e.selected);if(n)for(const t of n)ze(o,t,"move-dest"+(e.pieces.has(t)?" oc":""));const r=e.premovable.dests;if(r)for(const t of r)ze(o,t,"premove-dest"+(e.pieces.has(t)?" oc":""))}const n=e.premovable.current;if(n)for(const e of n)ze(o,e,"current-premove");else e.predroppable.current&&ze(o,e.predroppable.current.key,"current-premove");const r=e.exploding;if(r)for(const e of r.keys)ze(o,e,"exploding"+r.stage);return o}(e),u=new Set,p=new Set,f=new Map,m=new Map;let v,b,y,k,C,M,S,P,x,A;for(b=n.firstChild;b;){if(v=b.cgKey,He(b))if(y=r.get(v),C=i.get(v),M=c.get(v),k=b.cgPiece,!b.cgDragging||l&&l.orig===v||(b.classList.remove("dragging"),h(b,o(a(v),t)),b.cgDragging=!1),!M&&b.cgFading&&(b.cgFading=!1,b.classList.remove("fading")),y){if(C&&b.cgAnimating&&k===Fe(y)){const e=a(v);e[0]+=C[2],e[1]+=C[3],b.classList.add("anim"),h(b,o(e,t))}else b.cgAnimating&&(b.cgAnimating=!1,b.classList.remove("anim"),h(b,o(a(v),t)),e.addPieceZIndex&&(b.style.zIndex=Be(a(v),t)));k!==Fe(y)||M&&b.cgFading?M&&k===Fe(M)?(b.classList.add("fading"),b.cgFading=!0):Ie(f,k,b):u.add(v)}else Ie(f,k,b);else if(Re(b)){const e=b.className;d.get(v)===e?p.add(v):Ie(m,e,b)}b=b.nextSibling}for(const[e,r]of d)if(!p.has(e)){x=m.get(r),A=x&&x.pop();const s=o(a(e),t);if(A)A.cgKey=e,h(A,s);else{const t=w("square",r);t.cgKey=e,h(t,s),n.insertBefore(t,n.firstChild)}}for(const[s,c]of r)if(C=i.get(s),!u.has(s))if(S=f.get(Fe(c)),P=S&&S.pop(),P){P.cgKey=s,P.cgFading&&(P.classList.remove("fading"),P.cgFading=!1);const n=a(s);e.addPieceZIndex&&(P.style.zIndex=Be(n,t)),C&&(P.cgAnimating=!0,P.classList.add("anim"),n[0]+=C[2],n[1]+=C[3]),h(P,o(n,t))}else{const r=Fe(c),i=w("piece",r),l=a(s);i.cgPiece=r,i.cgKey=s,C&&(i.cgAnimating=!0,l[0]+=C[2],l[1]+=C[3]),h(i,o(l,t)),e.addPieceZIndex&&(i.style.zIndex=Be(l,t)),n.appendChild(i)}for(const t of f.values())je(e,t);for(const t of m.values())je(e,t)})(u),!e&&s.svg&&function(e,t,o){const n=e.drawable,r=n.current,s=r&&r.mouseSq?r:void 0,i=new Map,c=e.dom.bounds();for(const e of n.shapes.concat(n.autoShapes).concat(s?[s]:[]))e.dest&&i.set(e.dest,(i.get(e.dest)||0)+1);const a=n.shapes.concat(n.autoShapes).map((e=>({shape:e,current:!1,hash:Ce(e,i,!1,c)})));s&&a.push({shape:s,current:!0,hash:Ce(s,i,!0,c)});const l=a.map((e=>e.hash)).join(";");if(l===e.drawable.prevSvgHash)return;e.drawable.prevSvgHash=l;const d=t.querySelector("defs"),u=t.querySelector("g"),p=o.querySelector("g");!function(e,t,o){const n=new Map;let r;for(const o of t)o.shape.dest&&(r=e.brushes[o.shape.brush],o.shape.modifiers&&(r=Te(r,o.shape.modifiers)),n.set(r.key,r));const s=new Set;let i=o.firstChild;for(;i;)s.add(i.getAttribute("cgKey")),i=i.nextSibling;for(const[e,t]of n.entries())s.has(e)||o.appendChild(xe(t))}(n,a,d),ke(e,a.filter((e=>!e.shape.customSvg)),n.brushes,i,u),ke(e,a.filter((e=>e.shape.customSvg)),n.brushes,i,p)}(u,s.svg,s.customSvg)},d=()=>{We(u),function(e){const t=V(e),o=g(e.dom.bounds());let n=e.dom.elements.board.firstChild;for(;n;)(He(n)&&!n.cgAnimating||Re(n))&&h(n,o(a(n.cgKey),t)),n=n.nextSibling}(u)},u=i;return u.dom={elements:s,bounds:c,redraw:Ge(l),redrawNow:l,unbind:t},u.drawable.prevSvgHash="",We(u),l(!1),De(u,d),t||(u.dom.unbind=function(e,t){const o=[];if("ResizeObserver"in window||o.push($e(document.body,"chessground.resize",t)),!e.viewOnly){const t=Ee(e,ge,ae),n=Ee(e,he,le);for(const e of["touchmove","mousemove"])o.push($e(document,e,t));for(const e of["touchend","mouseup"])o.push($e(document,e,n));const r=()=>e.dom.bounds.clear();o.push($e(document,"scroll",r,{capture:!0,passive:!0})),o.push($e(window,"resize",r,{passive:!0}))}return()=>o.forEach((e=>e()))}(u,d)),u.events.insert&&u.events.insert(s),u}return Q(i,t||{}),function(e,t){function o(){!function(e){e.orientation=u(e.orientation),e.animation.current=e.draggable.current=e.selected=void 0}(e),t()}return{set(t){t.orientation&&t.orientation!==e.orientation&&o(),Z(e,t),(t.fen?ee:te)((e=>Q(e,t)),e)},state:e,getFen:()=>{return t=e.pieces,s.map((e=>n.map((o=>{const n=t.get(o+e);if(n){const e=X[n.role];return"white"===n.color?e.toUpperCase():e}return"1"})).join(""))).join("/").replace(/1{2,}/g,(e=>e.length.toString()));var t},toggleOrientation:o,setPieces(t){ee((e=>function(e,t){for(const[o,n]of t)n?e.pieces.set(o,n):e.pieces.delete(o)}(e,t)),e)},selectSquare(t,o){t?ee((e=>$(e,t,o)),e):e.selected&&(W(e),e.dom.redraw())},move(t,o){ee((e=>q(e,t,o)),e)},newPiece(t,o){ee((e=>L(e,t,o)),e)},playPremove(){if(e.premovable.current){if(ee(B,e))return!0;e.dom.redraw()}return!1},playPredrop(t){if(e.predroppable.current){const o=function(e,t){const o=e.predroppable.current;let n=!1;return!!o&&(t(o)&&L(e,{role:o.role,color:e.movable.color},o.key)&&(A(e.movable.events.afterNewPiece,o.role,o.key,{premove:!1,predrop:!0}),n=!0),T(e),n)}(e,t);return e.dom.redraw(),o}return!1},cancelPremove(){te(K,e)},cancelPredrop(){te(T,e)},cancelMove(){te((e=>{F(e),me(e)}),e)},stop(){te((e=>{z(e),me(e)}),e)},explode(t){!function(e,t){e.exploding={stage:1,keys:t},e.dom.redraw(),setTimeout((()=>{we(e,2),setTimeout((()=>we(e,void 0)),120)}),120)}(e,t)},setAutoShapes(t){te((e=>e.drawable.autoShapes=t),e)},setShapes(t){te((e=>e.drawable.shapes=t),e)},getKeyAtDomPos:t=>I(t,V(e),e.dom.bounds()),redrawAll:t,dragNewPiece(t,o,n){!function(e,t,o,n){const r="a0";e.pieces.set(r,t),e.dom.redraw();const s=v(o);e.draggable.current={orig:r,piece:t,origPos:s,pos:s,started:!0,element:()=>be(e,r),originTarget:o.target,newPiece:!0,force:!!n,keyHasChanged:!1},fe(e)}(e,t,o,n)},destroy(){z(e),e.dom.unbind&&e.dom.unbind(),e.dom.destroyed=!0}}}(c(),c)}function Ge(e){let t=!1;return()=>{t||(t=!0,requestAnimationFrame((()=>{e(),t=!1})))}}})(),Chessground=t})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>The Knight's Voyage</title> | |
<meta charset="UTF-8"> | |
<link rel="stylesheet" href="chessground.css"> | |
<style type="text/css"> | |
.timer { | |
text-align: center; | |
font-size: 80px; | |
} | |
.board-container-side-by-side { | |
display: flex; | |
} | |
#board-column { margin-pottom: 20px; } | |
body { | |
background-color: black; | |
color: white; | |
} | |
table { | |
background-color: #262421; | |
} | |
tr { | |
border: none; | |
} | |
p { text-align: center; font-size: 20px; } | |
td, th { | |
border: none; | |
text-align: center; | |
padding-right: 20px; | |
padding-left: 20px; | |
} | |
.splits-pb { color: rgb(22, 166, 255); } | |
.splits-ahead.splits-gain { color: rgb(41, 204, 84); } | |
.splits-ahead.splits-loss { color: rgb(112, 204, 137); } | |
.splits-behind.splits-gain { color: rgb(204, 120, 112); } | |
.splits-behind.splits-loss { color: rgb(204, 55, 41); } | |
.splits-glod { color: rgb(216, 175, 31); } | |
table { margin: auto; font-size: 30px; } | |
</style> | |
</head> | |
<script src="chessground.js"></script> | |
<script src="chess.js"></script> | |
<body> | |
<div id="board-container"> | |
<div id="board-column"> | |
<div id="board" style="width: 900px; height: 900px; margin: auto"></div> | |
</div> | |
<div id="splits"> | |
<table> | |
<thead> | |
<tr> | |
<th>Square</th> | |
<th>Time</th> | |
<th></th> | |
<th>Best</th> | |
<th># Moves</th> | |
<th>Min. #Moves</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr id="splits-total"> | |
<td class="splits-square">Total</td> | |
<td class="splits-time"></td> | |
<td class="splits-delta"></td> | |
<td class="splits-best-time"></td> | |
<td class="splits-num-moves"></td> | |
<td class="splits-min-moves"></td> | |
</tr> | |
<tr id="splits-best"> | |
<td class="splits-square">Sum of Best</td> | |
<td class="splits-time"></td> | |
<td class="splits-delta"></td> | |
<td class="splits-best-time"></td> | |
<td class="splits-num-moves"></td> | |
<td class="splits-min-moves"></td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<p class="timer">Time: <span id="timer-count">0.00</span></p> | |
<p> | |
<label for="fen">FEN:</label> | |
<input type="text" id="fen" name="fen" value=""> | |
<button id="load-fen">Load</button> | |
<br/> | |
<label for="fen-selector">Preset: </label> | |
<select name="fen-selector" id="fen-selector"> | |
<option value="7n/8/8/3Q4/8/8/8/8 b - - 0 1">One Queen</option> | |
<option value="7n/8/8/8/8/8/8/8 b - - 0 1">Clear Board</option> | |
<option value="7n/8/8/3B4/8/8/5B2/8 b - - 0 1">Two Bishops</option> | |
<option value="7n/8/8/1B6/5R2/8/8/8 b - - 0 1">Bishop & Rook</option> | |
<option value="7n/8/2N5/3N4/8/8/8/3B4 b - - 0 1">Two Knights & Bishop</option> | |
<option value="5n2/6R1/8/8/8/2R5/8/8 b - - 0 1">Two Rooks</option> | |
</select> | |
<br/> | |
<input type="checkbox" id="show-inaccessible" name="show-inaccessible" | |
value="on"> | |
<label for="show-inaccessible">Show Inaccessible squares</label> | |
<br/> | |
</p> | |
</body> | |
<script> | |
function formatTime(delta) { | |
return (delta / 1000).toFixed(2).toString(); | |
} | |
function updateTimer(timestamp) { | |
if (!startTime) { | |
startTime = timestamp; | |
lastSplitTime = timestamp; | |
} | |
document.querySelector("#timer-count").innerHTML = formatTime(timestamp - startTime); | |
if (targetId < targets.length) { | |
updateSplits(targets[targetId]); | |
timerUpdateRequest = requestAnimationFrame(updateTimer); | |
} | |
} | |
function isAttacked(game, target) { | |
let moves = game.moves({legal: false, verbose: true}); | |
return moves.some(move => move.to == target); | |
} | |
function passTurn(game) { | |
let parts = game.fen().split(" "); | |
parts[1] = parts[1] == "w" ? "b" : "w"; | |
let newFen = parts.join(" "); | |
game.load(newFen); | |
} | |
function successors(game) { | |
let successors = []; | |
let moves = game.moves({legal: false, verbose: true}); | |
moves.forEach((move) => { | |
if (game.get(move.to)) return; | |
let old_source = game.get(move.from); | |
let old_target = game.get(move.to); | |
game.put(old_source, move.to); | |
game.remove(move.from); | |
passTurn(game); | |
if (!isAttacked(game, move.to)) { | |
passTurn(game); | |
successors.push([move.from, move.to, game.fen()]) | |
passTurn(game); | |
} | |
if (old_target) | |
game.put(old_target, move.to); | |
else | |
game.remove(move.to); | |
if (old_source) | |
game.put(old_source, move.from); | |
else | |
game.remove(move.from); | |
passTurn(game); | |
}); | |
return successors; | |
} | |
function legalMoves(game) { | |
let moves = new Map(); | |
successors(game).forEach((entry) => { | |
let [previous, square, fen] = entry; | |
if (!moves.has(previous)) | |
moves.set(previous, [square]); | |
else | |
moves.get(previous).push(square); | |
}); | |
return moves; | |
} | |
function updateLegalMoves() { | |
board.set({ | |
turnColor: game.turn() == game.WHITE ? "white" : "black", | |
movable: {dests: legalMoves(game)}, | |
}); | |
} | |
function currentSquare(game) { | |
for (let y = 0; y < 8; y++) { | |
for (let x = 0; x < 8; x++) { | |
let file = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][y]; | |
let rank = (x + 1).toString(); | |
let square = file + rank; | |
if (game.get(square) && game.get(square).color == game.turn()) | |
return square; | |
} | |
} | |
return null; | |
} | |
function reachableSquares() { | |
let square = currentSquare(game); | |
let reachable = {}; | |
let stack = [[square, game.fen()]]; | |
while (stack.length != 0) { | |
let [square, fen] = stack.pop(); | |
if (reachable[square]) continue; | |
reachable[square] = []; | |
successors(Chess(fen)).forEach((entry) => { | |
let [previous, nextSquare, fen] = entry; | |
reachable[square].push(nextSquare); | |
if (!reachable[nextSquare]) | |
stack.push([nextSquare, fen]); | |
}); | |
} | |
return reachable; | |
} | |
function graphDistance(graph, source, dest) { | |
let queue = [[source, 0]]; | |
let i = 0; | |
let seen = {}; | |
seen[source] = true; | |
while (i < queue.length) { | |
let [node, dist] = queue[i++]; | |
if (node == dest) | |
return dist; | |
graph[node].forEach(succ => { | |
if (!seen[succ]) { | |
queue.push([succ, dist + 1]); | |
seen[succ] = true; | |
} | |
}); | |
} | |
} | |
function targetSquares() { | |
let reachable = reachableSquares(); | |
let targets = []; | |
for (let x = 0; x < 8; x++) { | |
for (let y = 0; y < 8; y++) { | |
let file = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][8 - y - 1]; | |
let rank = ((8 - x - 1) + 1).toString(); | |
let square = file + rank; | |
if (reachable[square]) | |
targets.push(square); | |
} | |
} | |
return targets; | |
} | |
function distances() { | |
let reachable = reachableSquares(); | |
let targets = targetSquares(); | |
let current = currentSquare(game); | |
let out = []; | |
targets.forEach(target => { | |
out.push(graphDistance(reachable, current, target)); | |
current = target; | |
}); | |
return out; | |
} | |
function createSplits() { | |
let numMoves = distances(); | |
let table = document.querySelector("#splits tbody") | |
let summaryRow = document.querySelector("#splits-total") | |
let bestRow = document.querySelector("#splits-best") | |
let moveSum = 0; | |
let sumOfBest = 0; | |
let bestRun = bestSplits["bestRun"] || {}; | |
let bestTime = 0; | |
targetSquares().forEach((square, i) => { | |
let row = document.createElement("tr"); | |
row.id = "splits-" + square; | |
let squareNode = document.createElement("td"); | |
squareNode.classList.add("splits-square"); | |
squareNode.appendChild(document.createTextNode(square)); | |
row.appendChild(squareNode); | |
let timeNode = document.createElement("td"); | |
timeNode.classList.add("splits-time"); | |
row.appendChild(timeNode); | |
let deltaNode = document.createElement("td"); | |
deltaNode.classList.add("splits-delta"); | |
row.appendChild(deltaNode); | |
let bestTimeNode = document.createElement("td"); | |
bestTimeNode.classList.add("splits-best-time"); | |
row.appendChild(bestTimeNode); | |
if (square in bestRun) { | |
bestTime += bestRun[square]; | |
bestTimeNode.appendChild(document.createTextNode(formatTime(bestTime))); | |
} | |
if (square in bestSplits) { | |
if (sumOfBest != null) sumOfBest += bestSplits[square]; | |
} | |
else | |
sumOfBest = null; | |
let numMovesNode = document.createElement("td"); | |
numMovesNode.classList.add("splits-num-moves"); | |
row.appendChild(numMovesNode); | |
let minMovesNode = document.createElement("td"); | |
minMovesNode.appendChild(document.createTextNode(numMoves[i])); | |
minMovesNode.classList.add("splits-min-moves"); | |
row.appendChild(minMovesNode); | |
moveSum += numMoves[i]; | |
table.insertBefore(row, summaryRow); | |
}); | |
document.querySelector("#splits-total .splits-min-moves").innerHTML = moveSum; | |
if ("total" in bestSplits) { | |
summaryRow.querySelector(".splits-best-time").innerHTML = | |
formatTime(bestSplits["total"]); | |
} | |
if (sumOfBest != null) { | |
bestRow.querySelector(".splits-best-time").innerHTML = | |
formatTime(sumOfBest); | |
} | |
} | |
function clearSplits() { | |
const table = document.querySelector("#splits tbody") | |
targets.forEach(square => { | |
table.removeChild(document.querySelector("#splits-" + square)); | |
}); | |
let summaryRow = document.querySelector("#splits-total"); | |
let bestRow = document.querySelector("#splits-best"); | |
summaryRow.querySelector(".splits-time").innerHTML = ""; | |
summaryRow.querySelector(".splits-time").classList.remove("splits-pb"); | |
summaryRow.querySelector(".splits-delta").innerHTML = ""; | |
summaryRow.querySelector(".splits-best-time").innerHTML = ""; | |
summaryRow.querySelector(".splits-num-moves").innerHTML = ""; | |
summaryRow.querySelector(".splits-min-moves").innerHTML = ""; | |
bestRow.querySelector(".splits-time").innerHTML = ""; | |
bestRow.querySelector(".splits-delta").innerHTML = ""; | |
bestRow.querySelector(".splits-best-time").innerHTML = ""; | |
bestRow.querySelector(".splits-num-moves").innerHTML = ""; | |
bestRow.querySelector(".splits-min-moves").innerHTML = ""; | |
} | |
function bestRunTime(target) { | |
let sum = 0; | |
for (let i = 0; i < targets.length; i++) { | |
if (!(targets[i] in bestSplits["bestRun"])) | |
return null; | |
sum += bestSplits["bestRun"][targets[i]]; | |
if (targets[i] == target) | |
return sum; | |
} | |
} | |
function updateSplits(target) { | |
const targetRow = document.querySelector("#splits-" + target); | |
const summaryRow = document.querySelector("#splits-total"); | |
if (startTime) { | |
const time = performance.now() - startTime; | |
const segmentTime = performance.now() - (lastSplitTime || startTime); | |
summaryRow.querySelector(".splits-time").innerHTML = formatTime(time); | |
targetRow.querySelector(".splits-time").innerHTML = formatTime(time); | |
if (bestSplits["bestRun"] && target in bestSplits["bestRun"]) { | |
let bestRunSegment = bestSplits["bestRun"][target]; | |
let previousTime = bestRunTime(target); | |
const deltaNode = targetRow.querySelector(".splits-delta"); | |
deltaNode.classList.remove("splits-ahead"); | |
deltaNode.classList.remove("splits-behind"); | |
deltaNode.classList.remove("splits-gain"); | |
deltaNode.classList.remove("splits-loss"); | |
deltaNode.classList.remove("splits-glod"); | |
if (time <= previousTime) | |
deltaNode.classList.add("splits-ahead"); | |
if (time > previousTime) | |
deltaNode.classList.add("splits-behind"); | |
if (segmentTime > bestRunSegment) | |
deltaNode.classList.add("splits-loss"); | |
if (segmentTime <= bestRunSegment) | |
deltaNode.classList.add("splits-gain"); | |
const timeStr = formatTime(time - previousTime); | |
deltaNode.innerHTML = (time > previousTime ? "+" : "") + timeStr; | |
} | |
} | |
else { | |
summaryRow.querySelector(".splits-time").innerHTML = formatTime(0); | |
targetRow.querySelector(".splits-time").innerHTML = formatTime(0); | |
} | |
targetRow.querySelector(".splits-num-moves").innerHTML = numMovesMadeSinceSplit; | |
summaryRow.querySelector(".splits-num-moves").innerHTML = totalMovesMade; | |
} | |
function storeSplit(target, time) { | |
splits[target] = time; | |
if (!bestSplits[target] || time <= bestSplits[target]) { | |
bestSplits[target] = time; | |
if (target == "total") { | |
bestSplits["bestRun"] = splits; | |
document.querySelector("#splits-total .splits-time").classList.add("splits-pb"); | |
} | |
else { | |
document.querySelector("#splits-" + target + " .splits-time").classList.add("splits-glod"); | |
} | |
} | |
localStorage.setItem(startPosition, JSON.stringify(bestSplits)); | |
let sumOfBest = 0; | |
targets.forEach(square => { | |
if (square in bestSplits && sumOfBest != null) sumOfBest += bestSplits[square]; | |
else sumOfBest = null; | |
}); | |
if (sumOfBest != null) { | |
document.querySelector("#splits-best .splits-best-time").innerHTML = | |
formatTime(sumOfBest); | |
} | |
} | |
function updateTarget() { | |
while (targetId < targets.length && game.get(targets[targetId])) { | |
updateSplits(targets[targetId]); | |
numMovesMadeSinceSplit = 0; | |
const endTime = performance.now(); | |
const splitStart = lastSplitTime || startTime || endTime; | |
storeSplit(targets[targetId], endTime - splitStart); | |
lastSplitTime = endTime; | |
targetId += 1; | |
if (targetId == targets.length) | |
storeSplit("total", endTime - startTime); | |
} | |
let shapes = []; | |
if (showInaccessible) | |
shapes = coveredSquaresHighlights(); | |
if (targetId < targets.length) { | |
shapes.push({orig: targets[targetId], brush: "green"}); | |
} | |
board.set({drawable: {shapes: shapes}}); | |
} | |
function coveredSquares() { | |
let reachable = reachableSquares(); | |
let squares = []; | |
for (let x = 0; x < 8; x++) { | |
for (let y = 0; y < 8; y++) { | |
let file = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][8 - y - 1]; | |
let rank = ((8 - x - 1) + 1).toString(); | |
let square = file + rank; | |
if (!reachable[square]) | |
squares.push(square); | |
} | |
} | |
return squares; | |
} | |
function coveredSquaresHighlights() { | |
return coveredSquares().map(s => { return {orig: s, brush: "red"} }); | |
} | |
function onMove(source, target, meta) { | |
totalMovesMade++; | |
numMovesMadeSinceSplit++; | |
game.put(game.get(source), target); | |
game.remove(source); | |
updateLegalMoves(); | |
if (targetId < targets.length) { | |
updateSplits(targets[targetId]); | |
if (!startTime) updateTimer(performance.now()); | |
} | |
updateTarget(); | |
board.selectSquare(target, true); | |
} | |
function resizeBoard() { | |
const viewportWidth = | |
Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); | |
const sideBySideWidth = viewportWidth - | |
(document.querySelector("#splits thead").clientWidth || 0); | |
const viewportHeight = Math.max(document.documentElement.clientHeight|| 0, | |
window.innerHeight || 0) - 100; | |
const usedWidth = sideBySideWidth > 320 ? sideBySideWidth : viewportWidth; | |
const size = Math.min(usedWidth, viewportHeight).toString() + "px"; | |
document.querySelector("#board").style.width = size; | |
document.querySelector("#board").style.height = size; | |
if (sideBySideWidth > 300) { | |
document.querySelector("#board-container").classList.add("board-container-side-by-side"); | |
document.querySelector("#board-column").style.flex = size; | |
} | |
else { | |
document.querySelector("#board-container").classList.remove("board-container-side-by-side"); | |
document.querySelector("#board-column").style.flex = null; | |
} | |
} | |
function soundOnMove(source, target, capture) { | |
new Audio('chess-move.ogg').play(); | |
} | |
function onSelect(square) { | |
if (!square || !game.get(square)) { | |
square = currentSquare(game); | |
board.selectSquare(square); | |
} | |
} | |
let board = null; | |
let startPosition = "7n/8/8/3Q4/8/8/8/8 b - - 0 1"; | |
let game = Chess(startPosition); | |
let startTime = null; | |
let showInaccessible = false; | |
let timerUpdateRequest = null; | |
let totalMovesMade = 0; | |
let numMovesMadeSinceSplit = 0; | |
let lastSplitTime = null; | |
let targets = targetSquares(); | |
let targetId = 0; | |
let splits = {}; | |
let bestSplits = {}; | |
if (localStorage.getItem(startPosition)) | |
bestSplits = JSON.parse(localStorage.getItem(startPosition)); | |
function setupPosition(fen) { | |
if (timerUpdateRequest) | |
cancelAnimationFrame(timerUpdateRequest); | |
clearSplits(); | |
startPosition = fen; | |
game = Chess(startPosition); | |
startTime = null; | |
totalMovesMade = 0; | |
numMovesMadeSinceSplit = 0; | |
lastSplitTime = null; | |
targets = targetSquares() | |
targetId = 0; | |
splits = {}; | |
bestSplits = {}; | |
if (localStorage.getItem(startPosition)) | |
bestSplits = JSON.parse(localStorage.getItem(startPosition)); | |
board.set(defaultConfig()); | |
createSplits(); | |
updateLegalMoves(board); | |
updateTarget(); | |
resizeBoard(); | |
} | |
function defaultConfig() { | |
const isWhite = game.turn() == game.WHITE; | |
return { | |
fen: game.fen(), | |
orientation: "white", | |
movable: { | |
free: false, | |
color: isWhite ? "white" : "black", | |
showDests: false, | |
events: {after: onMove}, | |
}, | |
selected: currentSquare(game), | |
drawable: {enabled: false}, | |
animation: {enabled: false}, | |
events: {move: soundOnMove, select: onSelect}, | |
coordinates: false | |
}; | |
} | |
document.addEventListener("DOMContentLoaded", function() { | |
board = Chessground.Chessground(document.querySelector("#board"), defaultConfig()); | |
createSplits(); | |
updateLegalMoves(board); | |
updateTarget(); | |
resizeBoard(); | |
window.addEventListener("resize", resizeBoard); | |
const showInaccessibleBox = document.querySelector("#show-inaccessible"); | |
showInaccessibleBox.onchange = () => { | |
showInaccessible = showInaccessibleBox.checked; | |
updateTarget(); | |
}; | |
const fenSelector = document.querySelector("#fen-selector"); | |
const fenInput = document.querySelector("#fen"); | |
fenSelector.onchange = () => { | |
fenInput.value = fenSelector.value; | |
}; | |
document.querySelector("#load-fen").onclick = () => { | |
setupPosition(fenInput.value); | |
}; | |
}); | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment