Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
mauricesvay committed May 31, 2012
0 parents commit e15858e
Show file tree
Hide file tree
Showing 290 changed files with 56,712 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
50 changes: 50 additions & 0 deletions README.md
@@ -0,0 +1,50 @@
Blindtest
=========

Blindtest is a multiplayer musical game (up to 16 simultaneous players). This is a very early version and surely contains bugs.

Gameplay
--------
* The main screen plays a 30sec audio sample of a random song
* 4 possible answers are displayed
* with their mobile phone, players have to guess who is the singer of the song

Installing
----------
* Get the source `git clone git://github.com/mauricesvay/Blindtest.git`
* `cd Blindtest`
* Install dependencies (socket.io, express, sqlite3) with `npm install .`

Starting the game
-----------------
* `node app.js`
* open `http://localhost:8080/spectate` in your browser. It is now the main screen.
* each player can now join the game at `http://MAIN_SCREEN_IP_ADDRESS:8080/`
* when all players are ready, you can start the game on the main screen.

Technical details
-----------------
* Audio samples and tracks info come from Deezer API
* Songs are picked randomly among french charts (1956-2012)
* Real time connection is made with socket.io
* The app has been tested on a Mac (server), iPhone (client), iPad (client) and is known for working on some Android devices (client).

License
-------
* The app includes:
* Soundmanager2 which is under BSD license
* Zepto.js: [http://zeptojs.com/license](http://zeptojs.com/license)
* jQuery: [http://jquery.org/license](http://jquery.org/license)
* animate.css: [http://daneden.me/animate/](http://daneden.me/animate/)
* Montserrat font: [http://www.google.com/webfonts/specimen/Montserrat](http://www.google.com/webfonts/specimen/Montserrat)

This app is under the BSD license:

Copyright (c) 2012, Maurice Svay
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
189 changes: 189 additions & 0 deletions app.js
@@ -0,0 +1,189 @@
var User = require('./lib/User');
var db = require('./lib/Songs');
var express = require('express');
var app = require('express').createServer()
var io = require('socket.io').listen(app);

var acceptUsers = true;
var started = false;
var MAX_PLAYERS = 16;
var MAX_SCORE = 5;

// var usernames = [];
var users = [];
var sockets = [];
var song;
var winner = '';

function userIndex(username) {
for (var i=0,l=users.length; i<l; i++) {
if (username === users[i].getName()) {
return i;
}
}
return -1;
}

var waitForPlayers = function() {
//wait for players, when go is sent, start
io.sockets.on('connection', function (socket) {

sockets.push(socket);

//Spectate events
socket.on('start', function (data) {
console.log("Game is starting...");
acceptUsers = false;
started = true;
startGame();
});
socket.on('spectate', function (data) {
socket.emit('users', {users: users});
});
socket.on('next', function (data) {
getNextSong();
});

socket.on('login', function (data) {
if (!acceptUsers) {
console.log("refused");
socket.emit('refused');
return;
}

if (data.username) {

console.log(">>> " + data.username);

var username = data.username;
var idx = userIndex(username);

if (idx === -1) {
users.push(new User(username));
socket.set('username', data.username);

socket.emit('login');
socket.broadcast.emit('users', {users: users});
} else {
console.log("refused");
socket.emit('refused');
//user already logged in, ignore
}

if (users.length >= MAX_PLAYERS) {
acceptUsers = false;
}
}
});

socket.on('disconnect', function(){
sockets.splice(sockets.indexOf(socket), 1);
socket.get('username', function(err, name){
if (name) {
var idx = userIndex(name);
if (idx !== -1) {
users.splice(idx,1);
socket.broadcast.emit('users', {users: users});
}
console.log("<<< " + name);
}
});
});

socket.on('button', function(data){

if (winner !== '') {
return;
}

socket.get('username', function(err, username){
var button = data.button;
var idx = userIndex(username);

if (song.suggestions[button].answer === true) {
//Score +1
users[idx].inc();

if (users[idx].getScore() >= MAX_SCORE) {
//Winner ?
socket.emit('winner', username);
socket.broadcast.emit('winner', username);
socket.broadcast.emit('users', {users: users});
winner = username;
console.log('=================================================');
console.log('WINNER : ' + username);
} else {
//Tell everyone we have a right answer
socket.emit('right');
socket.broadcast.emit('right', {username: username});
socket.broadcast.emit('users', {users: users});
}

console.log('=================================================');
for (var i=0,l=users.length; i<l; i++) {
console.log(users[i].getName() + " : " + users[idx].getScore());
}

} else {
socket.emit('wrong');
// console.log(username + " is wrong");
//@TODO : prevent user from playing until next round
}
})
});
});
};

var resetGame = function() {
acceptUsers = true;
started = false;
//@TODO: reset users
}

var startGame = function() {
getNextSong();
};

var getNextSong = function() {

if (winner !== '') {
return;
}

db.getRandomSong(function(data){
song = data;
console.log('=================================================');
console.log('Now playing : ' + song.artist.name + ' - ' + song.title);
updateClients();
});
};

var updateClients = function() {
for (var i=0; i<sockets.length; i++) {
sockets[i].emit('song', {song: song});
}
//Send new data to clients
};

var onCorrectAnswer = function() {
getNextSong();
}

app.listen(8080);

app.configure('development', function(){
app.use(express.static(__dirname + '/www'));
});

app.get('/', function (req, res) {
res.sendfile(__dirname + '/www/index.html');
});
app.get('/spectate', function (req, res) {
res.sendfile(__dirname + '/www/spectate.html');
});

io.configure('development', function(){
io.set('log level', 1);
});

waitForPlayers();
Binary file added data/FR_charts.sqlite
Binary file not shown.
77 changes: 77 additions & 0 deletions lib/Deezer.js
@@ -0,0 +1,77 @@
var http = require('http');
var querystring = require('querystring');

var Deezer = function() {
this.host = 'api.deezer.com';
this.base = '/2.0/';
}

Deezer.prototype.search = function(q, clbk) {
this.request({
'method' : 'search',
'params' : { 'q' : q }
}, clbk);
}

Deezer.prototype.getAlbum = function(id, clbk) {
this.request({
'method' : 'album',
'id' : id
}, clbk);
}

Deezer.prototype.getArtist = function(id, clbk) {
this.request({
'method' : 'artist',
'id' : id
}, clbk);
}

Deezer.prototype.getRelatedArtist = function(id, clbk) {
this.request({
'method' : 'artist',
'id' : id + '/related'
}, clbk);
}

Deezer.prototype.getTrack = function(id, clbk) {
this.request({
'method' : 'track',
'id' : id
}, clbk);
}

Deezer.prototype.request = function(options, clbk) {
var self = this;

var method = options.method;
var id = options.id || null;
var params = options.params || {};

var path = self.base + method;
if (id) {
path += '/' + id;
}
if (params) {
path += '?' + querystring.stringify(params);
}

if (method) {
http.get({
'host' : self.host,
'method' : 'GET',
'port' : 80,
'path' : path
}, function(response){
var out = '';
response.on('data', function(chunk){
out += chunk;
});
response.on('end', function(){
clbk(JSON.parse(out));
});
});
}
}

module.exports = Deezer;
66 changes: 66 additions & 0 deletions lib/Songs.js
@@ -0,0 +1,66 @@
var Deezer = require('./Deezer');
var dz = new Deezer();

function shuffle(o){
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}

function getRandomSongFromDB(clbk) {
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('data/FR_charts.sqlite', sqlite3.OPEN_READONLY);

var query = "SELECT * FROM Songs ORDER BY RANDOM() LIMIT 1";
db.get(query, function(err, row){
getTrack(row, clbk);
});
db.close();
}

function getTrack(row, clbk) {
var q = row.artist + ' ' + row.title;
dz.search(q, function(result){

//Track not found
if (!result.data.length) {
getRandomSongFromDB(clbk);
return;
}

if (result.data.length) {
var first = result.data.shift();
var id = first.artist.id;

dz.getRelatedArtist(id, function(related){
var suggestions = [];
suggestions.push({
name: first.artist.name,
answer: true
});

//Not enough suggestions
if (related.data.length < 4) {
getRandomSongFromDB(clbk);
return;
}

for (var i=0;i<3; i++) {
suggestions.push({
name : related.data[i].name,
answer : false
});
}
first.suggestions = shuffle(suggestions);
first.year = row.year;

clbk(first);
});
}
})
}

function getRandomSong(clbk) {
getRandomSongFromDB(clbk);
}

exports.getRandomSong = getRandomSong;

0 comments on commit e15858e

Please sign in to comment.