$(function() {
var from, to, promotion, rcvd;
var $side = 'w';
var $piece = null;
var $chess = new Chess();
var $gameOver = false;
var $chessboardWhite = $('.chess_board.white').clone();
var $chessboardBlack = $('.chess_board.black').clone();
function modalKeydownHandler(e) {
e.preventDefault();
if (e.which === 13 || e.which === 27) {
hideModal();
}
}
function offerKeydownHandler(e) {
e.preventDefault();
if (e.which === 13) {
hideOffer();
e.data.accept();
} else if (e.which === 27) {
hideOffer();
e.data.decline();
}
}
function showModal(message) {
$('#modal-message').text(message);
$('#modal-mask').fadeIn(200);
$(document).on('keydown', modalKeydownHandler);
}
function hideModal() {
$('#modal-mask').fadeOut(200);
$(document).off('keydown', modalKeydownHandler);
}
function showOffer(offer, options) {
$('#offer-message').text(offer);
$('#offer-mask').fadeIn(200);
$(document).on('keydown', options, offerKeydownHandler);
}
function hideOffer() {
$('#offer-mask').fadeOut(200);
$(document).off('keydown', offerKeydownHandler);
}
function selectPiece(el) {
el.addClass('selected');
}
function unselectPiece(el) {
el.removeClass('selected');
}
function isSelected(el) {
return el ? el.hasClass('selected') : false;
}
function movePiece(from, to, promotion, rcvd) {
var move = $chess.move({
'from': from,
'to': to,
promotion: promotion
});
if (move && !$gameOver) {
var tdFrom = $('td.' + from.toUpperCase());
var tdTo = $('td.' + to.toUpperCase());
//highlight moves
if ($('td').hasClass('last-target')){
$('td').removeClass('last-target last-origin');
}
tdFrom.addClass('last-origin');
tdTo.addClass('last-target');
var piece = tdFrom.find('a'); // piece being moved
var moveSnd = $("#moveSnd")[0];
unselectPiece(piece.parent());
if (tdTo.html() !== '') { //place captured piece next to the chessboard
$('#captured-pieces')
.find($chess.turn() === 'b' ? '.b' : '.w')
.append('
' + tdTo.find('a').html() + '');
}
tdTo.html(piece);
$piece = null;
// en passant move
if (move.flags === 'e'){
var enpassant = move.to.charAt(0) + move.from.charAt(1);
$('td.' + enpassant.toUpperCase()).html('');
}
//kingside castling
var rook;
if (move.flags === 'k'){
if (move.to === 'g1'){
rook = $('td.H1').find('a');
$('td.F1').html(rook);
}
else if (move.to === 'g8'){
rook = $('td.H8').find('a');
$('td.F8').html(rook);
}
}
//queenside castling
if (move.flags === 'q'){
if (move.to === 'c1'){
rook = $('td.A1').find('a');
$('td.D1').html(rook);
}
else if (move.to === 'c8'){
rook = $('td.A8').find('a');
$('td.D8').html(rook);
}
}
//promotion
if (move.flags === 'np' || move.flags === 'cp'){
var square = $('td.' + move.to.toUpperCase()).find('a');
var option = move.promotion;
var promotion_w = {
'q': '♕',
'r': '♖',
'n': '♘',
'b': '♗'
};
var promotion_b = {
'q': '♛',
'r': '♜',
'n': '♞',
'b': '♝'
};
if (square.hasClass('white')){
square.html(promotion_w[option]);
} else {
square.html(promotion_b[option]);
}
}
if ($('#sounds').is(':checked')) {
moveSnd.play();
}
//feedback
var fm = $('.feedback-move');
var fs = $('.feedback-status');
$chess.turn() === 'b' ? fm.text('Black to move.') : fm.text('White to move.');
fm.parent().toggleClass('blackfeedback whitefeedback');
$chess.in_check() ? fs.text(' Check.') : fs.text('');
//game over
if ($chess.game_over()) {
fm.text('');
var result = "";
if ($chess.in_checkmate())
result = $chess.turn() === 'b' ? 'Checkmate. White wins!' : 'Checkmate. Black wins!'
else if ($chess.in_draw())
result = "Draw.";
else if ($chess.in_stalemate())
result = "Stalemate.";
else if ($chess.in_threefold_repetition())
result = "Draw. (Threefold Repetition)";
else if ($chess.insufficient_material())
result = "Draw. (Insufficient Material)";
fs.text(result);
}
/* Add all moves to the table */
var pgn = $chess.pgn({ max_width: 5, newline_char: ',' });
var moves = pgn.split(',');
var last_move = moves.pop().split('.');
var move_number = last_move[0];
var move_pgn = $.trim(last_move[1]);
if (move_pgn.indexOf(' ') !== -1) {
var moves = move_pgn.split(' ');
move_pgn = moves[1];
}
$('#moves tbody tr').append('' + move_number + '. ' + move_pgn + ' | ');
if (rcvd === undefined) {
$socket.emit('new-move', {
'token': $token,
'move': move
});
}
if ($chess.game_over()) {
$gameOver = true;
$socket.emit('timer-clear-interval', {
'token': $token
});
$('.resign').hide();
$('.rematch').show();
showModal(result);
} else {
if ($chess.turn() === 'b') {
$socket.emit('timer-black', {
'token': $token
});
} else {
$socket.emit('timer-white', {
'token': $token
});
}
$('#clock li').each(function() {
$(this).toggleClass('ticking');
});
}
}
}
function unbindMoveHandlers() {
var moveFrom = $('.chess_board a');
var moveTo = $('.chess_board td');
moveFrom.off('click', movePieceFromHandler);
moveTo.off('click', movePieceToHandler);
moveFrom.attr('draggable', false).off('dragstart', dragstartHandler);
moveFrom.off('dragend');
moveTo.attr('draggable', false).off('drop', dropHandler);
moveTo.off('dragover');
}
function bindMoveHandlers() {
var moveFrom = $('.chess_board a');
var moveTo = $('.chess_board td');
moveFrom.on('click', movePieceFromHandler);
moveTo.on('click', movePieceToHandler);
if (dndSupported()) {
moveFrom.attr('draggable', true).on('dragstart', dragstartHandler);
moveTo.on('draggable', true).on('drop', dropHandler);
moveTo.on('dragover', function (e) {
e.preventDefault();
e.originalEvent.dataTransfer.dropEffect = 'move';
});
moveFrom.on('dragend', function (e) {
moveTo.removeClass('moving');
});
}
}
function escapeHTML(html) {
return $('').text(html).html();
}
/* socket.io */
function rematchAccepted() {
$socket.emit('rematch-confirm', {
'token': $token,
'time': $time * 60,
'increment': $increment
});
}
function rematchDeclined() {
$socket.emit('rematch-decline', {
'token': $token
});
}
$socket.emit('join', {
'token': $token,
'time': $time * 60,
'increment': $increment
});
$socket.on('token-invalid', function (data) {
showModal('Game link is invalid or has expired.');
});
$socket.on('joined', function (data) {
if (data.color === 'white') {
$side = 'w';
$('.chess_board.black').remove();
$socket.emit('timer-white', {
'token': $token
});
} else {
$side = 'b';
$('.chess_board.white').remove();
$('.chess_board.black').show();
}
$('#clock li.white').addClass('ticking');
$('#sendMessage').find('input').addClass($side === 'b' ? 'black' : 'white');
});
$socket.on('move', function (data) {
movePiece(from=data.move.from, to=data.move.to, promotion=data.move.promotion, rcvd=true);
if (typeof document.hidden === undefined) return;
if (document.hidden) {
var title = $('title').text();
$('title').text('* ' + title);
$(window).on('focus', removeAsterisk);
function removeAsterisk(e) {
$('title').text(title);
$(window).off('focus', removeAsterisk);
}
}
});
$socket.on('opponent-disconnected', function (data) {
$('.resign').off().remove();
$('#sendMessage').off();
$('#sendMessage').submit(function (e) {
e.preventDefault();
showModal("Your opponent has disconnected. You can't send messages.");
});
$('.rematch').off();
$('.rematch').click(function (e) {
e.preventDefault();
showModal('Your opponent has disconnected. You need to generate a new link.');
})
if (!$gameOver) {
showModal("Your opponent has disconnected.");
}
});
$socket.on('player-resigned', function (data) {
$gameOver = true;
$('.resign').hide();
$('.rematch').show();
unbindMoveHandlers();
var winner = data.color === 'w' ? 'Black' : 'White';
var loser = data.color === 'w' ? 'White' : 'Black';
var message = loser + ' resigned. ' + winner + ' wins.';
showModal(message);
$('.feedback-move').text('');
$('.feedback-status').text(message);
});
$socket.on('full', function (data) {
alert("This game already has two players. You have to create a new one.");
window.location = '/';
});
$socket.on('receive-message', function (data) {
var chat = $('ul#chat');
var chat_node = $('ul#chat')[0];
var messageSnd = $("#messageSnd")[0];
chat.append('' + escapeHTML(data.message) + '');
if (chat.is(':visible') && chat_node.scrollHeight > 300) {
setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
} else if (!chat.is(':visible') && !$('.new-message').is(':visible')) {
$('#bubble').before('You have a new message!');
}
if ($('#sounds').is(':checked')) {
messageSnd.play();
}
});
$socket.on('countdown', function (data) {
var color = data.color;
var opp_color = color === 'black' ? 'white' : 'black';
var min = Math.floor(data.time / 60);
var sec = data.time % 60;
if (sec.toString().length === 1) {
sec = '0' + sec;
}
$('#clock li.' + color).text(min + ':' + sec);
});
$socket.on('countdown-gameover', function (data) {
$gameOver = true;
unbindMoveHandlers();
var loser = data.color === 'black' ? 'Black' : 'White';
var winner = data.color === 'black' ? 'White' : 'Black';
var message = loser + "'s time is out. " + winner + " wins.";
$('.resign').hide();
$('.rematch').show();
showModal(message);
$('.feedback-move').text('');
$('.feedback-status').text(message);
});
$socket.on('rematch-offered', function (data) {
hideModal();
showOffer('Your opponent sent you a rematch offer.', {
accept: rematchAccepted,
decline: rematchDeclined
});
});
$socket.on('rematch-declined', function (data) {
showModal('Rematch offer was declined.');
});
$socket.on('rematch-confirmed', function (data) {
hideModal();
$side = $side === 'w' ? 'b' : 'w'; //swap sides
$piece = null;
$chess = new Chess();
$gameOver = false;
$('#clock li').each(function () {
$(this).text($time + ':00');
});
if ($('#clock li.black').hasClass('ticking')) {
$('#clock li.black').removeClass('ticking');
$('#clock li.white').addClass('ticking');
}
$('#moves tbody tr').empty();
$('#captured-pieces ul').each(function () {
$(this).empty();
})
$('.rematch').hide();
$('.resign').show();
if ($side === 'w') {
$('.chess_board.black').remove();
$('#board_wrapper').append($chessboardWhite.clone());
$socket.emit('timer-white', {
'token': $token
});
} else {
$('.chess_board.white').remove();
$('#board_wrapper').append($chessboardBlack.clone());
$('.chess_board.black').show();
}
bindMoveHandlers();
$('#sendMessage').find('input').removeClass('white black').addClass($side === 'b' ? 'black' : 'white');
});
/* gameplay */
$('#clock li').each(function() {
$(this).text($time + ':00');
});
$('#game-type').text($time + ' | ' + $increment);
function movePieceFromHandler(e) {
var piece = $(this);
if ((piece.hasClass('white') && $side !== 'w') ||
(piece.hasClass('black') && $side !== 'b')) {
if ($piece) {
movePiece(
from=$piece.parent().data('id').toLowerCase(),
to=$(this).parent().data('id').toLowerCase(),
promotion=$('#promotion option:selected').val()
);
}
} else {
if ($chess.turn() !== $side) {
return false;
}
if (e && $piece && isSelected($(this).parent())) {
unselectPiece($piece.parent());
$piece = null;
} else {
if ($piece) {
unselectPiece($piece.parent());
$piece = null;
}
$piece = $(this);
selectPiece($piece.parent());
}
}
if (e) { // only on click event, not drag and drop
e.stopImmediatePropagation();
e.preventDefault();
}
}
function movePieceToHandler(e) {
if ($piece) {
movePiece(
from=$piece.parent().data('id').toLowerCase(),
to=$(this).data('id').toLowerCase(),
promotion=$('#promotion option:selected').val()
)
}
}
bindMoveHandlers();
function dndSupported() {
return 'draggable' in document.createElement('span');
}
function dragstartHandler(e) {
var el = $(this);
$drgSrcEl = el;
$drgSrcEl.parent().addClass('moving');
e.originalEvent.dataTransfer.effectAllowed = 'move';
e.originalEvent.dataTransfer.setData('text/html', el.html());
movePieceFromHandler.call(this, undefined);
}
function dropHandler(e) {
e.stopPropagation();
e.preventDefault();
movePieceToHandler.call(this, undefined);
}
$('#modal-mask, #modal-ok').click(function (e) {
e.preventDefault();
hideModal();
});
$('#offer-accept').click(function (e) {
e.preventDefault();
hideOffer();
rematchAccepted();
});
$('#offer-decline').click(function (e) {
e.preventDefault();
hideOffer();
rematchDeclined();
});
$('#modal-window, #offer-window').click(function (e) {
e.stopPropagation();
});
$('.resign').click(function (e) {
e.preventDefault();
$socket.emit('resign', {
'token': $token,
'color': $side
});
});
$('.rematch').click(function (e) {
e.preventDefault();
showModal('Your offer has been sent.');
$socket.emit('rematch-offer', {
'token': $token
});
})
$('a.chat').click(function (e) {
$('#chat-wrapper').toggle();
$('.new-message').remove();
var chat_node = $('ul#chat')[0];
if (chat_node.scrollHeight > 300) {
setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
}
});
$('#chat-wrapper .close').click(function (e) {
$('#chat-wrapper').hide();
});
$('#sendMessage').submit(function (e) {
e.preventDefault();
var input = $(this).find('input');
var message = input.val();
var color = $side === 'b' ? 'black' : 'white';
if (!/^\W*$/.test(message)) {
input.val('');
$('ul#chat').append('' + escapeHTML(message) + '');
var chat_node = $('ul#chat')[0];
if (chat_node.scrollHeight > 300) {
setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
}
$socket.emit('send-message', {
'message': message,
'color': color,
'token': $token
});
}
});
});