io.js 5.0 KB

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