io.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. 'use strict';
  2. const io = require('socket.io').listen();
  3. const winston = require('./winston');
  4. const Immutable = require('immutable');
  5. const Map = Immutable.Map;
  6. const List = Immutable.List;
  7. var _games = Map();
  8. io.sockets.on('connection', socket => {
  9. socket.on('start', data => {
  10. let token;
  11. const b = new Buffer(Math.random() + new Date().getTime() + socket.id);
  12. token = b.toString('base64').slice(12, 28);
  13. // token is valid for 3 minutes
  14. const timeout = setTimeout(() => {
  15. if (_games.getIn([token, 'players']).isEmpty()) {
  16. _games = _games.delete(token);
  17. socket.emit('token-expired');
  18. }
  19. }, 3 * 60 * 1000);
  20. _games = _games.set(token, Map({
  21. creator: socket,
  22. players: List(),
  23. interval: null,
  24. timeout: timeout
  25. }));
  26. socket.emit('created', {token: token});
  27. });
  28. socket.on('join', data => {
  29. const game = _games.get(data.token);
  30. if (!game) {
  31. socket.emit('token-invalid');
  32. return;
  33. }
  34. const nOfPlayers = game.get('players').size;
  35. const colors = ['black', 'white'];
  36. let color;
  37. clearTimeout(game.get('timeout'));
  38. if (nOfPlayers >= 2) {
  39. socket.emit('full');
  40. return;
  41. } else if (nOfPlayers === 1) {
  42. if (game.getIn(['players', 0, 'color']) === 'black')
  43. color = 'white';
  44. else
  45. color = 'black';
  46. winston.log('info', 'Number of currently running games', {
  47. '#': _games.size
  48. });
  49. } else {
  50. color = colors[Math.floor(Math.random() * 2)];
  51. }
  52. // join room
  53. socket.join(data.token);
  54. _games = _games.updateIn([data.token, 'players'], players =>
  55. players.push(Map({
  56. socket: socket,
  57. color: color,
  58. time: data.time - data.inc + 1,
  59. inc: data.inc
  60. })));
  61. game.get('creator').emit('ready');
  62. socket.emit('joined', {color: color});
  63. if (nOfPlayers === 1) {
  64. io.to(data.token).emit('both-joined');
  65. }
  66. });
  67. socket.on('clock-run', data => runClock(data.color, data.token, socket));
  68. socket.on('new-move', data => {
  69. maybeEmit('move', data.move, data.token, socket);
  70. if (data.move.gameOver) {
  71. clearInterval(_games.getIn([data.token, 'interval']));
  72. }
  73. });
  74. socket.on('send-message', data =>
  75. maybeEmit('receive-message', data, data.token, socket));
  76. socket.on('resign', data => {
  77. if (!_games.has(data.token)) return;
  78. clearInterval(_games.getIn([data.token, 'interval']));
  79. io.to(data.token).emit('player-resigned', {
  80. color: data.color
  81. });
  82. });
  83. socket.on('rematch-offer', data =>
  84. maybeEmit('rematch-offered', {}, data.token, socket));
  85. socket.on('rematch-decline', data =>
  86. maybeEmit('rematch-declined', {}, data.token, socket));
  87. socket.on('rematch-accept', data => {
  88. if (!_games.has(data.token)) return;
  89. _games = _games.updateIn([data.token, 'players'], players =>
  90. players.map(player => player
  91. .set('time', data.time - data.inc + 1)
  92. .set('inc', data.inc)
  93. .update('color', color => color === 'black' ? 'white' : 'black')));
  94. io.to(data.token).emit('rematch-accepted');
  95. });
  96. socket.on('disconnect', () => {
  97. const token = findToken(socket);
  98. if (!token) return;
  99. maybeEmit('opponent-disconnected', {}, token, socket);
  100. clearInterval(_games.getIn([token, 'interval']));
  101. _games = _games.delete(token);
  102. });
  103. });
  104. function maybeEmit(event, data, token, socket) {
  105. if (!_games.has(token)) return;
  106. const opponent = getOpponent(token, socket);
  107. if (opponent) {
  108. opponent.get('socket').emit(event, data);
  109. }
  110. }
  111. function findToken(socket) {
  112. return _games.findKey((game, token) =>
  113. game.get('players').some(player => player.get('socket') === socket));
  114. }
  115. function runClock(color, token, socket) {
  116. if (!_games.has(token)) return;
  117. _games.getIn([token, 'players']).forEach((player, idx) => {
  118. if (player.get('socket') === socket && player.get('color') === color) {
  119. clearInterval(_games.getIn([token, 'interval']));
  120. _games = _games
  121. .updateIn([token, 'players', idx, 'time'], time =>
  122. time += player.get('inc'))
  123. .setIn([token, 'interval'], setInterval(() => {
  124. let timeLeft = 0;
  125. _games = _games.updateIn([token, 'players', idx, 'time'], time => {
  126. timeLeft = time - 1;
  127. return time - 1;
  128. });
  129. if (timeLeft >= 0) {
  130. io.to(token).emit('countdown', {
  131. time: timeLeft,
  132. color: color
  133. });
  134. } else {
  135. io.to(token).emit('countdown-gameover', {
  136. color: color
  137. });
  138. clearInterval(_games.getIn([token, 'interval']));
  139. }
  140. }, 1000));
  141. return false;
  142. }
  143. });
  144. }
  145. function getOpponent(token, socket) {
  146. let index = null;
  147. _games.getIn([token, 'players']).forEach((player, idx) => {
  148. if (player.get('socket') === socket) {
  149. index = Math.abs(idx - 1);
  150. return false;
  151. }
  152. });
  153. if (index !== null) {
  154. return _games.getIn([token, 'players', index]);
  155. }
  156. }
  157. module.exports = {
  158. io: io,
  159. // used for jasmine tests
  160. getGames: () => _games
  161. };