Browse Source

add rematch option

romanmatiasko 10 years ago
parent
commit
35371ac1dd
6 changed files with 295 additions and 59 deletions
  1. 148 22
      public/javascripts/play.js
  2. 40 21
      public/stylesheets/style.css
  3. 44 12
      public/stylesheets/style.scss
  4. 43 3
      server.js
  5. 18 0
      views/layout.jade
  6. 2 1
      views/play.jade

+ 148 - 22
public/javascripts/play.js

@@ -1,26 +1,51 @@
 $(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){
+  function modalKeydownHandler(e) {
     e.preventDefault();
-    if (e.which === 13) {
+    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);
+    $(document).on('keyup', modalKeydownHandler);
   }
 
   function hideModal() {
     $('#modal-mask').fadeOut(200);
-    $(document).off('keydown', modalKeydownHandler);
+    $(document).off('keyup', modalKeydownHandler);
+  }
+
+  function showOffer(offer, options) {
+    $('#offer-message').text(offer);
+    $('#offer-mask').fadeIn(200);
+    $(document).on('keyup', options, offerKeydownHandler);
+  }
+
+  function hideOffer() {
+    $('#offer-mask').fadeOut(200);
+    $(document).off('keyup', offerKeydownHandler);
   }
 
   function selectPiece(el) {
@@ -159,7 +184,7 @@ $(function() {
       var move_number = last_move[0];
       var move_pgn = $.trim(last_move[1]);
 
-      if (move_pgn.indexOf(' ') != -1) {
+      if (move_pgn.indexOf(' ') !== -1) {
         var moves = move_pgn.split(' ');
         move_pgn = moves[1];
       }
@@ -179,7 +204,8 @@ $(function() {
           'token': $token
         });
 
-        $('.resign').off().remove();
+        $('.resign').hide();
+        $('.rematch').show();
         showModal(result);
       } else {
         if ($chess.turn() === 'b') {
@@ -197,6 +223,20 @@ $(function() {
 
   /* 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,
@@ -225,12 +265,19 @@ $(function() {
 
   $socket.on('opponent-disconnected', function (data) {
     $('.resign').off().remove();
-    $('.chess_board a').off();
+    $('.chess_board a').off('click', movePieceFromHandler);
+    $('.chess_board td').off('click', movePieceToHandler);
+
     $('#sendMessage').off();
-    $('#sendMessage').submit(function(e) {
+    $('#sendMessage').submit(function (e) {
       e.preventDefault();
-      showModal("Your opponent has diconnected. You can't send messages.");
+      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.");
@@ -239,8 +286,10 @@ $(function() {
 
   $socket.on('player-resigned', function (data) {
     $gameOver = true;
-    $('.resign').off().remove();
-    $('.chess_board a').off();
+    $('.resign').hide();
+    $('.rematch').show();
+    $('.chess_board a').off('click', movePieceFromHandler);
+    $('.chess_board td').off('click', movePieceToHandler);
     var winner = data.color === 'w' ? 'Black' : 'White';
     var loser = data.color === 'w' ? 'White' : 'Black';
     var message = loser + ' resigned. ' + winner + ' wins.';
@@ -286,16 +335,67 @@ $(function() {
 
   $socket.on('countdown-gameover', function (data) {
     $gameOver = true;
-    $('.chess_board a').off();
+    $('.chess_board a').off('click', movePieceFromHandler);
+    $('.chess_board td').off('click', movePieceToHandler);
     var loser = data.color === 'black' ? 'Black' : 'White';
     var winner = data.color === 'black' ? 'White' : 'Black';
-    var message = loser + "'s time is out. " + winner + " won.";
-    $('.resign').off().remove();
+    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');
+    });
+
+    $('#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();
+    }
+
+    $('.chess_board a').on('click', movePieceFromHandler);
+    $('.chess_board td').on('click', movePieceToHandler);
+    $('#sendMessage').find('input').removeClass('white black').addClass($side === 'b' ? 'black' : 'white');
+  });
+
   /* gameplay */
 
   $('#clock li').each(function() {
@@ -303,10 +403,10 @@ $(function() {
   });
   $('#game-type').text($time + '|' + $increment);
 
-  $('.chess_board a').click(function (e) {
+  function movePieceFromHandler(e) {
     var piece = $(this);
-    if ((piece.hasClass('white') && $side != 'w') ||
-        (piece.hasClass('black') && $side != 'b')) {
+    if ((piece.hasClass('white') && $side !== 'w') ||
+        (piece.hasClass('black') && $side !== 'b')) {
       if ($piece) {
         movePiece(
           from=$piece.parent().data('id').toLowerCase(),
@@ -315,7 +415,7 @@ $(function() {
         )
       }
     } else {
-      if ($chess.turn() != $side) {
+      if ($chess.turn() !== $side) {
         return false;
       }
 
@@ -334,9 +434,9 @@ $(function() {
 
     e.stopImmediatePropagation();
     e.preventDefault();
-  });
+  }
 
-  $('.chess_board td').click(function (e) {
+  function movePieceToHandler(e) {
     if ($piece) {
       movePiece(
         from=$piece.parent().data('id').toLowerCase(),
@@ -344,24 +444,50 @@ $(function() {
         promotion=$('#promotion option:selected').val()
       )
     }
-  });
+  }
+
+  $('.chess_board a').on('click', movePieceFromHandler);
+  $('.chess_board td').on('click', movePieceToHandler);
 
   $('#modal-mask, #modal-ok').click(function (e) {
     e.preventDefault();
     hideModal();
   });
 
-  $('#modal-window').click(function (e) {
+  $('#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();

+ 40 - 21
public/stylesheets/style.css

@@ -102,7 +102,7 @@ h3 {
 .last-target {
   background: #77d1df !important; }
 
-#modal-mask {
+#modal-mask, #offer-mask {
   display: none;
   cursor: pointer;
   position: fixed;
@@ -112,8 +112,13 @@ h3 {
   background: #444;
   background: rgba(0, 0, 0, 0.7);
   z-index: 10; }
+  #modal-mask > p, #offer-mask > p {
+    color: #eee;
+    margin: 2em; }
+    #modal-mask > p strong, #offer-mask > p strong {
+      color: #fff; }
 
-#modal-window {
+#modal-window, #offer-window {
   width: 420px;
   height: 180px;
   position: fixed;
@@ -126,17 +131,26 @@ h3 {
   box-shadow: 0 10px 30px #333;
   cursor: auto;
   z-index: 15; }
-  #modal-window p {
+  #modal-window p, #offer-window p {
     text-align: center;
     padding: 1.5em; }
-  #modal-window .button {
-    position: absolute;
-    left: 50%;
-    bottom: 1em;
-    margin-left: -60px; }
-    #modal-window .button:active {
-      top: auto;
-      bottom: calc(1em + 1px); }
+  #modal-window .button:active, #offer-window .button:active {
+    top: auto;
+    bottom: calc(1em + 1px); }
+
+#modal-window .button {
+  position: absolute;
+  left: 50%;
+  bottom: 1em;
+  margin-left: -60px; }
+
+#offer-window .button {
+  position: absolute;
+  bottom: 1em; }
+#offer-window #offer-accept {
+  right: 4em; }
+#offer-window #offer-decline {
+  left: 4em; }
 
 .chat {
   text-decoration: none;
@@ -318,21 +332,26 @@ a.button {
 a.button:hover {
   color: white; }
 
-.resign {
+.resign, .rematch {
+  margin-right: 10px; }
+
+.button--red {
   background: #d12521;
   border-color: #bb211e;
   box-shadow: inset 0 -2px #bb211e;
   margin-right: 10px; }
+  .button--red:hover {
+    background: #de322e;
+    border-bottom: 2px solid #c82320;
+    box-shadow: inset 0 -2px #c82320;
+    -moz-transition: all 0.2s ease-in-out;
+    -webkit-transition: all 0.2s ease-in-out;
+    -o-transition: all 0.2s ease-in-out;
+    -ms-transition: all 0.2s ease-in-out;
+    transition: all 0.2s ease-in-out; }
 
-.resign:hover {
-  background: #de322e;
-  border-bottom: 2px solid #c82320;
-  box-shadow: inset 0 -2px #c82320;
-  -moz-transition: all 0.2s ease-in-out;
-  -webkit-transition: all 0.2s ease-in-out;
-  -o-transition: all 0.2s ease-in-out;
-  -ms-transition: all 0.2s ease-in-out;
-  transition: all 0.2s ease-in-out; }
+.rematch {
+  display: none; }
 
 p#waiting {
   font-family: 'Cherry Swash';

+ 44 - 12
public/stylesheets/style.scss

@@ -110,7 +110,7 @@ h3 {
   background: lighten($blue, 20%) !important;
 }
 
-#modal-mask{
+#modal-mask, #offer-mask{
   display: none;
   cursor: pointer;
   position: fixed;
@@ -120,9 +120,17 @@ h3 {
   background: #444;
   background: rgba(0, 0, 0, .7);
   z-index: 10;
+
+  > p{
+    color: #eee;
+    margin: 2em;
+    strong{
+      color: #fff;
+    }
+  }
 }
 
-#modal-window{
+#modal-window, #offer-window{
   width: 420px;
   height: 180px;
   position: fixed;
@@ -140,16 +148,31 @@ h3 {
     text-align: center;
     padding: 1.5em;
   }
+  .button:active{
+    top: auto;
+    bottom: calc(1em + 1px);
+  }
+}
+
+#modal-window{
   .button{
     position: absolute;
     left: 50%;
     bottom: 1em;
     margin-left: -60px;
+  }
+}
 
-    &:active{
-      top: auto;
-      bottom: calc(1em + 1px);
-    }
+#offer-window{
+  .button{
+    position: absolute;
+    bottom: 1em;
+  }
+  #offer-accept{
+    right: 4em;
+  }
+  #offer-decline{
+    left: 4em;
   }
 }
 
@@ -351,17 +374,26 @@ a.button:hover{
   color: white;
 }
 
-.resign {
+.resign, .rematch {
+  margin-right: 10px;
+}
+
+.button--red{
   background: $red;
   border-color: darken($red, 5%);
   box-shadow: inset 0 -2px darken($red, 5%);
   margin-right: 10px;
+
+  &:hover{
+    background: lighten($red, 5%);
+    border-bottom: 2px solid darken($red, 2%);
+    box-shadow: inset 0 -2px darken($red, 2%);
+    @include all-transition;
+  }
 }
-.resign:hover {
-  background: lighten($red, 5%);
-  border-bottom: 2px solid darken($red, 2%);
-  box-shadow: inset 0 -2px darken($red, 2%);
-  @include all-transition;
+
+.rematch{
+  display: none;
 }
 
 p#waiting {

+ 43 - 3
server.js

@@ -101,12 +101,12 @@ io.sockets.on('connection', function (socket) {
 
     //join room
     socket.join(data.token);
-    
+
     games[data.token].players.push({
       'id': socket.id,
       'socket': socket,
       'color': color,
-      'time': data.time - data.increment,
+      'time': data.time - data.increment + 1,
       'increment': data.increment
     });
 
@@ -162,6 +162,46 @@ io.sockets.on('connection', function (socket) {
     }
   });
 
+  socket.on('rematch-offer', function (data) {
+    var opponent;
+    
+    if (data.token in games) {
+      opponent = getOpponent(data.token, socket);
+      if (opponent) {
+        opponent.socket.emit('rematch-offered');
+      }
+    }
+  });
+
+  socket.on('rematch-decline', function (data) {
+    var opponent;
+
+    if (data.token in games) {
+      opponent = getOpponent(data.token, socket);
+      if (opponent) {
+        opponent.socket.emit('rematch-declined');
+      }
+    }
+  });
+
+  socket.on('rematch-confirm', function (data) {
+    var opponent;
+
+    if (data.token in games) {
+
+      for(var j in games[data.token].players) {
+        games[data.token].players[j].time = data.time - data.increment + 1;
+        games[data.token].players[j].increment = data.increment;
+        games[data.token].players[j].color = games[data.token].players[j].color === 'black' ? 'white' : 'black';
+      }
+
+      opponent = getOpponent(data.token, socket);
+      if (opponent) {
+        io.sockets.in(data.token).emit('rematch-confirmed');
+      }
+    }
+  })
+
   socket.on('disconnect', function (data) {
     var player, opponent, game;
     for (var token in games) {
@@ -199,7 +239,7 @@ function runTimer(color, token, socket) {
     if (player.socket === socket && player.color === color) {
 
       clearInterval(games[token].interval);
-      games[token].players[i].time += games[token].players[i].increment + 1;
+      games[token].players[i].time += games[token].players[i].increment;
 
       return games[token].interval = setInterval(function() {
         games[token].players[i].time -= 1;

+ 18 - 0
views/layout.jade

@@ -19,9 +19,27 @@ html
       script(src='http://html5shiv.googlecode.com/svn/trunk/html5.js')
   body
     #modal-mask
+      p
+        strong Esc: 
+        |  OK
+        br
+        strong Enter: 
+        |  OK
       #modal-window
         p#modal-message Message.
         a#modal-ok.button(href='#') OK
+    #offer-mask
+      p
+        strong Esc: 
+        |  Decline
+        br
+        strong Enter: 
+        |  Accept
+      #offer-window
+        p#offer-message Offer.
+        a#offer-accept.button(href='#') Accept
+        a#offer-decline.button.button--red(href='#') Decline
+
     #container_wrapper.clearfix
       #container.clearfix
         block content

+ 2 - 1
views/play.jade

@@ -13,7 +13,8 @@ block content
       li.black
     span#game-type
     a.button(href='/') New game
-    a.button.resign Resign
+    a.button.button--red.resign Resign
+    a.button.button--red.rematch Rematch
     a.chat
       img#bubble(src='/images/chat.svg', width='50', height='50')
       | Chat