var express = require('express') , favicon = require('serve-favicon') , bodyParser = require('body-parser') , logger = require('morgan') , cookieParser = require('cookie-parser') , session = require('express-session') , methodOverride = require('express-method-override') , errorhandler = require('errorhandler') , path = require('path') , crypto = require('crypto') , http = require('http') , winston = require('winston') , fs = require('fs'); var app = express(); app.set('port', process.env.OPENSHIFT_NODEJS_PORT || 8181); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); //app.use(favicon()); //app.use(logger('dev')); app.use(bodyParser()); app.use(methodOverride()); app.use(cookieParser('45710b553b5b7293753d03bd3601f70a')); app.use(session()); //app.use(app.router); app.use(express.static(path.join(__dirname, 'public'))); //app.use(errorHandler()); app.get('/', function(req, res) { res.render('index'); }); app.get('/about', function(req, res) { res.render('about'); }); app.get('/play/:token/:time/:increment', function(req, res) { res.render('play', { 'token': req.params.token, 'time': req.params.time, 'increment': req.params.increment }); }); app.get('/logs', function(req, res) { fs.readFile(__dirname + '/logs/games.log', function (err, data) { if (err) { res.redirect('/'); } res.set('Content-Type', 'text/plain'); res.send(data); }); }); var server = http.createServer(app).listen(app.get('port'), app.get('ipaddress'), function() { console.log("Express server listening on port " + app.get('port')); }); var games = {}; var timer; /** * Sockets */ var io = require('socket.io').listen(server, {log: false}); if (process.env.OPENSHIFT_NODEJS_IP) { io.configure(function(){ io.set('transports', ['websocket']); }); } 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); //token is valid for 5 minutes var timeout = setTimeout(function () { if (games[token].players.length === 0) { delete games[token]; socket.emit('token-expired'); } }, 5 * 60 * 1000); games[token] = { 'creator': socket, 'players': [], 'interval': null, 'timeout': timeout }; socket.emit('created', { 'token': token }); }); socket.on('join', function (data) { var game, color, time = data.time; if (!(data.token in games)) { socket.emit('token-invalid'); return; } clearTimeout(games[data.token].timeout); game = games[data.token]; if (game.players.length >= 2) { socket.emit('full'); return; } else if (game.players.length === 1) { if (game.players[0].color === 'black') { color = 'white'; } else { color = 'black'; } winston.log('info', 'Number of currently running games', { '#': Object.keys(games).length }); } else { var colors = ['black', 'white']; color = colors[Math.floor(Math.random() * 2)]; } //join room socket.join(data.token); games[data.token].players.push({ 'id': socket.id, 'socket': socket, 'color': color, 'time': data.time - data.increment + 1, 'increment': data.increment }); game.creator.emit('ready', {}); socket.emit('joined', { 'color': color }); }); socket.on('timer-white', function (data) { runTimer('white', data.token, socket); }); socket.on('timer-black', function (data) { runTimer('black', data.token, socket); }); socket.on('timer-clear-interval', function (data) { if (data.token in games) { clearInterval(games[data.token].interval); } }); socket.on('new-move', function (data) { var opponent; if (data.token in games) { opponent = getOpponent(data.token, socket); if (opponent) { opponent.socket.emit('move', { 'move': data.move }); } } }); socket.on('resign', function (data) { if (data.token in games) { clearInterval(games[data.token].interval); io.sockets.in(data.token).emit('player-resigned', { 'color': data.color }); } }); socket.on('rematch-offer', function (data) { var opponent; if (data.token in games) { opponent = getOpponent(data.token, socket); if (opponent) { opponent.socket.emit('rematch-offered'); } } }); socket.on('rematch-decline', function (data) { var opponent; if (data.token in games) { opponent = getOpponent(data.token, socket); if (opponent) { opponent.socket.emit('rematch-declined'); } } }); socket.on('rematch-confirm', function (data) { var opponent; if (data.token in games) { for(var j in games[data.token].players) { games[data.token].players[j].time = data.time - data.increment + 1; games[data.token].players[j].increment = data.increment; games[data.token].players[j].color = games[data.token].players[j].color === 'black' ? 'white' : 'black'; } opponent = getOpponent(data.token, socket); if (opponent) { io.sockets.in(data.token).emit('rematch-confirmed'); } } }) socket.on('disconnect', function (data) { var player, opponent, game; for (var token in games) { game = games[token]; for (var j in game.players) { player = game.players[j]; if (player.socket === socket) { opponent = game.players[Math.abs(j - 1)]; if (opponent) { opponent.socket.emit('opponent-disconnected'); } clearInterval(games[token].interval); delete games[token]; } } } }); socket.on('send-message', function (data) { if (data.token in games) { var opponent = getOpponent(data.token, socket); if (opponent) { opponent.socket.emit('receive-message', data); } } }); }); function runTimer(color, token, socket) { var player, time_left, game = games[token]; if (!game) return; 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) { io.sockets.in(token).emit('countdown', { 'time': time_left, 'color': color }); } else { io.sockets.in(token).emit('countdown-gameover', { 'color': color }); clearInterval(games[token].interval); } }, 1000); } } } function getOpponent(token, socket) { var player, game = games[token]; for (var j in game.players) { player = game.players[j]; if (player.socket === socket) { var opponent = game.players[Math.abs(j - 1)]; return opponent; } } }