Browse Source

timer events on server side

romanmatiasko 10 years ago
parent
commit
70313491bd

+ 2 - 2
public/javascripts/app.js

@@ -1,4 +1,4 @@
-var $URL, $WS, $token, $socket;
+var $URL, $WS, $socket;
 //var ENVIRONMENT = 'openshift';
 var ENVIRONMENT = 'dev';
 
@@ -12,4 +12,4 @@ if (ENVIRONMENT === 'dev') {
 
 $(document).ready(function () {
   $socket = io.connect($WS);
-});
+});

+ 41 - 3
public/javascripts/play.js

@@ -155,6 +155,16 @@ function movePiece(from, to, promotion, rcvd) {
     if ($chess.game_over()) {
       $('.resign').hide();
       alert(result);
+    } else {
+      if ($chess.turn() === 'b') {
+        $socket.emit('timer-black', {
+          'token': $token
+        });
+      } else {
+        $socket.emit('timer-white', {
+          'token': $token
+        });
+      }
     }
   }
 }
@@ -163,13 +173,18 @@ function movePiece(from, to, promotion, rcvd) {
 $(document).ready(function () {
 
   $socket.emit('join', {
-    'token': $token
+    'token': $token,
+    'time': $time * 60,
+    'increment': $increment
   });
 
   $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();
@@ -215,6 +230,23 @@ $(document).ready(function () {
       messageSnd.play();
     }
   });
+
+  $socket.on('countdown-white', function (data) {
+    console.log('white', data.time);
+  });
+
+  $socket.on('countdown-black', function (data) {
+    console.log('black', data.time);
+  });
+
+  $socket.on('countdown-gameover', function (data) {
+    var loser = data.color === 'black' ? 'Black' : 'White';
+    var winner = data.color === 'black' ? 'White' : 'Black';
+    $('.resign').hide();
+    alert(loser + "'s time is out. " + winner + " won.");
+    window.location = '/';
+  });
+
 });
 
 /* gameplay */
@@ -263,7 +295,9 @@ $(document).ready(function () {
   });
 
   $('.resign').click(function (e) {
-    $socket.emit('resign');
+    $socket.emit('resign', {
+      'token': $token
+    });
     alert('You resigned.');
     window.location = '/';
   });
@@ -296,7 +330,11 @@ $(document).ready(function () {
         setTimeout(function() { chat_node.scrollTop = chat_node.scrollHeight; }, 50);
       }
 
-      $socket.emit('send-message', { 'message': message, 'color': color });
+      $socket.emit('send-message', {
+        'message': message,
+        'color': color,
+        'token': $token
+      });
     }
   });
 

+ 15 - 6
public/javascripts/start.js

@@ -1,19 +1,28 @@
 $(document).ready(function () {
+  var $token, $time, $increment;
+
   $socket.on('created', function (data) {
     $token = data.token;
-    $('#game_link').val($URL + '/play/' + $token); // create game link
+
+    $('#game_link').val($URL + '/play/' + $token + '/' + $time + '/' + $increment); // create game link
     $('#game_link').click(function() {
       $(this).select(); // when clicked, link is automatically selected for convenience
     });
   });
 
   $socket.on('ready', function (data) {
-    document.location = $URL + '/play/' + $token;
+    document.location = $URL + '/play/' + $token + '/' + $time + '/' + $increment;
   });
 
-  $('#play').click(function (ev) { 
-    $socket.emit('start'); 
-    $('#waiting').slideDown(400); // show waiting for opponent message
-    ev.preventDefault();
+  $('#play').click(function (ev) {
+    var min = parseInt($('#minutes').val());
+    var sec = parseInt($('#seconds').val());
+    if (!isNaN(min) && min > 0 && min <= 50 && !isNaN(sec) && sec >= 0 && sec <= 50) {
+      $time = min;
+      $increment = sec;
+      $socket.emit('start');
+      $('#waiting').slideDown(400); // show waiting for opponent message
+      ev.preventDefault();
+    }
   });
 });

+ 25 - 1
public/stylesheets/style.css

@@ -145,8 +145,24 @@ header {
 
 #form {
   width: 100%;
-  height: 150px;
+  height: 200px;
   margin: 0 auto; }
+  #form fieldset {
+    border: 0;
+    text-align: right;
+    margin-bottom: 1em;
+    width: 80%; }
+    #form fieldset input {
+      margin-left: 1em;
+      width: 100px;
+      outline: none;
+      height: 40px;
+      line-height: 30px;
+      padding-left: 1em;
+      font-size: 1.125rem;
+      color: #424242;
+      border: 2px solid #2b222c;
+      border-left: 4px solid #2eafc2; }
 
 input, button {
   font-family: 'Open Sans'; }
@@ -523,6 +539,14 @@ span.promotion {
   header, #board_moves_wrapper, #sounds_label {
     max-width: 530px; } }
 @media only screen and (max-width: 759px) {
+  #form fieldset {
+    width: 100%;
+    text-align: center; }
+  #form label {
+    display: block;
+    line-height: 45px;
+    padding-left: 0; }
+
   html {
     font-size: 14px; }
 

