]> git.example.dev Git - binbsis50.git/commitdiff
added leaderboard
authorLuigi Pinca <luigipinca@gmail.com>
Thu, 14 Jun 2012 21:16:15 +0000 (23:16 +0200)
committerLuigi Pinca <luigipinca@gmail.com>
Thu, 14 Jun 2012 21:16:15 +0000 (23:16 +0200)
12 files changed:
app.js
lib/room.js
lib/stats.js
package.json
public/static/css/style.css
public/static/images/logo.png [deleted file]
public/static/images/sprites.png
routes/site.js
routes/user.js
views/index.jade
views/leaderboard.jade [new file with mode: 0644]
views/room.jade

diff --git a/app.js b/app.js
index bca2c84a69e4bd739eb74c08f01e3716ebd6c732..c47b4ef051860ed28356795369d5d1e8777a5de9 100644 (file)
--- a/app.js
+++ b/app.js
@@ -60,11 +60,12 @@ 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('/leaderboard', user.leaderboard);
 app.get('/login', site.login);
 app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
 app.get('/logout', user.logout);
+app.get('/signup', site.signup);
+app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
 app.get('/:room', site.room);
 app.get('/user/*', user.profile);
 
index b096b19da19a7c3b2dbe8e74d03e3b2a0fb0cce2..52dea95b35f754893cf6c8e55b7983d893d9044e 100644 (file)
@@ -300,13 +300,13 @@ module.exports = function(params) {
         // 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',
+                // Check if extracted track is in the list of already played tracks
+                if (playedtracks.indexOf(res) !== -1) {
+                    return sendLoadTrack();
+                }
+                playedtracks.push(res);
+                songsdb.hmget('song:'+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];
index c52689398143f672b1690cdc5774398c3e4f1ddc..a4020057e0c748a1c9fe4a0664dd3512c29f7c3d 100644 (file)
@@ -9,6 +9,8 @@ module.exports = function(db) {
         if (stats.points) {
             // Update total points
             db.hincrby(key, 'totpoints', stats.points);
+            // Update the score of the member in the sorted set
+            db.zincrby('users', stats.points, username);
         }
         if (stats.userscore) {
             // Set personal best
index 1893bc8be094f934b598afd90afea992b381a97a..2f3306229b4b7c99774c17995e8e16f3370cb219 100644 (file)
@@ -17,5 +17,5 @@
   "engines": {
     "node": "0.6.x"
   },
-  "version": "0.3.1-2"
+  "version": "0.3.1-3"
 }
\ No newline at end of file
index 935c9864b4f488a3e99bdb0cc9617cfa6119c748..e7a876ff77da8b3c01e7c1f42d3694416b5c6b63 100644 (file)
@@ -24,7 +24,7 @@ section {
     height: 34px;
     padding-left: 68px;
     line-height: 40px;
-    background: url('/static/images/logo.png') no-repeat 0px -54px;
+    background: url('/static/images/sprites.png') no-repeat 0px -182px;
     font-size: 25px;
 }
 .page-header .logo {
@@ -32,13 +32,13 @@ section {
     height: 54px;
     padding-left: 109px;
     line-height: 68px;
-    background: url('/static/images/logo.png') no-repeat 0 0;
+    background: url('/static/images/sprites.png') no-repeat 0px -128px;
     font-size: 34px;
     color: #0088CC;
 }
 .navbar .navbar-text {
     line-height:19px;
-    padding: 10px 10px 11px;
+    padding: 9px 10px 11px;
 }
 .form-horizontal .control-group {
     margin-bottom: 10px;
@@ -105,32 +105,62 @@ input {
     width: 70px;
     height: 70px;
 }
+.highscores, .profile {
+    font-weight: bold;
+}
 .profile {
     font-size: 24px;
-    font-weight: bold;
     line-height: 32px;
 }
+.highscores {
+    font-size: 20px;
+    line-height: 45px;
+}
 .profile .img {
     width: 32px;
     height: 32px;
-    float: left;
     margin-right: 5px;
     background: url('/static/images/sprites.png') no-repeat 0px -56px;
 }
-.stats {
+.highscores .img {
+    width: 80px;
+    height: 45px;
+    margin-right:5px;
+    background: url('/static/images/sprites.png') no-repeat 0px -216px;
+}
+.leaderboard-wrapper, .stats {
     border: 1px solid #ccc;
     border-left: 0;
     margin-top:8px;
 }
-.stats td {
+.leaderboard-wrapper {
+    height: 349px;
+    overflow: auto;
+    margin-bottom:18px;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+}
+.leaderboard {
+    margin:0;
+    border:0;
+}
+.leaderboard td, .stats td {
     border-left: 1px solid #ccc;
     border-top: 1px solid #ccc;
     vertical-align: middle;
 }
-.stats tbody tr:nth-child(odd) td {
+.leaderboard td [class^="icon-"] {
+    vertical-align: top;
+    margin-top: 2px;
+}
+.leaderboard tr:first-child td {
+    border-top: 0;
+}
+.leaderboard tbody tr:nth-child(odd) td, .stats tbody tr:nth-child(odd) td {
     background-color: #ddd;
 }
-.stats tbody tr:hover td {
+.leaderboard tbody tr:hover td, .stats tbody tr:hover td {
     background-color: #dadada;
 }
 .room {
@@ -555,7 +585,7 @@ input {
     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 {
+.registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, .highscores .img, .profile .img, #copy, #facebook-button, #twitter-button, #github-button {
     float:left;
 }
 #tracks img.artwork {
diff --git a/public/static/images/logo.png b/public/static/images/logo.png
deleted file mode 100644 (file)
index 2159e70..0000000
Binary files a/public/static/images/logo.png and /dev/null differ
index 45b1465515dabe2064dd748389b7d081ef5df509..bd671bb1abfd18b3d55aa701fea7027053d2e2e6 100644 (file)
Binary files a/public/static/images/sprites.png and b/public/static/images/sprites.png differ
index 0dce1020ad73e3e98593ba0e7ebbd3e4bdc44c93..b806d2142629f7113fa55143c17b4a2052ea4d3e 100644 (file)
@@ -14,7 +14,7 @@ var async = require('async')
 var task = function(genre) {
     return function(callback) {
         db.srandmember(genre, function(err, res) {
-            db.hget(res, "artworkUrl100", callback);
+            db.hget('song:'+res, 'artworkUrl100', callback);
         });
     };
 };
@@ -32,17 +32,7 @@ exports.index = function(req, res) {
     if (req.session.user) {
         res.local('loggedin', req.session.user);
     }
-    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");
+    res.render('index', {rooms:rooms});
 };
 
 /**
@@ -65,12 +55,23 @@ exports.artworks = function(req, res) {
     });
 };
 
+exports.login = function(req, res) {
+    res.render('login');
+};
+
+exports.signup = function(req, res) {
+    var captcha = new Captcha();
+    req.session.captchacode = captcha.getCode();
+    res.render('signup', {captchaurl:captcha.toDataURL()});
+};
+
+
 exports.room = function(req, res) {
     if (rooms.indexOf(req.params.room) !== -1) {
         if (req.session.user) {
             res.local('loggedin', req.session.user);
         }
-        res.render("room", {roomname:req.params.room,rooms:rooms});
+        res.render('room', {roomname:req.params.room,rooms:rooms});
     }
     else {
         res.send(404);
index b7237bb77b9548cfafba73353a403d9b7831a2e8..e1ca5863d61c86255448570392cd19df0d08dae6 100644 (file)
@@ -18,6 +18,46 @@ String.prototype.isEmail = function() {
     return this.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
 };
 
+/**
+ * Parameters to get users ordered by best guess time.
+ */
+
+var sortparams = [
+    'users'
+    , 'by'
+    , 'user:*->bestguesstime'
+    , 'get'
+    , '#'
+    , 'get'
+    , 'user:*->bestguesstime'
+    , 'limit'
+    , '0'
+    , '30'
+];
+
+/**
+ * Leaderboard helper function.
+ * Rearrange database results in an object.
+ */
+
+var buildLeaderboard = function(pointsresults, timesresults) {
+    var obj = {
+        pointsleaderboard: [],
+        timesleaderboard: []
+    }
+    for (var i=0; i<pointsresults.length; i+=2) {
+        obj.pointsleaderboard.push({
+            username: pointsresults[i],
+            totpoints: pointsresults[i+1]
+        });
+        obj.timesleaderboard.push({
+            username: timesresults[i],
+            bestguesstime: (timesresults[i+1] / 1000).toFixed(2)
+        });
+    }
+    return obj;
+};
+
 /**
  * Initialize dependencies.
  */
@@ -26,6 +66,85 @@ exports.use = function(options) {
     db = options.db;
 };
 
+/**
+ * Show a list of users ordered by points and best guess time (limit set to 30).
+ */
+
+exports.leaderboard = function(req, res) {
+    db.zrevrange('users', 0, 29, 'withscores', function(err, pointsresults) {
+        db.sort(sortparams, function (e, timesresults) {
+            var leaderboard = buildLeaderboard(pointsresults, timesresults);
+            res.render('leaderboard', leaderboard);
+        });
+    });
+};
+
+/**
+ * Login middlewares.
+ */
+
+exports.validateLogin = function(req, res, next) {
+    var errors = {};
+    
+    req.body.username = req.body.username.trim(); // Username sanitization
+    req.body.password = req.body.password.trim(); // Password sanitization
+    if (req.body.username === '') {
+        errors.username = "can't be empty";
+    }
+    if (req.body.password === '') {
+        errors.password = "can't be empty";
+    }
+    
+    req.session.oldvalues = {username: req.body.username};
+    if (errors.username || errors.password) {
+        req.session.errors = errors;
+        return res.redirect('/login');
+    }
+    
+    next();
+};
+
+exports.checkUser = function(req, res, next) {
+    var key = 'user:'+req.body.username;
+    db.exists(key, function(err, data) {
+        if (data === 1) {
+            // User exists, proceed with authentication
+            return next();
+        }
+        req.session.errors = {alert: 'The username you specified does not exists.'};
+        res.redirect('/login');
+    });
+};
+
+exports.authenticate = function(req, res) {
+    var key = 'user:'+req.body.username;
+    db.hmget(key, 'salt', 'password', function(err, data) {
+        var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
+        if (hash === data[1]) {
+            // Authentication succeeded, regenerate the session
+            req.session.regenerate(function() {
+                req.session.cookie.maxAge = 604800000; // One week
+                req.session.user = req.body.username;
+                res.redirect('/');
+            });
+            return;
+        }
+        req.session.errors = {alert: 'The password you specified is not correct.'};
+        res.redirect('/login');
+    });
+};
+
+/**
+ * Logout the user.
+ */
+
+exports.logout = function(req, res) {
+    // Destroy the session
+    req.session.destroy(function() {
+        res.redirect('/');
+    });
+};
+
 /**
  * Sign up middlewares.
  */
@@ -109,80 +228,14 @@ exports.createAccount = function(req, res) {
     // Add new user in the db
     db.hmset(userkey, user);
     db.set(mailkey, userkey);
-    db.sadd('users', userkey);
-    db.sadd('emails', mailkey);
+    db.zadd('users', 0, req.body.username);
+    db.sadd('emails', req.body.email);
     // Delete old fields values (we don't want these to be available in login view)
     delete req.session.oldvalues;
     var msg = 'You successfully created your account. You are now ready to login.';
     res.render('login', {success:msg});
 };
 
-/**
- * Login middlewares.
- */
-
-exports.validateLogin = function(req, res, next) {
-    var errors = {};
-    
-    req.body.username = req.body.username.trim(); // Username sanitization
-    req.body.password = req.body.password.trim(); // Password sanitization
-    if (req.body.username === '') {
-        errors.username = "can't be empty";
-    }
-    if (req.body.password === '') {
-        errors.password = "can't be empty";
-    }
-    
-    req.session.oldvalues = {username: req.body.username};
-    if (errors.username || errors.password) {
-        req.session.errors = errors;
-        return res.redirect('/login');
-    }
-    
-    next();
-};
-
-exports.checkUser = function(req, res, next) {
-    var key = 'user:'+req.body.username;
-    db.exists(key, function(err, data) {
-        if (data === 1) {
-            // User exists, proceed with authentication
-            return next();
-        }
-        req.session.errors = {alert: 'The username you specified does not exists.'};
-        res.redirect('/login');
-    });
-};
-
-exports.authenticate = function(req, res) {
-    var key = 'user:'+req.body.username;
-    db.hmget(key, 'salt', 'password', function(err, data) {
-        var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
-        if (hash === data[1]) {
-            // Authentication succeeded, regenerate the session
-            req.session.regenerate(function() {
-                req.session.cookie.maxAge = 604800000; // One week
-                req.session.user = req.body.username;
-                res.redirect('/');
-            });
-            return;
-        }
-        req.session.errors = {alert: 'The password you specified is not correct.'};
-        res.redirect('/login');
-    });
-};
-
-/**
- * Logout the user.
- */
-
-exports.logout = function(req, res) {
-    // Destroy the session
-    req.session.destroy(function() {
-        res.redirect('/');
-    });
-};
-
 /**
  * Show user profile.
  */
index a3fe6a087c285dea38cfe9d6e79b011e8ae76900..2af281473bfd948ff6e5e429a27112ffb48f2697 100644 (file)
@@ -14,6 +14,10 @@ html
                     ul.nav.pull-right
                         li.active
                             a(href="/") Home
+                        li
+                            a(target="_blank", href="/leaderboard")
+                                i.icon-list-alt.icon-white
+                                |  Leaderboard
                         if (typeof(loggedin) !== "undefined")
                             li
                                 p.navbar-text Logged in as 
diff --git a/views/leaderboard.jade b/views/leaderboard.jade
new file mode 100644 (file)
index 0000000..632e29e
--- /dev/null
@@ -0,0 +1,46 @@
+doctype html
+html
+    include header
+        title binb :: Leaderboard
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="#")
+                        .logo #{motto}
+        .container
+            section
+                .row
+                    .span7.offset1
+                        .highscores High Scores
+                            .img
+            section
+                .row
+                    .span7.offset1
+                        h4 Points
+                        div.leaderboard-wrapper
+                            table.table.table-striped.table-bordered.leaderboard
+                                tbody
+                                each user, i in pointsleaderboard
+                                    tr
+                                        td #{i+1}
+                                        td
+                                            a(href="/user/#{encodeURIComponent(user.username)}")
+                                                | #{user.username.replace(/&/g, '&amp;')}
+                                        td #{user.totpoints}
+                    .span7
+                        h4 Times
+                        div.leaderboard-wrapper
+                            table.table.table-striped.table-bordered.leaderboard
+                                tbody
+                                each user, i in timesleaderboard
+                                    tr
+                                        td #{i+1}
+                                        td
+                                            a(href="/user/#{encodeURIComponent(user.username)}")
+                                                | #{user.username.replace(/&/g, '&amp;')}
+                                        td 
+                                            i.icon-time
+                                            |  #{user.bestguesstime} sec
+            include footer
index 75a46ad1f936bc16b1ca7d9a4cd11894155291f0..c3b6c8685b7a4685f4f04befc03209615e9ed780 100644 (file)
@@ -16,6 +16,10 @@ html
                     ul.nav.pull-right
                         li
                             a(href="/") Home
+                        li
+                            a(target="_blank", href="/leaderboard")
+                                i.icon-list-alt.icon-white
+                                |  Leaderboard
                         li.active.dropdown
                             a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname} 
                                 b.caret