var express = require('express'), knox = require('knox'), fs = require('fs'), app = express(), http = require('http'), server = http.createServer(app), io = require('socket.io').listen(server); var s3 = knox.createClient({ key: process.env.ASDF_S3_KEY, secret: process.env.ASDF_S3_SECRET, bucket: 'cocoapaint' }); app.configure(function(){ app.use(express.logger()); app.use(express.methodOverride()); app.use(express.bodyParser()); app.use(express.static(__dirname + '/public')); }); /******************************* ROUTES */ app.get('/latest', function(req, res){ var data = { 'image_url': Images.latest() }; res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(data)); }); app.post('/upload', function(req, res) { var nick = req.body.nick; if (nick in game.images) return; if (game.state != State.DRAWING) return; var image = new Buffer(req.body.image, 'base64'); var filename = "/test/" + nick + "-" + Date.now() + "-" + "test" + ".png"; var s3req = s3.putBuffer(image, filename, { 'Content-Length': image.length, 'Content-Type': 'image/png', 'x-amz-acl': 'public-read' }, function(err, s3res){ if (200 == s3res.statusCode) { console.log('saved to %s', s3req.url); var imageData = { 'nick': nick, 'url': s3req.url } game.pushImage( imageData ); } }); }); /******************************* WEBSOCKETS */ var WORDS = "abstract cat dog lobster snail running column statue".split(" "); var State = { RESET: -1, WAITING: 0, DRAWING: 1, VOTING: 2, WINNING: 3, }; var Delay = { DRAWING: 60 * 1000, VOTING: 30 * 1000, WINNING: 15 * 1000, } function Channel() { this.state = State.WAITING; this.messages = []; this.message_id = 0; this.players = {}; this.playerCount = 0; this.images = {}; this.imageCount = 0; this.votes = {}; this.scores = {}; this.voteCount = 0; this.word = ""; this.timer = null; } // CHATROOM STUFF Channel.prototype.join = function(user, socket){ this.players[ socket.id ] = user; this.playerCount++; } Channel.prototype.part = function(socket){ delete this.players[ socket.id ]; this.playerCount = Math.max( 0, this.playerCount - 1 ); } Channel.prototype.pushMessage = function(message){ if (this.messages.length > 20) this.messages.shift(); this.messages.push(message); } // GAME STATE STUFF Channel.prototype.getState = function(){ return { state: this.state, word: this.word, players: this.players, images: this.images, scores: this.scores, voteCount: this.voteCount, imageCount: this.imageCount, }; } Channel.prototype.report = function(){ return { state: this.state, messages: this.messages, players: this.players } } Channel.prototype.newWord = function(){ return choice(WORDS); } Channel.prototype.setTimeout = function( callback, delay ){ var owner = this; if (this.timeout) clearTimeout( this.timeout ); this.timeout = setTimeout( function(){ callback.call( owner ) }, delay ); } // Someone joined/left, so either start a game or stop it. Channel.prototype.checkState = function(){ if ( this.hasEnoughPlayers() ) { if (this.state <= State.WAITING) this.setTimeout( this.startDrawing, 2000); } else if (this.state > State.WAITING) this.setTimeout( this.startWaiting, 2000); } Channel.prototype.hasEnoughPlayers = function(){ var count = 0; for (var i in this.players) { if (++count > 1) return true; } return false; } Channel.prototype.pushImage = function(image){ this.imageCount++; this.images[ image.nick ] = image; io.sockets.emit( 'event-image', image ); if (this.imageCount == this.playerCount) this.startVoting(); } Channel.prototype.addVote = function(data, user){ if (data.nick in this.images && !( user.nick in this.votes )) { this.voteCount++; this.votes[ user.nick ] = true; this.scores[ data.nick ] = this.scores[ data.nick ] ? this.scores[ data.nick ] + 1 : 1; if (this.voteCount == this.playerCount) this.startWinning(); } } Channel.prototype.reset = function(){ this.images = {}; this.imageCount = 0; this.votes = {}; this.scores = {}; this.voteCount = 0; } Channel.prototype.startDrawing = function(){ this.reset(); this.state = State.DRAWING; this.word = this.newWord(); io.sockets.emit( 'event-state', this.getState() ); this.setTimeout( this.startVoting, Delay.DRAWING ); } Channel.prototype.startVoting = function(){ this.state = State.VOTING; io.sockets.emit( 'event-state', this.getState() ); this.setTimeout( this.startWinning, Delay.VOTING ); } Channel.prototype.startWinning = function(){ this.state = State.WINNING; io.sockets.emit( 'event-state', this.getState() ); this.setTimeout( this.startDrawing, Delay.WINNING ); } Channel.prototype.startWaiting = function(){ this.reset(); this.state = State.WAITING; io.sockets.emit( 'event-state', this.getState() ); } var game = new Channel (); io.sockets.on( 'connection', function(socket){ socket.emit( 'event-welcome', game.report() ); socket.on( 'event-join', function(data){ data.id = socket.id; socket.user = data; game.join(data, socket); game.checkState(); io.sockets.emit('event-join', data); socket.emit('event-state', game.getState()); } ); socket.on( 'event-message', function(data){ game.pushMessage(data); socket.broadcast.emit('event-message', data); } ); socket.on( 'event-vote', function(data){ game.addVote(data, socket.user); } ); socket.on( 'disconnect', function(){ var user = socket.user; game.part(socket); game.checkState(); socket.broadcast.emit('event-part', user); } ); }); /******************************* ETC */ function choice (l) { return l[ rand(l.length) ] } function rand (n) { return Math.floor( Math.random() * n ) } /******************************* DONE */ var port = process.env.PORT || 5000; server.listen(port); console.log('Listening on port ' + port);