play.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. $(function() {
  2. var $side = 'w';
  3. var $piece = null;
  4. var $chess = new Chess();
  5. var $gameOver = false;
  6. function modalKeydownHandler(e){
  7. e.preventDefault();
  8. if (e.which === 13) {
  9. hideModal();
  10. }
  11. }
  12. function showModal(message) {
  13. $('#modal-message').text(message);
  14. $('#modal-mask').fadeIn(200);
  15. $(document).on('keydown', modalKeydownHandler);
  16. }
  17. function hideModal() {
  18. $('#modal-mask').fadeOut(200);
  19. $(document).off('keydown', modalKeydownHandler);
  20. }
  21. function selectPiece(el) {
  22. el.addClass('selected');
  23. }
  24. function unselectPiece(el) {
  25. el.removeClass('selected');
  26. }
  27. function isSelected(el) {
  28. return el ? el.hasClass('selected') : false;
  29. }
  30. function movePiece(from, to, promotion, rcvd) {
  31. var move = $chess.move({
  32. 'from': from,
  33. 'to': to,
  34. promotion: promotion
  35. });
  36. if (move && !$gameOver) {
  37. var tdFrom = $('td.' + from.toUpperCase());
  38. var tdTo = $('td.' + to.toUpperCase());
  39. //highlight moves
  40. if ($('td').hasClass('last-target')){
  41. $('td').removeClass('last-target last-origin');
  42. }
  43. tdFrom.addClass('last-origin');
  44. tdTo.addClass('last-target');
  45. var piece = tdFrom.find('a'); // piece being moved
  46. var moveSnd = $("#moveSnd")[0];
  47. unselectPiece(piece.parent());
  48. if (tdTo.html() !== '') { //place captured piece next to the chessboard
  49. $('#captured-pieces')
  50. .find($chess.turn() === 'b' ? '.b' : '.w')
  51. .append('<li>' + tdTo.find('a').html() + '</li>');
  52. }
  53. tdTo.html(piece);
  54. $piece = null;
  55. // en passant move
  56. if (move.flags === 'e'){
  57. var enpassant = move.to.charAt(0) + move.from.charAt(1);
  58. $('td.' + enpassant.toUpperCase()).html('');
  59. }
  60. //kingside castling
  61. var rook;
  62. if (move.flags === 'k'){
  63. if (move.to === 'g1'){
  64. rook = $('td.H1').find('a');
  65. $('td.F1').html(rook);
  66. }
  67. else if (move.to === 'g8'){
  68. rook = $('td.H8').find('a');
  69. $('td.F8').html(rook);
  70. }
  71. }
  72. //queenside castling
  73. if (move.flags === 'q'){
  74. if (move.to === 'c1'){
  75. rook = $('td.A1').find('a');
  76. $('td.D1').html(rook);
  77. }
  78. else if (move.to === 'c8'){
  79. rook = $('td.A8').find('a');
  80. $('td.D8').html(rook);
  81. }
  82. }
  83. //promotion
  84. if (move.flags === 'np' || move.flags === 'cp'){
  85. var square = $('td.' + move.to.toUpperCase()).find('a');
  86. var option = move.promotion;
  87. var promotion_w = {
  88. 'q': '&#9813;',
  89. 'r': '&#9814;',
  90. 'n': '&#9816;',
  91. 'b': '&#9815;'
  92. };
  93. var promotion_b = {
  94. 'q': '&#9819;',
  95. 'r': '&#9820;',
  96. 'n': '&#9822;',
  97. 'b': '&#9821;'
  98. };
  99. if (square.hasClass('white')){
  100. square.html(promotion_w[option]);
  101. } else {
  102. square.html(promotion_b[option]);
  103. }
  104. }
  105. if ($('#sounds').is(':checked')) {
  106. moveSnd.play();
  107. }
  108. //feedback
  109. var fm = $('.feedback-move');
  110. var fs = $('.feedback-status');
  111. $chess.turn() === 'b' ? fm.text('Black to move.') : fm.text('White to move.');
  112. fm.parent().toggleClass('blackfeedback whitefeedback');
  113. $chess.in_check() ? fs.text(' Check.') : fs.text('');
  114. //game over
  115. if ($chess.game_over()) {
  116. fm.text('');
  117. var result = "";
  118. if ($chess.in_checkmate())
  119. result = $chess.turn() === 'b' ? 'Checkmate. White wins!' : 'Checkmate. Black wins!'
  120. else if ($chess.in_draw())
  121. result = "Draw.";
  122. else if ($chess.in_stalemate())
  123. result = "Stalemate.";
  124. else if ($chess.in_threefold_repetition())
  125. result = "Draw. (Threefold Repetition)";
  126. else if ($chess.insufficient_material())
  127. result = "Draw. (Insufficient Material)";
  128. fs.text(result);
  129. }
  130. /* Add all moves to the table */
  131. var pgn = $chess.pgn({ max_width: 5, newline_char: ',' });
  132. var moves = pgn.split(',');
  133. var last_move = moves.pop().split('.');
  134. var move_number = last_move[0];
  135. var move_pgn = $.trim(last_move[1]);
  136. if (move_pgn.indexOf(' ') != -1) {
  137. var moves = move_pgn.split(' ');
  138. move_pgn = moves[1];
  139. }
  140. $('#moves tbody tr').append('<td><strong>' + move_number + '</strong>. ' + move_pgn + '</td>');
  141. if (rcvd === undefined) {
  142. $socket.emit('new-move', {
  143. 'token': $token,
  144. 'move': move
  145. });
  146. }
  147. if ($chess.game_over()) {
  148. $gameOver = true;
  149. $socket.emit('timer-clear-interval', {
  150. 'token': $token
  151. });
  152. $('.resign').off().remove();
  153. showModal(result);
  154. } else {
  155. if ($chess.turn() === 'b') {
  156. $socket.emit('timer-black', {
  157. 'token': $token
  158. });
  159. } else {
  160. $socket.emit('timer-white', {
  161. 'token': $token
  162. });
  163. }
  164. }
  165. }
  166. }
  167. /* socket.io */
  168. $socket.emit('join', {
  169. 'token': $token,
  170. 'time': $time * 60,
  171. 'increment': $increment
  172. });
  173. $socket.on('joined', function (data) {
  174. if (data.color === 'white') {
  175. $side = 'w';
  176. $('.chess_board.black').remove();
  177. $socket.emit('timer-white', {
  178. 'token': $token
  179. });
  180. } else {
  181. $side = 'b';
  182. $('.chess_board.white').remove();
  183. $('.chess_board.black').show();
  184. }
  185. $('#sendMessage').find('input').addClass($side === 'b' ? 'black' : 'white');
  186. });
  187. $socket.on('move', function (data) {
  188. movePiece(from=data.move.from, to=data.move.to, promotion=data.move.promotion, rcvd=true);
  189. });
  190. $socket.on('opponent-disconnected', function (data) {
  191. $('.resign').off().remove();
  192. $('.chess_board a').off();
  193. $('#sendMessage').off();
  194. $('#sendMessage').submit(function(e) {
  195. e.preventDefault();
  196. showModal("Your opponent has diconnected. You can't send messages.");
  197. });
  198. if (!$gameOver) {
  199. showModal("Your opponent has disconnected.");
  200. }
  201. });
  202. $socket.on('player-resigned', function (data) {
  203. $gameOver = true;
  204. $('.resign').off().remove();
  205. $('.chess_board a').off();
  206. var winner = data.color === 'w' ? 'Black' : 'White';
  207. var loser = data.color === 'w' ? 'White' : 'Black';
  208. var message = loser + ' resigned. ' + winner + ' wins.';
  209. showModal(message);
  210. $('.feedback-move').text('');
  211. $('.feedback-status').text(message);
  212. });
  213. $socket.on('full', function (data) {
  214. alert("This game already has two players. You have to create a new one.");
  215. window.location = '/';
  216. });
  217. $socket.on('receive-message', function (data) {
  218. var chat = $('ul#chat');
  219. var chat_node = $('ul#chat')[0];
  220. var messageSnd = $("#messageSnd")[0];
  221. chat.append('<li class="' + data.color + ' left" >' + data.message + '</li>');
  222. if (chat.is(':visible') && chat_node.scrollHeight > 300) {
  223. setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
  224. } else if (!chat.is(':visible') && !$('.new-message').is(':visible')) {
  225. $('#bubble').before('<span class="new-message">You have a new message!</span>');
  226. }
  227. if ($('#sounds').is(':checked')) {
  228. messageSnd.play();
  229. }
  230. });
  231. $socket.on('countdown', function (data) {
  232. var color = data.color;
  233. var opp_color = color === 'black' ? 'white' : 'black';
  234. var min = Math.floor(data.time / 60);
  235. var sec = data.time % 60;
  236. if (sec.toString().length === 1) {
  237. sec = '0' + sec;
  238. }
  239. $('#clock li.' + opp_color).removeClass('ticking');
  240. $('#clock li.' + color).addClass('ticking').text(min + ':' + sec);
  241. });
  242. $socket.on('countdown-gameover', function (data) {
  243. $gameOver = true;
  244. $('.chess_board a').off();
  245. var loser = data.color === 'black' ? 'Black' : 'White';
  246. var winner = data.color === 'black' ? 'White' : 'Black';
  247. var message = loser + "'s time is out. " + winner + " won.";
  248. $('.resign').off().remove();
  249. showModal(message);
  250. $('.feedback-move').text('');
  251. $('.feedback-status').text(message);
  252. });
  253. /* gameplay */
  254. $('#clock li').each(function() {
  255. $(this).text($time + ':00');
  256. });
  257. $('#game-type').text($time + '|' + $increment);
  258. $('.chess_board a').click(function (e) {
  259. var piece = $(this);
  260. if ((piece.hasClass('white') && $side != 'w') ||
  261. (piece.hasClass('black') && $side != 'b')) {
  262. if ($piece) {
  263. movePiece(
  264. from=$piece.parent().data('id').toLowerCase(),
  265. to=$(this).parent().data('id').toLowerCase(),
  266. promotion=$('#promotion option:selected').val()
  267. )
  268. }
  269. } else {
  270. if ($chess.turn() != $side) {
  271. return false;
  272. }
  273. if ($piece && isSelected($(this).parent())) {
  274. unselectPiece($piece.parent());
  275. $piece = null;
  276. } else {
  277. if ($piece) {
  278. unselectPiece($piece.parent());
  279. $piece = null;
  280. }
  281. $piece = $(this);
  282. selectPiece($piece.parent());
  283. }
  284. }
  285. e.stopImmediatePropagation();
  286. e.preventDefault();
  287. });
  288. $('.chess_board td').click(function (e) {
  289. if ($piece) {
  290. movePiece(
  291. from=$piece.parent().data('id').toLowerCase(),
  292. to=$(this).data('id').toLowerCase(),
  293. promotion=$('#promotion option:selected').val()
  294. )
  295. }
  296. });
  297. $('#modal-mask, #modal-ok').click(function (e) {
  298. e.preventDefault();
  299. hideModal();
  300. });
  301. $('#modal-window').click(function (e) {
  302. e.stopPropagation();
  303. });
  304. $('.resign').click(function (e) {
  305. $socket.emit('resign', {
  306. 'token': $token,
  307. 'color': $side
  308. });
  309. });
  310. $('a.chat').click(function (e) {
  311. $('#chat-wrapper').toggle();
  312. $('.new-message').remove();
  313. var chat_node = $('ul#chat')[0];
  314. if (chat_node.scrollHeight > 300) {
  315. setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
  316. }
  317. });
  318. $('#chat-wrapper .close').click(function (e) {
  319. $('#chat-wrapper').hide();
  320. });
  321. $('#sendMessage').submit(function (e) {
  322. e.preventDefault();
  323. var input = $(this).find('input');
  324. var message = input.val();
  325. var color = $side === 'b' ? 'black' : 'white';
  326. if (!/^\W*$/.test(message)) {
  327. input.val('');
  328. $('ul#chat').append('<li class="' + color + ' right" >' + message + '</li>');
  329. var chat_node = $('ul#chat')[0];
  330. if (chat_node.scrollHeight > 300) {
  331. setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
  332. }
  333. $socket.emit('send-message', {
  334. 'message': message,
  335. 'color': color,
  336. 'token': $token
  337. });
  338. }
  339. });
  340. });