User:קיפודנחש/chess-animator.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/*
this code was written entirely by User:קיפודנחש
it is placed by the author in the public domain - you can take it, use it, abuse it, modify it etc. in any way you want.
no attribution or acknowledgement required.
as usual - no warranty or guarantee comes with it - use it at your own risk
*/
$(function() {
var allPositionClasses = '01234567'
.split('')
.map(function(r) { return 'pgn-prow-' + r + ' pgn-pfile-' + r; } )
.join(' ');
function processOneDiv() {
var $div = $(this),
data = $div.data('chess'),
boards,
pieces = [],
timer,
delay = 800,
boardDiv = $div.find('.pgn-board-img'),
ctx,
currentPlyNum;
function createPiece(letter) {
var ll = letter.toLowerCase(),
color = letter == ll ? 'd' : 'l',
piece = $('<div>')
.data('piece', letter)
.addClass('pgn-chessPiece pgn-ptype-color-' + ll + color)
.appendTo(boardDiv);
pieces.push(piece);
return piece;
}
function processFen(fen) {
var fenAr = fen.split('/'),
board = [],
l;
for (var i in fenAr) {
var j = 0;
letters = fenAr[i].split('');
for (var li in letters) {
l = letters[li];
if (/[prnbqk]/i.test(l)) {
board[(7 - i) * 8 + j] = createPiece(l);
j++;
}
else
j += parseInt(l);
}
}
return board;
}
function processPly(board, ply) {
var newBoard = board.slice(),
source = ply[0], destination = ply[1], special = ply[2];
if (typeof(source) == typeof(ply)) { // castling. 2 source/dest pairs
newBoard = processPly(newBoard, source);
newBoard = processPly(newBoard, destination);
} else {
newBoard[destination] = newBoard[source];
delete newBoard[source];
if (special) {
if (typeof(special) == "string")
newBoard[destination] = createPiece(special); // promotion
else
delete newBoard[special]; // en passant
}
}
return newBoard;
}
function scrollNotationToView(notation) {
var daddy = notation.closest('.pgn-notations'),
daddysHeight = daddy.height(),
notationHeight = notation.height(),
notationTop = notation.position().top,
toMove;
if ( notationTop < 0 || notationTop + notationHeight > daddysHeight ) {
toMove = (daddysHeight - notationHeight) / 2 - notationTop,
scrollTop = daddy.prop('scrollTop'),
newScrolltop = scrollTop - toMove;
daddy.prop({scrollTop: newScrolltop});
}
}
function placeInSquare(element, square) {
element
.removeClass(allPositionClasses + ' pgn-piece-hidden')
.addClass('pgn-prow-' + parseInt(square / 8) + ' pgn-pfile-' + square % 8 );
}
function resetCanvas() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, 800, 800);
}
function drawArrow(source, dest) {
if (!ctx) return;
resetCanvas();
var x1 = 100 * (source % 8),
y1 = 100 * parseInt(source / 8),
x2 = 100 * (dest % 8),
y2 = 100 * (parseInt(dest / 8));
if ($div.hasClass('pgn-flip')) {
x1 = 700 - x1; x2 = 700 - x2; y1 = 700 - y1; y2 = 700 - y2;
}
var
dx = x2 - x1,
dy = y2 - y1,
len = Math.sqrt(dx*dx + dy*dy),
angle = Math.atan2(dy, -dx),
shape = [ [len, 20], [len, -20], [80, -20], [80, -50], [0, 0], [80, 50], [80, 20] ],
point = shape.shift();
gradient = ctx.createLinearGradient(0, 0, len, 0);
gradient.addColorStop(0, 'rgba(80,80,0,0.7');
gradient.addColorStop(1, 'rgba(80,80,0,0.3');
ctx.fillStyle = gradient;
// start by reseting current transformation matrix to the identity matrix, and clear
ctx.rotate(angle);
var x2t = x2 + 50, y2t = 750 - y2,
xt = Math.cos(angle) * x2t + Math.sin(angle) * y2t,
yt = Math.cos(angle) * y2t - Math.sin(angle) * x2t;
ctx.translate(xt, yt);
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
while ( point = shape.shift() )
ctx.lineTo(point[0], point[1]);
ctx.closePath();
ctx.fill();
}
function showPly() {
var showMarkers = currentPlyNum > 0 && !timer;
$('.pgn-ply-source, .pgn-ply-destination', $div)
.removeClass('pgn-ply-source pgn-ply-destination');
if (showMarkers) {
var ply = data.plys[currentPlyNum - 1];
if (ply && typeof(ply[0]) == typeof(ply)) ply = ply[0]; // castling
var source = ply[0], dest = ply[1];
placeInSquare($('.pgn-ply-source', $div), source);
boards[currentPlyNum][dest].addClass('pgn-ply-destination');
drawArrow(source, dest);
} else {
if (ctx) resetCanvas();
}
}
function gotoBoard(plyNum) {
var previous = currentPlyNum,
board = boards[plyNum],
hidden = pieces.filter(function(piece) { return $.inArray(piece, board) == -1; }),
appearNow = board.filter(function(piece) {
return typeof(previous) == 'number' && boards[previous].indexOf(piece) === -1;
} ),
notation;
currentPlyNum = plyNum;
for (var i in hidden) hidden[i].addClass('pgn-piece-hidden');
for (var j in board) {
board[j]
.removeClass(allPositionClasses + ' pgn-piece-hidden pgn-ply-destination')
.toggleClass('pgn-transition-immediate', appearNow.indexOf(board[j]) > -1)
.addClass('pgn-prow-' + parseInt(j / 8) + ' pgn-pfile-' + j % 8 );
}
if (plyNum == boards.length - 1) stopAutoplay();
$('.pgn-movelink', $div).removeClass('pgn-current-move');
notation = $('.pgn-movelink[data-ply=' + plyNum + ']', $div);
if (notation.length) {
notation.addClass('pgn-current-move');
scrollNotationToView(notation);
}
showPly();
}
function advance() {
if (currentPlyNum < boards.length - 1) gotoBoard(currentPlyNum + 1);
}
function retreat() {
if (currentPlyNum > 0) gotoBoard(currentPlyNum - 1);
}
function gotoStart() {
gotoBoard(0);
stopAutoplay();
}
function gotoEnd() {
gotoBoard(boards.length - 1);
}
function clickPlay() {
if (currentPlyNum == boards.length - 1) gotoBoard(0);
if (timer)
stopAutoplay();
else
startAutoplay();
}
function changeDelay() {
if (delay < 400) delay = 400;
if (timer) {
stopAutoplay();
startAutoplay();
}
}
function slower() {
delay += Math.min(delay, 1600);
changeDelay();
}
function faster() {
delay = delay > 3200 ? delay - 1600 : delay / 2;
changeDelay();
}
function stopAutoplay() {
clearTimeout(timer);
$('.pgn-button-play', $div).removeClass('pgn-image-button-on');
timer = null;
showPly();
}
function startAutoplay() {
timer = setInterval(advance, delay);
$('.pgn-button-play', $div).addClass('pgn-image-button-on');
}
function flipBoard() {
$div.toggleClass('pgn-flip');
$('.pgn-button-flip', $div).toggleClass('pgn-image-button-on');
showPly();
}
function clickNotation() {
stopAutoplay();
gotoBoard($(this).data('ply'));
}
function connectButtons() {
$('.pgn-button-advance', $div).click(advance);
$('.pgn-button-retreat', $div).click(retreat);
$('.pgn-button-tostart', $div).click(gotoStart);
$('.pgn-button-toend', $div).click(gotoEnd);
$('.pgn-button-play', $div).click(clickPlay);
$('.pgn-button-faster', $div).click(faster);
$('.pgn-button-slower', $div).click(slower);
$('.pgn-button-flip', $div).click(flipBoard);
$('.pgn-movelink', $div).click(clickNotation);
}
if (data) {
var board,
ply,
display = data.display || data.plys.length;
$div.find('.pgn-chessPiece:not(.pgn-ply-source)').remove(); // the parser put its own pieces for "noscript" viewers
board = processFen(data.fen);
boards = [board];
for (var ind in data.plys) {
ply = data.plys[ind];
board = processPly(board, ply);
boards.push(board);
}
connectButtons();
gotoBoard(display); // call before canvas mambo-jumbo, so if it throws exception, we'll still be on the right ply.
var canvas = $('<canvas>')
.css( { height: '100%', width: '100%' } )
.attr( { width: 800, height: 800 } )
.prependTo(boardDiv);
ctx = canvas[0].getContext("2d");
$.extend(ctx, { fillStyle: 'rgba(0,0,0,0.55)' } );
showPly(); // was already called once from gotoBoard(), but canvas was not ready, so call again.
}
}
$('.pgnviewer').each(processOneDiv);
});