]> git.example.dev Git - binbsis50.git/commitdiff
added accounts and simple stats tracking
authorLuigi Pinca <luigipinca@gmail.com>
Sun, 15 Apr 2012 21:54:34 +0000 (23:54 +0200)
committerLuigi Pinca <luigipinca@gmail.com>
Sun, 15 Apr 2012 21:54:34 +0000 (23:54 +0200)
18 files changed:
README.md
config.js
package.json
public/static/css/bootstrap.min.css
public/static/css/style.css
public/static/images/sprites.png
public/static/images/wheel.png [deleted file]
public/static/js/bootstrap.min.js
public/static/js/home.js
public/static/js/room.js
server.js
views/header.jade
views/index.jade
views/login.jade [new file with mode: 0644]
views/room.jade
views/signup.jade [new file with mode: 0644]
views/user.jade [new file with mode: 0644]
views/uv.jade [new file with mode: 0644]

index 709377f81392fb0508d35cb786eb7b5892b247c5..11a9e71de8f694316d77307bd0910f68875a17a1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Binb #
+# binb #
 
-Binb is a simple, realtime, multiplayer, competitive music listening game.
+binb is a simple, realtime, multiplayer, competitive music listening game.
 
 To play the game: [http://binb.nodejitsu.com](http://binb.nodejitsu.com) 
 
@@ -8,7 +8,7 @@ To play the game: [http://binb.nodejitsu.com](http://binb.nodejitsu.com)
 
 Ideal setup is a browser with websocket support and able to decode .m4a format natively. 
 
-For this reason Binb is optimized for Google Chrome but also works in all major browsers (if version is recently enough).
+For this reason binb is optimized for Google Chrome but also works in all major browsers (if version is recently enough).
 
 ## Shout-Outs ##
 
@@ -21,4 +21,4 @@ Have a bug? Please create an [issue](https://github.com/lpinca/binb/issues) here
 
 ## Copyright and license ##
 
-Binb is released under the MIT license. See LICENSE for details.
+binb is released under the MIT license. See LICENSE for details.
index f967a487d2a28319bfced108e7593722375e772e..07c720bbab054eb698d5bd3773d22dcc05bceef1 100644 (file)
--- a/config.js
+++ b/config.js
@@ -2,7 +2,9 @@
 
 exports.configure = function() {
        this.port = 80;
-       this.redisurl = '';
+       this.songsdburl = '';
+       this.usersdburl = '';
+       this.sessionsecret = '';
        this.songsinarun = 15;
        this.threshold = 2; // Edit distance threshold
        this.rooms = ["pop", "rock", "mixed"];
index 11125da467ee2b77c8505f34cfddac83ea560374..fc21ecda83317d7260c13cd640cdf214632543d1 100644 (file)
@@ -2,7 +2,11 @@
   "name": "binb",
   "dependencies": {
     "async": "latest",
+    "canvas": "latest",
+    "connect": "latest",
+    "connect-redis": "latest",
     "express": "latest",
+    "express-form": "latest",
     "jade": "latest",
     "redis-url": "latest",
     "socket.io": "latest"
@@ -14,5 +18,5 @@
   "engines": {
     "node": "0.6.x"
   },
-  "version": "0.2.0-14"
+  "version": "0.3.0-1"
 }
\ No newline at end of file
index ea97c00307747e8191d78094165bb563d6309d8d..7429196c8faa0e1622db0bd28d40edeea2d5b34e 100644 (file)
@@ -140,6 +140,45 @@ cite{font-style:normal;}
 .label-info:hover{background-color:#2d6987;}
 .label-inverse{background-color:#333333;}
 .label-inverse:hover{background-color:#1a1a1a;}
+table{max-width:100%;border-collapse:collapse;border-spacing:0;background-color:transparent;}
+.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
+.table th{font-weight:bold;}
+.table thead th{vertical-align:bottom;}
+.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
+.table tbody+tbody{border-top:2px solid #dddddd;}
+.table-condensed th,.table-condensed td{padding:4px 5px;}
+.table-bordered{border:1px solid #dddddd;border-left:0;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
+.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
+.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
+.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
+.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
+.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
+.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}
+table .span1{float:none;width:24px;margin-left:0;}
+table .span2{float:none;width:84px;margin-left:0;}
+table .span3{float:none;width:144px;margin-left:0;}
+table .span4{float:none;width:204px;margin-left:0;}
+table .span5{float:none;width:264px;margin-left:0;}
+table .span6{float:none;width:324px;margin-left:0;}
+table .span7{float:none;width:384px;margin-left:0;}
+table .span8{float:none;width:444px;margin-left:0;}
+table .span9{float:none;width:504px;margin-left:0;}
+table .span10{float:none;width:564px;margin-left:0;}
+table .span11{float:none;width:624px;margin-left:0;}
+table .span12{float:none;width:684px;margin-left:0;}
+table .span13{float:none;width:744px;margin-left:0;}
+table .span14{float:none;width:804px;margin-left:0;}
+table .span15{float:none;width:864px;margin-left:0;}
+table .span16{float:none;width:924px;margin-left:0;}
+table .span17{float:none;width:984px;margin-left:0;}
+table .span18{float:none;width:1044px;margin-left:0;}
+table .span19{float:none;width:1104px;margin-left:0;}
+table .span20{float:none;width:1164px;margin-left:0;}
+table .span21{float:none;width:1224px;margin-left:0;}
+table .span22{float:none;width:1284px;margin-left:0;}
+table .span23{float:none;width:1344px;margin-left:0;}
+table .span24{float:none;width:1404px;margin-left:0;}
 form{margin:0 0 18px;}
 fieldset{padding:0;margin:0;border:0;}
 legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}legend small{font-size:13.5px;color:#999999;}
@@ -414,6 +453,110 @@ button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-
 .btn-small .caret{margin-top:6px;}
 .btn-large .caret{margin-top:6px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
 .btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
+.nav{margin-left:0;margin-bottom:18px;list-style:none;}
+.nav>li>a{display:block;}
+.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
+.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
+.nav li+.nav-header{margin-top:9px;}
+.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
+.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.nav-list>li>a{padding:3px 15px;}
+.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
+.nav-list [class^="icon-"]{margin-right:2px;}
+.nav-list .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
+.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";}
+.nav-tabs:after,.nav-pills:after{clear:both;}
+.nav-tabs>li,.nav-pills>li{float:left;}
+.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
+.nav-tabs{border-bottom:1px solid #ddd;}
+.nav-tabs>li{margin-bottom:-1px;}
+.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
+.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;}
+.nav-stacked>li{float:none;}
+.nav-stacked>li>a{margin-right:0;}
+.nav-tabs.nav-stacked{border-bottom:0;}
+.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
+.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
+.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
+.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
+.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
+.nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
+.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;}
+.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;}
+.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;}
+.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
+.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
+.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
+.tabs-stacked .open>a:hover{border-color:#999999;}
+.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";}
+.tabbable:after{clear:both;}
+.tab-content{display:table;width:100%;}
+.tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.tabs-below .nav-tabs{border-top:1px solid #ddd;}
+.tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
+.tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
+.tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
+.tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;}
+.tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
+.tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
+.tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
+.tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
+.tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
+.tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
+.tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
+.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;}
+.navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
+.navbar .container{width:auto;}
+.btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#222222;}
+.btn-navbar:active,.btn-navbar.active{background-color:#080808 \9;}
+.btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
+.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
+.nav-collapse.collapse{height:auto;}
+.navbar{color:#999999;}.navbar .brand:hover{text-decoration:none;}
+.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
+.navbar .navbar-text{margin-bottom:0;line-height:40px;}
+.navbar .btn,.navbar .btn-group{margin-top:5px;}
+.navbar .btn-group .btn{margin-top:0;}
+.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";}
+.navbar-form:after{clear:both;}
+.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
+.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;}
+.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
+.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
+.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;}
+.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
+.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
+.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
+.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.navbar-fixed-top{top:0;}
+.navbar-fixed-bottom{bottom:0;}
+.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
+.navbar .nav.pull-right{float:right;}
+.navbar .nav>li{display:block;float:left;}
+.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
+.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
+.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;}
+.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;}
+.navbar .nav.pull-right{margin-left:10px;margin-right:0;}
+.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
+.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
+.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
+.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
+.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
+.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
+.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
+.navbar .nav.pull-right .dropdown-menu,.navbar .nav .dropdown-menu.pull-right{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before,.navbar .nav .dropdown-menu.pull-right:before{left:auto;right:12px;}
+.navbar .nav.pull-right .dropdown-menu:after,.navbar .nav .dropdown-menu.pull-right:after{left:auto;right:13px;}
 .thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";}
 .thumbnails:after{clear:both;}
 .thumbnails>li{float:left;margin:0 0 18px 20px;}
@@ -421,6 +564,15 @@ button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-
 a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
 .thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
 .thumbnail .caption{padding:9px;}
+.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;}
+.alert-heading{color:inherit;}
+.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
+.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
+.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
+.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
+.alert-block{padding-top:14px;padding-bottom:14px;}
+.alert-block>p,.alert-block>ul{margin-bottom:0;}
+.alert-block p+p{margin-top:5px;}
 .modal-open .dropdown-menu{z-index:2050;}
 .modal-open .dropdown.open{*z-index:2050;}
 .modal-open .popover{z-index:2060;}
@@ -452,5 +604,14 @@ a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105,
 .dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";}
 .dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
 .typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.invisible{visibility:hidden;}
 .fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
 .collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
index 435573f61f915f91281e38c229599f1021834e01..e95c08f9a4482c8a6dcf9135c3de9aa91957d642 100644 (file)
@@ -1,9 +1,48 @@
 body {
        background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
+       padding-top:45px;
 }
 section {
        margin-top:30px;
 }
+.motto {
+       color: #999999;
+       font-size:13px;
+       text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.navbar .navbar-text {
+       line-height:19px;
+       padding: 10px 10px 11px;
+}
+.form-horizontal .control-group {
+       margin-bottom: 10px;
+}
+.form-horizontal .control-label {
+       width: 100px;
+}
+.form-horizontal .controls {
+       margin-left: 120px;
+}
+form .clearfix {
+       margin-bottom: 10px;
+}
+.well {
+       background-color: #DDDDDD;
+       margin-bottom: 18px;
+}
+.alert {
+       margin-bottom: 9px;
+}
+#signup-button {
+       margin-left: 120px;
+       margin-top: 9px;
+}
+#captcha-input {
+       width: 126px;
+}
+#captcha {
+       margin-right:20px;
+}
 .page-header {
        padding: 0;
        margin: 0 0 17px 0;
@@ -26,7 +65,7 @@ input {
 .modal-footer {
        text-align: left;
 }
-#join {
+.modal-footer .btn {
        float: right;
 }
 .thumbnails {
@@ -48,6 +87,34 @@ input {
        width: 70px;
        height: 70px;
 }
+.profile {
+       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;
+}
+.stats {
+       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;
+}
+.stats tbody tr:nth-child(odd) td {
+       background-color: #ddd;
+}
+.stats tbody tr:hover td {
+       background-color: #dadada;
+}
 .room {
        height: 25px;
        line-height: 25px;
@@ -61,10 +128,6 @@ input {
        text-align: center;
        text-transform: capitalize;
 }
-.btn-group {
-       float: right;
-       margin: 21px 10px 0 0;
-}
 .dropdown-menu {
        min-width: 140px;
 }
@@ -77,12 +140,32 @@ input {
 .matched {
        color: #f3a22f;
 }
-.gameover {
-       padding-left:32px;
-       height:32px;
-       line-height:32px;
+.cups, .medals {
+       margin-left: auto;
+       margin-right: auto;
 }
-.gameover .name {
+.cups {
+       width: 16px;
+       height: 16px;
+}
+.medals {
+       width: 32px;
+       height: 32px;
+}
+.rank1 {
+       background: url('/static/images/sprites.png') no-repeat -32px -16px;
+}
+.rank2 {
+       background: url('/static/images/sprites.png') no-repeat -32px -48px;
+}
+.rank3 {
+       background: url('/static/images/sprites.png') no-repeat -32px -80px;
+}
+.scoreboard th, .scoreboard td {
+       vertical-align: middle;
+       text-align: center;
+}
+.scoreboard .name {
        font-weight:bold;
 }
 .relative {
@@ -113,7 +196,7 @@ input {
        width: 24px;
        height:24px;
        top: 49px;
-       background: url('/static/images/wheel.png') no-repeat 0 0;
+       background: url('/static/images/sprites.png') no-repeat 0px -32px;
 }
 #wheel-left {
        left:51px;
@@ -282,7 +365,12 @@ input {
        text-align: center;
        margin-bottom:18px;
 }
+#users-wrapper {
+       margin-left: 120px;
+       width: 300px;
+}
 #users {
+       padding-left:20px;
        margin-bottom: 90px;
        max-height: 380px;
        overflow: auto;
@@ -298,17 +386,30 @@ input {
 }
 #users .private {
        display: none;
+       font-size: 9.75px;
+       padding: 2px 4px;
+       position: absolute;
+       left: -19px;
 }
 #users .private, #users .name, .gameover .name {
        margin-right: 4px;
 }
-#users .label {
-       font-size:9.75px;
-       padding: 2px 4px;
-       line-height: 18px;
-       vertical-align: top;
+.registered, #users .round-rank {
+       display: inline-block;
+       vertical-align: middle;
+}
+.registered, .round-rank {
+       height: 16px;
+       width: 16px;
+       margin-right:2px;
 }
-#users .name {
+.registered {
+       background: url('/static/images/sprites.png') no-repeat 0px -16px;
+}
+.registered:hover {
+       background: url('/static/images/sprites.png') no-repeat -16px -16px;
+}
+#users .name, .registered {
        cursor: pointer;
 }
 #users .you {
@@ -317,11 +418,6 @@ input {
 #users .points, #users .round-points {
        margin-right: 10px;
 }
-.round-rank {
-       height:16px;
-       width:16px;
-       margin-right:2px;
-}
 .stand1 {
        background: url('/static/images/sprites.png') no-repeat 0 0;
 }
@@ -331,12 +427,9 @@ input {
 .stand3 {
        background: url('/static/images/sprites.png') no-repeat -32px 0;
 }
-#users .round-rank {
-       display:inline-block;
-       vertical-align: middle;
-}
 #users .guess-time {
        font-size: 11px;
+       line-height: 1px;
 }
 #toggle-chat {
        position: absolute;
index e8b8b2df73bc2ceebbf61b3bc1de0949af7d8d39..45b1465515dabe2064dd748389b7d081ef5df509 100644 (file)
Binary files a/public/static/images/sprites.png and b/public/static/images/sprites.png differ
diff --git a/public/static/images/wheel.png b/public/static/images/wheel.png
deleted file mode 100644 (file)
index 50d4cb7..0000000
Binary files a/public/static/images/wheel.png and /dev/null differ
index ca2c2504f3480efa8787f14c12a2a993143d3bba..a2849c4d3c4241788b6228de5407c7665846b02b 100644 (file)
@@ -1,7 +1,7 @@
 /**
 * Bootstrap.js by @fat & @mdo
-* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js
+* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-alert.js
 * Copyright 2012 Twitter, Inc.
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 */
-!function(a){a(function(){a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this;if(this.isShown)return;a("body").addClass("modal-open"),this.isShown=!0,this.$element.trigger("show"),g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");!b.$element.parent().length&&b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();if(!this.isShown)return;var e=this;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.trigger("hide").removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery)
\ No newline at end of file
+!function(a){a(function(){a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this;if(this.isShown)return;a("body").addClass("modal-open"),this.isShown=!0,this.$element.trigger("show"),g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");!b.$element.parent().length&&b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();if(!this.isShown)return;var e=this;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.trigger("hide").removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery)
\ No newline at end of file
index 2520e7cd816b7ab6492c609b11228341b0c22441..3ae8103818aacfdc404f9730e007124ee263c853 100644 (file)
@@ -7,9 +7,6 @@ $(function() {
                        }
                });
        }
-       var mottos = ['guess the song.', 'name that tune.', 'i know this track.'];
-       var motto = mottos[Math.floor(Math.random()*mottos.length)];
-       $('#app-name small').text(motto);
        $.get("/artworks", function(data) {
                $(".thumbnail").each(function(index) {
                        var i = index * 6;
index b422947fadec008dd43484cbbb96b3012e09d997..0c6cb6e31fd7816c9887a21fb7836e9007385f1f 100644 (file)
@@ -19,8 +19,6 @@
        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 mottos = ['guess the song.', 'name that tune.', 'i know this track.'];
-
        var DOM = {};
 
        // Exact match version of jQuery :contains selector
        };
        
        // Prompt for name and send it.
-       var joinRoom = function(msg) {
+       var joinAnonymously = function(msg) {
                if (/nickname\s*\=/.test(document.cookie) && !msg) {
                        nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1"));
-                       socket.emit('joinroom', {nickname:nickname,roomname:roomname});
+                       socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
                }
                else {
                        if (!$('body').hasClass('modal-open')) {
@@ -51,7 +49,7 @@
                                        var val = $.trim(login.val());
                                        if (val !== "") {
                                                nickname = val;
-                                               socket.emit('joinroom', {nickname:nickname,roomname:roomname});
+                                               socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
                                        }
                                        else {
                                                var txt = "Nickname can't be empty.";
 
        // Your submitted name was invalid
        var invalidNickName = function(feedback) {
-               joinRoom(feedback+"<br/>Try with another one:");
+               joinAnonymously(feedback+"<br/>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 = '<div class="modal-header"><h3>Already in a room</h3></div>';
+               html += '<div class="modal-body"><div class="alert alert-error alert-block">';
+               html += '<h4 class="alert-heading">Warning!</h4>You are already in a room.<br/>';
+               html += 'Leave the other room and refresh this page or close this one.</div></div>';
+               $(html).appendTo(DOM.modal);
+               DOM.modal.modal('show');
        };
        
-       // You joined the game
+       // You joined the room
        var ready = function(data) {
-               if (!/nickname\s*\=/.test(document.cookie)) {
+               if (!data.loggedin && !/nickname\s*\=/.test(document.cookie)) {
                        document.cookie = "nickname="+escape(nickname)+";path=/;";
                }
                DOM.modal.modal('hide').empty();
                var found = false;
                for (var i=0; i<users.length; i++) {
                        var user = users[i];
-                       var li = $('<li></li>');
+                       var li = $('<li class="relative"></li>');
                        var pvt = $('<span class="private label label-info">P</span>');
                        var username = $('<span class="name"></span>').text(user.nickname);
                        var points = $('<span class="points">('+user.points+')</span>');
                        var roundpointsel = $('<span class="round-points"></span>');
                        var guesstime = $('<span class="guess-time"></span>');
                        li.append(pvt, username, points, roundrank, roundpointsel, guesstime);
+                       if (user.registered) {
+                               var href = 'href="/user/'+username.text().replace(/"/g, "&quot;")+'"';
+                               pvt.after('<a class="registered" target="_blank" '+href+'></a>');
+                       }
                        DOM.users.append(li);
                        if (pvtmsgto === user.nickname) {
                                pvt.show();
                DOM.recipient.hide();
                DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();});
                var el = $("span.name:econtains("+usrname+")");
-               el.prev().show();
+               el.prevAll(".private").show();
                el.unbind('click');
                el.click(clearPrivate);
                pvtmsgto = usrname;
                DOM.recipient.text("");
                DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
                var el = $("span.name:econtains("+pvtmsgto+")");
-               el.prev().hide();
+               el.prevAll(".private").hide();
                el.unbind("click");
                el.click(function() {
                        addPrivate($(this).text());
        };
 
        var gameOver = function(data) {
-               var users = [];
-               for (var key in data.users) {
-                       users.push(data.users[key]);
-               }
-               users.sort(function(a, b) {return b.points - a.points;});
                var html = '<div class="modal-header"><h3>Game Over</h3></div>';
-               html += '<div class="modal-body">';
+               html += '<div class="modal-body"><table class="table table-striped scoreboard">';
+               html += '<thead><tr><th>#</th><th>Name</th><th>Points</th>';
+               html += '<th><div class="cups stand1"></div></th><th><div class="cups stand2"></div></th>';
+               html += '<th><div class="cups stand3"></div></th><th>Guessed</th><th>Best time</th>';
+               html += '</thead><tbody>';
                for(var i=0;i<3;i++) {
-                       if (users[i]) {
-                               var rank = i+1;
-                               var offset = -16 + (-32 * i);
-                               var style = ' style="background:url(/static/images/sprites.png)';
-                               style += ' no-repeat 0px '+offset+'px;"';
-                               html += '<div class="gameover"'+style+'>'+rank+')';
-                               html += ' <span class="name">'+users[i].nickname;
-                               html += '</span>('+users[i].points+')</div>';
+                       if (data.users[i]) {
+                               var playername = data.users[i].nickname.replace(/</g, "&lt;")
+                                                               .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
+                               html += '<tr><td><div class="medals rank'+(i+1)+'"></div></td>';
+                               html += '<td class="name">'+playername+'</td>';
+                               html += '<td>'+data.users[i].points+'</td>';
+                               html += '<td>'+data.users[i].golds+'</td><td>'+data.users[i].silvers+'</td>';
+                               html += '<td>'+data.users[i].bronzes+'</td><td>'+data.users[i].guessed+'</td>';
+                               var besttime = "N/A";
+                               if (data.users[i].bestguesstime !== 30000) {
+                                       besttime = (data.users[i].bestguesstime/1000).toFixed(1)+" s";
+                               }
+                               html += '<td>'+besttime+'</td></tr>';
                        }
                }
-               html +='</div>';
+               html +='</tbody></table></div>';
                html += '<div class="modal-footer">A new game will start in <span></span> second/s</div>';
                DOM.modal.append($(html));
                DOM.modal.modal('show');
 
        // Set up the room.
        $(function() {
-               var motto = mottos[Math.floor(Math.random()*mottos.length)];
-               $('#app-name small').text(motto);
                setVariables();
                DOM.modal.modal({keyboard:false,show:false,backdrop:"static"});
                DOM.togglechat.click(hideChat);
                socket.on("connect", function() {
                        jplayer = $("#player").jPlayer({
                                ready: function() {
-                                       joinRoom();
+                                       socket.emit('loggedin', function(data) {
+                                               if (data) {
+                                                       nickname = data;
+                                                       socket.emit('joinroom', roomname);
+                                               }
+                                               else {
+                                                       joinAnonymously();
+                                               }
+                                       });
                                        if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) {
                                                addVolumeControl();
                                        }
                                volume: 1
                        });
                });
+               socket.on('alreadyinaroom', alreadyInARoom);
                socket.on('invalidnickname', invalidNickName);
                socket.on('ready', ready);
                socket.on("disconnect", disconnect);
index 9b91ab5f8b400d1c3b302a6509199a36c151fc6b..2e851d2ed7e91d32b1be3a1a8c8d27753620b8fb 100644 (file)
--- a/server.js
+++ b/server.js
 var async = require("async");
+var crypto = require("crypto");
+var canvas = require("canvas");
 var express = require("express");
+var form = require("express-form");
+var parseCookie = require('connect').utils.parseCookie;
+var redisstore = require('connect-redis')(express);
+var config = require("./config.js").configure();
+
+// Setting up Redis
+var songsdb = require("redis-url").createClient(config.songsdburl);
+var usersdb = require("redis-url").createClient(config.usersdburl);
+
+songsdb.on('error', function(err) {
+       console.log("Error: "+err);
+});
+
+usersdb.on('error', function(err) {
+       console.log("Error: "+err);
+});
+
+// Setting up Express
+var sessionstore = new redisstore({client:usersdb});
 var http = express.createServer();
 
 // Configuration
-var config = require("./config.js").configure();
 http.use(express.static(__dirname + '/public'));
+http.use(express.bodyParser());
+http.use(express.cookieParser());
+http.use(express.session({secret:config.sessionsecret,store:sessionstore}));
 http.set("view options", {layout:false});
 http.set('view engine', 'jade');
 
 // Routes
 http.get("/", function(req, res) {
+       if (req.session.user) {
+               res.local('loggedin', req.session.user);
+       }
        res.render("index", {rooms:config.rooms});
 });
 
+// Captcha generator
+const CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+var Captcha = function() {
+       var code = "";
+       while (code.length < 4) {
+               code += CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)];
+       }
+       var _canvas = new canvas(64, 26);
+       var ctx = _canvas.getContext('2d');
+       ctx.fillStyle = "#DDDDDD";
+       ctx.fillRect(0, 0, 64, 26);
+       ctx.font = "bold 20px Helvetica";
+       ctx.lineWidth = 1;
+       ctx.textAlign = "center";
+       ctx.strokeStyle = "#080";
+       ctx.strokeText(code, 31, 20);
+       ctx.save();
+       this.getCode = function() {
+               return code;
+       }
+       this.toDataURL = function() {
+               return _canvas.toDataURL();
+       }
+};
+
+http.get("/signup", function(req, res) {
+       var captcha = new Captcha();
+       req.session.captchacode = captcha.getCode();
+       res.render("signup", {captchaurl:captcha.toDataURL()});
+});
+
+http.post("/signup", 
+       form(
+               form.filter("username").trim().required().not(/binb/, "is reserved")
+                       .is(/^[^\x00-\x1F\x7F]{1,15}$/, "1 to 15 characters required"),
+               form.filter("email").required().isEmail("is not an email address"),
+               form.filter("password").required()
+                       .is(/^[A-Za-z0-9]{6,15}$/, "6 to 15 alphanumeric characters required"),
+               form.filter("captcha").required()
+       ),
+       function(req, res) {
+               if (req.form.isValid) {
+                       if (req.session.captchacode !== req.form.captcha) {
+                               var errors = {captcha:['no match']};
+                               var captcha = new Captcha();
+                               req.session.captchacode = captcha.getCode();
+                               return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
+                       }
+                       var userkey = "user:"+req.form.username;
+                       usersdb.exists(userkey, function(err, data) {
+                               if (data === 1) { // User already exists
+                                       var errors = {alert: "A user with name "+req.form.username+" already exists."};
+                                       var captcha = new Captcha();
+                                       req.session.captchacode = captcha.getCode();
+                                       return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
+                               }
+                               var mailkey = "email:"+req.form.email;
+                               usersdb.exists(mailkey, function(e, d) {
+                                       if (d === 1) { // Email already exists
+                                               var errors = {alert: "A user with that email already exists."};
+                                               var captcha = new Captcha();
+                                               req.session.captchacode = captcha.getCode();
+                                               return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
+                                       }
+                                       var salt = "";
+                                       while (salt.length < 8) {
+                                               salt += CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)];
+                                       }
+                                       var hash = crypto.createHash('sha256')
+                                                               .update(salt+req.form.password).digest('hex');
+                                       var date = new Date();
+                                       var joindate = date.getDate()+"/"+(date.getMonth()+1)+"/"+date.getFullYear();
+                                       usersdb.hmset(userkey, "username", req.form.username,
+                                                                       "email", req.form.email,
+                                                                       "password", hash,
+                                                                       "salt", salt,
+                                                                       "joindate", joindate,
+                                                                       "totpoints", 0,
+                                                                       "bestscore", 0,
+                                                                       "golds", 0,
+                                                                       "silvers", 0,
+                                                                       "bronzes", 0,
+                                                                       "bestguesstime", 30000,
+                                                                       "worstguesstime", 0,
+                                                                       "totguesstime", 0,
+                                                                       "guessed", 0,
+                                                                       "victories", 0,
+                                                                       "secondplaces", 0,
+                                                                       "thirdplaces", 0);
+                                       usersdb.set(mailkey, userkey);
+                                       usersdb.sadd("users", userkey);
+                                       usersdb.sadd("emails", mailkey);
+                                       var msg = "You successfully created your account. You are now ready to login.";
+                                       res.render("login", {success:msg});
+                               });
+                       });
+               }
+               else {
+                       var captcha = new Captcha();
+                       req.session.captchacode = captcha.getCode();
+                       res.render("signup", {errors:req.form.getErrors(),captchaurl:captcha.toDataURL()});
+               }
+       }
+);
+
+http.get("/login", function(req, res) {
+       res.render("login");
+});
+
+http.post("/login", 
+       form(
+               form.filter("username").trim().required(),
+               form.filter("password").trim().required()
+       ),
+       function(req, res) {
+               if (req.form.isValid) {
+                       var errors = {alert: "The username and/or password you specified are not correct."};
+                       var key = "user:"+req.form.username;
+                       usersdb.exists(key, function(err, data) {
+                               if (data === 1) { // User exists
+                                       usersdb.hmget(key, "salt", "password", function(e, resp) {
+                                               var hash = crypto.createHash('sha256')
+                                                                       .update(resp[0]+req.body.password).digest('hex');
+                                               if (hash === resp[1]) {
+                                                       req.session.regenerate(function() {
+                                                               req.session.cookie.maxAge = 604800000; // One week
+                                                               req.session.user = req.form.username;
+                                                               res.redirect('/');
+                                                       });
+                                               }
+                                               else {
+                                                       res.render("login", {errors:errors});
+                                               }
+                                       });
+                               }
+                               else {
+                                       res.render("login", {errors:errors});
+                               }
+                       });
+               }
+               else {
+                       res.render("login", {errors:req.form.getErrors()});
+               }
+       }
+);
+
+http.get("/logout", function(req, res) {
+       req.session.destroy(function() {
+               res.redirect("/");
+       });
+});
+
+var makeCallBack = function(genre) {
+       return function(callback) {
+               songsdb.srandmember(genre, function(err, res) {
+                       songsdb.hget(res, "artworkUrl100", callback);
+               });
+       };
+};
+
 http.get("/artworks", function(req, res) {
        var callitems = [];
        for (var i=0; i<config.rooms.length; i++) {
@@ -30,25 +217,44 @@ http.get("/artworks", function(req, res) {
        });
 });
 
-http.get("/:room", function(req, res, next) {
+http.get("/:room", function(req, res) {
        if (config.rooms.indexOf(req.params.room) !== -1) {
+               if (req.session.user) {
+                       res.local('loggedin', req.session.user);
+               }
                res.render("room", {roomname:req.params.room,rooms:config.rooms});
        }
        else {
-               next();
+               res.send(404);
        }
 });
 
+http.get("/user/*", function(req, res) {
+       var key = "user:"+req.params[0];
+       usersdb.exists(key, function(err, data) {
+               if (data === 1) {
+                       usersdb.hgetall(key, function(e, obj) {
+                               obj.bestguesstime = (obj.bestguesstime/1000).toFixed(1);
+                               obj.worstguesstime = (obj.worstguesstime/1000).toFixed(1);
+                               if (obj.guessed !== "0") {
+                                       obj.meanguesstime = ((obj.totguesstime/obj.guessed)/1000).toFixed(1);
+                               }
+                               delete obj.email;
+                               delete obj.password;
+                               delete obj.salt;
+                               delete obj.totguesstime;
+                               res.render("user", obj);
+                       });
+               }
+               else {
+                       res.send(404);
+               }
+       });
+});
+
 // Starting HTTP server
 http.listen(config.port);
 
-// Setting up Redis
-var redis = require("redis-url").createClient(config.redisurl);
-
-redis.on('error', function(err) {
-       console.log("Error: "+err);
-});
-
 // Setting up Socket.IO
 var io = require("socket.io").listen(http);
 
@@ -59,12 +265,82 @@ io.set('log level', 1);                                            // reduce logging
 // enable transports
 io.set('transports', ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
 
-var makeCallBack = function(genre) {
-       return function(callback) {
-               redis.srandmember(genre, function(err, res) {
-                       redis.hget(res, "artworkUrl100", callback);
+io.set('authorization', function(data, accept) {
+       if(data.headers.cookie) {
+               var cookie = parseCookie(data.headers.cookie);
+               sessionstore.get(cookie['connect.sid'], function(err, session) {
+                       if (err || !session) {
+                               accept('Error', false);
+                       }
+                       else {
+                               data.session = session;
+                               accept(null, true);
+                       }
                });
-       };
+       }
+       else {
+               return accept('No cookie transmitted.', false);
+       }
+});
+
+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(data) {
+               if (session.user && typeof data === "string" && config.rooms.indexOf(data) !== -1) {
+                       if (getUserSocket(session.user)) { // User already in a room
+                               socket.emit('alreadyinaroom');
+                               return;
+                       }
+                       socket.nickname = session.user;
+                       Rooms[data].joinRoom(socket);
+               }
+       }); 
+       socket.on('joinanonymously', function(data) {
+               if (!socket.nickname && typeof data === "object" && typeof data.nickname === "string" &&
+                       data.nickname !== "" && typeof data.roomname === "string" && 
+                       config.rooms.indexOf(data.roomname) !== -1) {
+                       Rooms[data.roomname].setNickName(socket, data);
+               }
+       });
+       socket.on('getstatus', function() {
+               if (socket.roomname) {
+                       Rooms[socket.roomname].sendStatus(socket);
+               }
+       });
+       socket.on('sendchatmsg', function(data) {
+               if (socket.roomname) {
+                       Rooms[socket.roomname].sendChatMessage(socket, data);
+               }
+       });
+       socket.on('guess', function(data) {
+               if (socket.roomname && typeof data === "string") {
+                       Rooms[socket.roomname].guess(socket, data);
+               }
+       });
+       socket.on("disconnect", function() {
+               if (socket.roomname && socket.nickname) {
+                       Rooms[socket.roomname].userLeft(socket);
+               }
+       });
+});
+
+// Sockets of all rooms
+var sockets = Object.create(null);
+
+// Get the socket of a player
+var getUserSocket = function(nickname) {
+       return sockets[nickname];
 };
 
 /*
@@ -179,10 +455,51 @@ var amatch = function(subject, guess, enableartistrules) {
        return false;
 };
 
-var sockets = Object.create(null);
-
-var getUserSocket = function(nickname) {
-       return sockets[nickname];
+var collectStats = function(username, stats) {
+       var key = "user:"+username;
+       if (stats.points) {
+               usersdb.hincrby(key, "totpoints", stats.points);
+       }
+       if (stats.userscore) {
+               // Set personal best
+               usersdb.hget(key, "bestscore", function(err, res) {
+                       if (res < stats.userscore) {
+                               usersdb.hset(key, "bestscore", stats.userscore);
+                       }
+               });
+       }
+       if (stats.gold) {
+               usersdb.hincrby(key, "golds", 1);
+       }
+       if (stats.silver) {
+               usersdb.hincrby(key, "silvers", 1);
+       }
+       if (stats.bronze) {
+               usersdb.hincrby(key, "bronzes", 1);
+       }
+       if (stats.guesstime) {
+               usersdb.hincrby(key, "guessed", 1);
+               usersdb.hincrby(key, "totguesstime", stats.guesstime);
+               usersdb.hget(key, "bestguesstime", function(err, res) {
+                       if (stats.guesstime < res) {
+                               usersdb.hset(key, "bestguesstime", stats.guesstime);
+                       }
+               });
+               usersdb.hget(key, "worstguesstime", function(err, res) {
+                       if (stats.guesstime > res) {
+                               usersdb.hset(key, "worstguesstime", stats.guesstime);
+                       }
+               });
+       }
+       if (stats.firstplace) {
+               usersdb.hincrby(key, "victories", 1);
+       }
+       if (stats.secondplace) {
+               usersdb.hincrby(key, "secondplaces", 1);
+       }
+       if (stats.thirdplace) {
+               usersdb.hincrby(key, "thirdplaces", 1);
+       }
 };
 
 function Room(name) {
@@ -191,7 +508,7 @@ function Room(name) {
        var totusers = 0;
        
        var usersData = Object.create(null);
-       var playedtracks = []; // Used to prevent the same song from playing twice in one game
+       var playedtracks = Object.create(null); // Used to prevent the same song from playing twice in one game
        
        var artistName = null;
        var artistlcase = null;
@@ -212,17 +529,26 @@ function Room(name) {
                return totusers;
        };
 
-       var addUser = function(socket) {
+       var addUser = function(socket, loggedin) {
                sockets[socket.nickname] = socket;
                usersData[socket.nickname] = {
                        nickname: socket.nickname,
+                       registered: loggedin,
                        points: 0,
                        roundpoints: 0,
                        matched: null,
-                       guesstime: null
+                       guessed: 0,
+                       guesstime: null,
+                       bestguesstime: 30000,
+                       golds: 0,
+                       silvers: 0,
+                       bronzes: 0
                };
                totusers = totusers + 1;
                io.sockets.in('home').emit('update', {room:roomname,players:totusers});
+               // Broadcast new user event
+               socket.emit('ready', {users:usersData,trackscount:trackscount,loggedin:loggedin});
+               socket.broadcast.to(roomname).emit('newuser', {nickname:socket.nickname,users:usersData});
        };
 
        var removeUser = function(socket) {
@@ -241,6 +567,12 @@ function Room(name) {
                return false;
        };
        
+       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);
@@ -252,7 +584,7 @@ function Room(name) {
                if (data.nickname.length > 15) {
                        feedback = '<span class="label label-important">That name is too long.</span>';
                }
-               else if (data.nickname === "Binb") {
+               else if (data.nickname === "binb") {
                        feedback = '<span class="label label-important">That name is reserved.</span>';
                }
                else if (getUserSocket(data.nickname)) {
@@ -261,15 +593,22 @@ function Room(name) {
                if (feedback) {
                        return invalidNickName(socket, feedback);
                }
-
-               socket.nickname = data.nickname;
-               socket.roomname = roomname;
-               socket.join(roomname);
                
-               // Add user to the list of active users and broadcast the event
-               addUser(socket);
-               socket.emit('ready', {users:usersData,trackscount:trackscount});
-               socket.broadcast.to(roomname).emit('newuser', {nickname:socket.nickname,users:usersData});
+               var key = "user:"+data.nickname;
+               usersdb.exists(key, function(err, resp) {
+                       if (resp === 1) { // User already exists
+                               feedback = '<span class="label label-important">That name belongs '
+                               feedback += 'to a registered user.</span>';
+                               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.)
@@ -285,7 +624,7 @@ function Room(name) {
                        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});
+                               socket.emit('chatmsg', {from:"binb",to:socket.nickname,chatmsg:msg});
                                return;
                        }
                        io.sockets.in(roomname).emit('chatmsg', {from:socket.nickname,chatmsg:data});
@@ -300,29 +639,71 @@ function Room(name) {
        };
 
        var addPoints = function(socket, allinone) {
-               usersData[socket.nickname].matched = 'both';
+               usersData[socket.nickname].guesstime = 30000 - songtimeleft;
+               var stats = {};
                switch (finishline) {
                        case 1:
                                finishline++;
-                               usersData[socket.nickname].guesstime = 30000 - songtimeleft;
                                usersData[socket.nickname].roundpoints = 6;
-                               usersData[socket.nickname].points += (allinone) ? 6 : 5;
+                               if (allinone) {
+                                       usersData[socket.nickname].points += 6;
+                                       stats.points = 6;
+                               }
+                               else {
+                                       usersData[socket.nickname].points += 5;
+                                       stats.points = 5;
+                               }
+                               usersData[socket.nickname].golds++;
+                               stats.gold = true;
                                break;
                        case 2:
                                finishline++;
-                               usersData[socket.nickname].guesstime = 30000 - songtimeleft;
                                usersData[socket.nickname].roundpoints = 5;
-                               usersData[socket.nickname].points += (allinone) ? 5 : 4;
+                               if (allinone) {
+                                       usersData[socket.nickname].points += 5;
+                                       stats.points = 5;
+                               }
+                               else {
+                                       usersData[socket.nickname].points += 4;
+                                       stats.points = 4;
+                               }
+                               usersData[socket.nickname].silvers++;
+                               stats.silver = true;
                                break;
                        case 3:
                                finishline++;
-                               usersData[socket.nickname].guesstime = 30000 - songtimeleft;
                                usersData[socket.nickname].roundpoints = 4;
-                               usersData[socket.nickname].points += (allinone) ? 4 : 3;
+                               if (allinone) {
+                                       usersData[socket.nickname].points += 4;
+                                       stats.points = 4;
+                               }
+                               else {
+                                       usersData[socket.nickname].points += 3;
+                                       stats.points = 3;
+                               }
+                               usersData[socket.nickname].bronzes++;
+                               stats.bronze = true;
                                break;
                        default:
                                usersData[socket.nickname].roundpoints = 3;
-                               usersData[socket.nickname].points += (allinone) ? 3 : 2;
+                               if (allinone) {
+                                       usersData[socket.nickname].points += 3;
+                                       stats.points = 3;
+                               }
+                               else {
+                                       usersData[socket.nickname].points += 2;
+                                       stats.points = 2;
+                               }
+               }
+               usersData[socket.nickname].matched = 'both';
+               usersData[socket.nickname].guessed++;
+               if (usersData[socket.nickname].guesstime < usersData[socket.nickname].bestguesstime) {
+                       usersData[socket.nickname].bestguesstime = usersData[socket.nickname].guesstime;
+               }
+               if (usersData[socket.nickname].registered) {
+                       stats.userscore = usersData[socket.nickname].points;
+                       stats.guesstime = usersData[socket.nickname].guesstime;
+                       collectStats(socket.nickname, stats);
                }
        };
        
@@ -340,6 +721,10 @@ function Room(name) {
                                        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++;
@@ -347,6 +732,10 @@ function Room(name) {
                                        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');
@@ -387,6 +776,11 @@ function Room(name) {
                for (var key in usersData) {
                        if (!roundonly) {
                                usersData[key].points = 0;
+                               usersData[key].guessed = 0;
+                               usersData[key].bestguesstime = 30000;
+                               usersData[key].golds = 0;
+                               usersData[key].silvers = 0;
+                               usersData[key].bronzes = 0;
                        }
                        usersData[key].roundpoints = 0;
                        usersData[key].matched = null;
@@ -395,21 +789,21 @@ function Room(name) {
        };
 
        var sendLoadTrack = function() {
-               redis.srandmember(roomname, function(err, res) {
-                       redis.hmget(res, "trackId", "artistName", "trackName", "collectionName", "previewUrl",
+               songsdb.srandmember(roomname, function(err, res) {
+                       songsdb.hmget(res, "artistName", "trackName", "collectionName", "previewUrl",
                                                        "artworkUrl60", "trackViewUrl", function(e, replies) {
-                               if (playedtracks[replies[0]]) {
+                               if (playedtracks[res]) {
                                        return sendLoadTrack();
                                }
-                               playedtracks[replies[0]] = true;
-                               artistName = replies[1];
+                               playedtracks[res] = true;
+                               artistName = replies[0];
                                artistlcase = artistName.toLowerCase();
-                               trackName = replies[2];
+                               trackName = replies[1];
                                tracklcase = trackName.toLowerCase();
-                               collectionName = replies[3];
-                               previewUrl = replies[4];
-                               artworkUrl = replies[5];
-                               trackViewUrl = replies[6];
+                               collectionName = replies[2];
+                               previewUrl = replies[3];
+                               artworkUrl = replies[4];
+                               trackViewUrl = replies[5];
                                io.sockets.in(roomname).emit('loadtrack', {previewUrl:previewUrl});
                                setTimeout(sendPlayTrack, 5000);
                        });
@@ -453,7 +847,22 @@ function Room(name) {
 
        var gameOver = function() {
                status = 3; // Game over
-               io.sockets.in(roomname).emit('gameover', {users:usersData});
+               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);
        };
@@ -464,13 +873,13 @@ function Room(name) {
 
        var reset = function() {
                songcounter = 0;
-               playedtracks = [];
+               playedtracks = Object.create(null);
                sendLoadTrack();
        };
 
        // Start the room
        this.start = function() {
-               redis.scard(roomname, function(err, res) {
+               songsdb.scard(roomname, function(err, res) {
                        trackscount = res;
                });
                sendLoadTrack();
@@ -483,38 +892,4 @@ for (var i=0; i<config.rooms.length; i++) {
        Rooms[config.rooms[i]].start();
 }
 
-console.log("Bimb started and listening on port "+config.port);
-
-io.sockets.on('connection', function(socket) {
-       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('joinroom', function(data) {
-               if (!socket.nickname && typeof data === "object" && typeof data.nickname === "string" &&
-                       data.nickname !== "" && typeof data.roomname === "string" && 
-                       config.rooms.indexOf(data.roomname) !== -1) {
-                       Rooms[data.roomname].setNickName(socket, data);
-               }
-       });
-       socket.on('getstatus', function() {
-               Rooms[socket.roomname].sendStatus(socket);
-       });
-       socket.on('sendchatmsg', function(data) {
-               Rooms[socket.roomname].sendChatMessage(socket, data);
-       });
-       socket.on('guess', function(data) {
-               if (typeof data === "string") {
-                       Rooms[socket.roomname].guess(socket, data);
-               }
-       });
-       socket.on("disconnect", function() {
-               if (socket.nickname !== undefined) {
-                       Rooms[socket.roomname].userLeft(socket);
-               }
-       });
-});
+console.log("binb started and listening on port "+config.port);
index 8e4ede5657be7080ca8567a07a357802f00bd570..c9fe1a0d47a753156cddee5c3750b6392f35a6f8 100644 (file)
@@ -1,22 +1,21 @@
-doctype html
-html
-       head
-               link(href="/static/css/bootstrap.min.css", rel="stylesheet")
-               link(href="/static/css/style.css", rel="stylesheet")
-               meta(charset="UTF-8")
-               meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
-               meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
-               meta(name="author", content="Luigi Pinca")
-               script(src="/static/js/jquery.min.js")
-               script(src="/socket.io/socket.io.js")
-               script(type="text/javascript")
-                       var _gaq = _gaq || [];
-                       _gaq.push(['_setAccount', 'UA-29865853-1']);
-                       _gaq.push(['_trackPageview']);
-                       (function() {
-                               var ga = document.createElement('script');
-                               ga.type = 'text/javascript'; ga.async = true;
-                               ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
-                                       '.google-analytics.com/ga.js';
-                               var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-                       })();
+mottos = ['guess the song.', 'name that tune.', 'i know this track.']
+motto = mottos[Math.floor(Math.random()*mottos.length)]
+head
+       link(href="/static/css/bootstrap.min.css", rel="stylesheet")
+       link(href="/static/css/style.css", rel="stylesheet")
+       meta(charset="UTF-8")
+       meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
+       meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
+       meta(name="author", content="Luigi Pinca")
+       script(src="/static/js/jquery.min.js")
+       script(type="text/javascript")
+               var _gaq = _gaq || [];
+               _gaq.push(['_setAccount', 'UA-29865853-1']);
+               _gaq.push(['_trackPageview']);
+               (function() {
+                       var ga = document.createElement('script');
+                       ga.type = 'text/javascript'; ga.async = true;
+                       ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
+                               '.google-analytics.com/ga.js';
+                       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+               })();
index 2ab98b622f0c2302fde9eeebf19eeeaa1657841e..aca79314ae4e12432106e03f935ed80d964ee8d2 100644 (file)
@@ -1,27 +1,37 @@
-include header
-               title Binb
+doctype html
+html
+       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
-               script(type="text/javascript")
-                       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);
-                       })();
+               include uv.jade
+               .navbar.navbar-fixed-top
+                       .navbar-inner
+                               .container
+                                       a.brand(href="/") binb, 
+                                               span.motto #{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
-                               .page-header
-                                       h1#app-name Binb, 
-                                               small
                                .row
                                        .span7
                                                h3 What's this?
-                                               p Binb is a realtime, multiplayer, competitive music listening game.
+                                               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 
diff --git a/views/login.jade b/views/login.jade
new file mode 100644 (file)
index 0000000..09bc953
--- /dev/null
@@ -0,0 +1,78 @@
+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="/") binb, 
+                                               span.motto #{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") &times;
+                                                               strong Oh snap!
+                                                               |  #{errors.alert}
+                                               else if (typeof(success) !== "undefined")
+                                                       .alert.alert-success
+                                                               a.close(data-dismiss="alert") &times;
+                                                               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
index 68cdd00c75ed3624efffc51477e0c93237008081..bc97ddac2a23cdab50b36c683d1d61427a3a7e16 100644 (file)
@@ -1,22 +1,40 @@
-include header
-               title Binb - #{roomname}
+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
-               script(type="text/javascript")
-                       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);
-                       })();
+               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
@@ -35,21 +53,9 @@ include header
                                                #volume.relative
                                        .span8
                                                .page-header
-                                                       h1#app-name Binb, 
-                                                               small
+                                                       h1#app-name binb, 
+                                                               small #{motto}
                                                        #total-tracks <span></span> tracks.
-                                                       .btn-group
-                                                               button.btn.btn-mini.dropdown-toggle(data-toggle="dropdown")
-                                                                       | #{roomname}
-                                                                       span.caret
-                                                               ul.dropdown-menu
-                                                                       each item in rooms
-                                                                               if item != roomname
-                                                                                       li
-                                                                                               a(href="/#{item}") #{item}
-                                                                       li.divider
-                                                                       li
-                                                                               a(href="/") home
                                                #summary.row
                                                        .span2
                                                                .title Rank
@@ -61,10 +67,10 @@ include header
                                                                .title Track
                                                                .track
                                                p#feedback Waiting for connection...
-                                               input#guess.span8(type="text")
+                                               input#guess.span8(type="text",placeholder="guess the artist and/or title here")
                        section.relative
                                .row
-                                       .span5.offset2
+                                       #users-wrapper.span5.offset2
                                                ul#users.unstyled
                                        .span8
                                                a#toggle-chat Hide chat
diff --git a/views/signup.jade b/views/signup.jade
new file mode 100644 (file)
index 0000000..c82727f
--- /dev/null
@@ -0,0 +1,117 @@
+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="/") binb, 
+                                               span.motto #{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") &times;
+                                                               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
diff --git a/views/user.jade b/views/user.jade
new file mode 100644 (file)
index 0000000..cbe03c6
--- /dev/null
@@ -0,0 +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="#") binb, 
+                                               span.motto #{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
new file mode 100644 (file)
index 0000000..2adbc49
--- /dev/null
@@ -0,0 +1,11 @@
+script(type="text/javascript")
+       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);
+       })();