io.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. 'use strict';
  2. import IO from 'socket.io';
  3. import winston from './winston';
  4. import {Map, List} from 'immutable';
  5. const io = IO.listen();
  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. if (!game) {
  30. socket.emit('token-invalid');
  31. return;
  32. }
  33. const nOfPlayers = game.get('players').size;
  34. const colors = ['black', 'white'];
  35. let color;
  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. if (nOfPlayers === 1) {
  64. io.sockets.in(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('resign', data => {
  75. if (!_games.has(data.token)) return;
  76. clearInterval(_games.getIn([data.token, 'interval']));
  77. io.sockets.in(data.token).emit('player-resigned', {
  78. color: data.color
  79. });
  80. });
  81. socket.on('rematch-offer', data =>
  82. maybeEmit('rematch-offered', {}, data.token, socket));
  83. socket.on('rematch-decline', data =>
  84. maybeEmit('rematch-declined', {}, data.token, socket));
  85. socket.on('rematch-confirm', data => {
  86. if (!_games.has(data.token)) return;
  87. _games = _games.updateIn([data.token, 'players'], players =>
  88. players.map(player => player
  89. .set('time', data.time - data.inc + 1)
  90. .set('inc', data.inc)
  91. .update('color', color => color === 'black' ? 'white' : 'black')));
  92. io.sockets.in(data.token).emit('rematch-confirmed');
  93. });
  94. socket.on('disconnect', data => {
  95. let tokenToDelete;
  96. _games.forEach((game, token) => {
  97. const opponent = getOpponent(token, socket);
  98. if (opponent) {
  99. opponent.get('socket').emit('opponent-disconnected');
  100. clearInterval(game.get('interval'));
  101. tokenToDelete = token;
  102. return false;
  103. }
  104. });
  105. if (tokenToDelete) {
  106. _games = _games.delete(tokenToDelete);
  107. }
  108. });
  109. socket.on('send-message', data =>
  110. maybeEmit('receive-message', data, data.token, socket));
  111. });
  112. function maybeEmit(event, data, token, socket) {
  113. if (!_games.has(token)) return;
  114. const opponent = getOpponent(token, socket);
  115. if (opponent) {
  116. opponent.get('socket').emit(event, data);
  117. }
  118. }
  119. function runClock(color, token, socket) {
  120. if (!_games.has(token)) return;
  121. _games.getIn([token, 'players']).forEach((player, idx) => {
  122. if (player.get('socket') === socket && player.get('color') === color) {
  123. clearInterval(_games.getIn([token, 'interval']));
  124. _games = _games
  125. .updateIn([token, 'players', idx, 'time'], time =>
  126. time += player.get('inc'))
  127. .setIn([token, 'interval'], setInterval(() => {
  128. let timeLeft = 0;
  129. _games = _games.updateIn([token, 'players', idx, 'time'], time => {
  130. timeLeft = time - 1;
  131. return time - 1;
  132. });
  133. if (timeLeft >= 0) {
  134. io.sockets.in(token).emit('countdown', {
  135. time: timeLeft,
  136. color: color
  137. });
  138. } else {
  139. io.sockets.in(token).emit('countdown-gameover', {
  140. color: color
  141. });
  142. clearInterval(_games.getIn([token, 'interval']));
  143. }
  144. }, 1000));
  145. return false;
  146. }
  147. });
  148. }
  149. function getOpponent(token, socket) {
  150. let index = null;
  151. _games.getIn([token, 'players']).forEach((player, idx) => {
  152. if (player.get('socket') === socket) {
  153. index = Math.abs(idx - 1);
  154. return false;
  155. }
  156. });
  157. if (index !== null) {
  158. return _games.getIn([token, 'players', index]);
  159. }
  160. }
  161. export default io;