User:קיפודנחש/chess-animator.js

From mediawiki.org

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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: 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);

});