io.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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 colors = ['black', 'white'];
  30. let color;
  31. if (!game) {
  32. socket.emit('token-invalid');
  33. return;
  34. }
  35. clearTimeout(game.get('timeout'));
  36. if (game.get('players').size >= 2) {
  37. socket.emit('full');
  38. return;
  39. } else if (game.get('players').size === 1) {
  40. if (game.getIn(['players', 0, 'color']) === 'black')
  41. color = 'white';
  42. else
  43. color = 'black';
  44. winston.log('info', 'Number of currently running games', {
  45. '#': games.size
  46. });
  47. } else {
  48. color = colors[Math.floor(Math.random() * 2)];
  49. }
  50. // join room
  51. socket.join(data.token);
  52. games = games.updateIn([data.token, 'players'], players =>
  53. players.push(Map({
  54. id: socket.id,
  55. socket: socket,
  56. color: color,
  57. time: data.time - data.inc + 1,
  58. inc: data.inc
  59. })));
  60. game.get('creator').emit('ready');
  61. socket.emit('joined', {color: color});
  62. });
  63. socket.on('clock-run', data => runClock(data.color, data.token, socket));
  64. socket.on('new-move', data => {
  65. maybeEmit('move', data.move, data.token, socket);
  66. if (data.move.gameOver) {
  67. clearInterval(games.getIn([data.token, 'interval']));
  68. }
  69. });
  70. socket.on('resign', data => {
  71. if (!games.has(data.token)) return;
  72. clearInterval(games.getIn([data.token, 'interval']));
  73. io.sockets.in(data.token).emit('player-resigned', {
  74. color: data.color
  75. });
  76. });
  77. socket.on('rematch-offer', data =>
  78. maybeEmit('rematch-offered', {}, data.token, socket));
  79. socket.on('rematch-decline', data =>
  80. maybeEmit('rematch-declined', {}, data.token, socket));
  81. socket.on('rematch-confirm', data => {
  82. if (!games.has(data.token)) return;
  83. games = games.updateIn([data.token, 'players'], players =>
  84. players.map(player => player
  85. .set('time', data.time - data.inc + 1)
  86. .set('inc', data.inc)
  87. .update('color', color => color === 'black' ? 'white' : 'black')));
  88. io.sockets.in(data.token).emit('rematch-confirmed');
  89. });
  90. socket.on('disconnect', data => {
  91. let tokenToDelete;
  92. games.forEach((game, token) => {
  93. const opponent = getOpponent(token, socket);
  94. if (opponent) {
  95. opponent.get('socket').emit('opponent-disconnected');
  96. clearInterval(game.get('interval'));
  97. tokenToDelete = token;
  98. return false;
  99. }
  100. });
  101. if (tokenToDelete) {
  102. games = games.delete(tokenToDelete);
  103. }
  104. });
  105. socket.on('send-message', data =>
  106. maybeEmit('receive-message', data, data.token, socket));
  107. });
  108. function maybeEmit(event, data, token, socket) {
  109. if (!games.has(token)) return;
  110. const opponent = getOpponent(token, socket);
  111. if (opponent) {
  112. opponent.get('socket').emit(event, data);
  113. }
  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.sockets.in(token).emit('countdown', {
  131. time: timeLeft,
  132. color: color
  133. });
  134. } else {
  135. io.sockets.in(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 = io;