From: Luigi Pinca Date: Sun, 27 May 2012 16:21:11 +0000 (+0200) Subject: code cleanup X-Git-Url: https://git.saalbach.dev/?a=commitdiff_plain;h=b0457a1075b52d8eb9af51e5023380b6e918f8a1;p=binbsis50.git code cleanup --- diff --git a/app.js b/app.js new file mode 100644 index 0000000..bca2c84 --- /dev/null +++ b/app.js @@ -0,0 +1,184 @@ +/** + * Module dependencies. + */ + +var config = require('./config') + , express = require('express') + , parseCookie = require('connect').utils.parseCookie + , redisstore = require('connect-redis')(express) + , redisurl = require('redis-url') + , site = require('./routes/site') + , user = require('./routes/user'); + +/** + * Setting up redis. + */ + +var songsdb = redisurl.createClient(config.songsdburl); +var usersdb = redisurl.createClient(config.usersdburl); + +songsdb.on('error', function(err) { + console.log(err.toString()); +}); + +usersdb.on('error', function(err) { + console.log(err.toString()); +}); + +/** + * Setting up Express. + */ +var sessionstore = new redisstore({client:usersdb}) + , app = express.createServer(); + +// Configuration +app.use(express.static(__dirname + '/public'), {maxAge: 2592000000}); +app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000})); +app.use(express.bodyParser()); +app.use(express.cookieParser()); +app.use(express.session({secret:config.sessionsecret,store:sessionstore})); + +app.set('view engine', 'jade'); +app.set('view options', {layout:false}); + +app.dynamicHelpers({ + errors: function(req, res) { + var errors = req.session.errors; + delete req.session.errors; + return errors; + }, + oldvalues: function(req, res) { + var oldvalues = req.session.oldvalues; + delete req.session.oldvalues; + return oldvalues; + } +}); + +// Routes +site.use({db:songsdb,rooms:config.rooms}); +user.use({db:usersdb}); + +app.get('/', site.index); +app.get('/artworks', site.artworks); +app.get('/signup', site.signup); +app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount); +app.get('/login', site.login); +app.post('/login', user.validateLogin, user.checkUser, user.authenticate); +app.get('/logout', user.logout); +app.get('/:room', site.room); +app.get('/user/*', user.profile); + +// App listen +app.listen(config.port); + +/** + * Setting up Socket.IO. + */ + +var io = require('socket.io').listen(app) + , sockets = Object.create(null); // Sockets of all rooms + +// Configuration +io.enable('browser client minification'); +io.enable('browser client etag'); +io.enable('browser client gzip'); +io.set('log level', 1); +io.set('transports', [ + 'websocket' + , 'htmlfile' + , 'xhr-polling' + , 'jsonp-polling' +]); + +// Authorization +io.set('authorization', function(data, accept) { + if(!data.headers.cookie) { + return accept('no cookie transmitted', false); + } + var cookie = parseCookie(data.headers.cookie); + sessionstore.get(cookie['connect.sid'], function(err, session) { + if (err) { + return accept(err.toString(), false); + } + else if (!session) { + return accept('session not found', false); + } + data.session = session; + accept(null, true); + }); +}); + +io.sockets.on('connection', function(socket) { + var session = socket.handshake.session; + socket.on('getoverview', function() { + var data = Object.create(null); + for (var prop in rooms) { + data[prop] = rooms[prop].getPopulation(); + } + socket.join('home'); + socket.emit('overview', data); + }); + socket.on('loggedin', function(fn) { + return (session.user) ? fn(session.user) : fn(false); + }); + socket.on('joinroom', function(room) { + if (session.user && typeof room === 'string' && config.rooms.indexOf(room) !== -1) { + if (sockets[session.user]) { // User already in a room + socket.emit('alreadyinaroom'); + return; + } + socket.nickname = session.user; + rooms[room].joinRoom(socket); + } + }); + socket.on('joinanonymously', function(nickname, roomname) { + if (!socket.nickname && typeof nickname === 'string' && nickname !== '' && + typeof roomname === 'string' && config.rooms.indexOf(roomname) !== -1) { + rooms[roomname].setNickName(socket, nickname); + } + }); + socket.on('getstatus', function() { + if (socket.roomname) { + rooms[socket.roomname].sendStatus(socket); + } + }); + socket.on('sendchatmsg', function(msg, to) { + if (socket.roomname && typeof msg === 'string') { + rooms[socket.roomname].sendChatMessage(msg, socket, to); + } + }); + socket.on('guess', function(guess) { + if (socket.roomname && typeof guess === 'string') { + rooms[socket.roomname].guess(socket, guess); + } + }); + socket.on('disconnect', function() { + if (socket.roomname) { + rooms[socket.roomname].removeUser(socket.nickname); + } + }); +}); + +/** + * Setting up the rooms. + */ + +var roomoptions = { + songsdb: songsdb, + usersdb: usersdb, + io: io, + sockets: sockets, + songsinarun: config.songsinarun, + fifolength: config.songsinarun * config.gameswithnorepeats, + threshold: config.allowederrors +}; + +var Room = require('./lib/room')(roomoptions) + , rooms = Object.create(null); // The Object that contains all the room instances + +for (var i=0; i k) { + return false; + } + var d = []; + for (var i=0; i <= s1.length; i++) { + d[i] = []; // Now d is a matrix with s1.length + 1 rows + d[i][0] = i; + } + for (var j=1; j <= s2.length; j++) { + d[0][j] = j; + } + for (i=1; i <= s1.length; i++) { + var l = ((i-k) < 1) ? 1 : i-k; + var m = ((i+k) > s2.length) ? s2.length : i+k; + for (j=l; j<=m; j++) { + if (s1.charAt(i-1) === s2.charAt(j-1)) { + d[i][j] = d[i-1][j-1]; + } + else { + if ((j === l) && (d[i][j-1] === undefined)) { + d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1); + } + else if ((j === m) && (d[i-1][j] === undefined)) { + d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1); + } + else { + d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1); + } + } + } + } + return d[s1.length][s2.length] <= k; +}; + +/** + * Return `amatch` function. + */ + +module.exports = function(allowederrors) { + + /** + * Edit distance threshold. + */ + + var threshold = allowederrors; + + var amatch = function(subject, guess, enableartistrules) { + if (checkDistance(subject, guess, threshold)) { + return true; + } + + var splitted, trimmed; + + // Ignore dots + if (subject.match(/\./) && + checkDistance(subject.replace(/\./g, ""), guess, threshold)) { + return true; + } + // Ignore commas + if (subject.match(/,/) && + checkDistance(subject.replace(/,/g, ""), guess, threshold)) { + return true; + } + // Ignore dashes + if (subject.match(/\-/) && + checkDistance(subject.replace(/\-/g, ""), guess, threshold)) { + return true; + } + // Allow to write "and" in place of "+" + if (subject.match(/\+/) && + checkDistance(subject.replace(/\+/, "and"), guess, threshold)) { + return true; + } - /* - Check if the edit distance between two strings is smaller than a threshold k. - We dont need to trace back the optimal alignment, so we can run the Levenshtein distance algorithm - in better than O(n*m). - We use only a diagonal stripe of width 2k+1 in the matrix. - See Algorithms on strings, trees, and sequences: computer science and computational biology. - Cambridge, UK: Cambridge University Press. pp 263-264. ISBN 0-521-58519-8. - */ - var checkDistance = function(s1, s2, k) { - if (k === 0) { - return s1 === s2; - } - if (Math.abs(s1.length - s2.length) > k) { - return false; - } - var d = []; - for (var i=0; i <= s1.length; i++) { - d[i] = []; // Now d is a matrix with s1.length + 1 rows - d[i][0] = i; - } - for (var j=0; j <= s2.length; j++) { - d[0][j] = j; - } - for (i=1; i <= s1.length; i++) { - var l = ((i-k) < 1) ? 1 : i-k; - var m = ((i+k) > s2.length) ? s2.length : i+k; - for (j=l; j<=m; j++) { - if (s1.charAt(i-1) === s2.charAt(j-1)) { - d[i][j] = d[i-1][j-1]; - } - else { - if ((j === l) && (d[i][j-1] === undefined)) { - d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1); - } - else if ((j === m) && (d[i-1][j] === undefined)) { - d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1); - } - else { - d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1); - } - } - } - } - return d[s1.length][s2.length] <= k; - }; + if (enableartistrules) { + // Ignore "the" at the beginning of artist name + if (subject.match(/^the /)) { + var nothe = subject.replace(/^the /, ""); + if (checkDistance(nothe, guess, threshold)) { + return true; + } + if (nothe.match(/jimi hendrix experience/) && + checkDistance(nothe.replace(/ experience/, ""), guess, threshold)) { + return true; + } + } + // Split artist name on "&" (artist name can be composed by more names) + splitted = subject.split("&"); + if (splitted.length !== 1) { + for (var i=0; i 15) { - feedback = 'That name is too long.'; - } - else if (data.nickname === "binb") { - feedback = 'That name is reserved.'; - } - else if (sockets[data.nickname]) { - feedback = 'Name already taken.'; - } - if (feedback) { - return invalidNickName(socket, feedback); - } - - var key = "user:"+data.nickname; - usersdb.exists(key, function(err, resp) { - if (resp === 1) { // User already exists - feedback = 'That name belongs '; - feedback += 'to a registered user.'; - return invalidNickName(socket, feedback); - } - else { - socket.nickname = data.nickname; - socket.roomname = roomname; - socket.join(roomname); - // Add user to the list of active users - addUser(socket, false); - } - }); - }; - - // A user has left (DCed, etc.) - this.userLeft = function(nickname) { - removeUser(nickname); - io.sockets.in(roomname).emit('userleft', {nickname:nickname,users:usersData}); - }; - - this.sendChatMessage = function (socket, data) { - if (typeof data === "string") { - var datalcase = data.toLowerCase(); - if (allowedguess && (amatch(artistlcase, datalcase, true) || - amatch(tracklcase, datalcase))) { - var msg = "You are probably right, but you have to use the box above."; - socket.emit('chatmsg', {from:"binb",to:socket.nickname,chatmsg:msg}); - return; - } - io.sockets.in(roomname).emit('chatmsg', {from:socket.nickname,chatmsg:data}); - } - else if (typeof data === "object" && typeof data.to === "string" && - userExists(data.to) && typeof data.chatmsg === "string") { - // Private message - socket.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg}); - var recipient = sockets[data.to]; - recipient.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg}); - } - }; - - var addPoints = function(nickname, allinone) { - usersData[nickname].guesstime = 30000 - songtimeleft; - var stats = {}; - switch (finishline) { - case 1: - finishline++; - usersData[nickname].roundpoints = 6; - if (allinone) { - usersData[nickname].points += 6; - stats.points = 6; - } - else { - usersData[nickname].points += 5; - stats.points = 5; - } - usersData[nickname].golds++; - stats.gold = true; - break; - case 2: - finishline++; - usersData[nickname].roundpoints = 5; - if (allinone) { - usersData[nickname].points += 5; - stats.points = 5; - } - else { - usersData[nickname].points += 4; - stats.points = 4; - } - usersData[nickname].silvers++; - stats.silver = true; - break; - case 3: - finishline++; - usersData[nickname].roundpoints = 4; - if (allinone) { - usersData[nickname].points += 4; - stats.points = 4; - } - else { - usersData[nickname].points += 3; - stats.points = 3; - } - usersData[nickname].bronzes++; - stats.bronze = true; - break; - default: - usersData[nickname].roundpoints = 3; - if (allinone) { - usersData[nickname].points += 3; - stats.points = 3; - } - else { - usersData[nickname].points += 2; - stats.points = 2; - } - } - usersData[nickname].matched = 'both'; - usersData[nickname].guessed++; - usersData[nickname].totguesstime += usersData[nickname].guesstime; - - if (usersData[nickname].registered) { - stats.userscore = usersData[nickname].points; - stats.guesstime = usersData[nickname].guesstime; - collectStats(nickname, stats); - } - }; - - this.guess = function(socket, guess) { - if (allowedguess) { - if (!usersData[socket.nickname].matched) { // No track no artist - if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) { - addPoints(socket.nickname, true); - socket.emit('bothmatched'); - io.sockets.in(roomname).emit('updateusers', {users:usersData}); - } - else if (amatch(artistlcase, guess, true)) { - usersData[socket.nickname].roundpoints++; - usersData[socket.nickname].points++; - usersData[socket.nickname].matched = 'artist'; - socket.emit('artistmatched'); - io.sockets.in(roomname).emit('updateusers', {users:usersData}); - if (usersData[socket.nickname].registered) { - var stats = {points:1,userscore:usersData[socket.nickname].points}; - collectStats(socket.nickname, stats); - } - } - else if (amatch(tracklcase, guess)) { - usersData[socket.nickname].roundpoints++; - usersData[socket.nickname].points++; - usersData[socket.nickname].matched = 'title'; - socket.emit('titlematched'); - io.sockets.in(roomname).emit('updateusers', {users:usersData}); - if (usersData[socket.nickname].registered) { - var stats = {points:1,userscore:usersData[socket.nickname].points}; - collectStats(socket.nickname, stats); - } - } - else { - socket.emit('nomatch'); - } - } - else if (usersData[socket.nickname].matched !== 'both') { // Track or artist - if (usersData[socket.nickname].matched === 'artist') { - if (amatch(tracklcase, guess)) { - addPoints(socket.nickname, false); - socket.emit('bothmatched'); - io.sockets.in(roomname).emit('updateusers', {users:usersData}); - } - else { - socket.emit('nomatch'); - } - } - else { - if (amatch(artistlcase, guess, true)) { - addPoints(socket.nickname, false); - socket.emit('bothmatched'); - io.sockets.in(roomname).emit('updateusers', {users:usersData}); - } - else { - socket.emit('nomatch'); - } - } - } - else { // The user has guessed both track and artist - socket.emit('stoptrying'); - } - } - else { - socket.emit('noguesstime'); - } - }; - - var resetPoints = function(roundonly) { - for (var key in usersData) { - if (!roundonly) { - usersData[key].points = 0; - usersData[key].guessed = 0; - usersData[key].totguesstime = 0; - usersData[key].golds = 0; - usersData[key].silvers = 0; - usersData[key].bronzes = 0; - } - usersData[key].roundpoints = 0; - usersData[key].matched = null; - usersData[key].guesstime = null; - } - }; - - var sendLoadTrack = function() { - songsdb.srandmember(roomname, function(err, res) { - songsdb.hmget(res, "artistName", "trackName", "collectionName", "previewUrl", - "artworkUrl60", "trackViewUrl", function(e, replies) { - if (playedtracks.indexOf(res) !== -1) { - return sendLoadTrack(); - } - playedtracks.push(res); - artistName = replies[0]; - artistlcase = artistName.toLowerCase(); - trackName = replies[1]; - tracklcase = trackName.toLowerCase(); - collectionName = replies[2]; - previewUrl = replies[3]; - artworkUrl = replies[4]; - trackViewUrl = replies[5]; - io.sockets.in(roomname).emit('loadtrack', {previewUrl:previewUrl}); - setTimeout(sendPlayTrack, 5000); - }); - }); - status = 1; // Loading next song - }; - - var sendPlayTrack = function() { - songcounter = songcounter + 1; - status = 0; // Playing track - io.sockets.in(roomname).emit('playtrack', {counter:songcounter,tot:songsinarun, - users:usersData}); - songTimeLeft(Date.now()+30000, 50); - allowedguess = true; - setTimeout(sendTrackInfo, 30000); - }; - - var songTimeLeft = function(end, delay) { - songtimeleft = end - Date.now(); - if (songtimeleft < delay) { - return; - } - setTimeout(songTimeLeft, delay, end, delay); - }; - - var sendTrackInfo = function() { - io.sockets.in(roomname).emit('trackinfo', {artworkUrl:artworkUrl,artistName:artistName, - trackName:trackName,collectionName:collectionName, - trackViewUrl:trackViewUrl}); - finishline = 1; - allowedguess = false; - if (songcounter < songsinarun) { - resetPoints(true); - sendLoadTrack(); - } - else { - status = 2; // Sending last track info - setTimeout(gameOver, 5000); - } - }; - - var gameOver = function() { - status = 3; // Game over - var users = []; - for (var key in usersData) { - users.push(usersData[key]); - } - users.sort(function(a, b) {return b.points - a.points;}); - var podium = users.slice(0,3); - io.sockets.in(roomname).emit('gameover', {users:podium}); - if (podium[0] && podium[0].registered) { - collectStats(podium[0].nickname, {firstplace:true}); - } - if (podium[1] && podium[1].registered) { - collectStats(podium[1].nickname, {secondplace:true}); - } - if (podium[2] && podium[2].registered) { - collectStats(podium[2].nickname, {thirdplace:true}); - } - resetPoints(false); - setTimeout(reset, 5000); - }; - - this.sendStatus = function(socket) { - socket.emit('status', {status:status,timeleft:songtimeleft,previewUrl:previewUrl}); - }; - - var reset = function() { - songcounter = 0; - if (playedtracks.length === fifolength) { - playedtracks.splice(0, songsinarun); - } - sendLoadTrack(); - }; - - // Start the room - this.start = function() { - songsdb.scard(roomname, function(err, res) { - trackscount = res; - }); - sendLoadTrack(); - }; - } - - return Room; + /** + * Room dependencies. + */ + + var amatch = require('./match')(params.threshold) + , collectStats = require('./stats')(params.usersdb) + , fifolength = params.fifolength + , io = params.io + , sockets = params.sockets + , songsdb = params.songsdb + , songsinarun = params.songsinarun + , usersdb = params.usersdb; + + /** + * Room class. + */ + + function Room(roomname) { + + var allowedguess = false + , artistlcase + , artistName + , artworkUrl + , collectionName + , finishline = 1 + , playedtracks = [] // The list of already played songs + , previewUrl + , songcounter = 0 + , songtimeleft // Milliseconds + , status + , tracklcase + , trackName + , trackscount = 0 + , trackViewUrl + , totusers = 0 + , usersData = Object.create(null); + + // Add a new user in the room. + var addUser = function(socket, loggedin) { + sockets[socket.nickname] = socket; + usersData[socket.nickname] = { + nickname: socket.nickname, + registered: loggedin, + points: 0, + roundpoints: 0, + matched: null, + guessed: 0, + guesstime: null, + totguesstime: 0, + golds: 0, + silvers: 0, + bronzes: 0 + }; + totusers++; + // Broadcast new user event + io.sockets.in('home').emit('update', roomname, totusers); + socket.emit('ready', usersData, trackscount, loggedin); + socket.broadcast.to(roomname).emit('newuser', socket.nickname, usersData); + }; + + // A user has left (DCed, etc.) + this.removeUser = function(nickname) { + // Delete the references + delete sockets[nickname]; + delete usersData[nickname]; + totusers--; + // Broadcast the event + io.sockets.in('home').emit('update', roomname, totusers); + io.sockets.in(roomname).emit('userleft', nickname, usersData); + }; + + // Return the number of users in the room. + this.getPopulation = function() { + return totusers; + }; + + this.joinRoom = function(socket) { + socket.roomname = roomname; + socket.join(roomname); + addUser(socket, true); + }; + + // A user requested an invalid name + var invalidNickName = function(socket, feedback) { + socket.emit('invalidnickname', feedback); + }; + + // A user is submitting a name + this.setNickName = function(socket, nickname) { + var feedback = null; + + if (nickname.length > 15) { + feedback = 'That name is too long.'; + } + else if (nickname === 'binb') { + feedback = 'That name is reserved.'; + } + else if (sockets[nickname]) { + feedback = 'Name already taken.'; + } + + if (feedback) { + return invalidNickName(socket, feedback); + } + + // Check if requested nickname belong to a registered user + var key = 'user:'+nickname; + usersdb.exists(key, function(err, resp) { + if (resp === 1) { + feedback = 'That name belongs '; + feedback += 'to a registered user.'; + return invalidNickName(socket, feedback); + } + socket.nickname = nickname; + socket.roomname = roomname; + socket.join(roomname); + addUser(socket, false); + }); + }; + + // A user is sending a chat message + this.sendChatMessage = function(msg, socket, to) { + if (typeof to === 'string') { + // Check if the recipient is in the room + if (usersData[to]) { + socket.emit('chatmsg', msg, socket.nickname, to); + var recipient = sockets[to]; + recipient.emit('chatmsg', msg, socket.nickname, to); + } + return; + } + // Censor answers from chat + var msglcase = msg.toLowerCase(); + if (allowedguess && (amatch(artistlcase, msglcase, true) || + amatch(tracklcase, msglcase))) { + var notice = 'You are probably right, but you have to use the box above.'; + socket.emit('chatmsg', notice, 'binb', socket.nickname); + return; + } + io.sockets.in(roomname).emit('chatmsg', msg, socket.nickname); + }; + + // User points and statistics + var addPointsAndStats = function(nickname, allinone) { + usersData[nickname].guesstime = 30000 - songtimeleft; + var stats = {}; + switch (finishline) { + case 1: + finishline++; + usersData[nickname].roundpoints = 6; + if (allinone) { + usersData[nickname].points += 6; + stats.points = 6; + } + else { + usersData[nickname].points += 5; + stats.points = 5; + } + usersData[nickname].golds++; + stats.gold = true; + break; + case 2: + finishline++; + usersData[nickname].roundpoints = 5; + if (allinone) { + usersData[nickname].points += 5; + stats.points = 5; + } + else { + usersData[nickname].points += 4; + stats.points = 4; + } + usersData[nickname].silvers++; + stats.silver = true; + break; + case 3: + finishline++; + usersData[nickname].roundpoints = 4; + if (allinone) { + usersData[nickname].points += 4; + stats.points = 4; + } + else { + usersData[nickname].points += 3; + stats.points = 3; + } + usersData[nickname].bronzes++; + stats.bronze = true; + break; + default: + usersData[nickname].roundpoints = 3; + if (allinone) { + usersData[nickname].points += 3; + stats.points = 3; + } + else { + usersData[nickname].points += 2; + stats.points = 2; + } + } + usersData[nickname].matched = 'both'; + usersData[nickname].guessed++; + usersData[nickname].totguesstime += usersData[nickname].guesstime; + + if (usersData[nickname].registered) { + stats.userscore = usersData[nickname].points; + stats.guesstime = usersData[nickname].guesstime; + collectStats(nickname, stats); + } + }; + + // A user is sending a guess + this.guess = function(socket, guess) { + if (allowedguess) { + if (!usersData[socket.nickname].matched) { // No track no artist + if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) { + addPointsAndStats(socket.nickname, true); + socket.emit('bothmatched'); + io.sockets.in(roomname).emit('updateusers', usersData); + } + else if (amatch(artistlcase, guess, true)) { + usersData[socket.nickname].roundpoints++; + usersData[socket.nickname].points++; + usersData[socket.nickname].matched = 'artist'; + socket.emit('artistmatched'); + io.sockets.in(roomname).emit('updateusers', usersData); + if (usersData[socket.nickname].registered) { + var stats = {points:1,userscore:usersData[socket.nickname].points}; + collectStats(socket.nickname, stats); + } + } + else if (amatch(tracklcase, guess)) { + usersData[socket.nickname].roundpoints++; + usersData[socket.nickname].points++; + usersData[socket.nickname].matched = 'title'; + socket.emit('titlematched'); + io.sockets.in(roomname).emit('updateusers', usersData); + if (usersData[socket.nickname].registered) { + var stats = {points:1,userscore:usersData[socket.nickname].points}; + collectStats(socket.nickname, stats); + } + } + else { + socket.emit('nomatch'); + } + } + else if (usersData[socket.nickname].matched !== 'both') { // Track or artist + if (usersData[socket.nickname].matched === 'artist') { + if (amatch(tracklcase, guess)) { + addPointsAndStats(socket.nickname, false); + socket.emit('bothmatched'); + io.sockets.in(roomname).emit('updateusers', usersData); + } + else { + socket.emit('nomatch'); + } + } + else { + if (amatch(artistlcase, guess, true)) { + addPointsAndStats(socket.nickname, false); + socket.emit('bothmatched'); + io.sockets.in(roomname).emit('updateusers', usersData); + } + else { + socket.emit('nomatch'); + } + } + } + else { // The user has guessed both track and artist + socket.emit('stoptrying'); + } + } + else { + socket.emit('noguesstime'); + } + }; + + var resetPoints = function(roundonly) { + for (var key in usersData) { + if (!roundonly) { + usersData[key].points = 0; + usersData[key].guessed = 0; + usersData[key].totguesstime = 0; + usersData[key].golds = 0; + usersData[key].silvers = 0; + usersData[key].bronzes = 0; + } + usersData[key].roundpoints = 0; + usersData[key].matched = null; + usersData[key].guesstime = null; + } + }; + + // Extract a random track from the database and send the load event + var sendLoadTrack = function() { + songsdb.srandmember(roomname, function(err, res) { + songsdb.hmget(res, 'artistName', 'trackName', 'collectionName', 'previewUrl', + 'artworkUrl60', 'trackViewUrl', function(e, replies) { + // Check if extracted track is in the list of already played tracks + if (playedtracks.indexOf(res) !== -1) { + return sendLoadTrack(); + } + playedtracks.push(res); + artistName = replies[0]; + artistlcase = artistName.toLowerCase(); + trackName = replies[1]; + tracklcase = trackName.toLowerCase(); + collectionName = replies[2]; + previewUrl = replies[3]; + artworkUrl = replies[4]; + trackViewUrl = replies[5]; + io.sockets.in(roomname).emit('loadtrack', previewUrl); + setTimeout(sendPlayTrack, 5000); + }); + }); + status = 1; // Loading next song + }; + + // Timer for the playing song + var songTimeLeft = function(end, delay) { + songtimeleft = end - Date.now(); + if (songtimeleft < delay) { + return; + } + setTimeout(songTimeLeft, delay, end, delay); + }; + + var sendPlayTrack = function() { + songcounter++; + status = 0; // Playing track + var data = { + counter:songcounter, + tot:songsinarun, + users:usersData + }; + io.sockets.in(roomname).emit('playtrack', data); + songTimeLeft(Date.now() + 30000, 50); + allowedguess = true; + setTimeout(sendTrackInfo, 30000); + }; + + var sendTrackInfo = function() { + var trackinfo = { + artworkUrl:artworkUrl, + artistName:artistName, + trackName:trackName, + trackViewUrl:trackViewUrl, + collectionName:collectionName + }; + io.sockets.in(roomname).emit('trackinfo', trackinfo); + finishline = 1; + allowedguess = false; + + if (songcounter < songsinarun) { + resetPoints(true); + sendLoadTrack(); + return; + } + + status = 2; // Sending last track info + setTimeout(gameOver, 5000); + }; + + var gameOver = function() { + status = 3; // Game over + + // Build podium + var users = []; + for (var key in usersData) { + users.push(usersData[key]); + } + users.sort(function(a, b) {return b.points - a.points;}); + var podium = users.slice(0,3); + io.sockets.in(roomname).emit('gameover', podium); + + // Collect podium stats + if (podium[0] && podium[0].registered) { + collectStats(podium[0].nickname, {firstplace:true}); + } + if (podium[1] && podium[1].registered) { + collectStats(podium[1].nickname, {secondplace:true}); + } + if (podium[2] && podium[2].registered) { + collectStats(podium[2].nickname, {thirdplace:true}); + } + + resetPoints(false); + songcounter = 0; + // Check if FIFO is full + if (playedtracks.length === fifolength) { + playedtracks.splice(0, songsinarun); + } + + // Start a new game + setTimeout(sendLoadTrack, 5000); + }; + + // Send the room status + this.sendStatus = function(socket) { + var data = { + status:status, + timeleft:songtimeleft, + previewUrl:previewUrl + }; + socket.emit('status', data); + }; + + // Start the room + this.start = function() { + songsdb.scard(roomname, function(err, res) { + trackscount = res; + }); + sendLoadTrack(); + }; + } + + return Room; }; diff --git a/lib/stats.js b/lib/stats.js index 3f89aa7..c526893 100644 --- a/lib/stats.js +++ b/lib/stats.js @@ -1,51 +1,62 @@ +/** + * Return `collectStats` function. + */ + module.exports = function(db) { - var collectStats = function(username, stats) { - var key = "user:"+username; - if (stats.points) { - db.hincrby(key, "totpoints", stats.points); - } - if (stats.userscore) { - // Set personal best - db.hget(key, "bestscore", function(err, res) { - if (res < stats.userscore) { - db.hset(key, "bestscore", stats.userscore); - } - }); - } - if (stats.gold) { - db.hincrby(key, "golds", 1); - } - if (stats.silver) { - db.hincrby(key, "silvers", 1); - } - if (stats.bronze) { - db.hincrby(key, "bronzes", 1); - } - if (stats.guesstime) { - db.hincrby(key, "guessed", 1); - db.hincrby(key, "totguesstime", stats.guesstime); - db.hget(key, "bestguesstime", function(err, res) { - if (stats.guesstime < res) { - db.hset(key, "bestguesstime", stats.guesstime); - } - }); - db.hget(key, "worstguesstime", function(err, res) { - if (stats.guesstime > res) { - db.hset(key, "worstguesstime", stats.guesstime); - } - }); - } - if (stats.firstplace) { - db.hincrby(key, "victories", 1); - } - if (stats.secondplace) { - db.hincrby(key, "secondplaces", 1); - } - if (stats.thirdplace) { - db.hincrby(key, "thirdplaces", 1); - } - }; + var collectStats = function(username, stats) { + var key = 'user:'+username; + if (stats.points) { + // Update total points + db.hincrby(key, 'totpoints', stats.points); + } + if (stats.userscore) { + // Set personal best + db.hget(key, 'bestscore', function(err, res) { + if (res < stats.userscore) { + db.hset(key, 'bestscore', stats.userscore); + } + }); + } + if (stats.gold) { + // Update the number of golds + db.hincrby(key, 'golds', 1); + } + if (stats.silver) { + db.hincrby(key, 'silvers', 1); + } + if (stats.bronze) { + db.hincrby(key, 'bronzes', 1); + } + if (stats.guesstime) { + // Update the number of guessed tracks + db.hincrby(key, 'guessed', 1); + // Update total guess time + db.hincrby(key, 'totguesstime', stats.guesstime); + // Set best answer time + db.hget(key, 'bestguesstime', function(err, res) { + if (stats.guesstime < res) { + db.hset(key, 'bestguesstime', stats.guesstime); + } + }); + // Set worst answer time + db.hget(key, 'worstguesstime', function(err, res) { + if (stats.guesstime > res) { + db.hset(key, 'worstguesstime', stats.guesstime); + } + }); + } + if (stats.firstplace) { + // Update the number of first places + db.hincrby(key, 'victories', 1); + } + if (stats.secondplace) { + db.hincrby(key, 'secondplaces', 1); + } + if (stats.thirdplace) { + db.hincrby(key, 'thirdplaces', 1); + } + }; - return collectStats; + return collectStats; }; diff --git a/lib/user.js b/lib/user.js new file mode 100644 index 0000000..3b88b5f --- /dev/null +++ b/lib/user.js @@ -0,0 +1,23 @@ +/** + * Export the class. + */ + +module.exports = function(username, email, salt, hash, joindate) { + this.username = username; + this.email = email; + this.salt = salt; + this.password = hash; + this.joindate = joindate; + this.totpoints = 0; + this.bestscore = 0; + this.golds = 0; + this.silvers = 0; + this.bronzes = 0; + this.bestguesstime = 30000; + this.worstguesstime = 0; + this.totguesstime = 0; + this.guessed = 0; + this.victories = 0; + this.secondplaces = 0; + this.thirdplaces = 0; +}; diff --git a/package.json b/package.json index e9c3486..5f28688 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,16 @@ "connect": "1.8.x", "connect-redis": "1.3.x", "express": "2.5.x", - "express-form": "0.6.x", "jade": "0.26.x", "redis-url": "0.1.x", "socket.io": "0.9.x" }, "subdomain": "binb", "scripts": { - "start": "server.js" + "start": "app.js" }, "engines": { "node": "0.6.x" }, - "version": "0.3.0-13" -} \ No newline at end of file + "version": "0.3.1" +} diff --git a/public/static/css/style.css b/public/static/css/style.css index 348a550..935c986 100644 --- a/public/static/css/style.css +++ b/public/static/css/style.css @@ -8,617 +8,617 @@ font-style: normal; } body { - background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7; - padding-top:45px; + background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7; + padding-top:45px; } section { - margin-top:30px; + margin-top:30px; } .navbar .brand { - padding: 2px 20px 4px; + padding: 2px 20px 4px; } .logo { - font-family: 'ChristopherhandRegular'; + font-family: 'ChristopherhandRegular'; } .navbar .brand .logo { - height: 34px; - padding-left: 68px; - line-height: 40px; - background: url('/static/images/logo.png') no-repeat 0px -54px; - font-size: 25px; + height: 34px; + padding-left: 68px; + line-height: 40px; + background: url('/static/images/logo.png') no-repeat 0px -54px; + font-size: 25px; } .page-header .logo { - display: inline-block; - height: 54px; - padding-left: 109px; - line-height: 68px; - background: url('/static/images/logo.png') no-repeat 0 0; - font-size: 34px; - color: #0088CC; + display: inline-block; + height: 54px; + padding-left: 109px; + line-height: 68px; + background: url('/static/images/logo.png') no-repeat 0 0; + font-size: 34px; + color: #0088CC; } .navbar .navbar-text { - line-height:19px; - padding: 10px 10px 11px; + line-height:19px; + padding: 10px 10px 11px; } .form-horizontal .control-group { - margin-bottom: 10px; + margin-bottom: 10px; } .form-horizontal .control-label { - width: 100px; + width: 100px; } .form-horizontal .controls { - margin-left: 120px; + margin-left: 120px; } form .clearfix { - margin-bottom: 10px; + margin-bottom: 10px; } .well { - background-color: #DDDDDD; - margin-bottom: 18px; + background-color: #DDDDDD; + margin-bottom: 18px; } .alert { - margin-bottom: 9px; + margin-bottom: 9px; } #signup-button { - margin-left: 120px; - margin-top: 9px; + margin-left: 120px; + margin-top: 9px; } #captcha-input { - width: 126px; + width: 126px; } #captcha { - margin-right:20px; + margin-right:20px; } .page-header { - padding: 0; - margin: 0 0 17px 0; - border-bottom:1px solid #ddd; - -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); - -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); - box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); + padding: 0; + margin: 0 0 17px 0; + border-bottom:1px solid #ddd; + -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); + -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); + box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); } input { - margin-bottom: 0; + margin-bottom: 0; } .modal-footer { - text-align: left; + text-align: left; } .modal-footer .btn { - float: right; + float: right; } .thumbnails { - margin-bottom: 0px; + margin-bottom: 0px; } .thumbnails > li { - margin: 0 0 18px 80px; + margin: 0 0 18px 80px; } .thumbnail { - border: 1px solid #ccc; - height: 140px; - opacity: 0.7; + border: 1px solid #ccc; + height: 140px; + opacity: 0.7; } .thumbnail:hover { - opacity: 1; + opacity: 1; } .thumbnail img { - float: left; - width: 70px; - height: 70px; + float: left; + width: 70px; + height: 70px; } .profile { - font-size: 24px; - font-weight: bold; - line-height: 32px; + font-size: 24px; + font-weight: bold; + line-height: 32px; } .profile .img { - width: 32px; - height: 32px; - float: left; - margin-right: 5px; - background: url('/static/images/sprites.png') no-repeat 0px -56px; + width: 32px; + height: 32px; + float: left; + margin-right: 5px; + background: url('/static/images/sprites.png') no-repeat 0px -56px; } .stats { - border: 1px solid #ccc; - border-left: 0; - margin-top:8px; + border: 1px solid #ccc; + border-left: 0; + margin-top:8px; } .stats td { - border-left: 1px solid #ccc; - border-top: 1px solid #ccc; - vertical-align: middle; + border-left: 1px solid #ccc; + border-top: 1px solid #ccc; + vertical-align: middle; } .stats tbody tr:nth-child(odd) td { - background-color: #ddd; + background-color: #ddd; } .stats tbody tr:hover td { - background-color: #dadada; + background-color: #dadada; } .room { - height: 25px; - line-height: 25px; - position: absolute; - top: 61px; - background-color: rgba(51,51,51, 0.7); - color: white; - font-weight: bold; - font-size: 14px; - width: 210px; - text-align: center; - text-transform: capitalize; + height: 25px; + line-height: 25px; + position: absolute; + top: 61px; + background-color: rgba(51,51,51, 0.7); + color: white; + font-weight: bold; + font-size: 14px; + width: 210px; + text-align: center; + text-transform: capitalize; } .dropdown-menu { - min-width: 140px; + min-width: 140px; } .dropdown-menu a { - padding: 2px 10px; + padding: 2px 10px; } .dropdown-menu .divider { - margin: 4px 1px; + margin: 4px 1px; } .matched { - color: #f3a22f; + color: #f3a22f; } .cups, .medals { - margin-left: auto; - margin-right: auto; + margin-left: auto; + margin-right: auto; } .cups { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } .medals { - width: 32px; - height: 32px; + width: 32px; + height: 32px; } .rank1 { - background: url('/static/images/sprites.png') no-repeat -32px -16px; + background: url('/static/images/sprites.png') no-repeat -32px -16px; } .rank2 { - background: url('/static/images/sprites.png') no-repeat -32px -48px; + background: url('/static/images/sprites.png') no-repeat -32px -48px; } .rank3 { - background: url('/static/images/sprites.png') no-repeat -32px -80px; + background: url('/static/images/sprites.png') no-repeat -32px -80px; } .scoreboard th, .scoreboard td { - vertical-align: middle; - text-align: center; + vertical-align: middle; + text-align: center; } .scoreboard .name { - font-weight:bold; + font-weight:bold; } .relative { - position: relative; + position: relative; } #app-name { - display: inline-block; + display: inline-block; } #total-tracks { - float: right; - color: #BFBFBF; - margin-top: 34px; + float: right; + color: #BFBFBF; + margin-top: 34px; } #cassette { - margin-top:22px; - height: 137px; - background: url('/static/images/cassette.png') no-repeat 0 0; + margin-top:22px; + height: 137px; + background: url('/static/images/cassette.png') no-repeat 0 0; } #countdown { - position: absolute; - width: 26px; - text-align:center; - top:80px; - left:175px; + position: absolute; + width: 26px; + text-align:center; + top:80px; + left:175px; } #wheel-left, #wheel-right { - position:absolute; - width: 24px; - height:24px; - top: 49px; - background: url('/static/images/sprites.png') no-repeat 0px -32px; + position:absolute; + width: 24px; + height:24px; + top: 49px; + background: url('/static/images/sprites.png') no-repeat 0px -32px; } #wheel-left { - left:51px; + left:51px; } #wheel-right { - left:145px; + left:145px; } #tape-left, #tape-right { - height:70px; - width:70px; - position:absolute; - top:25px; - background-color: black; - -webkit-border-radius: 70px; - -moz-border-radius: 70px; - border-radius: 70px; - z-index:-1; + height:70px; + width:70px; + position:absolute; + top:25px; + background-color: black; + -webkit-border-radius: 70px; + -moz-border-radius: 70px; + border-radius: 70px; + z-index:-1; } #tape-left { - left: 20px; + left: 20px; } #tape-right { - left: 106px; + left: 106px; } #progress-bar, #progress { - height:7px; - -webkit-border-radius:4px; - -moz-border-radius:4px; - border-radius:4px; - -webkit-box-shadow: inset 0px 1px 2px 0px #333; - -moz-box-shadow: inset 0px 1px 2px 0px #333; - box-shadow: inset 0px 1px 2px 0px #333; + height:7px; + -webkit-border-radius:4px; + -moz-border-radius:4px; + border-radius:4px; + -webkit-box-shadow: inset 0px 1px 2px 0px #333; + -moz-box-shadow: inset 0px 1px 2px 0px #333; + box-shadow: inset 0px 1px 2px 0px #333; } #progress-bar { - position:absolute; - width: 148px; - top:85px; - left:20px; - border:1px solid #404040; - border:1px solid rgba(0, 0, 0, 0.5); + position:absolute; + width: 148px; + top:85px; + left:20px; + border:1px solid #404040; + border:1px solid rgba(0, 0, 0, 0.5); } #progress { - background-color: #6184b7; - width:0px; + background-color: #6184b7; + width:0px; } #touch-backdrop { - width:220px; - height:137px; - -webkit-border-radius:5px; - -moz-border-radius:5px; - border-radius:5px; - background:rgba(50,50,50,0.8); + width:220px; + height:137px; + -webkit-border-radius:5px; + -moz-border-radius:5px; + border-radius:5px; + background:rgba(50,50,50,0.8); } #touch-play { - margin-top: 54px; - margin-left: 77px; + margin-top: 54px; + margin-left: 77px; } #volume { - height:159px; + height:159px; } #volume-button, #volume-slider, #volume-total, #volume-current, #volume-handle { - position: absolute; + position: absolute; } #volume-button { - bottom: 0; - height: 20px; - width: 20px; + bottom: 0; + height: 20px; + width: 20px; } #volume-button .button { - display:block; - height: 18px; - width: 18px; - border:1px solid; - border-color: #CCCCCC #CCCCCC #AAAAAA; - -webkit-border-radius:3px; - -moz-border-radius:3px; - border-radius:3px; - cursor:pointer; - filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0); - background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%); - background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%); - background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%); - background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0)); - background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%); - background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%); + display:block; + height: 18px; + width: 18px; + border:1px solid; + border-color: #CCCCCC #CCCCCC #AAAAAA; + -webkit-border-radius:3px; + -moz-border-radius:3px; + border-radius:3px; + cursor:pointer; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0); + background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%); + background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%); + background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0)); + background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%); + background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%); } #volume-button .button:hover { - border-color:#999999; - -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); - box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); + border-color:#999999; + -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); } #volume-button .button:active { - border-color:#999999 #AAAAAA #CCCCCC; - -webkit-box-shadow: 0px 1px 2px 0px #aaa inset; - -moz-box-shadow: 0px 1px 2px 0px #aaa inset; - box-shadow: 0px 1px 2px 0px #aaa inset; - filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc); - background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); - background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); - background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); - background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc)); - background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); - background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%); + border-color:#999999 #AAAAAA #CCCCCC; + -webkit-box-shadow: 0px 1px 2px 0px #aaa inset; + -moz-box-shadow: 0px 1px 2px 0px #aaa inset; + box-shadow: 0px 1px 2px 0px #aaa inset; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc); + background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); + background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); + background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc)); + background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%); + background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%); } #volume-button .button #icon { - margin: 1px; - width: 16px; - height: 16px; + margin: 1px; + width: 16px; + height: 16px; } #volume-button .button .volume-none { - background: url('/static/images/sprites.png') no-repeat 0 -112px; + background: url('/static/images/sprites.png') no-repeat 0 -112px; } #volume-button .button .volume-low { - background: url('/static/images/sprites.png') no-repeat -16px -112px; + background: url('/static/images/sprites.png') no-repeat -16px -112px; } #volume-button .button .volume-medium { - background: url('/static/images/sprites.png') no-repeat -32px -112px; + background: url('/static/images/sprites.png') no-repeat -32px -112px; } #volume-button .button .volume-high { - background: url('/static/images/sprites.png') no-repeat -48px -112px; + background: url('/static/images/sprites.png') no-repeat -48px -112px; } #volume-slider { - display: none; - height: 116px; - width: 20px; - top: -116px; - -webkit-border-radius:3px; - -moz-border-radius:3px; - border-radius:3px; - background: rgba(50, 50, 50, 0.1); - z-index: 1; + display: none; + height: 116px; + width: 20px; + top: -116px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + border-radius:3px; + background: rgba(50, 50, 50, 0.1); + z-index: 1; } #volume-total, #volume-current { - left: 8px; - top: 8px; - width: 4px; - height: 100px; + left: 8px; + top: 8px; + width: 4px; + height: 100px; } #volume-total, #volume-current, #volume-handle { - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; } #volume-total { - -webkit-box-shadow: inset 0px 0px 3px 0px #333; - -moz-box-shadow: inset 0px 0px 3px 0px #333; - box-shadow: inset 0px 0px 3px 0px #333; + -webkit-box-shadow: inset 0px 0px 3px 0px #333; + -moz-box-shadow: inset 0px 0px 3px 0px #333; + box-shadow: inset 0px 0px 3px 0px #333; } #volume-current { - -webkit-box-shadow: inset 0px 0px 1px 0px #000; - -moz-box-shadow: inset 0px 0px 1px 0px #000; - box-shadow: inset 0px 0px 1px 0px #000; - background-color: #6184b7; + -webkit-box-shadow: inset 0px 0px 1px 0px #000; + -moz-box-shadow: inset 0px 0px 1px 0px #000; + box-shadow: inset 0px 0px 1px 0px #000; + background-color: #6184b7; } #volume-handle { - left: 1px; - width: 16px; - height: 4px; - background: #ddd; - background: rgba(255, 255, 255, 0.9); - cursor: N-resize; - border:1px solid #999; + left: 1px; + width: 16px; + height: 4px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + cursor: N-resize; + border:1px solid #999; } #summary { - text-align: center; - margin-bottom:18px; + text-align: center; + margin-bottom:18px; } #users-wrapper { - margin-left: 120px; - width: 300px; + margin-left: 120px; + width: 300px; } #users { - padding-left:20px; - margin-bottom: 90px; - max-height: 380px; - overflow: auto; + padding-left:20px; + margin-bottom: 90px; + max-height: 380px; + overflow: auto; } #users .name, #tracks .artist { - font-weight: bold; + font-weight: bold; } #message-wrapper { - text-align: right; + text-align: right; } #users li, #tracks li, #chat li { - color: #404040; + color: #404040; } #users li { - height: 18px; - position: relative; + height: 18px; + position: relative; } #users .private { - display: none; - font-size: 9.75px; - padding: 2px 4px; - position: absolute; - left: -19px; + display: none; + font-size: 9.75px; + padding: 2px 4px; + position: absolute; + left: -19px; } .registered, .round-rank { - height: 16px; - width: 16px; - margin: 1px 2px 0px 0px; + height: 16px; + width: 16px; + margin: 1px 2px 0px 0px; } .registered { - background: url('/static/images/sprites.png') no-repeat 0px -16px; + background: url('/static/images/sprites.png') no-repeat 0px -16px; } .registered:hover { - background: url('/static/images/sprites.png') no-repeat -16px -16px; + background: url('/static/images/sprites.png') no-repeat -16px -16px; } #users .name { - margin-right: 4px; + margin-right: 4px; } #users .name, .registered { - cursor: pointer; + cursor: pointer; } #users .you { - cursor: auto; + cursor: auto; } #users .points, #users .round-points { - margin-right: 10px; + margin-right: 10px; } .stand1 { - background: url('/static/images/sprites.png') no-repeat 0 0; + background: url('/static/images/sprites.png') no-repeat 0 0; } .stand2 { - background: url('/static/images/sprites.png') no-repeat -16px 0; + background: url('/static/images/sprites.png') no-repeat -16px 0; } .stand3 { - background: url('/static/images/sprites.png') no-repeat -32px 0; + background: url('/static/images/sprites.png') no-repeat -32px 0; } #users .guess-time { - font-size: 11px; - line-height: 18px; + font-size: 11px; + line-height: 18px; } #toggle-chat { - position: absolute; - top: -17px; - left: 805px; - color: #BFBFBF; - height: 16px; - line-height: 16px; - font-size: 11px; - border: 1px solid #DDDDDD; - -moz-border-radius: 4px 4px 0 0; - -webkit-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; - cursor: pointer; - padding: 0 10px; - text-decoration: none; + position: absolute; + top: -17px; + left: 805px; + color: #BFBFBF; + height: 16px; + line-height: 16px; + font-size: 11px; + border: 1px solid #DDDDDD; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + cursor: pointer; + padding: 0 10px; + text-decoration: none; } #toggle-chat:hover { - border-color: #CCCCCC; - background-color: #f5f5f5; - color: #333333; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(top, #ffffff, #e6e6e6); - filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); + border-color: #CCCCCC; + background-color: #f5f5f5; + color: #333333; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(top, #ffffff, #e6e6e6); + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); } #toggle-chat:active { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); - background-color: #e6e6e6; - background-color: #d9d9d9 \9; - outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + background-color: #e6e6e6; + background-color: #d9d9d9 \9; + outline: 0; } #chat-wrapper { - margin-bottom: 4px; - height: 160px; - background-color: white; + margin-bottom: 4px; + height: 160px; + background-color: white; } #chat { - height: 152px; - width: 446px; - margin: 4px 6px; - overflow: auto; + height: 152px; + width: 446px; + margin: 4px 6px; + overflow: auto; } .bordered { - border: 1px solid #ccc; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075); - box-shadow: 0 1px 2px rgba(0,0,0,.075); + border: 1px solid #ccc; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075); + box-shadow: 0 1px 2px rgba(0,0,0,.075); } #chat .join, #chat .left { - font-style: italic; + font-style: italic; } #chat .private { - color: #a65fc3; + color: #a65fc3; } #feedback { - text-align: center; + text-align: center; } #feedback .correct, #users .correct{ - color: #46A546; + color: #46A546; } #feedback .wrong, #chat .error { - color: #C43C35; + color: #C43C35; } #guess.correct { - border-color: #46A546; + border-color: #46A546; } #guess.correct:focus { - -webkit-box-shadow: 0 0 6px #7aba7b; - -moz-box-shadow: 0 0 6px #7aba7b; - box-shadow: 0 0 6px #7aba7b; + -webkit-box-shadow: 0 0 6px #7aba7b; + -moz-box-shadow: 0 0 6px #7aba7b; + box-shadow: 0 0 6px #7aba7b; } #guess.wrong { - border-color: #C43C35; + border-color: #C43C35; } #guess.wrong:focus { - -webkit-box-shadow: 0 0 6px #d59392; - -moz-box-shadow: 0 0 6px #d59392; - box-shadow: 0 0 6px #d59392; + -webkit-box-shadow: 0 0 6px #d59392; + -moz-box-shadow: 0 0 6px #d59392; + box-shadow: 0 0 6px #d59392; } #tracks { - margin: 18px 0; - max-height: 240px; - overflow: auto; + margin: 18px 0; + max-height: 240px; + overflow: auto; } #tracks li { - margin: 0 2px 2px 0; - padding: 8px; - min-height: 40px; - background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5); - background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5); - background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5); - background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5); - background: linear-gradient(center top , #FBFBFB, #F5F5F5); + margin: 0 2px 2px 0; + padding: 8px; + min-height: 40px; + background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5); + background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5); + background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5); + background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5); + background: linear-gradient(center top , #FBFBFB, #F5F5F5); } .registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, #copy, #facebook-button, #twitter-button, #github-button { - float:left; + float:left; } #tracks img.artwork { - width: 40px; - height: 40px; - margin-right:10px; + width: 40px; + height: 40px; + margin-right:10px; } #tracks .info { - margin-right:15px; + margin-right:15px; } #tracks .artist { - margin-top: 1px; - font-size: 14px; + margin-top: 1px; + font-size: 14px; } #tracks .round-rank { - margin-top:12px; + margin-top:12px; } #tracks .round-points { - margin-top: 11px; + margin-top: 11px; } #tracks a { - float:right; - display:block; - width:24px; - height:24px; - margin-top:8px; + float:right; + display:block; + width:24px; + height:24px; + margin-top:8px; } #disclaimer { - position: absolute; - left: 60px; - bottom: 18px; - color: #BFBFBF; - width:340px; + position: absolute; + left: 60px; + bottom: 18px; + color: #BFBFBF; + width:340px; } footer { - padding: 0; - margin-top: 17px; - border-top:1px solid #DDDDDD; - color: #BFBFBF; - text-shadow: 0 1px 0 #FFFFFF; + padding: 0; + margin-top: 17px; + border-top:1px solid #DDDDDD; + color: #BFBFBF; + text-shadow: 0 1px 0 #FFFFFF; } #footer-inner, #twitter-button, #github-button { - height:20px; + height:20px; } #footer-inner { - border-top:1px solid white; - padding-top:5px; - margin-bottom:30px; + border-top:1px solid white; + padding-top:5px; + margin-bottom:30px; } #copy { - margin: 0 20px 0 20px; + margin: 0 20px 0 20px; } #facebook-button { - width:90px; - height:21px + width:90px; + height:21px } #twitter-button, #github-button { - width:100px; + width:100px; } #copy, #footer-right { - line-height:20px; + line-height:20px; } #footer-right { - float:right; - margin-right:20px; + float:right; + margin-right:20px; } diff --git a/public/static/js/home.js b/public/static/js/home.js index 3ae8103..935345b 100644 --- a/public/static/js/home.js +++ b/public/static/js/home.js @@ -1,31 +1,31 @@ $(function() { - if ($.browser.mozilla) { - // Block ESC button in firefox (it breaks all socket connection). - $(document).keypress(function(event) { - if(event.keyCode === 27) { - return false; - } - }); - } - $.get("/artworks", function(data) { - $(".thumbnail").each(function(index) { - var i = index * 6; - var j = i + 6; - for(i; i < j; i++) { - $('').appendTo($(this)); - } - }); - }); - var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false}); - socket.on("connect", function() { - socket.emit("getoverview"); - socket.on("overview", function(data) { - for (var prop in data) { - $("#"+prop).text(data[prop]); - } - }); - socket.on("update", function(data) { - $("#"+data.room).text(data.players); - }); - }); + if ($.browser.mozilla) { + // Block ESC button in firefox (it breaks all socket connection). + $(document).keypress(function(event) { + if(event.keyCode === 27) { + return false; + } + }); + } + $.get("/artworks", function(data) { + $(".thumbnail").each(function(index) { + var i = index * 6; + var j = i + 6; + for(i; i < j; i++) { + $('').appendTo($(this)); + } + }); + }); + var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false}); + socket.on("connect", function() { + socket.emit("getoverview"); + socket.on("overview", function(data) { + for (var prop in data) { + $("#"+prop).text(data[prop]); + } + }); + socket.on("update", function(room, players) { + $("#"+room).text(players); + }); + }); }); diff --git a/public/static/js/room.js b/public/static/js/room.js index 2051455..185bf62 100644 --- a/public/static/js/room.js +++ b/public/static/js/room.js @@ -1,749 +1,753 @@ (function() { - - var touchplay = null; - var elapsedtime = 0; - var jplayer = null; - var nickname = null; - var socket = null; - var pvtmsgto = null; - var roundpoints = 0; - var stopanimation = false; - var states = ['A song is already playing, please wait for the next one...', - 'Game is about to start...', 'Game is over', 'New game will start soon...']; - var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!', - 'Correct, do you also know the artist?']; - var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!', - 'Do you also know the title?']; - var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!', - 'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!']; - var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!', - 'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?', - 'Try again']; - var historyvalues = []; - var historycursor = 0; - var DOM = {}; - - String.prototype.encodeEntities = function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }; - - // Exact match version of jQuery :contains selector - $.expr[":"].econtains = function(obj, index, meta, stack) { - return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1"); - }; - - // Prompt for name and send it. - var joinAnonymously = function(msg) { - if (/nickname\s*\=/.test(document.cookie) && !msg) { - nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1")); - socket.emit('joinanonymously', {nickname:nickname,roomname:roomname}); - } - else { - if (!$('body').hasClass('modal-open')) { - var html = ''; - html += ''; - html += ''; - - $(html).appendTo(DOM.modal); - var login = $('#login'); - var button = $('#join'); - button.click(function() { - var val = $.trim(login.val()); - if (val !== "") { - nickname = val; - socket.emit('joinanonymously', {nickname:nickname,roomname:roomname}); - } - else { - var txt = "Nickname can't be empty."; - invalidNickName(''+txt+''); - } - login.val(""); - }); - login.keyup(function(event) { - if (event.keyCode === 13) { - button.click(); - } - }); - DOM.modal.modal('show'); - DOM.modal.on('shown', function() { - login.focus(); - }); - } - else { - $('.modal-body p').html(msg); - $('#login').focus(); - } - } - }; - - // Your submitted name was invalid - var invalidNickName = function(feedback) { - joinAnonymously(feedback+"
Try with another one:"); - }; - - /* Triggered when a logged user tries to join a room from another tab or another browser - and he is already in a room */ - var alreadyInARoom = function() { - var html = ''; - html += ''; - $(html).appendTo(DOM.modal); - DOM.modal.modal('show'); - }; - - // You joined the room - var ready = function(data) { - if (!data.loggedin && !/nickname\s*\=/.test(document.cookie)) { - document.cookie = "nickname="+escape(nickname)+";path=/;"; - } - DOM.modal.modal('hide').empty(); - $('#total-tracks span').text(data.trackscount); - var msg = nickname+" joined the game"; - var joinspan = $(""); - joinspan.text(msg); - addChatEntry(joinspan); - updateUsers(data); - - DOM.messagebox.keydown(function(event) { - if (event.keyCode === 13) { - var val = $.trim(DOM.messagebox.val()); - if (val !== "") { - var data = (pvtmsgto) ? {to:pvtmsgto,chatmsg:val} : val; - socket.emit('sendchatmsg', data); - } - DOM.messagebox.val(""); - } - }); - - DOM.guessbox.keydown(function(event) { - switch (event.keyCode) { - case 13: // return - var guess = $.trim(DOM.guessbox.val()); - if (guess !== "") { - socket.emit('guess', guess.toLowerCase()); - historyvalues.push(guess); - if (historyvalues.length > 20) { - historyvalues.splice(0, 1); - } - historycursor = historyvalues.length; - } - DOM.guessbox.val(""); - break; - case 38: // up-arrow - if (historycursor > 0) { - DOM.guessbox.val(historyvalues[--historycursor]); - } - break; - case 40: // down-arrow - if (historycursor < historyvalues.length - 1) { - DOM.guessbox.val(historyvalues[++historycursor]); - } - else { - historycursor = historyvalues.length; - DOM.guessbox.val(""); - } - } - }); - - DOM.guessbox.focus(); - - socket.on('newuser', userJoin); - socket.on('userleft', userLeft); - socket.on('updateusers', updateUsers); - socket.on('chatmsg', getChatMessage); - socket.on('loadtrack', loadTrack); - socket.on('playtrack', playTrack); - socket.on('trackinfo', addTrackInfo); - socket.on('artistmatched', function() { - var feedback = amstrings[Math.floor(Math.random()*amstrings.length)]; - addFeedback(feedback, "correct"); - }); - socket.on('titlematched', function() { - var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)]; - addFeedback(feedback, "correct"); - }); - socket.on('bothmatched', function() { - var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)]; - addFeedback(feedback, "correct"); - }); - socket.on('nomatch', function() { - var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)]; - addFeedback(feedback, "wrong"); - }); - socket.on('stoptrying', function() { - addFeedback('You guessed both artist and title. Please wait...'); - }); - socket.on('noguesstime', function() { - addFeedback('You have to wait the next song...'); - }); - socket.on('gameover', gameOver); - socket.on('status', setStatus); - socket.emit('getstatus'); - }; - - var setStatus = function(data) { - if (data.status === 0) { - cassetteAnimation(Date.now()+data.timeleft, true); - } - if (data.status === 1) { - loadTrack(data); - } - addFeedback(states[data.status]); - }; - - // A new player joined the game - var userJoin = function(data) { - var msg = data.nickname+" joined the game"; - var joinspan = $(""); - joinspan.text(msg); - addChatEntry(joinspan); - updateUsers(data); - }; - - // A user left the game - var userLeft = function(data) { - var leftmsg = data.nickname+" left the game"; - var leftspan = $(""); - leftspan.text(leftmsg); - addChatEntry(leftspan); - updateUsers(data); - }; - - // Update the list of users - var updateUsers = function(data) { - DOM.users.empty(); - var users = []; - for (var key in data.users) { - users.push(data.users[key]); - } - users.sort(function(a, b) {return b.points - a.points;}); - // Flag to test if our private recipient is in the list of active users - var found = false; - for (var i=0; i'); - var pvt = $('P'); - var username = $('').text(user.nickname); - var points = $('('+user.points+')'); - var roundrank = $(''); - var roundpointsel = $(''); - var guesstime = $(''); - li.append(pvt, username, points, roundrank, roundpointsel, guesstime); - if (user.registered) { - var href = 'href="/user/'+encodeURI(username.html())+'"'; - pvt.after(''); - } - DOM.users.append(li); - if (pvtmsgto === user.nickname) { - pvt.show(); - username.click(clearPrivate); - found = true; - } - else { - username.click(function() { - addPrivate($(this).text()); - }); - } - if (nickname === user.nickname) { - username.addClass("you"); - roundpoints = user.roundpoints; - DOM.rank.text(i+1); - DOM.points.text(user.points); - } - if (user.roundpoints > 0) { - roundpointsel.text('+'+user.roundpoints); - if (user.roundpoints === 1) { - username.addClass("matched"); - } - else { - if (user.roundpoints > 3) { - var stand = 7 - user.roundpoints; - roundrank.addClass("round-rank stand"+stand); - var gtime = (user.guesstime / 1000).toFixed(1); - guesstime.text(gtime+" s"); - } - username.addClass("correct"); - } - } - } - if (!found && pvtmsgto) { - var width = DOM.recipient.outerWidth(true) + 1; - DOM.recipient.css('margin-right','0'); - DOM.recipient.text(""); - DOM.messagebox.animate({'width':'+='+width+'px'}, "fast"); - pvtmsgto = null; - DOM.messagebox.focus(); - } - }; - - var addPrivate = function(usrname) { - if (pvtmsgto) { - clearPrivate(); - } - if (nickname === usrname) { - return; - } - DOM.recipient.css('margin-right','4px'); - DOM.recipient.text("To "+usrname+":"); - var width = DOM.recipient.outerWidth(true) + 1; - DOM.recipient.hide(); - DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();}); - var el = $("span.name:econtains("+usrname.encodeEntities()+")"); - el.prevAll(".private").show(); - el.unbind('click'); - el.click(clearPrivate); - pvtmsgto = usrname; - DOM.messagebox.focus(); - }; - - var clearPrivate = function() { - var width = DOM.recipient.outerWidth(true) + 1; - DOM.recipient.css('margin-right','0'); - DOM.recipient.text(""); - DOM.messagebox.animate({'width':'+='+width+'px'}, "fast"); - var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")"); - el.prevAll(".private").hide(); - el.unbind("click"); - el.click(function() { - addPrivate($(this).text()); - }); - pvtmsgto = null; - DOM.messagebox.focus(); - }; - - // Receive a chat message - var getChatMessage = function(data) { - var prefix = data.from; - var msgspan = $(""); - if (data.to) { - // Private Message - prefix = (nickname === data.from) ? '(To '+data.to+')' : '(From '+prefix+')'; - msgspan.addClass("private"); - } - var msg = prefix+": "+data.chatmsg; - msgspan.text(msg); - addChatEntry(msgspan); - }; - - var loadTrack = function(data) { - jplayer.jPlayer("mute"); - jplayer.jPlayer("setMedia", {m4a: data.previewUrl}); - }; - - // Play a track - var playTrack = function(data) { - if (touchplay) { - touchplay.removeClass("btn-danger disabled").addClass("btn-success"); - touchplay.html(' Play'); - } - jplayer.jPlayer("unmute"); - jplayer.jPlayer("play"); - updateUsers(data); - cassetteAnimation(Date.now()+30000, true); - if (data.counter === 1) { - DOM.modal.modal('hide').empty(); - DOM.tracks.empty(); - } - DOM.track.text(data.counter+'/'+data.tot); - addFeedback('What is this song?'); - }; - - // Start cassette animation - var cassetteAnimation = function(endtime, forward) { - var millisleft = endtime - Date.now(); - var secleft = millisleft / 1000; - var width, deg, offsetleft, offsetright, css; - if (forward) { - width = 148 - (148*secleft/30); - deg = 360 - (360*secleft/30); - offsetleft = 44 - 24*secleft/30; - offsetright = 130 - 24*secleft/30; - DOM.progress.width(width); - css = { - '-moz-transform' : 'rotate('+deg+'deg)', - '-webkit-transform' : 'rotate('+deg+'deg)', - '-o-transform' : 'rotate('+deg+'deg)', - '-ms-transform' : 'rotate('+deg+'deg)', - 'transform' : 'rotate('+deg+'deg)' - }; - DOM.cassettewheels.css(css); - DOM.tapeleft.css('left', offsetleft+'px'); - DOM.taperight.css('left', offsetright+'px'); - } - else { - width = 148*secleft/5; - deg = 360*secleft/5; - offsetleft = 20 + 24*secleft/5; - offsetright = 106 + 24*secleft/5; - DOM.progress.width(width); - css = { - '-moz-transform' : 'rotate('+deg+'deg)', - '-webkit-transform' : 'rotate('+deg+'deg)', - '-o-transform' : 'rotate('+deg+'deg)', - '-ms-transform' : 'rotate('+deg+'deg)', - 'transform' : 'rotate('+deg+'deg)' - }; - DOM.cassettewheels.css(css); - DOM.tapeleft.css('left', offsetleft+'px'); - DOM.taperight.css('left', offsetright+'px'); - } - if (forward) { - DOM.countdown.text(secleft.toFixed(1)); - if (touchplay) {elapsedtime = 30 - Math.round(secleft);} - } - else { - DOM.countdown.text(Math.round(secleft)); - } - if (stopanimation || millisleft < 50) { - return; - } - setTimeout(function() {cassetteAnimation(endtime, forward);}, 50); - }; - - // Add track info - var addTrackInfo = function(data) { - if (touchplay) { - touchplay.removeClass("btn-success").addClass("btn-danger disabled"); - touchplay.html(' Wait'); - } - cassetteAnimation(Date.now()+5000, false); - var html = '
  • '; - html += '
    '+data.artistName+'
    '; - var titleattr = ''; - var trackname = data.trackName; - if (data.trackName.length > 45) { - titleattr = data.trackName.replace(/"/g, """); - trackname = data.trackName.substring(0,42) + '...'; - } - html += '
    '+trackname+'
    '; - var attrs = ''; - var rp = ''; - if (roundpoints > 0) { - rp = '+'+roundpoints; - if (roundpoints > 3) { - var stand = 7 - roundpoints; - attrs += 'class="round-rank stand'+stand+'"'; - } - } - html += '
    '+rp+'
    '; - html += ''; - html += '
  • '; - DOM.tracks.prepend($(html)); - }; - - // Game over countdown - var countDown = function(endtime) { - var millisleft = endtime - Date.now(); - var secleft = millisleft / 1000; - $('.modal-footer span').text(Math.round(secleft)); - if (millisleft < 200) { - return; - } - setTimeout(function() {countDown(endtime);}, 200); - }; - - var gameOver = function(data) { - var html = ''; - html += ''; - html += ''; - DOM.modal.append($(html)); - DOM.modal.modal('show'); - countDown(Date.now()+10000); - }; - - // Let the user know when he / she has disconnected - var disconnect = function() { - stopanimation = true; - jplayer.jPlayer("stop"); - var errormsg = "ERROR: You have disconnected."; - var errorspan = $(""); - errorspan.text(errormsg); - addChatEntry(errorspan); - addFeedback('Something wrong happened'); - DOM.users.empty(); - }; - - // Add a chat entry, whether message, notification, etc. - var addChatEntry = function(childNode) { - var li = $("
  • "); - li.append(childNode); - DOM.chat.append(li); - DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight; - }; - - var hideChat = function() { - DOM.togglechat.text("Show chat").unbind('click'); - DOM.chatwrapper.toggle(300); - DOM.tracks.animate({maxHeight:"434px"}, 300); - DOM.togglechat.click(showChat); - }; - - var showChat = function() { - DOM.togglechat.text("Hide chat").unbind('click'); - DOM.chatwrapper.toggle(300); - DOM.tracks.animate({maxHeight:"240px"}, 300, function() { - DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight; - }); - DOM.togglechat.click(hideChat); - }; - - var addFeedback = function(txt, style) { - if (typeof style === 'string') { - var fbspan = $(''); - fbspan.text(txt); - DOM.feedback.html(fbspan); - DOM.guessbox.addClass(style); - setTimeout(function() {DOM.guessbox.removeClass(style);}, 350); - return; - } - DOM.feedback.text(txt); - }; - - var addVolumeControl = function() { - var volumebutton = $('
    '+ - '
    '+ - '
    '+ // Outer background - '
    '+ // Rail - '
    '+ // Current volume - '
    '+ // Handle - '
    '+ - '
    ').appendTo("#volume"); - var icon = volumebutton.find('#icon'); - var volumeslider = volumebutton.find('#volume-slider'); - var volumetotal = volumebutton.find('#volume-total'); - var volumecurrent = volumebutton.find('#volume-current'); - var volumehandle = volumebutton.find('#volume-handle'); - var mouseisdown = false; - var mouseisover = false; - var oldvalue = 1; - var clicked = false; - - var positionVolumeHandle = function(volume) { - if (!volumeslider.is(':visible')) { - volumeslider.show(); - positionVolumeHandle(volume); - volumeslider.hide(); - return; - } - var totalheight = volumetotal.height(); - var totalposition = volumetotal.position(); - var newtop = totalheight - (totalheight * volume); - volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2)); - volumecurrent.height(totalheight - newtop ); - volumecurrent.css('top', totalposition.top + newtop); - }; - - var handleIcon = function (volume) { - if (volume === 0) { - icon.removeClass().addClass('volume-none'); - } - else if (volume <= 0.33) { - icon.removeClass().addClass('volume-low'); - } - else if (volume <= 0.66) { - icon.removeClass().addClass('volume-medium'); - } - else { - icon.removeClass().addClass('volume-high'); - } - }; - - var setVolume = function(volume) { - handleIcon(volume); - oldvalue = volume; - jplayer.jPlayer("volume", volume); - }; - - var handleVolumeMove = function(e) { - var totaloffset = volumetotal.offset(); - var newy = e.pageY - totaloffset.top; - var railheight = volumetotal.height(); - var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10); - var volume = (railheight - newy) / railheight; - - if (newy < 0) { - newy = 0; - } - else if (newy > railheight) { - newy = railheight; - } - - volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2)); - volumecurrent.height(railheight - newy); - volumecurrent.css('top',newy+totalTop); - - volume = Math.max(0,volume); - volume = Math.min(volume,1); - - setVolume(volume); - - var d = new Date(); - d.setTime(d.getTime() + 31536000000); // One year in milliseconds - document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";"; - }; - - var loadFromCookie = function() { - if (/volume\s*\=/.test(document.cookie)) { - var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1"); - value = parseFloat(value); - positionVolumeHandle(value); - setVolume(value); - } - else { - positionVolumeHandle(1); - } - }; - - volumebutton.hover(function() { - volumeslider.show(); - mouseisover = true; - }, function() { - mouseisover = false; - if (!mouseisdown) { - volumeslider.hide(); - } - }); - - volumeslider.on('mouseover', function() { - mouseisover = true; - }).on('mousedown', function (e) { - handleVolumeMove(e); - mouseisdown = true; - return false; - }); - - $(document).on('mouseup', function (e) { - mouseisdown = false; - if (!mouseisover) { - volumeslider.hide(); - } - }).on('mousemove', function (e) { - if (mouseisdown) { - handleVolumeMove(e); - } - }); - - volumebutton.find('.button').click(function() { - if (!clicked) { - clicked = true; - if (oldvalue !== 0) { - jplayer.jPlayer("volume", 0); - positionVolumeHandle(0); - handleIcon(0); - } - } - else { - clicked = false; - if (oldvalue !== 0) { - jplayer.jPlayer("volume", oldvalue); - positionVolumeHandle(oldvalue); - handleIcon(oldvalue); - } - } - }); - loadFromCookie(); - }; - - var setVariables = function() { - DOM.modal = $('#modal'); - DOM.guessbox = $('#guess'); - DOM.users = $('#users'); - DOM.rank = $('#summary .rank'); - DOM.points = $('#summary .points'); - DOM.messagebox = $('#message'); - DOM.recipient = $('#recipient'); - DOM.tracks = $('#tracks'); - DOM.track = $('#summary .track'); - DOM.progress = $('#progress'); - DOM.cassettewheels = $('#cassette .wheel'); - DOM.tapeleft = $('#tape-left'); - DOM.taperight = $('#tape-right'); - DOM.countdown = $('#countdown'); - DOM.chat = $('#chat'); - DOM.feedback = $('#feedback'); - DOM.togglechat = $('#toggle-chat'); - DOM.chatwrapper = $('#chat-outer-wrapper'); - }; - - // Set up the room. - $(function() { - setVariables(); - DOM.modal.modal({keyboard:false,show:false,backdrop:"static"}); - DOM.togglechat.click(hideChat); - if ($.browser.mozilla) { - // Block ESC button in firefox (it breaks all socket connection). - $(document).keypress(function(event) { - if(event.keyCode === 27) { - return false; - } - }); - } - socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false}); - socket.on("connect", function() { - jplayer = $("#player").jPlayer({ - ready: function() { - socket.emit('loggedin', function(data) { - if (data) { - nickname = data; - socket.emit('joinroom', roomname); - } - else { - joinAnonymously(); - } - }); - if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) { - addVolumeControl(); - } - else { - var touchbackdrop = $('
    '+ - '
    ').appendTo("#cassette"); - touchplay = $('#touch-play'); - touchplay.click(function() { - if (!$(this).hasClass("btn-danger")) { - touchplay = null; - jplayer.jPlayer('play', elapsedtime); - touchbackdrop.remove(); - } - }); - } - }, - swfPath: "/static/swf/", - //solution: "flash, html", - supplied: "m4a", - preload: "auto", - volume: 1 - }); - }); - socket.on('alreadyinaroom', alreadyInARoom); - socket.on('invalidnickname', invalidNickName); - socket.on('ready', ready); - socket.on("disconnect", disconnect); - }); + + var touchplay = null; + var elapsedtime = 0; + var jplayer = null; + var nickname = null; + var socket = null; + var pvtmsgto = null; + var roundpoints = 0; + var stopanimation = false; + var states = ['A song is already playing, please wait for the next one...', + 'Game is about to start...', 'Game is over', 'New game will start soon...']; + var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!', + 'Correct, do you also know the artist?']; + var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!', + 'Do you also know the title?']; + var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!', + 'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!']; + var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!', + 'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?', + 'Try again']; + var historyvalues = []; + var historycursor = 0; + var DOM = {}; + + String.prototype.encodeEntities = function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }; + + // Exact match version of jQuery :contains selector + $.expr[":"].econtains = function(obj, index, meta, stack) { + return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1"); + }; + + // Prompt for name and send it. + var joinAnonymously = function(msg) { + if (/nickname\s*\=/.test(document.cookie) && !msg) { + nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1")); + socket.emit('joinanonymously', nickname, roomname); + } + else { + if (!$('body').hasClass('modal-open')) { + var html = ''; + html += ''; + html += ''; + + $(html).appendTo(DOM.modal); + var login = $('#login'); + var button = $('#join'); + button.click(function() { + var val = $.trim(login.val()); + if (val !== "") { + nickname = val; + socket.emit('joinanonymously', nickname, roomname); + } + else { + var txt = "Nickname can't be empty."; + invalidNickName(''+txt+''); + } + login.val(""); + }); + login.keyup(function(event) { + if (event.keyCode === 13) { + button.click(); + } + }); + DOM.modal.modal('show'); + DOM.modal.on('shown', function() { + login.focus(); + }); + } + else { + $('.modal-body p').html(msg); + $('#login').focus(); + } + } + }; + + // Your submitted name was invalid + var invalidNickName = function(feedback) { + joinAnonymously(feedback+"
    Try with another one:"); + }; + + /* Triggered when a logged user tries to join a room from another tab or another browser + and he is already in a room */ + var alreadyInARoom = function() { + var html = ''; + html += ''; + $(html).appendTo(DOM.modal); + DOM.modal.modal('show'); + }; + + // You joined the room + var ready = function(usersData, trackscount, loggedin) { + if (!loggedin && !/nickname\s*\=/.test(document.cookie)) { + document.cookie = "nickname="+escape(nickname)+";path=/;"; + } + DOM.modal.modal('hide').empty(); + $('#total-tracks span').text(trackscount); + var msg = nickname+" joined the game"; + var joinspan = $(""); + joinspan.text(msg); + addChatEntry(joinspan); + updateUsers(usersData); + + DOM.messagebox.keydown(function(event) { + if (event.keyCode === 13) { + var val = $.trim(DOM.messagebox.val()); + if (val !== "") { + if (pvtmsgto) { + socket.emit('sendchatmsg', val, pvtmsgto); + } + else { + socket.emit('sendchatmsg', val); + } + } + DOM.messagebox.val(""); + } + }); + + DOM.guessbox.keydown(function(event) { + switch (event.keyCode) { + case 13: // return + var guess = $.trim(DOM.guessbox.val()); + if (guess !== "") { + socket.emit('guess', guess.toLowerCase()); + historyvalues.push(guess); + if (historyvalues.length > 20) { + historyvalues.splice(0, 1); + } + historycursor = historyvalues.length; + } + DOM.guessbox.val(""); + break; + case 38: // up-arrow + if (historycursor > 0) { + DOM.guessbox.val(historyvalues[--historycursor]); + } + break; + case 40: // down-arrow + if (historycursor < historyvalues.length - 1) { + DOM.guessbox.val(historyvalues[++historycursor]); + } + else { + historycursor = historyvalues.length; + DOM.guessbox.val(""); + } + } + }); + + DOM.guessbox.focus(); + + socket.on('newuser', userJoin); + socket.on('userleft', userLeft); + socket.on('updateusers', updateUsers); + socket.on('chatmsg', getChatMessage); + socket.on('loadtrack', loadTrack); + socket.on('playtrack', playTrack); + socket.on('trackinfo', addTrackInfo); + socket.on('artistmatched', function() { + var feedback = amstrings[Math.floor(Math.random()*amstrings.length)]; + addFeedback(feedback, "correct"); + }); + socket.on('titlematched', function() { + var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)]; + addFeedback(feedback, "correct"); + }); + socket.on('bothmatched', function() { + var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)]; + addFeedback(feedback, "correct"); + }); + socket.on('nomatch', function() { + var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)]; + addFeedback(feedback, "wrong"); + }); + socket.on('stoptrying', function() { + addFeedback('You guessed both artist and title. Please wait...'); + }); + socket.on('noguesstime', function() { + addFeedback('You have to wait the next song...'); + }); + socket.on('gameover', gameOver); + socket.on('status', setStatus); + socket.emit('getstatus'); + }; + + var setStatus = function(data) { + if (data.status === 0) { + cassetteAnimation(Date.now()+data.timeleft, true); + } + if (data.status === 1) { + loadTrack(data.previewUrl); + } + addFeedback(states[data.status]); + }; + + // A new player joined the game + var userJoin = function(username, usersData) { + var msg = username+" joined the game"; + var joinspan = $(""); + joinspan.text(msg); + addChatEntry(joinspan); + updateUsers(usersData); + }; + + // A user left the game + var userLeft = function(username, usersData) { + var leftmsg = username+" left the game"; + var leftspan = $(""); + leftspan.text(leftmsg); + addChatEntry(leftspan); + updateUsers(usersData); + }; + + // Update the list of users + var updateUsers = function(usersData) { + DOM.users.empty(); + var users = []; + for (var key in usersData) { + users.push(usersData[key]); + } + users.sort(function(a, b) {return b.points - a.points;}); + // Flag to test if our private recipient is in the list of active users + var found = false; + for (var i=0; i'); + var pvt = $('P'); + var username = $('').text(user.nickname); + var points = $('('+user.points+')'); + var roundrank = $(''); + var roundpointsel = $(''); + var guesstime = $(''); + li.append(pvt, username, points, roundrank, roundpointsel, guesstime); + if (user.registered) { + var href = 'href="/user/'+encodeURI(username.html())+'"'; + pvt.after(''); + } + DOM.users.append(li); + if (pvtmsgto === user.nickname) { + pvt.show(); + username.click(clearPrivate); + found = true; + } + else { + username.click(function() { + addPrivate($(this).text()); + }); + } + if (nickname === user.nickname) { + username.addClass("you"); + roundpoints = user.roundpoints; + DOM.rank.text(i+1); + DOM.points.text(user.points); + } + if (user.roundpoints > 0) { + roundpointsel.text('+'+user.roundpoints); + if (user.roundpoints === 1) { + username.addClass("matched"); + } + else { + if (user.roundpoints > 3) { + var stand = 7 - user.roundpoints; + roundrank.addClass("round-rank stand"+stand); + var gtime = (user.guesstime / 1000).toFixed(1); + guesstime.text(gtime+" s"); + } + username.addClass("correct"); + } + } + } + if (!found && pvtmsgto) { + var width = DOM.recipient.outerWidth(true) + 1; + DOM.recipient.css('margin-right','0'); + DOM.recipient.text(""); + DOM.messagebox.animate({'width':'+='+width+'px'}, "fast"); + pvtmsgto = null; + DOM.messagebox.focus(); + } + }; + + var addPrivate = function(usrname) { + if (pvtmsgto) { + clearPrivate(); + } + if (nickname === usrname) { + return; + } + DOM.recipient.css('margin-right','4px'); + DOM.recipient.text("To "+usrname+":"); + var width = DOM.recipient.outerWidth(true) + 1; + DOM.recipient.hide(); + DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();}); + var el = $("span.name:econtains("+usrname.encodeEntities()+")"); + el.prevAll(".private").show(); + el.unbind('click'); + el.click(clearPrivate); + pvtmsgto = usrname; + DOM.messagebox.focus(); + }; + + var clearPrivate = function() { + var width = DOM.recipient.outerWidth(true) + 1; + DOM.recipient.css('margin-right','0'); + DOM.recipient.text(""); + DOM.messagebox.animate({'width':'+='+width+'px'}, "fast"); + var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")"); + el.prevAll(".private").hide(); + el.unbind("click"); + el.click(function() { + addPrivate($(this).text()); + }); + pvtmsgto = null; + DOM.messagebox.focus(); + }; + + // Receive a chat message + var getChatMessage = function(chatmsg, from, to) { + var prefix = from; + var msgspan = $(""); + if (to) { + // Private Message + prefix = (nickname === from) ? '(To '+to+')' : '(From '+prefix+')'; + msgspan.addClass("private"); + } + var msg = prefix+": "+chatmsg; + msgspan.text(msg); + addChatEntry(msgspan); + }; + + var loadTrack = function(previewUrl) { + jplayer.jPlayer("mute"); + jplayer.jPlayer("setMedia", {m4a: previewUrl}); + }; + + // Play a track + var playTrack = function(data) { + if (touchplay) { + touchplay.removeClass("btn-danger disabled").addClass("btn-success"); + touchplay.html(' Play'); + } + jplayer.jPlayer("unmute"); + jplayer.jPlayer("play"); + updateUsers(data.users); + cassetteAnimation(Date.now()+30000, true); + if (data.counter === 1) { + DOM.modal.modal('hide').empty(); + DOM.tracks.empty(); + } + DOM.track.text(data.counter+'/'+data.tot); + addFeedback('What is this song?'); + }; + + // Start cassette animation + var cassetteAnimation = function(endtime, forward) { + var millisleft = endtime - Date.now(); + var secleft = millisleft / 1000; + var width, deg, offsetleft, offsetright, css; + if (forward) { + width = 148 - (148*secleft/30); + deg = 360 - (360*secleft/30); + offsetleft = 44 - 24*secleft/30; + offsetright = 130 - 24*secleft/30; + DOM.progress.width(width); + css = { + '-moz-transform' : 'rotate('+deg+'deg)', + '-webkit-transform' : 'rotate('+deg+'deg)', + '-o-transform' : 'rotate('+deg+'deg)', + '-ms-transform' : 'rotate('+deg+'deg)', + 'transform' : 'rotate('+deg+'deg)' + }; + DOM.cassettewheels.css(css); + DOM.tapeleft.css('left', offsetleft+'px'); + DOM.taperight.css('left', offsetright+'px'); + } + else { + width = 148*secleft/5; + deg = 360*secleft/5; + offsetleft = 20 + 24*secleft/5; + offsetright = 106 + 24*secleft/5; + DOM.progress.width(width); + css = { + '-moz-transform' : 'rotate('+deg+'deg)', + '-webkit-transform' : 'rotate('+deg+'deg)', + '-o-transform' : 'rotate('+deg+'deg)', + '-ms-transform' : 'rotate('+deg+'deg)', + 'transform' : 'rotate('+deg+'deg)' + }; + DOM.cassettewheels.css(css); + DOM.tapeleft.css('left', offsetleft+'px'); + DOM.taperight.css('left', offsetright+'px'); + } + if (forward) { + DOM.countdown.text(secleft.toFixed(1)); + if (touchplay) {elapsedtime = 30 - Math.round(secleft);} + } + else { + DOM.countdown.text(Math.round(secleft)); + } + if (stopanimation || millisleft < 50) { + return; + } + setTimeout(function() {cassetteAnimation(endtime, forward);}, 50); + }; + + // Add track info + var addTrackInfo = function(data) { + if (touchplay) { + touchplay.removeClass("btn-success").addClass("btn-danger disabled"); + touchplay.html(' Wait'); + } + cassetteAnimation(Date.now()+5000, false); + var html = '
  • '; + html += '
    '+data.artistName+'
    '; + var titleattr = ''; + var trackname = data.trackName; + if (data.trackName.length > 45) { + titleattr = data.trackName.replace(/"/g, """); + trackname = data.trackName.substring(0,42) + '...'; + } + html += '
    '+trackname+'
    '; + var attrs = ''; + var rp = ''; + if (roundpoints > 0) { + rp = '+'+roundpoints; + if (roundpoints > 3) { + var stand = 7 - roundpoints; + attrs += 'class="round-rank stand'+stand+'"'; + } + } + html += '
    '+rp+'
    '; + html += ''; + html += '
  • '; + DOM.tracks.prepend($(html)); + }; + + // Game over countdown + var countDown = function(endtime) { + var millisleft = endtime - Date.now(); + var secleft = millisleft / 1000; + $('.modal-footer span').text(Math.round(secleft)); + if (millisleft < 200) { + return; + } + setTimeout(function() {countDown(endtime);}, 200); + }; + + var gameOver = function(podium) { + var html = ''; + html += ''; + html += ''; + DOM.modal.append($(html)); + DOM.modal.modal('show'); + countDown(Date.now()+10000); + }; + + // Let the user know when he / she has disconnected + var disconnect = function() { + stopanimation = true; + jplayer.jPlayer("stop"); + var errormsg = "ERROR: You have disconnected."; + var errorspan = $(""); + errorspan.text(errormsg); + addChatEntry(errorspan); + addFeedback('Something wrong happened'); + DOM.users.empty(); + }; + + // Add a chat entry, whether message, notification, etc. + var addChatEntry = function(childNode) { + var li = $("
  • "); + li.append(childNode); + DOM.chat.append(li); + DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight; + }; + + var hideChat = function() { + DOM.togglechat.text("Show chat").unbind('click'); + DOM.chatwrapper.toggle(300); + DOM.tracks.animate({maxHeight:"434px"}, 300); + DOM.togglechat.click(showChat); + }; + + var showChat = function() { + DOM.togglechat.text("Hide chat").unbind('click'); + DOM.chatwrapper.toggle(300); + DOM.tracks.animate({maxHeight:"240px"}, 300, function() { + DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight; + }); + DOM.togglechat.click(hideChat); + }; + + var addFeedback = function(txt, style) { + if (typeof style === 'string') { + var fbspan = $(''); + fbspan.text(txt); + DOM.feedback.html(fbspan); + DOM.guessbox.addClass(style); + setTimeout(function() {DOM.guessbox.removeClass(style);}, 350); + return; + } + DOM.feedback.text(txt); + }; + + var addVolumeControl = function() { + var volumebutton = $('
    '+ + '
    '+ + '
    '+ // Outer background + '
    '+ // Rail + '
    '+ // Current volume + '
    '+ // Handle + '
    '+ + '
    ').appendTo("#volume"); + var icon = volumebutton.find('#icon'); + var volumeslider = volumebutton.find('#volume-slider'); + var volumetotal = volumebutton.find('#volume-total'); + var volumecurrent = volumebutton.find('#volume-current'); + var volumehandle = volumebutton.find('#volume-handle'); + var mouseisdown = false; + var mouseisover = false; + var oldvalue = 1; + var clicked = false; + + var positionVolumeHandle = function(volume) { + if (!volumeslider.is(':visible')) { + volumeslider.show(); + positionVolumeHandle(volume); + volumeslider.hide(); + return; + } + var totalheight = volumetotal.height(); + var totalposition = volumetotal.position(); + var newtop = totalheight - (totalheight * volume); + volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2)); + volumecurrent.height(totalheight - newtop ); + volumecurrent.css('top', totalposition.top + newtop); + }; + + var handleIcon = function (volume) { + if (volume === 0) { + icon.removeClass().addClass('volume-none'); + } + else if (volume <= 0.33) { + icon.removeClass().addClass('volume-low'); + } + else if (volume <= 0.66) { + icon.removeClass().addClass('volume-medium'); + } + else { + icon.removeClass().addClass('volume-high'); + } + }; + + var setVolume = function(volume) { + handleIcon(volume); + oldvalue = volume; + jplayer.jPlayer("volume", volume); + }; + + var handleVolumeMove = function(e) { + var totaloffset = volumetotal.offset(); + var newy = e.pageY - totaloffset.top; + var railheight = volumetotal.height(); + var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10); + var volume = (railheight - newy) / railheight; + + if (newy < 0) { + newy = 0; + } + else if (newy > railheight) { + newy = railheight; + } + + volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2)); + volumecurrent.height(railheight - newy); + volumecurrent.css('top',newy+totalTop); + + volume = Math.max(0,volume); + volume = Math.min(volume,1); + + setVolume(volume); + + var d = new Date(); + d.setTime(d.getTime() + 31536000000); // One year in milliseconds + document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";"; + }; + + var loadFromCookie = function() { + if (/volume\s*\=/.test(document.cookie)) { + var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1"); + value = parseFloat(value); + positionVolumeHandle(value); + setVolume(value); + } + else { + positionVolumeHandle(1); + } + }; + + volumebutton.hover(function() { + volumeslider.show(); + mouseisover = true; + }, function() { + mouseisover = false; + if (!mouseisdown) { + volumeslider.hide(); + } + }); + + volumeslider.on('mouseover', function() { + mouseisover = true; + }).on('mousedown', function (e) { + handleVolumeMove(e); + mouseisdown = true; + return false; + }); + + $(document).on('mouseup', function (e) { + mouseisdown = false; + if (!mouseisover) { + volumeslider.hide(); + } + }).on('mousemove', function (e) { + if (mouseisdown) { + handleVolumeMove(e); + } + }); + + volumebutton.find('.button').click(function() { + if (!clicked) { + clicked = true; + if (oldvalue !== 0) { + jplayer.jPlayer("volume", 0); + positionVolumeHandle(0); + handleIcon(0); + } + } + else { + clicked = false; + if (oldvalue !== 0) { + jplayer.jPlayer("volume", oldvalue); + positionVolumeHandle(oldvalue); + handleIcon(oldvalue); + } + } + }); + loadFromCookie(); + }; + + var setVariables = function() { + DOM.modal = $('#modal'); + DOM.guessbox = $('#guess'); + DOM.users = $('#users'); + DOM.rank = $('#summary .rank'); + DOM.points = $('#summary .points'); + DOM.messagebox = $('#message'); + DOM.recipient = $('#recipient'); + DOM.tracks = $('#tracks'); + DOM.track = $('#summary .track'); + DOM.progress = $('#progress'); + DOM.cassettewheels = $('#cassette .wheel'); + DOM.tapeleft = $('#tape-left'); + DOM.taperight = $('#tape-right'); + DOM.countdown = $('#countdown'); + DOM.chat = $('#chat'); + DOM.feedback = $('#feedback'); + DOM.togglechat = $('#toggle-chat'); + DOM.chatwrapper = $('#chat-outer-wrapper'); + }; + + // Set up the room. + $(function() { + setVariables(); + DOM.modal.modal({keyboard:false,show:false,backdrop:"static"}); + DOM.togglechat.click(hideChat); + if ($.browser.mozilla) { + // Block ESC button in firefox (it breaks all socket connection). + $(document).keypress(function(event) { + if(event.keyCode === 27) { + return false; + } + }); + } + socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false}); + socket.on("connect", function() { + jplayer = $("#player").jPlayer({ + ready: function() { + socket.emit('loggedin', function(data) { + if (data) { + nickname = data; + socket.emit('joinroom', roomname); + } + else { + joinAnonymously(); + } + }); + if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) { + addVolumeControl(); + } + else { + var touchbackdrop = $('
    '+ + '
    ').appendTo("#cassette"); + touchplay = $('#touch-play'); + touchplay.click(function() { + if (!$(this).hasClass("btn-danger")) { + touchplay = null; + jplayer.jPlayer('play', elapsedtime); + touchbackdrop.remove(); + } + }); + } + }, + swfPath: "/static/swf/", + //solution: "flash, html", + supplied: "m4a", + preload: "auto", + volume: 1 + }); + }); + socket.on('alreadyinaroom', alreadyInARoom); + socket.on('invalidnickname', invalidNickName); + socket.on('ready', ready); + socket.on("disconnect", disconnect); + }); })(); diff --git a/routes/site.js b/routes/site.js new file mode 100644 index 0000000..5e09ea1 --- /dev/null +++ b/routes/site.js @@ -0,0 +1,78 @@ +/** + * Module dependencies. + */ + +var async = require('async') + , Captcha = require('../lib/captcha') + , db + , rooms; + +/** + * Generate a task for async. + */ + +var task = function(genre) { + return function(callback) { + db.srandmember(genre, function(err, res) { + db.hget(res, "artworkUrl100", callback); + }); + }; +}; + +/** + * Initialize dependencies. + */ + +exports.use = function(options) { + db = options.db; + rooms = options.rooms; +}; + +exports.index = function(req, res) { + if (req.session.user) { + res.local('loggedin', req.session.user.replace(/&/g, "&")); + } + res.render("index", {rooms:rooms}); +}; + +exports.signup = function(req, res) { + var captcha = new Captcha(); + req.session.captchacode = captcha.getCode(); + res.render("signup", {captchaurl:captcha.toDataURL()}); +}; + +exports.login = function(req, res) { + res.render("login"); +}; + +/** + * Extract at random in each room, some album covers and return the result as a JSON. + */ + +exports.artworks = function(req, res) { + var tasks = []; + for (var i=0; i Players - include footer + include header + title binb + script(src="/socket.io/socket.io.js") + script(src="/static/js/bootstrap.min.js") + script(src="/static/js/home.js") + body + include uv.jade + .navbar.navbar-fixed-top + .navbar-inner + .container + a.brand(href="/") + .logo #{motto} + ul.nav.pull-right + li.active + a(href="/") Home + if (typeof(loggedin) !== "undefined") + li + p.navbar-text Logged in as + a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin} + li + a(href="/logout") Logout + else + li + a(href="/signup") Sign up + li + a(href="/login") Login + .container + section + .row + .span7 + h3 What's this? + p binb is a realtime, multiplayer, competitive music listening game. + .span9 + h3 How to play? + p All you have to do is to guess the song that is playing. A fixed number + | of songs will run and for each one correctly guessed you will earn an + | amount of points. That amount depends on the number of correct guesses + | (artist and/or title of the song) and on how fast you will be on + | answering compared to other players. At the end a scoreboard will + | report the best three players of the match. If you have read this + | far, what are you waiting? Click on a room below and prove your + | music knowledge! + section + .row + .span16 + ul.thumbnails + each item in rooms + li.span4 + a.thumbnail.relative(href="/#{item}") + .room #{item} - Players + include footer diff --git a/views/login.jade b/views/login.jade index 9cca2c4..ca0ee1b 100644 --- a/views/login.jade +++ b/views/login.jade @@ -1,78 +1,79 @@ doctype html -html - include header - title binb :: login - script(src="/static/js/bootstrap.min.js") - body - include uv.jade - .navbar.navbar-fixed-top - .navbar-inner - .container - a.brand(href="/") - .logo #{motto} - ul.nav.pull-right - li - a(href="/") Home - li - a(href="/signup") Sign up - li.active - a(href="/login") Login - .container - section - .row - .span3 - h3 New user? - a(href="/signup") Click here to create an account. - .span13 - if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined")) - .alert.alert-error - a.close(data-dismiss="alert") × - strong Oh snap! - | #{errors.alert} - else if (typeof(success) !== "undefined") - .alert.alert-success - a.close(data-dismiss="alert") × - strong Well done! - | #{success} - form.form-horizontal.well(method="post",action="/login") - fieldset - if (typeof(errors) !== "undefined") - if (typeof(errors.username) !== "undefined") - .control-group.error - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - value="#{username}") - span.help-inline #{errors.username[0]} - else - .control-group - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - value="#{username}") - if (typeof(errors.password) !== 'undefined') - .control-group.error - label.control-label(for="password") Password - .controls - input#password(type="password",name="password") - span.help-inline #{errors.password[0]} - else - .control-group - label.control-label(for="password") Password - .controls - input#password(type="password",name="password") - else - .control-group - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - placeholder="enter your nickname...") - .control-group - label.control-label(for="password") Password - .controls - input#password(type="password",name="password", - placeholder="enter your password...") - button#signup-button.btn.btn-primary(type="submit") - i.icon-user.icon-white - | Login - include footer +html + include header + title binb :: login + script(src="/static/js/bootstrap.min.js") + body + include uv.jade + .navbar.navbar-fixed-top + .navbar-inner + .container + a.brand(href="/") + .logo #{motto} + ul.nav.pull-right + li + a(href="/") Home + li + a(href="/signup") Sign up + li.active + a(href="/login") Login + .container + section + .row + .span3 + h3 New user? + a(href="/signup") Click here to create an account. + .span13 + if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined")) + .alert.alert-error + a.close(data-dismiss="alert") × + strong Oh snap! + | #{errors.alert} + else if (typeof(success) !== "undefined") + .alert.alert-success + a.close(data-dismiss="alert") × + strong Well done! + | #{success} + form.form-horizontal.well(method="post",action="/login") + fieldset + if (typeof(errors) !== "undefined") + if (typeof(errors.username) !== "undefined") + .control-group.error + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + value="#{oldvalues.username}") + span.help-inline #{errors.username} + else + .control-group + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + value="#{oldvalues.username}") + if (typeof(errors.password) !== 'undefined') + .control-group.error + label.control-label(for="password") Password + .controls + input#password(type="password",name="password") + span.help-inline #{errors.password} + else + .control-group + label.control-label(for="password") Password + .controls + input#password(type="password",name="password", + placeholder="enter your password...") + else + .control-group + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + placeholder="enter your nickname...") + .control-group + label.control-label(for="password") Password + .controls + input#password(type="password",name="password", + placeholder="enter your password...") + button#signup-button.btn.btn-primary(type="submit") + i.icon-user.icon-white + | Login + include footer diff --git a/views/room.jade b/views/room.jade index 7c374c2..0d8af23 100644 --- a/views/room.jade +++ b/views/room.jade @@ -1,90 +1,90 @@ doctype html html - include header - title binb :: #{roomname} - script(src="/socket.io/socket.io.js") - script(src="/static/js/bootstrap.min.js") - script(src="/static/js/jquery.jplayer.min.js") - script(type='text/javascript') - var roomname = "#{roomname}"; - script(src="/static/js/room.js") - body - include uv.jade - .navbar.navbar-fixed-top - .navbar-inner - .container - ul.nav.pull-right - li - a(href="/") Home - li.active.dropdown - a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname} - b.caret - ul.dropdown-menu - each item in rooms - if item != roomname - li - a(href="/#{item}") #{item} - if (typeof(loggedin) !== "undefined") - li - p.navbar-text Logged in as - a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin} - li - a(href="/logout") Logout - else - li - a(href="/signup") Sign up - li - a(href="/login") Login - #player - #modal.modal.fade - .container - section - .row - .span4.offset1 - #cassette.relative - #wheel-left.wheel - #tape-left - #tape-right - #wheel-right.wheel - #progress-bar - #progress - #countdown - .span2 - #volume.relative - .span8 - .page-header - .logo #{motto} - #total-tracks tracks. - #summary.row - .span2 - .title Rank - .rank - .span4 - .title Points - .points - .span2 - .title Track - .track - p#feedback Waiting for connection... - input#guess.span8(type="text",tabindex="1", - placeholder="guess the artist and/or title here") - section.relative - .row - #users-wrapper.span5.offset2 - ul#users.unstyled - .span8 - a#toggle-chat Hide chat - #chat-outer-wrapper - #chat-wrapper.bordered - ul#chat.unstyled - #message-wrapper - span#recipient - input#message.span8(type="text",tabindex="2") - ul#tracks.unstyled - #disclaimer - div I do not own any right on the songs that are played here. - div Tracks are played using iTunes api preview. - div Original idea from - a(target="_blank", href="http://beatquest.fm/") beatquest.fm - |. - include footer + include header + title binb :: #{roomname} + script(src="/socket.io/socket.io.js") + script(src="/static/js/bootstrap.min.js") + script(src="/static/js/jquery.jplayer.min.js") + script(type='text/javascript') + var roomname = "#{roomname}"; + script(src="/static/js/room.js") + body + include uv.jade + .navbar.navbar-fixed-top + .navbar-inner + .container + ul.nav.pull-right + li + a(href="/") Home + li.active.dropdown + a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname} + b.caret + ul.dropdown-menu + each item in rooms + if item != roomname + li + a(href="/#{item}") #{item} + if (typeof(loggedin) !== "undefined") + li + p.navbar-text Logged in as + a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin} + li + a(href="/logout") Logout + else + li + a(href="/signup") Sign up + li + a(href="/login") Login + #player + #modal.modal.fade + .container + section + .row + .span4.offset1 + #cassette.relative + #wheel-left.wheel + #tape-left + #tape-right + #wheel-right.wheel + #progress-bar + #progress + #countdown + .span2 + #volume.relative + .span8 + .page-header + .logo #{motto} + #total-tracks tracks. + #summary.row + .span2 + .title Rank + .rank + .span4 + .title Points + .points + .span2 + .title Track + .track + p#feedback Waiting for connection... + input#guess.span8(type="text",tabindex="1", + placeholder="guess the artist and/or title here") + section.relative + .row + #users-wrapper.span5.offset2 + ul#users.unstyled + .span8 + a#toggle-chat Hide chat + #chat-outer-wrapper + #chat-wrapper.bordered + ul#chat.unstyled + #message-wrapper + span#recipient + input#message.span8(type="text",tabindex="2") + ul#tracks.unstyled + #disclaimer + div I do not own any right on the songs that are played here. + div Tracks are played using iTunes api preview. + div Original idea from + a(target="_blank", href="http://beatquest.fm/") beatquest.fm + |. + include footer diff --git a/views/signup.jade b/views/signup.jade index 7303bb1..a32c652 100644 --- a/views/signup.jade +++ b/views/signup.jade @@ -1,117 +1,119 @@ doctype html -html - include header - title binb :: sign up - script(src="/static/js/bootstrap.min.js") - body - include uv.jade - .navbar.navbar-fixed-top - .navbar-inner - .container - a.brand(href="/") - .logo #{motto} - ul.nav.pull-right - li - a(href="/") Home - li.active - a(href="/signup") Sign up - li - a(href="/login") Login - .container - section - .row - .span3 - h3 Not a new user? - a(href="/login") Click here to log in. - .span13 - h3 Why sign up? - p Registration is optional, but if you are a regular user consider creating - | an account. This will provide the following advantages: - ul - li You will be the one and only owner of your nickname (no one can use - | your nickname in your place). - li Some simple stats related to your account will be collected. - if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined")) - .alert.alert-error - a.close(data-dismiss="alert") × - strong Oh snap! - | #{errors.alert} - form.form-horizontal.well(method="post",action="/signup") - fieldset - if (typeof(errors) !== "undefined") - if (typeof(errors.username) !== "undefined") - .control-group.error - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - value="#{username}") - span.help-inline #{errors.username[0]} - else - .control-group - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - value="#{username}") - if (typeof(errors.email) !== 'undefined') - .control-group.error - label.control-label(for="email") Email - .controls - input#email(type="text",name="email", - value="#{email}") - span.help-inline #{errors.email[0]} - else - .control-group - label.control-label(for="email") Email - .controls - input#email(type="text",name="email", - value="#{email}") - if (typeof(errors.password) !== 'undefined') - .control-group.error - label.control-label(for="password") Password - .controls - input#password(type="password",name="password") - span.help-inline #{errors.password[0]} - else - .control-group - label.control-label(for="password") Password - .controls - input#password(type="password",name="password") - if (typeof(errors.captcha) !== 'undefined') - .control-group.error - label.control-label(for="captcha-input") Are you human? - .controls - img#captcha(src="#{captchaurl}") - input#captcha-input(type="text",name="captcha") - span.help-inline #{errors.captcha[0]} - else - .control-group - label.control-label(for="captcha-input") Are you human? - .controls - img#captcha(src="#{captchaurl}") - input#captcha-input(type="text",name="captcha") - else - .control-group - label.control-label(for="username") Name - .controls - input#username(type="text",name="username", - placeholder="enter a nickname...") - .control-group - label.control-label(for="email") Email - .controls - input#email(type="text",name="email", - placeholder="enter a valid email...") - .control-group - label.control-label(for="password") Password - .controls - input#password(type="password",name="password", - placeholder="enter a password...") - .control-group - label.control-label(for="captcha-input") Are you human? - .controls - img#captcha(src="#{captchaurl}") - input#captcha-input(type="text",name="captcha", - placeholder="type what you see...") - button#signup-button.btn.btn-success(type="submit") - i.icon-user.icon-white - | Sign up! - include footer +html + include header + title binb :: sign up + script(src="/static/js/bootstrap.min.js") + body + include uv.jade + .navbar.navbar-fixed-top + .navbar-inner + .container + a.brand(href="/") + .logo #{motto} + ul.nav.pull-right + li + a(href="/") Home + li.active + a(href="/signup") Sign up + li + a(href="/login") Login + .container + section + .row + .span3 + h3 Not a new user? + a(href="/login") Click here to log in. + .span13 + h3 Why sign up? + p Registration is optional, but if you are a regular user consider creating + | an account. This will provide the following advantages: + ul + li You will be the one and only owner of your nickname (no one can use + | your nickname in your place). + li Some simple stats related to your account will be collected. + if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined")) + .alert.alert-error + a.close(data-dismiss="alert") × + strong Oh snap! + | #{errors.alert} + form.form-horizontal.well(method="post",action="/signup") + fieldset + if (typeof(errors) !== "undefined") + if (typeof(errors.username) !== "undefined") + .control-group.error + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + value="#{oldvalues.username}") + span.help-inline #{errors.username} + else + .control-group + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + value="#{oldvalues.username}") + if (typeof(errors.email) !== 'undefined') + .control-group.error + label.control-label(for="email") Email + .controls + input#email(type="text",name="email", + value="#{oldvalues.email}") + span.help-inline #{errors.email} + else + .control-group + label.control-label(for="email") Email + .controls + input#email(type="text",name="email", + value="#{oldvalues.email}") + if (typeof(errors.password) !== 'undefined') + .control-group.error + label.control-label(for="password") Password + .controls + input#password(type="password",name="password") + span.help-inline #{errors.password} + else + .control-group + label.control-label(for="password") Password + .controls + input#password(type="password",name="password", + placeholder="enter a password...") + if (typeof(errors.captcha) !== 'undefined') + .control-group.error + label.control-label(for="captcha-input") Are you human? + .controls + img#captcha(src="#{captchaurl}") + input#captcha-input(type="text",name="captcha") + span.help-inline #{errors.captcha} + else + .control-group + label.control-label(for="captcha-input") Are you human? + .controls + img#captcha(src="#{captchaurl}") + input#captcha-input(type="text",name="captcha", + placeholder="type what you see...") + else + .control-group + label.control-label(for="username") Name + .controls + input#username(type="text",name="username", + placeholder="enter a nickname...") + .control-group + label.control-label(for="email") Email + .controls + input#email(type="text",name="email", + placeholder="enter a valid email...") + .control-group + label.control-label(for="password") Password + .controls + input#password(type="password",name="password", + placeholder="enter a password...") + .control-group + label.control-label(for="captcha-input") Are you human? + .controls + img#captcha(src="#{captchaurl}") + input#captcha-input(type="text",name="captcha", + placeholder="type what you see...") + button#signup-button.btn.btn-success(type="submit") + i.icon-user.icon-white + | Sign up! + include footer diff --git a/views/user.jade b/views/user.jade index 5942e3b..c4c91a5 100644 --- a/views/user.jade +++ b/views/user.jade @@ -1,89 +1,89 @@ doctype html html - include header - title binb :: #{username} info - body - include uv.jade - .navbar.navbar-fixed-top - .navbar-inner - .container - a.brand(href="#") - .logo #{motto} - .container - section - .row - .span7.offset1 - .profile #{username} - .img - div member since #{joindate} - section - .row - .span7.offset1 - h4 Points - table.table.table-striped.table-bordered.stats - tbody - tr - td Total - td #{totpoints} - tr - td Best score - td #{bestscore} - tr - td Guessed songs - td #{guessed} - h4 Times - table.table.table-striped.table-bordered.stats - tbody - tr - td Best guess time - if (bestguesstime !== "30.0") - td #{bestguesstime} sec - else - td N/A - tr - td Worst guess time - if (worstguesstime !== "0.0") - td #{worstguesstime} sec - else - td N/A - tr - td Mean guess time - if (typeof meanguesstime !== "undefined") - td #{meanguesstime} sec - else - td N/A - .span7 - h4 Awards - table.table.table-striped.table-bordered.stats - tbody - tr - td Gold cups - td - .cups.stand1 - td #{golds} - tr - td Silver cups - td - .cups.stand2 - td #{silvers} - tr - td Bronze cups - td - .cups.stand3 - td #{bronzes} - tr - td Victories - td - .medals.rank1 - td #{victories} - tr - td Second places - td - .medals.rank2 - td #{secondplaces} - tr - td Third places - td - .medals.rank3 - td #{thirdplaces} - include footer + include header + title binb :: #{username} info + body + include uv.jade + .navbar.navbar-fixed-top + .navbar-inner + .container + a.brand(href="#") + .logo #{motto} + .container + section + .row + .span7.offset1 + .profile #{username} + .img + div member since #{joindate} + section + .row + .span7.offset1 + h4 Points + table.table.table-striped.table-bordered.stats + tbody + tr + td Total + td #{totpoints} + tr + td Best score + td #{bestscore} + tr + td Guessed songs + td #{guessed} + h4 Times + table.table.table-striped.table-bordered.stats + tbody + tr + td Best guess time + if (bestguesstime !== "30.0") + td #{bestguesstime} sec + else + td N/A + tr + td Worst guess time + if (worstguesstime !== "0.0") + td #{worstguesstime} sec + else + td N/A + tr + td Mean guess time + if (typeof meanguesstime !== "undefined") + td #{meanguesstime} sec + else + td N/A + .span7 + h4 Awards + table.table.table-striped.table-bordered.stats + tbody + tr + td Gold cups + td + .cups.stand1 + td #{golds} + tr + td Silver cups + td + .cups.stand2 + td #{silvers} + tr + td Bronze cups + td + .cups.stand3 + td #{bronzes} + tr + td Victories + td + .medals.rank1 + td #{victories} + tr + td Second places + td + .medals.rank2 + td #{secondplaces} + tr + td Third places + td + .medals.rank3 + td #{thirdplaces} + include footer diff --git a/views/uv.jade b/views/uv.jade index 9bb615a..4b29b34 100644 --- a/views/uv.jade +++ b/views/uv.jade @@ -1,11 +1,11 @@ script - var uvOptions = {}; - (function() { - var uv = document.createElement('script'); - uv.type = 'text/javascript'; - uv.async = true; - uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + - 'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js'; - var s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(uv, s); - })(); + var uvOptions = {}; + (function() { + var uv = document.createElement('script'); + uv.type = 'text/javascript'; + uv.async = true; + uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + + 'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js'; + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(uv, s); + })();