+ 33 - 1
public/stylesheets/style.scss

@@ -159,8 +159,28 @@ header {
 
 #form{
   width: 100%;
-  height: 150px;
+  height: 200px;
   margin: 0 auto;
+
+  fieldset{
+    border: 0;
+    text-align: right;
+    margin-bottom: 1em;
+    width: 80%;
+
+    input{
+      margin-left: 1em;
+      width: 100px;
+      outline: none;
+      height: 40px;
+      line-height: 30px;
+      padding-left: 1em;
+      font-size: 1.125rem;
+      color: #424242;
+      border: 2px solid #2b222c;
+      border-left: 4px solid #2eafc2;
+    }
+  }
 }
 
 input, button{
@@ -605,6 +625,18 @@ span.promotion{
 }
 
 @media only screen and (max-width: 759px){
+  #form{
+    fieldset{
+      width: 100%;
+      text-align: center;
+    }
+    label{
+      display: block;
+      line-height: 45px;
+      padding-left: 0;
+    }
+  }
+
   html{
     font-size: 14px;
   }

+ 93 - 29
server.js

@@ -32,9 +32,11 @@ app.get('/about', function(req, res) {
   res.render('about');
 });
 
-app.get('/play/:token', function(req, res) {
+app.get('/play/:token/:time/:increment', function(req, res) {
   res.render('play', {
-    'token': req.params.token
+    'token': req.params.token,
+    'time': req.params.time,
+    'increment': req.params.increment
   });
 });
 
@@ -43,6 +45,7 @@ var server = http.createServer(app).listen(app.get('port'), app.get('ipaddress')
 });
 
 var games = {};
+var timer;
 
 /**
  * Sockets
@@ -56,15 +59,16 @@ if (process.env.OPENSHIFT_NODEJS_IP) {
 }
 
 io.sockets.on('connection', function (socket) {
+  
   socket.on('start', function (data) {
     var token;
-
     var b = new Buffer(Math.random() + new Date().getTime() + socket.id);
     token = b.toString('base64').slice(12, 32);
 
     games[token] = {
       'creator': socket,
-      'players': []
+      'players': [],
+      'interval': null
     };
 
     socket.emit('created', {
@@ -73,7 +77,7 @@ io.sockets.on('connection', function (socket) {
   });
 
   socket.on('join', function (data) {
-    var game, color;
+    var game, color, time = data.time;
 
     if (!(data.token in games)) {
       return;
@@ -84,8 +88,8 @@ io.sockets.on('connection', function (socket) {
     if (game.players.length >= 2) {
       socket.emit('full');
       return;
-    } else if (game.players.length == 1) {
-      if (game.players[0].color == 'black') {
+    } else if (game.players.length === 1) {
+      if (game.players[0].color === 'black') {
         color = 'white';
       } else {
         color = 'black';
@@ -99,7 +103,9 @@ io.sockets.on('connection', function (socket) {
     games[data.token].players.push({
       'id': socket.id,
       'socket': socket,
-      'color': color
+      'color': color,
+      'time': data.time - data.increment + 1,
+      'increment': data.increment
     });
 
     game.creator.emit('ready', {});
@@ -109,6 +115,14 @@ io.sockets.on('connection', function (socket) {
     });
   });
 
+  socket.on('timer-white', function (data) {
+    runTimer('white', data.token, socket);
+  });
+
+  socket.on('timer-black', function (data) {
+    runTimer('black', data.token, socket);
+  });
+
   socket.on('new-move', function (data) {
     var receiver, game;
 
@@ -132,45 +146,95 @@ io.sockets.on('connection', function (socket) {
   });
 
   socket.on('resign', function (data) {
-    cancelGame('opponent-resigned', socket);
+    cancelGame('opponent-resigned', data.token, socket);
   });
 
   socket.on('disconnect', function (data) {
-    cancelGame('opponent-disconnected', socket);
+    cancelGame('opponent-disconnected', null, socket);
   });
 
   socket.on('send-message', function (data) {
-    var opponent = getOpponent(socket);
+    var opponent = getOpponent(data.token, socket);
     opponent.socket.emit('receive-message', data);
   });
 });
 
-function getOpponent(socket) {
-  for (var token in games) {
-    var game = games[token];
+function runTimer(color, token, socket) {
+  var player, time_left, game = games[token];
+
+  for (var i in game.players) {
+    player = game.players[i];
+
+    if (player.socket === socket && player.color === color) {
+
+      clearInterval(games[token].interval);
+      games[token].players[i].time += games[token].players[i].increment;
+
+      return games[token].interval = setInterval(function() {
+        games[token].players[i].time -= 1;
+        time_left = games[token].players[i].time;
+
+        if (time_left >= 0) {
+          socket.emit('countdown-' + color, {
+            'time': time_left
+          });
+        } else {
+          var opponent = getOpponent(token, socket);
+
+          socket.emit('countdown-gameover', {
+            'color': color
+          });
+          opponent.socket.emit('countdown-gameover', {
+            'color': color
+          });
+          clearInterval(games[token].interval);
+          delete games[token];
+        }
+      }, 1000);
+    }
+  }
+}
 
-    for (var j in game.players) {
-      var player = game.players[j];
+function getOpponent(token, socket) {
+  var player, game = games[token];
 
-      if (player.socket == socket) {
-        var opponent = game.players[Math.abs(j - 1)];
-        return opponent;
-      }
+  for (var j in game.players) {
+    player = game.players[j];
+
+    if (player.socket == socket) {
+      var opponent = game.players[Math.abs(j - 1)];
+
+      return opponent;
     }
   }
 }
 
-function cancelGame(event, socket) {
-  for (var token in games) {
-    var game = games[token];
+function cancelGame(event, token, socket) {
+  var player, game, opponent;
+
+  function removeGame(game, opponent) {    
+    clearInterval(game.interval);
+
+    opponent.socket.emit(event);
+    delete games[token];
+  }
+
+  if (token) {
+    game = games[token];
+    opponent = getOpponent(token, socket);
+    removeGame(game, opponent);
+  } else {
+
+    for (var token in games) {
+    game = games[token];
 
-    for (var j in game.players) {
-      var player = game.players[j];
+      for (var j in game.players) {
+        player = game.players[j];
 
-      if (player.socket == socket) {
-        var opponent = game.players[Math.abs(j - 1)];
-        delete games[token];
-        opponent.socket.emit(event);
+        if (player.socket == socket) {
+          opponent = game.players[Math.abs(j - 1)];
+          removeGame(game, opponent);
+        }
       }
     }
   }

+ 5 - 5
views/about.jade

@@ -1,8 +1,5 @@
 extends layout
 
-block ribbon
-  a(href='https://github.com/romanmatiasko/reti-chess', class='fork clearfix')
-
 block content
   h1.knight
   h2 About Reti Chess
@@ -17,10 +14,13 @@ block content
     |  for move validation and check/mate/draw detection.
 
   h3 Why Reti?
-  p The project is named after famous Czechoslovakian chess player Richard Réti. After him is also named a chess opening which begins with the moves: 1. Nf3 d5, 2. d4.
+  p The project is named after famous Czechoslovakian chess player Richard Réti. 
+    | After him is also named a chess opening which begins with the moves: 1. Nf3 d5, 2. d4.
 
   h3 What features chess supports?
-  p This is a very lightweight version of chess. You can only play real-time against the human, an AI is not available. Reti Chess doesn't have any timer, the time for a move is unlimited. Although, if you reload the window or disconnect from the game, the game will be cancelled.
+  p This is a lightweight version of chess. You can only play real-time against the human, an AI is not available. 
+    | Reti Chess supports simple chat and also timer. You can set timer up to 50 minutes with increment of max 50 seconds per side. 
+    | If you reload the window or disconnect from the game, the game will be cancelled.
 
   h3 People behind this project
   p

+ 8 - 3
views/index.jade

@@ -7,9 +7,14 @@ block content
   p.margin-fifty.center A lightweight real-time chess app built in Node.js, Express framework and Socket.IO
 
   div#form
-    form#create_form(action='#', method='post')
-      input#game_link.game_link(type='text', readonly, value='', value='Game link will be generated here.')
-    input.button#play(type='button', value='Play')
+    form#create_form(action='')
+      fieldset
+        label Minutes per side: 
+          input#minutes(type='number', value='30', min='1', max='50', required)
+        label(style='padding-left: 2em;') Increment in seconds:
+          input#seconds(type='number', value='0', min='0', max='50', required)
+      input#game_link.game_link(type='text', readonly, value='Game link will be generated here.')
+      button.button#play(type='submit') Play
   
     p#waiting Waiting for opponent to connect.
   

+ 1 - 0
views/layout.jade

@@ -17,6 +17,7 @@ html
     script(type='text/javascript', src='/javascripts/jquery.stickyFooter.js')
     script(type='text/javascript', src='/javascripts/chess.js')
     script(type='text/javascript', src='/javascripts/app.js')
+    block head
     link(href='http://fonts.googleapis.com/css?family=Cherry+Swash|Open+Sans:400,600', rel='stylesheet', type='text/css')
     //if IE
       script(src='http://html5shiv.googlecode.com/svn/trunk/html5.js')

+ 7 - 4
views/play.jade

@@ -1,5 +1,12 @@
 extends layout
 
+block head
+  script(type='text/javascript')
+    $token = '#{token}';
+    $time = #{time};
+    $increment = #{increment};
+  script(type='text/javascript', src='/javascripts/play.js')
+
 block content
   header.clearfix
     a.button(href='/') New game
@@ -7,10 +14,6 @@ block content
     a.chat
       img#bubble(src='/images/chat.svg', width='50', height='50')
       | Chat
-
-  script(type='text/javascript')
-    $token = '#{token}';
-  script(type='text/javascript', src='/javascripts/play.js')
   audio(id='moveSnd', preload='auto')
     source(src='/sounds/move.mp3')
     source(src='/sounds/move.ogg')