├── .gitignore ├── Procfile ├── README.md ├── db ├── connection.js └── schema.sql ├── index.js ├── package-lock.json ├── package.json ├── public ├── img │ ├── background.jpeg │ ├── banner.png │ ├── logo │ │ ├── logo.png │ │ ├── logo1080px.png │ │ ├── logo300px.png │ │ ├── logo500px.png │ │ ├── logo700px.png │ │ └── logo900px.png │ ├── profile-placeholder-3.jpg │ └── screenshots │ │ ├── answer.jpg │ │ ├── gamemode.jpg │ │ ├── gameplay.jpg │ │ ├── login.jpg │ │ ├── playlist.jpg │ │ ├── results.jpg │ │ └── signup.jpg ├── js │ ├── game.js │ └── multi_game.js └── stylesheets │ ├── change.css │ ├── game.css │ ├── home.css │ ├── landing.css │ ├── lobby.css │ ├── login.css │ ├── main.css │ ├── multiplayer.css │ ├── playlists.css │ ├── profile.css │ └── signup.css ├── routes ├── play.js └── users.js ├── scripts └── music.js ├── tests ├── testRoutes.js └── testSignup.js └── views ├── pages ├── game.ejs ├── game_mc.ejs ├── game_multi.ejs ├── index.ejs ├── landing.ejs ├── leaderboard.ejs ├── lobby.ejs ├── login.ejs ├── multiplayer.ejs ├── playlists.ejs ├── profile.ejs ├── reset.ejs └── signup.ejs └── partials ├── game_nav.ejs ├── header.ejs └── nav.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | # Node build artifacts 2 | node_modules 3 | npm-debug.log 4 | 5 | # Local development 6 | *.env 7 | *.dev 8 | .DS_Store 9 | 10 | # Docker 11 | Dockerfile 12 | docker-compose.yml 13 | 14 | # todo-list 15 | todo.txt 16 | gameplay.txt 17 | 18 | # demos 19 | demo1.json 20 | demo2.json -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![LOGO](public/img/logo/logo.png) Tuning 2 | 3 | Tuning is a party game, where the players are groups of friends that compete against one another in a fast-paced game of guessing the song artist that is associated with a 30 second music clip that plays. 4 | 5 | Link to website: 6 | https://tuning-game.herokuapp.com/ 7 | 8 | ## Motivation 9 | The aim of Tuning is to provide entertainment and a fun activity for groups of friends to play on weekends, game nights, trivia nights, bars, etc. which gives them an opportunity to socialize and create closer bonds amongst each other, and make new friends. 10 | 11 | ## Main features 12 | 13 | - Single player with 6 different genres to choose from 14 | - Multiplayer rooms via room codes to play with your friends! 15 | - Leaderboards recording the top scores from each genre 16 | 17 | ## Technologies 18 | - [Node](https://nodejs.org/en/) 19 | - [Express](https://expressjs.com/) 20 | - [Spotify API](https://developer.spotify.com/documentation/web-api/) 21 | - [socket.io](https://socket.io/) 22 | - [Postgres](https://www.postgresql.org/) 23 | - [Bootstrap](https://getbootstrap.com/) 24 | - [Heroku](https://www.heroku.com/) 25 | 26 | ## Developers 27 | 28 | - [Moses Lee](https://github.com/MosesLee98) 29 | - [Andy Liu](https://github.com/AndyLiuCodes) 30 | - [Kevin Gandham](https://github.com/kgandham1996) 31 | - [Trevor Grabham](https://github.com/trevorgrabham) 32 | - [Tyler Trinh](https://github.com/bvtrinh) 33 | -------------------------------------------------------------------------------- /db/connection.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const pool = new Pool({ 3 | connectionString: process.env.DATABASE_URL, 4 | //ssl: true 5 | }); 6 | 7 | module.exports = pool; -------------------------------------------------------------------------------- /db/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users 2 | ( 3 | id SERIAL PRIMARY KEY, 4 | username VARCHAR(20), 5 | password TEXT 6 | 7 | ); 8 | 9 | CREATE TABLE songs 10 | ( 11 | artistID varchar, 12 | artistName text, 13 | songID varchar PRIMARY KEY, 14 | songName text, 15 | genre json, 16 | URL text 17 | ); 18 | 19 | CREATE TABLE scores 20 | ( 21 | username varchar, 22 | score INT, 23 | mode varchar, 24 | genre varchar, 25 | dateplayed timestamp 26 | ); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const cookieParser = require('cookie-parser'); 4 | const expressSession = require('express-session'); 5 | const bodyparser = require('body-parser'); 6 | const users = require('./routes/users'); 7 | const gameplay = require('./routes/play'); 8 | const music = require('./scripts/music'); 9 | const http = require('http'); 10 | const pool = require('./db/connection'); 11 | const fs = require('fs'); 12 | const socketIO = require('socket.io'); 13 | const PORT = process.env.PORT || 5000; 14 | 15 | // Load environment variables 16 | // Need this for testing 17 | require('dotenv').config(); 18 | 19 | const app = express(); 20 | 21 | // Configuration settings 22 | app.use(express.static(path.join(__dirname, 'public'))); 23 | app.use(express.json()); 24 | app.use(express.urlencoded({ extended: true })); 25 | app.use(bodyparser.json()); 26 | app.use( 27 | expressSession({ secret: process.env.SESSION_SECRET, saveUninitialized: false, resave: false }) 28 | ); 29 | app.use(cookieParser()); 30 | app.set('views', path.join(__dirname, 'views')); 31 | app.set('view engine', 'ejs'); 32 | 33 | app.get('/', (req, res) => { 34 | if (req.session.username) { 35 | res.redirect('play'); 36 | } else { 37 | res.render('pages/index'); 38 | } 39 | }); 40 | 41 | // Routers 42 | app.use('/users', users); 43 | app.use('/play', gameplay); 44 | 45 | var rooms = []; 46 | 47 | // Join 48 | app.post('/multiplayer/join', (req, res) => { 49 | if (req.session.username) { 50 | var roomCode = req.body.roomCode; 51 | if (roomCode in rooms && !rooms[roomCode].started && rooms[roomCode].pCount < 8) { 52 | res.render('pages/lobby', { username: req.session.username, room: 'join', code: roomCode }); 53 | } else { 54 | res.render('pages/multiplayer', { 55 | username: req.session.username, 56 | errors: [ { msg: 'Invalid code. Please try again' } ], 57 | }); 58 | } 59 | } else { 60 | res.redirect('/users/login'); 61 | } 62 | }); 63 | 64 | app.get('*', function(req, res) { 65 | res 66 | .status(404) 67 | .send('ERROR 404: The page you requested is invalid or is missing, please try something else'); 68 | }); 69 | 70 | console.log('Running ' + process.env.NODE_ENV + ' environment...'); 71 | // if (process.env.NODE_ENV == 'production') { 72 | // console.log('------STARTING SONG DATABASE UPDATE------'); 73 | // music.updateSongDB(); 74 | // setTimeout(function() { 75 | // music.updateSongDBSpecific('2016-08-27'); 76 | // }, 30000); 77 | 78 | // setTimeout(function() { 79 | // music.updateSongDBSpecific('2013-08-27'); 80 | // }, 60000); 81 | 82 | // setTimeout(function() { 83 | // music.updateSongDBSpecific('2010-08-27'); 84 | // }, 90000); 85 | 86 | // setTimeout(function() { 87 | // music.updateSongDBSpecific('2007-08-27'); 88 | // }, 120000); 89 | 90 | // setTimeout(function() { 91 | // music.updateSongDBSpecific('2004-08-27'); 92 | // }, 150000); 93 | 94 | // setInterval(music.alertUpdate, 10 * 24 * 60 * 60 * 1000 - 20); 95 | // setInterval(music.updateSongDB, 10 * 24 * 60 * 60 * 1000); 96 | // } 97 | 98 | // ***** MULTIPLAYER ***** 99 | 100 | //User goes press create room 101 | 102 | // generate alphanumeric lobby code of length 4 103 | // eg: 4GK9, F671, KP2L 104 | function generateCode() { 105 | var code = ''; 106 | // alphanumeric chars 107 | var chars = [ 108 | 'A', 109 | 'B', 110 | 'C', 111 | 'D', 112 | 'E', 113 | 'F', 114 | 'G', 115 | 'H', 116 | 'I', 117 | 'J', 118 | 'K', 119 | 'L', 120 | 'M', 121 | 'N', 122 | 'O', 123 | 'P', 124 | 'Q', 125 | 'R', 126 | 'S', 127 | 'T', 128 | 'U', 129 | 'V', 130 | 'W', 131 | 'X', 132 | 'Y', 133 | 'Z', 134 | '0', 135 | '1', 136 | '2', 137 | '3', 138 | '4', 139 | '5', 140 | '6', 141 | '7', 142 | '8', 143 | '9', 144 | ]; 145 | for (var i = 6; i > 0; --i) { 146 | code += chars[Math.floor(Math.random() * chars.length)]; 147 | } 148 | return code; 149 | } 150 | 151 | var server = app.listen(PORT, () => console.log(`Listening on ${PORT}`)); 152 | 153 | module.exports = app; 154 | 155 | const io = socketIO(server); 156 | 157 | io.on('connection', (socket) => { 158 | console.log('made socket connection'); 159 | var roomID; 160 | var username; 161 | socket.on('create', function() { 162 | let roomCode; 163 | 164 | do { 165 | roomCode = generateCode(); 166 | } while (roomCode in rooms); 167 | 168 | rooms[roomCode] = { 169 | id: roomCode, 170 | players: [], 171 | started: false, 172 | pCount: 0, 173 | genre: 'pop', 174 | ready: [], 175 | answered: [], 176 | songIndex: 0, 177 | scores: {}, 178 | }; 179 | // console.log(roomCode) 180 | // console.log(rooms) 181 | socket.emit('roomcode', roomCode); 182 | }); 183 | 184 | socket.on('join', function(room, user) { 185 | roomID = room; 186 | username = user; 187 | 188 | if (room in rooms && rooms[room].pCount != 8 && rooms[room].started == false) { 189 | socket.join(room); 190 | rooms[room].players.push(user); 191 | rooms[room].pCount += 1; 192 | console.log(rooms[room]); 193 | io.sockets.in(room).emit('userJoin', rooms[room]); 194 | } else { 195 | console.log('no room or has started already'); 196 | } 197 | }); 198 | 199 | socket.on('ready', function(user, code) { 200 | rooms[code].ready.push(user); 201 | //send message to other clients in room to update 202 | io.sockets.in(code).emit('ready', user); 203 | 204 | if (rooms[code].ready.length == rooms[code].players.length) { 205 | let time = 3000; 206 | io.sockets.in(code).emit('messageReceived', 'Game is beginning in', 'Server'); 207 | 208 | let lobbyTimer = setInterval(() => { 209 | if (!(roomID in rooms)) { 210 | clearInterval(lobbyTimer); 211 | } else if (rooms[code].ready.length != rooms[code].players.length) { 212 | console.log(rooms[code]); 213 | io.sockets.in(code).emit('messageReceived', 'Timer stopped', 'Server'); 214 | clearInterval(lobbyTimer); 215 | } 216 | 217 | //change this into a mod something 218 | if (time == 3000) { 219 | io.sockets.in(code).emit('messageReceived', '3', 'Server'); 220 | } else if (time == 2000) { 221 | io.sockets.in(code).emit('messageReceived', '2', 'Server'); 222 | } else if (time == 1000) { 223 | io.sockets.in(code).emit('messageReceived', '1', 'Server'); 224 | } else if (time == 0) { 225 | io.sockets.in(code).emit('messageReceived', 'Go', 'Server'); 226 | music.getRelatedArtists(rooms[code].genre, function(returnVal) { 227 | music.getRelatedSongs(returnVal, function(finalPlaylist) { 228 | io.sockets.in(code).emit('loadPlaylist', finalPlaylist); 229 | rooms[code].started = true; 230 | }); 231 | }); 232 | clearInterval(lobbyTimer); 233 | } 234 | time = time - 100; 235 | }, 100); 236 | } 237 | }); 238 | 239 | socket.on('unready', function(user, code) { 240 | var m = rooms[code].ready.indexOf(user); 241 | 242 | if (m != -1) { 243 | rooms[code].ready.splice(m, 1); 244 | } 245 | //send message to other clients in room to update 246 | io.sockets.in(code).emit('unready', user); 247 | }); 248 | 249 | socket.on('messageSent', function(user, code, msg) { 250 | //send message into chat broadcast 251 | io.sockets.in(code).emit('messageReceived', msg, user); 252 | }); 253 | 254 | socket.on('genre', function(newGenre, code) { 255 | rooms[code].genre = newGenre; 256 | //send message that genre changed to room 257 | io.sockets.in(code).emit('updateGenre', rooms[code]); 258 | }); 259 | 260 | socket.on('answered', function(username, value, start) { 261 | if (!(username in rooms[roomID].scores)) { 262 | rooms[roomID].scores[username] = 0; 263 | io.sockets.in(roomID).emit('updateGameTable', rooms[roomID]); 264 | } else { 265 | rooms[roomID].scores[username] += value; 266 | io.sockets.in(roomID).emit('updateGameTable', rooms[roomID]); 267 | } 268 | 269 | rooms[roomID].answered.push(username); 270 | 271 | console.log(rooms[roomID]); 272 | 273 | if ( 274 | rooms[roomID].answered.length >= rooms[roomID].players.length && 275 | rooms[roomID].started == true 276 | ) { 277 | if (rooms[roomID].songIndex == 5) { 278 | io.sockets.in(roomID).emit('loadResultsPage', rooms[roomID]); 279 | let scores = rooms[roomID].scores; 280 | let genre = rooms[roomID].genre; 281 | Object.keys(scores).forEach(function(user) { 282 | let d = new Date(); 283 | 284 | dformat = 285 | [ d.getFullYear(), d.getMonth() + 1, d.getDate() ].join('-') + 286 | ' ' + 287 | [ d.getHours(), d.getMinutes(), d.getSeconds() ].join(':'); 288 | pool.query( 289 | `insert into scores values ('${user}',${scores[ 290 | user 291 | ]}, 'multiplayer', '${genre}', '${dformat}')` 292 | ); 293 | }); 294 | } else { 295 | var time = 3; 296 | var roundCountdown = setInterval(() => { 297 | if (time == 0 && rooms[roomID].started == true) { 298 | io.sockets.in(roomID).emit('countdown', 0); 299 | io.sockets.in(roomID).emit('loadNextSong', rooms[roomID].songIndex); 300 | rooms[roomID].songIndex += 1; 301 | rooms[roomID].answered = []; 302 | clearInterval(roundCountdown); 303 | } 304 | io.sockets.in(roomID).emit('countdown', time); 305 | time -= 1; 306 | }, 1000); 307 | } 308 | } 309 | }); 310 | 311 | socket.on('playagain', function() { 312 | rooms[roomID].started = false; 313 | rooms[roomID].ready = []; 314 | rooms[roomID].answered = []; 315 | rooms[roomID].songIndex = 0; 316 | rooms[roomID].scores = {}; 317 | socket.emit('again'); 318 | console.log(rooms[roomID]); 319 | }); 320 | 321 | socket.on('disconnect', function() { 322 | if (roomID in rooms) { 323 | //removes user from room 324 | var i = rooms[roomID].players.indexOf(username); 325 | var j = rooms[roomID].ready.indexOf(username); 326 | var l = rooms[roomID].answered.indexOf(username); 327 | 328 | if (i != -1) { 329 | rooms[roomID].players.splice(i, 1); 330 | } 331 | if (j != -1) { 332 | rooms[roomID].ready.splice(j, 1); 333 | } 334 | if (l != -1) { 335 | rooms[roomID].answered.splice(j, 1); 336 | } 337 | 338 | delete rooms[roomID].scores[username]; 339 | rooms[roomID].pCount -= 1; 340 | 341 | if (rooms[roomID].pCount == 0 && rooms[roomID].started == false) { 342 | delete rooms[roomID]; 343 | } 344 | io.sockets.in(roomID).emit('updateGameTable', rooms[roomID]); 345 | 346 | io.sockets.in(roomID).emit('userLeave', rooms[roomID]); 347 | 348 | console.log(rooms); 349 | console.log('user disconnected'); 350 | } 351 | }); 352 | }); 353 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tuning", 3 | "version": "1.0.0", 4 | "description": "A song trivia web app with leaderboards and multiplayer", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js", 8 | "test": "node ./node_modules/mocha/bin/mocha --exit ./tests" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/bvtrinh/tuning.git" 13 | }, 14 | "keywords": [ 15 | "heroku", 16 | "node", 17 | "express", 18 | "bootstrap", 19 | "music", 20 | "spotify-api", 21 | "trivia-game" 22 | ], 23 | "author": "", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/bvtrinh/tuning/issues" 27 | }, 28 | "homepage": "https://github.com/bvtrinh/tuning#readme", 29 | "dependencies": { 30 | "async": "^3.1.0", 31 | "bcrypt": "^5.0.0", 32 | "body-parser": "^1.19.0", 33 | "cookie-parser": "^1.4.4", 34 | "dotenv": "^8.2.0", 35 | "ejs": "^2.7.1", 36 | "express": "^4.17.1", 37 | "express-session": "^1.17.0", 38 | "express-validator": "^6.2.0", 39 | "fs": "0.0.1-security", 40 | "http": "0.0.0", 41 | "jsonfile": "^5.0.0", 42 | "node-spotify-api": "^1.1.1", 43 | "pg": "^7.12.1", 44 | "socket.io": "^2.4.0", 45 | "supertest": "^4.0.2" 46 | }, 47 | "devDependencies": { 48 | "chai": "^4.2.0", 49 | "mocha": "^6.2.2", 50 | "nodemon": "^1.19.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/img/background.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/background.jpeg -------------------------------------------------------------------------------- /public/img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/banner.png -------------------------------------------------------------------------------- /public/img/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo.png -------------------------------------------------------------------------------- /public/img/logo/logo1080px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo1080px.png -------------------------------------------------------------------------------- /public/img/logo/logo300px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo300px.png -------------------------------------------------------------------------------- /public/img/logo/logo500px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo500px.png -------------------------------------------------------------------------------- /public/img/logo/logo700px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo700px.png -------------------------------------------------------------------------------- /public/img/logo/logo900px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/logo/logo900px.png -------------------------------------------------------------------------------- /public/img/profile-placeholder-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/profile-placeholder-3.jpg -------------------------------------------------------------------------------- /public/img/screenshots/answer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/answer.jpg -------------------------------------------------------------------------------- /public/img/screenshots/gamemode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/gamemode.jpg -------------------------------------------------------------------------------- /public/img/screenshots/gameplay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/gameplay.jpg -------------------------------------------------------------------------------- /public/img/screenshots/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/login.jpg -------------------------------------------------------------------------------- /public/img/screenshots/playlist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/playlist.jpg -------------------------------------------------------------------------------- /public/img/screenshots/results.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/results.jpg -------------------------------------------------------------------------------- /public/img/screenshots/signup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvtrinh/tuning/df47ec139704b0d7c4f024477adfdc289b7c0979/public/img/screenshots/signup.jpg -------------------------------------------------------------------------------- /public/js/game.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // Set the initial volume 3 | $('#audio-playback').prop('volume', 0.1); 4 | $('#score').html('Score: 0'); 5 | 6 | // Should hide this div with css 7 | $('#result-btns').hide(); 8 | 9 | // Get the playlist object from the server 10 | $.ajax({ 11 | url: '/play/get_playlist', 12 | method: 'POST', 13 | dataType: 'JSON', 14 | success: function(data) { 15 | gameplay(data, score); 16 | }, 17 | error: function(err) { 18 | console.log(err); 19 | }, 20 | }); 21 | 22 | // Functionality of gameplay 23 | function gameplay(playlist) { 24 | var num_songs = playlist.length; 25 | var curr_song = 0; 26 | 27 | // Disable the button so user's don't accidentally submit before song is loaded 28 | $('.btn').attr('disabled', true); 29 | 30 | // Countdown between next songs, update the song url and what current song the user is on 31 | // Display the multiple choice answers in the buttons 32 | countdown_update(playlist[curr_song], curr_song, num_songs); 33 | 34 | // The case where the user doesn't submit any answers before the preview ends 35 | $('#audio-playback').on('ended', function() { 36 | chg_btn_color(get_btn_i(playlist[curr_song].songname), 'green'); 37 | // Hide the alert after a 3s 38 | $('.alert').fadeTo(3000, 500).slideUp(500, function() { 39 | $('.alert').slideUp(500); 40 | }); 41 | 42 | // Go to the next song in the playlist 43 | curr_song++; 44 | next_song(playlist, curr_song, num_songs); 45 | }); 46 | 47 | // A user submits an answer, the answer is marked and the user is alerted accordingly 48 | $('.btn').click(function() { 49 | var guess = $(this).html(); 50 | var guess_id = $(this).attr('id').slice(3, 4); 51 | $('#audio-playback').trigger('pause'); 52 | 53 | // Validate the guess against the correct answer using the innerHTML of the buttons 54 | mark_guess(guess, playlist[curr_song].songname, guess_id); 55 | 56 | // Go to the next song in the playlist 57 | curr_song++; 58 | next_song(playlist, curr_song, num_songs); 59 | }); 60 | 61 | // Update the progress bar visually to match the current time of the song 62 | $('#audio-playback').on('playing', function() { 63 | percent = 100; 64 | var progress = setInterval(function() { 65 | var audio = document.getElementById('audio-playback'); 66 | var duration = audio.duration; 67 | var curr_time = audio.currentTime; 68 | percent = (duration - curr_time) / duration * 100; 69 | 70 | $('#progressbar').css('width', percent + '%').attr('aria-valuenow', percent); 71 | 72 | if (curr_time > duration || audio.paused) { 73 | $('#progressbar').css('width', 100 + '%').attr('aria-valuenow', 100); 74 | clearInterval(progress); 75 | } 76 | }, 100); 77 | }); 78 | } 79 | 80 | // Displays a countdown timer in between songs 81 | function countdown_update(song, curr_song, num_songs) { 82 | // Number of seconds in between songs 83 | var time2play = 3; 84 | var countdown = setInterval(function() { 85 | $('#countdown').fadeIn(500); 86 | $('#countdown').html(time2play); 87 | time2play--; 88 | 89 | if (time2play < 0) { 90 | clearInterval(countdown); 91 | $('#countdown').html('GO!'); 92 | $('#countdown').delay(1000).fadeOut(500); 93 | $('#audio-playback').trigger('load'); 94 | $('#audio-playback').trigger('play'); 95 | $('.btn').attr('disabled', false); 96 | $('#countdown').html(' '); 97 | update_song_view(song, curr_song, num_songs); 98 | } 99 | }, 1000); 100 | } 101 | 102 | function shuffle(arr) { 103 | const len = arr.length; 104 | var temp; 105 | var rand_int; 106 | for (var i = 0; i < len; i++) { 107 | rand_int = Math.floor(Math.random() * (len - i)) + i; 108 | temp = arr[i]; 109 | arr[i] = arr[rand_int]; 110 | arr[rand_int] = temp; 111 | } 112 | return arr; 113 | } 114 | 115 | // Updates the song url and current song number 116 | function update_song_view(song, i, num_songs) { 117 | $('#song_counter').html(parseFloat(i + 1) + '/' + num_songs); 118 | $('#song-playback').attr('src', song.url); 119 | var btn_nums = [ 0, 1, 2, 3 ]; 120 | btn_nums = shuffle(btn_nums); 121 | var correct_btn = btn_nums.pop(); 122 | $('#btn' + correct_btn).html(song.songname); 123 | $('#btn' + correct_btn).css('background', '#23272b'); 124 | var id_num; 125 | 126 | for (var i = 0; i < 3; i++) { 127 | id_num = btn_nums.pop(); 128 | $('#btn' + id_num).html(song.related_songs[i]); 129 | $('#btn' + id_num).css('background', '#23272b'); 130 | } 131 | } 132 | 133 | // Load the next song in the playlist 134 | function next_song(playlist, curr_song, num_songs) { 135 | // Disable buttons to prevent accidental submissions 136 | $('.btn').attr('disabled', true); 137 | 138 | // Check if the last song in the playlist has played 139 | if (is_finished(curr_song, num_songs)) { 140 | var score = $('#score').html(); 141 | score = parseFloat(score.slice(6, score.length)); 142 | // Show the final score and button to redirect to other pages 143 | upload_score(score); 144 | show_results(); 145 | show_playlist(playlist); 146 | } else { 147 | // Songs still remaining 148 | countdown_update(playlist[curr_song], curr_song, num_songs); 149 | } 150 | } 151 | 152 | function chg_btn_color(id, color) { 153 | $('#btn' + id).css('background', color); 154 | } 155 | 156 | function get_btn_i(song) { 157 | var val; 158 | song = song.replace(''', "'"); 159 | for (var i = 0; i < 4; i++) { 160 | val = $('#btn' + i).html(); 161 | if (val == song) { 162 | return i; 163 | } 164 | } 165 | } 166 | 167 | // Check if the user has guessed correctly 168 | function mark_guess(song_guess, correct_song, guess_id) { 169 | // There are escaped HTML characters in the string for "'" 170 | correct_song = correct_song.replace(''', "'"); 171 | 172 | if (song_guess == correct_song) { 173 | $('#progressbar').css('width', 100 + '%').attr('aria-valuenow', 100); 174 | var score = $('#score').html(); 175 | score = parseFloat(score.slice(6, score.length)); 176 | score = score + parseInt(1000 * (percent / 100)); 177 | $('#score').html('Score: ' + score); 178 | } else { 179 | chg_btn_color(guess_id, 'red'); 180 | } 181 | 182 | chg_btn_color(get_btn_i(correct_song), 'green'); 183 | // This is hides the alert after a set amount of time 184 | $('.alert').fadeTo(3000, 500).slideUp(500, function() { 185 | $('.alert').slideUp(500); 186 | }); 187 | } 188 | 189 | // Check if its the last song in the playlist 190 | function is_finished(curr_song, num_songs) { 191 | return curr_song >= num_songs ? true : false; 192 | } 193 | 194 | // Hide the input and progress divs and show the redirect buttons 195 | function show_results() { 196 | $('#mc_btns').hide(); 197 | $('#progress-box').hide(); 198 | $('#result-btns').show(); 199 | $('#page-title').html('Results').hide().fadeIn(500); 200 | } 201 | 202 | // Show the playlist after the game is finished 203 | function show_playlist(playlist) { 204 | var list = ''; 205 | playlist.forEach(function(song) { 206 | list += '
  • ' + song.songname + '
  • '; 207 | }); 208 | $('#songlist').append(list); 209 | } 210 | 211 | function upload_score(score) { 212 | $.ajax({ 213 | url: '/users/upscore', 214 | method: 'POST', 215 | data: { userScore: score }, 216 | dataType: 'application/json', 217 | success: function() { 218 | console.log('success'); 219 | }, 220 | error: function(err) { 221 | console.log(err); 222 | }, 223 | }); 224 | } 225 | }); 226 | -------------------------------------------------------------------------------- /public/js/multi_game.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | //updates the room info when user joins 3 | socket.on('userJoin', function(room) { 4 | let players = room.players; 5 | let pCount = room.pCount; 6 | let roomGenre = room.genre; 7 | 8 | $('#roomCode').html(`Room code:
    ${roomCode}`); 9 | 10 | $('#players').empty(); 11 | for (let i = 0; i < players.length; i++) { 12 | updatePlayerTable(players[i], room); 13 | } 14 | 15 | $('.numPlayers').html(`${pCount}/8 Players`); 16 | 17 | $('.genre').val(roomGenre); 18 | }); 19 | 20 | //updates the room info when user leaves 21 | socket.on('userLeave', function(room) { 22 | let players = room.players; 23 | let pCount = room.pCount; 24 | let roomGenre = room.genre; 25 | $('#players').empty(); 26 | for (let i = 0; i < players.length; i++) { 27 | updatePlayerTable(players[i], room); 28 | } 29 | 30 | $('.numPlayers').html(`${pCount}/8 Players`); 31 | 32 | $('.genre').val(roomGenre); 33 | }); 34 | 35 | function updatePlayerTable(username, room) { 36 | let state = 'Unready'; 37 | if (room.ready.includes(username)) { 38 | state = 'Ready'; 39 | } 40 | $('#players').append( 41 | `${username}${state}` 42 | ); 43 | } 44 | 45 | $(document).ready(function() { 46 | $('#readyState').click(function() { 47 | if ($(`.${user} > .state`).html() == 'Unready') { 48 | socket.emit('ready', user, roomCode); 49 | } else { 50 | socket.emit('unready', user, roomCode); 51 | } 52 | }); 53 | }); 54 | 55 | socket.on('ready', function(user) { 56 | $(`.${user} > .state`).html('Ready'); 57 | }); 58 | 59 | socket.on('unready', function(user) { 60 | $(`.${user} > .state`).html('Unready'); 61 | }); 62 | 63 | $(function() { 64 | $('#genre').on('change', function() { 65 | let newGenre = $('option:selected').val(); 66 | socket.emit('genre', newGenre, roomCode); 67 | }); 68 | }); 69 | 70 | socket.on('updateGenre', function(room) { 71 | let roomGenre = room.genre; 72 | $('.genre').val(roomGenre); 73 | }); 74 | 75 | $(document).ready(function() { 76 | $('.messageSend').click(function() { 77 | if ($('.messageInput').val().trim() != '') { 78 | // if statement to prevent "empty" messages from sending 79 | var msg = $('.messageInput').val(); 80 | 81 | // auto scrolling chat box 82 | var height = 0; 83 | $('.history p').each(function(i, value) { 84 | height += parseInt($(this).height()); 85 | }); 86 | height += ''; 87 | $('.history').animate({ scrollTop: height }); 88 | 89 | socket.emit('messageSent', user, roomCode, msg); 90 | $('.messageInput').val(''); 91 | } 92 | }); 93 | }); 94 | 95 | // func to submit message on enter keypress 96 | $(document).ready(function() { 97 | $('.messageInput').keypress(function(e) { 98 | if (e.which == 13 && $('.messageInput').val().trim() != '') { 99 | var msg = $('.messageInput').val(); 100 | 101 | // auto scrolling chat box 102 | var height = 0; 103 | $('.history p').each(function(i, value) { 104 | height += parseInt($(this).height()); 105 | }); 106 | height += ''; 107 | $('.history').animate({ scrollTop: height }); 108 | 109 | socket.emit('messageSent', user, roomCode, msg); 110 | $('.messageInput').val(''); 111 | 112 | e.preventDefault(); 113 | } 114 | }); 115 | }); 116 | 117 | socket.on('messageReceived', function(msg, user) { 118 | $('.history').append($(`

    ${user}: ${msg}

    `)); 119 | }); 120 | 121 | socket.on('loadPlaylist', function(loadPlaylist) { 122 | playlist = loadPlaylist; 123 | $('#lobby').hide(); 124 | $('#results').hide(); 125 | $('#game').show(); 126 | 127 | // Disable the button so user's don't accidentally submit before song is loaded 128 | $('.mc').attr('disabled', true); 129 | 130 | socket.emit('answered', user, 0); 131 | }); 132 | 133 | socket.on('countdown', function(time) { 134 | //hide buttons 135 | $(document).ready(function() { 136 | $('#audio-playback').trigger('pause'); 137 | }); 138 | if (time == 0) { 139 | $('#countdown').html('GO!'); 140 | $('#countdown').delay(1000).fadeOut(500); 141 | // Set the initial volume 142 | $('#audio-playback').prop('volume', 0.1); 143 | $('#audio-playback').trigger('load'); 144 | $('#audio-playback').trigger('play'); 145 | $('.mc').attr('disabled', false); 146 | } else { 147 | //display countdown 148 | $('#countdown').fadeIn(500); 149 | $('#countdown').html(time); 150 | } 151 | }); 152 | 153 | socket.on('loadNextSong', function(index) { 154 | currSong = playlist[index]; 155 | update_song_view(currSong, index); 156 | }); 157 | 158 | // Check if the user has guessed correctly 159 | function mark_guess(song_guess, correct_song, guess_id) { 160 | // There are escaped HTML characters in the string for "'" 161 | correct_song = correct_song.replace(''', "'"); 162 | 163 | if (song_guess == correct_song) { 164 | $('#progressbar').css('width', 100 + '%').attr('aria-valuenow', 100); 165 | let score = parseInt(1000 * (percent / 100)); 166 | socket.emit('answered', user, score); 167 | } else { 168 | chg_btn_color(guess_id, 'red'); 169 | socket.emit('answered', user, 0); 170 | } 171 | 172 | chg_btn_color(get_btn_i(correct_song), 'green'); 173 | // This is hides the alert after a set amount of time 174 | $('.alert').fadeTo(3000, 500).slideUp(500, function() { 175 | $('.alert').slideUp(500); 176 | }); 177 | 178 | $('.mc').attr('disabled', true); 179 | } 180 | 181 | function chg_btn_color(id, color) { 182 | $('#btn' + id).css('background', color); 183 | } 184 | 185 | function get_btn_i(song) { 186 | var val; 187 | song = song.replace(''', "'"); 188 | for (var i = 0; i < 4; i++) { 189 | val = $('#btn' + i).html(); 190 | if (val == song) { 191 | return i; 192 | } 193 | } 194 | } 195 | 196 | socket.on('updateGameTable', function(room) { 197 | var answered = room.scores; //{username -> score} 198 | // https://stackoverflow.com/questions/25500316/sort-a-dictionary-by-value-in-javascript 199 | // Create array from dictionary 200 | var items = Object.keys(answered).map(function(key) { 201 | return [ key, answered[key] ]; 202 | }); 203 | 204 | // Sort the array based on the second element 205 | items.sort(function(first, second) { 206 | return second[1] - first[1]; 207 | }); 208 | 209 | // Clear the body of the table 210 | $('#scoreboard > tbody').empty(); 211 | 212 | // Instead reformat table here 213 | items.forEach(function(item) { 214 | var row = ` ${item[0]} ${item[1]} `; 215 | $('#scoreboard > tbody').append(row); 216 | }); 217 | }); 218 | 219 | $(document).ready(function() { 220 | $('#audio-playback').on('playing', function() { 221 | percent = 100; 222 | var progress = setInterval(function() { 223 | var audio = document.getElementById('audio-playback'); 224 | var duration = audio.duration; 225 | var curr_time = audio.currentTime; 226 | percent = (duration - curr_time) / duration * 100; 227 | 228 | $('#progressbar').css('width', percent + '%').attr('aria-valuenow', percent); 229 | 230 | if (curr_time > duration || audio.paused) { 231 | $('#progressbar').css('width', 100 + '%').attr('aria-valuenow', 100); 232 | clearInterval(progress); 233 | } 234 | }, 100); 235 | }); 236 | }); 237 | $(document).ready(function() { 238 | $('#audio-playback').on('ended', function() { 239 | chg_btn_color(get_btn_i(currSong.songname), 'green'); 240 | // Hide the alert after a 3s 241 | $('.alert').fadeTo(3000, 500).slideUp(500, function() { 242 | $('.alert').slideUp(500); 243 | }); 244 | 245 | // Go to the next song in the playlist 246 | socket.emit('answered', user, 0); 247 | }); 248 | }); 249 | 250 | $(document).ready(function() { 251 | $('.mc').click(function() { 252 | var guess = $(this).html(); 253 | var guess_id = $(this).attr('id').slice(3, 4); 254 | //$("#audio-playback").trigger("pause"); 255 | 256 | // Validate the guess against the correct answer using the innerHTML of the buttons 257 | mark_guess(guess, currSong.songname, guess_id); 258 | }); 259 | }); 260 | 261 | socket.on('loadResultsPage', function(room) { 262 | $(document).ready(function() { 263 | $('#audio-playback').trigger('pause'); 264 | }); 265 | 266 | room.players.forEach(function(user) { 267 | $(`.${user} > .state`).html('Unready'); 268 | }); 269 | 270 | //POSTS PLAYERLIST AT END 271 | var answered = room.scores; //{username -> score} 272 | // https://stackoverflow.com/questions/25500316/sort-a-dictionary-by-value-in-javascript 273 | // Create array from dictionary 274 | var items = Object.keys(answered).map(function(key) { 275 | return [ key, answered[key] ]; 276 | }); 277 | 278 | // Sort the array based on the second element 279 | items.sort(function(first, second) { 280 | return second[1] - first[1]; 281 | }); 282 | 283 | // Clear the body of the table 284 | $('#resultsPlayers').empty(); 285 | 286 | // Instead reformat table here 287 | items.forEach(function(item) { 288 | var row = ` ${item[0]} ${item[1]} `; 289 | $('#resultsPlayers').append(row); 290 | }); 291 | 292 | let songNum = 1; 293 | $('#resultsSongs').empty(); 294 | 295 | playlist.forEach(function(song) { 296 | var row = ` ${songNum} ${song.songname} ${song.artistname} `; 297 | $('#resultsSongs').append(row); 298 | songNum += 1; 299 | }); 300 | 301 | $('#game').hide(); 302 | $('#results').show(); 303 | }); 304 | 305 | $(document).ready(function() { 306 | $('#playagain').click(function() { 307 | socket.emit('playagain'); 308 | }); 309 | }); 310 | 311 | socket.on('again', function() { 312 | $('#results').hide(); 313 | $('#lobby').show(); 314 | }); 315 | 316 | function update_song_view(song, i) { 317 | $('#song_counter').html(parseFloat(i + 1) + '/5'); 318 | $('#song-playback').attr('src', song.url); 319 | var btn_nums = [ 0, 1, 2, 3 ]; 320 | btn_nums = shuffle(btn_nums); 321 | var correct_btn = btn_nums.pop(); 322 | $('#btn' + correct_btn).html(song.songname); 323 | $('#btn' + correct_btn).css('background', '#23272b'); 324 | var id_num; 325 | 326 | for (var i = 0; i < 3; i++) { 327 | id_num = btn_nums.pop(); 328 | $('#btn' + id_num).html(song.related_songs[i]); 329 | $('#btn' + id_num).css('background', '#23272b'); 330 | } 331 | } 332 | 333 | function shuffle(arr) { 334 | const len = arr.length; 335 | var temp; 336 | var rand_int; 337 | for (var i = 0; i < len; i++) { 338 | rand_int = Math.floor(Math.random() * (len - i)) + i; 339 | temp = arr[i]; 340 | arr[i] = arr[rand_int]; 341 | arr[rand_int] = temp; 342 | } 343 | return arr; 344 | } 345 | }); 346 | -------------------------------------------------------------------------------- /public/stylesheets/change.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 3 | -webkit-background-size: cover; 4 | -moz-background-size: cover; 5 | background-size: cover; 6 | -o-background-size: cover; 7 | } 8 | 9 | .arvo { 10 | font-family: 'Arvo', serif; 11 | } -------------------------------------------------------------------------------- /public/stylesheets/game.css: -------------------------------------------------------------------------------- 1 | /* Set a background image by replacing the URL below */ 2 | body { 3 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 4 | -webkit-background-size: cover; 5 | -moz-background-size: cover; 6 | background-size: cover; 7 | -o-background-size: cover; 8 | } 9 | 10 | .results-btns { 11 | display: none; 12 | } 13 | 14 | .container { 15 | background: white; 16 | padding-top: 15px; 17 | padding-bottom: 15px; 18 | margin-top: 30px; 19 | margin-bottom: 10px; 20 | border-radius: 10px; 21 | } 22 | 23 | .arvo { 24 | font-family: 'Arvo', serif; 25 | } 26 | 27 | .title{ 28 | text-align: center; 29 | font-size: 70px; 30 | position: relative; 31 | top: 4%; 32 | color: black; 33 | text-shadow: 34 | -1px -1px 0 #000, 35 | 1px -1px 0 #000, 36 | -1px 1px 0 #000, 37 | 1px 1px 0 #000; 38 | } 39 | 40 | .mc { 41 | padding: 5rem 7rem; 42 | width: 100%; 43 | height: 90%; 44 | margin: 3px; 45 | } 46 | 47 | .center-menu { 48 | float: none; 49 | position: absolute; 50 | top: 50%; 51 | left: 50%; 52 | transform: translate(-50%, -50%); 53 | } 54 | 55 | .flip{ 56 | -webkit-transform: rotate(-180deg); 57 | } -------------------------------------------------------------------------------- /public/stylesheets/home.css: -------------------------------------------------------------------------------- 1 | .jumbotron { 2 | background: #364167; 3 | color: white; 4 | } 5 | 6 | h2.index-cap { 7 | font-family: 'Quicksand', sans-serif; 8 | } 9 | 10 | h1.title { 11 | font-size: 6rem; 12 | } 13 | 14 | .arvo { 15 | font-family: 'Arvo', serif; 16 | } 17 | 18 | .main-info { 19 | background: white; 20 | } 21 | 22 | .dev-profile { 23 | border-radius: 50%; 24 | vertical-align: middle; 25 | } 26 | 27 | /* Set a background image by replacing the URL below */ 28 | body { 29 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 30 | -webkit-background-size: cover; 31 | -moz-background-size: cover; 32 | background-size: cover; 33 | -o-background-size: cover; 34 | } 35 | 36 | #mainContent{ 37 | padding: 0 50px 50px 50px; 38 | } 39 | 40 | .row{ 41 | margin-top: 9%; 42 | } 43 | 44 | .container{ 45 | padding: 0px; 46 | border-top-left-radius: 5px; 47 | border-top-right-radius: 5px; 48 | } 49 | 50 | .auto-align{ 51 | margin-top: auto; 52 | margin-bottom: auto; 53 | } 54 | 55 | hr{ 56 | margin: 100px 0; 57 | } 58 | 59 | .dev{ 60 | margin-top:2% !important; 61 | } 62 | 63 | .carousel{ 64 | border-radius: 4px 4px 0 0; 65 | overflow: hidden; 66 | } 67 | 68 | #bannerHr{ 69 | border-color:grey; 70 | margin: 50px 0; 71 | } 72 | 73 | .list-group{ 74 | margin-top: 20px; 75 | } -------------------------------------------------------------------------------- /public/stylesheets/landing.css: -------------------------------------------------------------------------------- 1 | 2 | html, body{ 3 | height: 100%; 4 | } 5 | 6 | /* Set a background image by replacing the URL below */ 7 | body { 8 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 9 | -webkit-background-size: cover; 10 | -moz-background-size: cover; 11 | background-size: cover; 12 | -o-background-size: cover; 13 | } 14 | 15 | .nav-item{ 16 | padding-right: 15px; 17 | } 18 | 19 | .content{ 20 | height: 80%; 21 | width: 80%; 22 | margin: 0 auto; 23 | position: relative; 24 | top: 5%; 25 | border-radius: 10px; 26 | min-height: 300px; 27 | 28 | } 29 | 30 | .arvo { 31 | font-family: 'Arvo', serif; 32 | } 33 | 34 | .title{ 35 | text-align: center; 36 | font-size: 70px; 37 | position: relative; 38 | top: 4%; 39 | color: white; 40 | text-shadow: 41 | -1px -1px 0 #000, 42 | 1px -1px 0 #000, 43 | -1px 1px 0 #000, 44 | 1px 1px 0 #000; 45 | } 46 | 47 | .game{ 48 | height: 100%; 49 | } 50 | .container{ 51 | height: 80%; 52 | width: 90%; 53 | position: relative; 54 | top: 10%; 55 | } 56 | 57 | .gameOpt{ 58 | background: darkgrey; 59 | width: 50%; 60 | height: 40%; 61 | } 62 | 63 | .row{ 64 | margin-top: 6%; 65 | text-align: center; 66 | } 67 | 68 | .btn { 69 | background-color: #364167; 70 | font-size: 2.5vw; 71 | text-shadow: 72 | -1px -1px 0 #000, 73 | 1px -1px 0 #000, 74 | -1px 1px 0 #000, 75 | 1px 1px 0 #000; 76 | border-color: white; 77 | width: 100%; 78 | margin-bottom: 6%; 79 | align-content: center; 80 | } -------------------------------------------------------------------------------- /public/stylesheets/lobby.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height: 100%; 3 | } 4 | 5 | /* Set a background image by replacing the URL below */ 6 | body { 7 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 8 | -webkit-background-size: cover; 9 | -moz-background-size: cover; 10 | background-size: cover; 11 | -o-background-size: cover; 12 | } 13 | 14 | .content{ 15 | height: 80%; 16 | width: 90%; 17 | margin: 0 auto; 18 | position: relative; 19 | top: 5%; 20 | border-radius: 10px; 21 | min-height: 300px; 22 | 23 | } 24 | 25 | .arvo { 26 | font-family: 'Arvo', serif; 27 | } 28 | 29 | .display-3 { 30 | color: white; 31 | text-shadow: 32 | -1px -1px 0 #000, 33 | 1px -1px 0 #000, 34 | -1px 1px 0 #000, 35 | 1px 1px 0 #000; 36 | border-color: white; 37 | } 38 | 39 | .actions{ 40 | height: 100%; 41 | } 42 | 43 | .container{ 44 | height: 80%; 45 | width: 100%; 46 | position: relative; 47 | } 48 | 49 | .row { 50 | margin-top: 4%; 51 | text-align: center; 52 | } 53 | 54 | .subRow { 55 | margin-top: 180%; 56 | } 57 | 58 | .actionBtns { 59 | background-color: #364167; 60 | /* text-shadow: 61 | -1px -1px 0 #000, 62 | 1px -1px 0 #000, 63 | -1px 1px 0 #000, 64 | 1px 1px 0 #000; */ 65 | border-color: white; 66 | margin-bottom: 2%; 67 | width: 97%; 68 | } 69 | 70 | .numPlayers { 71 | background-color: #364167; 72 | /* text-shadow: 73 | -1px -1px 0 #000, 74 | 1px -1px 0 #000, 75 | -1px 1px 0 #000, 76 | 1px 1px 0 #000; */ 77 | border-color: white; 78 | color: #eee; 79 | } 80 | 81 | .infoBoxes { 82 | background-color: #364167; 83 | border-color: white; 84 | color: #eee; 85 | } 86 | 87 | .chatBox { 88 | width: 100%; 89 | background-color: #eee; 90 | border: solid 1px #f3f3f3; 91 | 92 | } 93 | 94 | /* form { 95 | background: #eee; 96 | padding: 3px; 97 | bottom: 0; 98 | border-color: #000; 99 | border-top-style: solid; 100 | border-top-width: 1px; 101 | } */ 102 | 103 | .chatInput input { 104 | border-style: solid; 105 | border-width: 1px; 106 | padding: 10px; 107 | width: 85%; 108 | margin-right: .5%; 109 | } 110 | 111 | .chatInput button { 112 | width: 9%; 113 | background: #364167; 114 | border: none; 115 | padding: 1%; 116 | margin-left: 0.5%; 117 | color: #eee; 118 | } 119 | 120 | #messages { 121 | list-style-type: none; 122 | margin: 0; 123 | padding: 0; 124 | height: auto; 125 | max-height: 300px; 126 | min-height: 200px; 127 | overflow: auto; 128 | } 129 | 130 | .history{ 131 | width: 100%; 132 | height: 200px; 133 | overflow-y: auto; 134 | text-align: justify; 135 | padding-left: 10px; 136 | } 137 | 138 | .results-btns { 139 | display: none; 140 | } 141 | 142 | #game { 143 | display: none; 144 | } 145 | 146 | .mc { 147 | padding: 5rem 7rem; 148 | width: 100%; 149 | height: 90%; 150 | margin: 3px; 151 | } 152 | 153 | .gameplay { 154 | background: white; 155 | padding-bottom: 15px; 156 | margin-top: 30px; 157 | margin-bottom: 10px; 158 | border-radius: 10px; 159 | position: unset; 160 | } 161 | 162 | .game-title { 163 | color: black; 164 | } 165 | 166 | #roomCode { 167 | height: 20vh; 168 | padding-top: 7vh; 169 | } 170 | 171 | .results { 172 | background: white; 173 | padding-bottom: 15px; 174 | margin-top: 30px; 175 | margin-bottom: 10px; 176 | border-radius: 10px; 177 | position: unset; 178 | } 179 | 180 | .resultsTitle { 181 | color: #364167; 182 | } 183 | 184 | #results{ 185 | display: none; 186 | } -------------------------------------------------------------------------------- /public/stylesheets/login.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | /* Set a background image by replacing the URL below */ 7 | body { 8 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') 9 | no-repeat center center fixed; 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | background-size: cover; 13 | -o-background-size: cover; 14 | } 15 | 16 | .card { 17 | margin: 0; 18 | top: 50%; 19 | -ms-transform: translateY(-50%); 20 | transform: translateY(-50%); 21 | } 22 | 23 | a { 24 | font-size: 70%; 25 | opacity: 0.4; 26 | color: black; 27 | text-decoration: underline; 28 | } 29 | -------------------------------------------------------------------------------- /public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | .jumbotron { 2 | background: #532F8C; 3 | color: white; 4 | padding-bottom: 80px; } 5 | .jumbotron .btn-primary { 6 | background: #845ac7; 7 | border-color: #845ac7; } 8 | .jumbotron .btn-primary:hover { 9 | background: #7646c1; } 10 | .jumbotron p { 11 | color: #d9ccee; 12 | max-width: 75%; 13 | margin: 1em auto 2em; } 14 | .navbar + .jumbotron { 15 | margin-top: -20px; } 16 | .jumbotron .lang-logo { 17 | display: block; 18 | background: #B01302; 19 | border-radius: 50%; 20 | overflow: hidden; 21 | width: 100px; 22 | height: 100px; 23 | margin: auto; 24 | border: 2px solid white; } 25 | .jumbotron .lang-logo img { 26 | max-width: 100%; } 27 | 28 | -------------------------------------------------------------------------------- /public/stylesheets/multiplayer.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | /* Set a background image by replacing the URL below */ 7 | body { 8 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') 9 | no-repeat center center fixed; 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | background-size: cover; 13 | -o-background-size: cover; 14 | } 15 | 16 | .content { 17 | height: 80%; 18 | width: 80%; 19 | margin: 0 auto; 20 | position: relative; 21 | top: 5%; 22 | border-radius: 10px; 23 | min-height: 300px; 24 | } 25 | 26 | .col-centered { 27 | margin: 0 auto; 28 | float: none; 29 | } 30 | 31 | .arvo { 32 | font-family: 'Arvo', serif; 33 | } 34 | 35 | .title { 36 | text-align: center; 37 | font-size: 6vw; 38 | position: relative; 39 | top: 4%; 40 | color: white; 41 | text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; 42 | } 43 | 44 | .actions { 45 | height: 100%; 46 | } 47 | 48 | .container { 49 | height: 80%; 50 | width: 90%; 51 | position: relative; 52 | top: 10%; 53 | } 54 | 55 | .row { 56 | margin-top: 6%; 57 | text-align: center; 58 | margin-bottom: 10%; 59 | } 60 | 61 | .actionBtns { 62 | background-color: #364167; 63 | font-size: 4vw; 64 | height: 175%; 65 | text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; 66 | border-color: white; 67 | width: 100%; 68 | margin-bottom: 6%; 69 | } 70 | -------------------------------------------------------------------------------- /public/stylesheets/playlists.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height: 100%; 3 | } 4 | 5 | /* Set a background image by replacing the URL below */ 6 | body { 7 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') no-repeat center center fixed; 8 | -webkit-background-size: cover; 9 | -moz-background-size: cover; 10 | background-size: cover; 11 | -o-background-size: cover; 12 | } 13 | 14 | .content{ 15 | height: 80%; 16 | width: 80%; 17 | margin: 0 auto; 18 | position: relative; 19 | top: 5%; 20 | border-radius: 10px; 21 | min-height: 300px; 22 | 23 | } 24 | 25 | .arvo { 26 | font-family: 'Arvo', serif; 27 | } 28 | 29 | .title{ 30 | text-align: center; 31 | font-size: 70px; 32 | position: relative; 33 | top: 4%; 34 | color: white; 35 | text-shadow: 36 | -1px -1px 0 #000, 37 | 1px -1px 0 #000, 38 | -1px 1px 0 #000, 39 | 1px 1px 0 #000; 40 | } 41 | 42 | .playlists{ 43 | height: 100%; 44 | } 45 | 46 | .container{ 47 | height: 80%; 48 | width: 90%; 49 | position: relative; 50 | top: 10%; 51 | } 52 | 53 | .row { 54 | margin-top: 6%; 55 | text-align: center; 56 | margin-bottom: 10%; 57 | } 58 | 59 | .playlistBtns { 60 | background-color: #364167; 61 | font-size: 4vw; 62 | height: 175%; 63 | text-shadow: 64 | -1px -1px 0 #000, 65 | 1px -1px 0 #000, 66 | -1px 1px 0 #000, 67 | 1px 1px 0 #000; 68 | border-color: white; 69 | width: 100%; 70 | margin-bottom: 6%; 71 | } -------------------------------------------------------------------------------- /public/stylesheets/profile.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | /* Set a background image by replacing the URL below */ 7 | body { 8 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') 9 | no-repeat center center fixed; 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | background-size: cover; 13 | -o-background-size: cover; 14 | } 15 | 16 | .nav-item { 17 | padding-right: 15px; 18 | } 19 | 20 | .content { 21 | background: white; 22 | height: 80%; 23 | width: 80%; 24 | margin: 0 auto; 25 | position: relative; 26 | top: 5%; 27 | border: 2px solid darkgrey; 28 | border-radius: 10px; 29 | min-height: 300px; 30 | display: block; 31 | overflow-y: auto; 32 | } 33 | 34 | .arvo { 35 | font-family: 'Arvo', serif; 36 | } 37 | 38 | .scoretitle { 39 | font-family: 'Arvo', serif; 40 | font-size: 2vw; 41 | text-align: center; 42 | padding: 10px; 43 | } 44 | 45 | .title { 46 | text-align: center; 47 | font-size: 6vw; 48 | position: relative; 49 | top: 5%; 50 | } 51 | 52 | .game { 53 | height: 100%; 54 | } 55 | .container { 56 | height: 60%; 57 | width: 60%; 58 | position: relative; 59 | top: 15%; 60 | } 61 | 62 | .gameOpt { 63 | background: darkgrey; 64 | width: 50%; 65 | height: 40%; 66 | } 67 | 68 | .row { 69 | margin-top: 6%; 70 | } 71 | 72 | #table { 73 | margin: auto; 74 | width: 90%; 75 | } 76 | 77 | #Reset_Password { 78 | text-align: center; 79 | } 80 | -------------------------------------------------------------------------------- /public/stylesheets/signup.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | /* Set a background image by replacing the URL below */ 7 | body { 8 | background: url('https://images.unsplash.com/photo-1526327760257-75f515c74478?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80') 9 | no-repeat center center fixed; 10 | -webkit-background-size: cover; 11 | -moz-background-size: cover; 12 | background-size: cover; 13 | -o-background-size: cover; 14 | } 15 | 16 | .card { 17 | margin: 0; 18 | top: 50%; 19 | -ms-transform: translateY(-50%); 20 | transform: translateY(-50%); 21 | } 22 | 23 | a { 24 | font-size: 70%; 25 | opacity: 0.4; 26 | color: black; 27 | text-decoration: underline; 28 | margin: 29%; 29 | } 30 | -------------------------------------------------------------------------------- /routes/play.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const music = require('../scripts/music'); 4 | const fs = require('fs'); 5 | 6 | router.get('/', (req, res) => { 7 | if (req.session.username) { 8 | req.session.playtype = null; 9 | req.session.genre = null; 10 | res.render('pages/landing', { username: req.session.username }); 11 | } else { 12 | res.redirect('/'); 13 | } 14 | }); 15 | 16 | router.get('/playtype/single', (req, res) => { 17 | if (req.session.username) { 18 | req.session.playtype = req.params.playtype; 19 | res.render('pages/playlists', { username: req.session.username }); 20 | } else { 21 | res.redirect('/'); 22 | } 23 | }); 24 | 25 | router.get('/playlists', (req, res) => { 26 | if (req.session.username) { 27 | req.session.genre = null; 28 | res.render('pages/playlists'); 29 | } else { 30 | res.redirect('/'); 31 | } 32 | }); 33 | 34 | router.get('/genre/:genre', (req, res) => { 35 | if (req.session.username) { 36 | req.session.genre = req.params.genre; 37 | var results = { 38 | username: req.session.username, 39 | genre: capitalize_words(req.session.genre), 40 | }; 41 | 42 | // redirect to the play page passing req.session.genre as the genre variable 43 | res.render('pages/game_mc', results); 44 | } else { 45 | res.redirect('/'); 46 | } 47 | }); 48 | 49 | router.post('/get_playlist', (req, res) => { 50 | music.getRelatedArtists(req.session.genre, function(returnVal) { 51 | music.getRelatedSongs(returnVal, function(finalPlaylist) { 52 | console.log(finalPlaylist); 53 | res.send(finalPlaylist); 54 | }); 55 | }); 56 | }); 57 | 58 | router.get('/playtype/multiplayer', (req, res) => { 59 | if (req.session.username) { 60 | req.session.playtype = req.params.playtype; 61 | res.render('pages/multiplayer', { username: req.session.username, errors: null }); 62 | } else { 63 | res.redirect('/users/login'); 64 | } 65 | }); 66 | 67 | router.get('/multiplayer/create', (req, res) => { 68 | if (req.session.username) { 69 | res.render('pages/lobby', { username: req.session.username, room: 'create', code: null }); 70 | } else { 71 | res.redirect('/users/login'); 72 | } 73 | }); 74 | 75 | function capitalize_words(str) { 76 | return str.replace(/\w\S*/g, function(txt) { 77 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 78 | }); 79 | } 80 | 81 | module.exports = router; 82 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { check, validationResult } = require('express-validator'); 4 | const pool = require('../db/connection'); 5 | const bcrypt = require('bcrypt'); 6 | const saltRounds = 10; 7 | const genres_types = [ 'pop', 'rap', 'country', 'hip hop', 'rock', 'trap' ]; 8 | 9 | router.get('/signup', (req, res) => { 10 | res.render('pages/signup', { errors: null }); 11 | }); 12 | 13 | router.post( 14 | '/sign_up', 15 | [ 16 | check('password', 'password is too short').isLength({ min: 5 }), 17 | check('username', 'username is too long').isLength({ max: 15 }), 18 | check('username', 'username is too short').isLength({ min: 5 }), 19 | ], 20 | (req, res) => { 21 | var username = req.body.username.toLowerCase(); 22 | var password = req.body.password; 23 | var confirmPassword = req.body.confirmPassword; 24 | var msg; 25 | pool.query( 26 | `SELECT * FROM users WHERE LOWER(username) = LOWER('${username}')`, 27 | (error, results) => { 28 | if (error) { 29 | throw error; 30 | } 31 | 32 | if (results.rows.length != 0) { 33 | msg = 'Username is already in use'; 34 | res.status(400).render('pages/signup', { errors: [ { msg: msg } ] }); 35 | } else { 36 | var errors = validationResult(req); 37 | if (!(password === confirmPassword)) { 38 | msg = 'Passwords do not match'; 39 | res.status(400).render('pages/signup', { errors: [ { msg: msg } ] }); 40 | } else if (!errors.isEmpty()) { 41 | res.status(400).render('pages/signup', errors); 42 | } else { 43 | bcrypt.hash(password, saltRounds, (err, hash) => { 44 | pool.query( 45 | `INSERT INTO users (username, password) VALUES ('${username}', '${hash}')`, 46 | (error) => { 47 | if (error) { 48 | throw error; 49 | } 50 | } 51 | ); 52 | }); 53 | 54 | req.session.username = username; 55 | res.redirect('/play'); 56 | } 57 | } 58 | } 59 | ); 60 | } 61 | ); 62 | 63 | router.get('/login', (req, res) => { 64 | res.render('pages/login', { errors: null }); 65 | }); 66 | 67 | router.post('/sign_in', (req, res) => { 68 | var username = req.body.username.toLowerCase(); 69 | var password = req.body.password; 70 | var errors = null; 71 | // hash 72 | // validate on db 73 | var msg = 'Incorrect username and/or password'; 74 | pool.query( 75 | `SELECT password FROM users WHERE LOWER(username) = '${username}'`, 76 | (error, results) => { 77 | if (error) { 78 | throw error; 79 | } 80 | 81 | if (results.rows.length == 0) { 82 | res.render('pages/login', { errors: [ { msg: msg } ] }); 83 | } else { 84 | const hash = results.rows[0].password.toString(); 85 | bcrypt.compare(password, hash, function(err, response) { 86 | if (response) { 87 | req.session.username = username; 88 | res.redirect('/play'); 89 | } else { 90 | msg = 'Incorrect username and/or password'; 91 | res.render('pages/login', { errors: [ { msg: msg } ] }); 92 | return msg; 93 | } 94 | }); 95 | } 96 | } 97 | ); 98 | }); 99 | 100 | router.get('/logout', (req, res) => { 101 | if (req.session.username) { 102 | req.session.destroy((err) => {}); 103 | res.redirect('/'); 104 | } else { 105 | res.redirect('/users/login'); 106 | } 107 | }); 108 | 109 | router.get('/profile', (req, res) => { 110 | if (req.session.username) { 111 | pool.query( 112 | `SELECT score, dateplayed, genre, mode FROM scores WHERE LOWER(username) = '${req.session 113 | .username}' ORDER BY dateplayed DESC LIMIT 5`, 114 | (error, results) => { 115 | if (error) { 116 | throw error; 117 | } 118 | res.render('pages/profile', { 119 | username: req.session.username, 120 | results: results.rows, 121 | selected: 'Recent Scores', 122 | genres: genres_types, 123 | }); 124 | } 125 | ); 126 | } else { 127 | res.redirect('/users/login'); 128 | } 129 | }); 130 | 131 | router.get('/profile/:data', (req, res) => { 132 | if (req.session.username) { 133 | if (req.params.data == 'Overall Stats') { 134 | pool.query( 135 | `SELECT SUM(score) as total, COUNT(username) as games FROM scores WHERE LOWER(username) = '${req 136 | .session.username}' GROUP BY LOWER(username)`, 137 | (error, results) => { 138 | if (error) { 139 | throw error; 140 | } 141 | res.render('pages/profile', { 142 | username: req.session.username, 143 | results: results.rows, 144 | selected: req.params.data, 145 | genres: genres_types, 146 | }); 147 | } 148 | ); 149 | } else { 150 | pool.query( 151 | `SELECT SUM(score) as total, COUNT(username) as games FROM scores WHERE (LOWER(username) = '${req 152 | .session.username}' AND genre = '${req.params.data}') GROUP BY LOWER(username)`, 153 | (error, results) => { 154 | if (error) { 155 | throw error; 156 | } 157 | res.render('pages/profile', { 158 | username: req.session.username, 159 | results: results.rows, 160 | selected: req.params.data, 161 | genres: genres_types, 162 | }); 163 | } 164 | ); 165 | } 166 | } else { 167 | res.redirect('/users/login'); 168 | } 169 | }); 170 | 171 | router.get('/reset', (req, res) => { 172 | if (req.session.username) { 173 | res.render('pages/reset', { username: req.session.username, errors: null }); 174 | } else { 175 | res.redirect('/users/profile'); 176 | } 177 | }); 178 | 179 | router.post('/reset', (req, res) => { 180 | var oldpassword = req.body.Currentpassword; 181 | var newpassword = req.body.Newpassword; 182 | var confirm = req.body.ConfirmNewpassword; 183 | var username = req.session.username; 184 | var msg = 'Passwords do nost match'; 185 | 186 | // hash old password and compare this with password in database 187 | pool.query( 188 | `SELECT password FROM users WHERE LOWER(username) = '${username}'`, 189 | (error, results) => { 190 | if (error) { 191 | throw error; 192 | } 193 | const hash = results.rows[0].password.toString(); 194 | bcrypt.compare(oldpassword, hash, function(err, response) { 195 | if (response) { 196 | // check to new if new passwords match 197 | if (newpassword == confirm) { 198 | // hash new password and update db with new passwords 199 | bcrypt.hash(newpassword, saltRounds, (err, hash) => { 200 | pool.query( 201 | `UPDATE users SET password = '${hash}' WHERE LOWER(username) = '${username}'` 202 | ); 203 | }); 204 | res.redirect('/play'); 205 | } else { 206 | res.render('pages/reset', { 207 | username: req.session.username, 208 | errors: [ { msg: msg } ], 209 | }); 210 | } 211 | } else { 212 | msg = 'Incorrect password'; 213 | res.render('pages/reset', { 214 | username: req.session.username, 215 | errors: [ { msg: msg } ], 216 | }); 217 | } 218 | }); 219 | } 220 | ); 221 | }); 222 | 223 | router.get('/leaderboard', (req, res) => { 224 | if (req.session.username) { 225 | // just in case we access this straigh after a game, we reset the genre and playtype 226 | req.session.genre = null; 227 | req.session.playtype = null; 228 | 229 | pool.query(`select * from scores order by score desc limit 10`, (err, results) => { 230 | if (err) { 231 | throw err; 232 | } else { 233 | res.render('pages/leaderboard', { 234 | username: req.session.username, 235 | data: results.rows, 236 | bestgenre: 'placeholder for now', 237 | genre: 'placeholder for now', 238 | gamesplayed: 'placeholder for now', 239 | genres: genres_types, 240 | selected: req.params.genre, 241 | }); 242 | } 243 | }); 244 | } else { 245 | res.redirect('/users/login'); 246 | } 247 | }); 248 | 249 | router.get('/leaderboard/:genre', (req, res) => { 250 | if (req.session.username) { 251 | // just in case we access this straigh after a game, we reset the genre and playtype 252 | req.session.genre = null; 253 | req.session.playtype = null; 254 | 255 | pool.query( 256 | `select username, score, genre from scores where genre = '${req.params 257 | .genre}' order by score desc limit 10`, 258 | (err, results) => { 259 | if (err) { 260 | throw err; 261 | } else { 262 | res.render('pages/leaderboard', { 263 | username: req.session.username, 264 | data: results.rows, 265 | bestgenre: 'placeholder for now', 266 | gamesplayed: 'placeholder for now', 267 | genres: genres_types, 268 | selected: req.params.genre, 269 | }); 270 | } 271 | } 272 | ); 273 | } else { 274 | res.redirect('/users/login'); 275 | } 276 | }); 277 | 278 | router.post('/upscore', (req, res) => { 279 | let username = req.session.username; 280 | let score = req.body.userScore; 281 | let genre = req.session.genre; 282 | let gamemode = 'single'; 283 | let d = new Date(); 284 | 285 | //format date properly 286 | dformat = 287 | [ d.getFullYear(), d.getMonth() + 1, d.getDate() ].join('-') + 288 | ' ' + 289 | [ d.getHours(), d.getMinutes(), d.getSeconds() ].join(':'); 290 | 291 | pool.query( 292 | `INSERT INTO scores values ('${username}',${score}, '${gamemode}', '${genre}', '${dformat}')`, 293 | function(err, res) { 294 | if (err) { 295 | return console.log(err); 296 | } 297 | } 298 | ); 299 | }); 300 | module.exports = router; 301 | -------------------------------------------------------------------------------- /scripts/music.js: -------------------------------------------------------------------------------- 1 | const Spotify = require('node-spotify-api') //newly added https://github.com/ceckenrode/node-spotify-api https://developer.spotify.com/documentation/web-api/reference/ 2 | const async = require("async") //https://www.npmjs.com/package/async 3 | const pool = require('../db/connection'); 4 | 5 | // Load environment variables 6 | require('dotenv').config(); 7 | 8 | const spotify = new Spotify({ 9 | id: process.env.SPOTIFY_ID, 10 | secret: process.env.SPOTIFY_SECRET 11 | }); 12 | 13 | //Used with .filter function to grab only unique artists in an array 14 | function onlyUnique(value, index, self) { 15 | return self.indexOf(value) === index; 16 | } 17 | 18 | //Not hating on Lil Nas X 19 | //Literally the X part of Lil Nas X messes up the updating of the song database 20 | //because when we use grab the artists from the top-songs of the month, it returns 21 | //all artist that participated in a track. For example, Kygo X Whitney Houston, 22 | //Our regex then splits up Kygo and Whitney Houston by detecting the X, but since Lil 23 | //Nas X has an X it splits Lil Nas X => [Lil Nas, ''], so now when we iterate through our list 24 | //'' messes up when we query all the an artist's top tracks 25 | function removeLilNasX(artists) { 26 | return (artists.includes("Lil Nas X") == false); 27 | } 28 | 29 | module.exports = { 30 | 31 | updateSongDB: function() { 32 | //Grabs all unique artists for top 100 songs 33 | getChart('hot-100', function (err, chart) { 34 | if (err) { 35 | console.log(err) 36 | return 37 | } 38 | let topArtist = [] 39 | 40 | //grabs all the artists for top 100 songs 41 | for (let i = 0; i < chart.songs.length; i++) { 42 | topArtist.push(chart.songs[i].artist) 43 | } 44 | 45 | topArtist = topArtist.filter(removeLilNasX) 46 | //Regex for seperating Artists from a song, like if a song features another artists 47 | let re = /[&,+]|\bFeaturing\b|\b X \b/g 48 | 49 | let seperatedArtists = [] 50 | 51 | for (let i = 0; i < topArtist.length; i++) { 52 | seperatedArtists = seperatedArtists.concat(topArtist[i].split(re)) 53 | } 54 | 55 | //Cleaning up spaces at end and beginning of an artist name string 56 | for (let i = 0; i < seperatedArtists.length; i++) { 57 | seperatedArtists[i] = seperatedArtists[i].trim() 58 | } 59 | 60 | //Grab all the unique artists 61 | let uniqueArtists = seperatedArtists.filter(onlyUnique) 62 | 63 | //console.log(uniqueArtists) 64 | //console.log("----------------------------") 65 | //Used to limit our web api calls to 1 parallel connection at a time 66 | 67 | async.eachLimit(uniqueArtists, 5, function (artist, callback) { 68 | setTimeout(function () { 69 | spotify.search({ type: 'artist', query: artist }, function (err, data) { 70 | if (err) { 71 | return console.log('Error occurred: ' + err); 72 | } 73 | 74 | //we use index 0, because that is the most popular artists -> the artist we queried 75 | let artistId = data.artists.items[0].id 76 | let artistGenres = {} 77 | for (let n = 0; n < data.artists.items[0].genres.length; n++) { 78 | artistGenres[data.artists.items[0].genres[n]] = 1 79 | } 80 | let artistName = artist.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); //escape special characters 81 | spotify.request(`https://api.spotify.com/v1/artists/${artistId}/top-tracks?country=CA`, function (err, data) { 82 | if (err) { 83 | return console.log("this is the error for top tracks query : " + err) 84 | } 85 | 86 | let hasPreviews = [] 87 | 88 | //put genre into a json format 89 | for (let i = 0; i < data.tracks.length; i++) { 90 | if (data.tracks[i].preview_url != null) { 91 | hasPreviews.push(data.tracks[i]) 92 | } 93 | } 94 | 95 | if (hasPreviews.length == 0) { 96 | return console.log("No songs available for: " + artistName) 97 | } 98 | 99 | //escape all special characters in song name, so we can insert into db 100 | for (let i = 0; i < hasPreviews.length; i++) { 101 | hasPreviews[i].name = hasPreviews[i].name.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); 102 | } 103 | 104 | for (let i = 0; i < hasPreviews.length; i++) { 105 | pool.query(`select * from songs where songid = '${hasPreviews[i].id}'`, function (err, res) { 106 | if (err) { 107 | return console.log(err) 108 | } 109 | 110 | if (res.rowCount == 0) { 111 | pool.query(`INSERT INTO songs values('${artistId}', '${artistName}', '${hasPreviews[i].id}', '${hasPreviews[i].name}', '${JSON.stringify(artistGenres)}', '${hasPreviews[i].preview_url}')`, function (err, res) { 112 | if (err) { 113 | console.log("=========================================") 114 | console.log("artists ID: " + artistId) 115 | console.log("Artists Name: " + artistName) 116 | console.log("Song ID " + hasPreviews[i].id) 117 | console.log("Song Name " + hasPreviews[i].name) 118 | console.log("Genres " + artistGenres) 119 | console.log("URL: " + hasPreviews[i].preview_url) 120 | console.log("=========================================") 121 | return console.log(err) 122 | } 123 | console.log("OK") 124 | }) 125 | } 126 | }) 127 | 128 | } 129 | }) 130 | }); 131 | console.log("Done upload artist: " + artist) 132 | callback() 133 | }, 3000) 134 | 135 | }, 136 | function (err) { 137 | return console.log(err) 138 | }) 139 | }) 140 | }, 141 | 142 | updateSongDBSpecific:function(year) { 143 | //Grabs all unique artists for top 100 songs 144 | getChart('hot-100', year, function (err, chart) { 145 | if (err) { 146 | console.log(err) 147 | return 148 | } 149 | let topArtist = [] 150 | 151 | //grabs all the artists for top 100 songs 152 | for (let i = 0; i < chart.songs.length; i++) { 153 | topArtist.push(chart.songs[i].artist) 154 | } 155 | 156 | topArtist = topArtist.filter(removeLilNasX) 157 | //Regex for seperating Artists from a song, like if a song features another artists 158 | let re = /[&,+]|\bFeaturing\b|\b X \b/g 159 | 160 | let seperatedArtists = [] 161 | 162 | for (let i = 0; i < topArtist.length; i++) { 163 | seperatedArtists = seperatedArtists.concat(topArtist[i].split(re)) 164 | } 165 | 166 | //Cleaning up spaces at end and beginning of an artist name string 167 | for (let i = 0; i < seperatedArtists.length; i++) { 168 | seperatedArtists[i] = seperatedArtists[i].trim() 169 | } 170 | 171 | //Grab all the unique artists 172 | let uniqueArtists = seperatedArtists.filter(onlyUnique) 173 | 174 | //console.log(uniqueArtists) 175 | //console.log("----------------------------") 176 | //Used to limit our web api calls to 1 parallel connection at a time 177 | 178 | async.eachLimit(uniqueArtists, 5, function (artist, callback) { 179 | setTimeout(function () { 180 | spotify.search({ type: 'artist', query: artist }, function (err, data) { 181 | if (err) { 182 | return console.log('Error occurred: ' + err); 183 | } 184 | 185 | //we use index 0, because that is the most popular artists -> the artist we queried 186 | let artistId = data.artists.items[0].id 187 | let artistGenres = {} 188 | for (let n = 0; n < data.artists.items[0].genres.length; n++) { 189 | artistGenres[data.artists.items[0].genres[n]] = 1 190 | } 191 | let artistName = artist.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); //escape special characters 192 | spotify.request(`https://api.spotify.com/v1/artists/${artistId}/top-tracks?country=CA`, function (err, data) { 193 | if (err) { 194 | return console.log("this is the error for top tracks query : " + err) 195 | } 196 | 197 | let hasPreviews = [] 198 | 199 | //put genre into a json format 200 | for (let i = 0; i < data.tracks.length; i++) { 201 | if (data.tracks[i].preview_url != null) { 202 | hasPreviews.push(data.tracks[i]) 203 | } 204 | } 205 | 206 | if (hasPreviews.length == 0) { 207 | return console.log("No songs available for: " + artistName) 208 | } 209 | 210 | //escape all special characters in song name, so we can insert into db 211 | for (let i = 0; i < hasPreviews.length; i++) { 212 | hasPreviews[i].name = hasPreviews[i].name.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); 213 | } 214 | 215 | for (let i = 0; i < hasPreviews.length; i++) { 216 | pool.query(`select * from songs where songid = '${hasPreviews[i].id}'`, function (err, res) { 217 | if (err) { 218 | return console.log(err) 219 | } 220 | 221 | if (res.rowCount == 0) { 222 | pool.query(`INSERT INTO songs values('${artistId}', '${artistName}', '${hasPreviews[i].id}', '${hasPreviews[i].name}', '${JSON.stringify(artistGenres)}', '${hasPreviews[i].preview_url}')`, function (err, res) { 223 | if (err) { 224 | console.log("=========================================") 225 | console.log("artists ID: " + artistId) 226 | console.log("Artists Name: " + artistName) 227 | console.log("Song ID " + hasPreviews[i].id) 228 | console.log("Song Name " + hasPreviews[i].name) 229 | console.log("Genres " + artistGenres) 230 | console.log("URL: " + hasPreviews[i].preview_url) 231 | console.log("=========================================") 232 | return console.log(err) 233 | } 234 | console.log("OK") 235 | }) 236 | } 237 | }) 238 | 239 | } 240 | }) 241 | }); 242 | console.log("Done upload artist: " + artist) 243 | callback() 244 | }, 3000) 245 | 246 | }, 247 | function (err) { 248 | return console.log(err) 249 | }) 250 | }) 251 | }, 252 | 253 | alertUpdate: function() { 254 | return console.log("------STARTING SONG DATABASE UPDATE------") 255 | }, 256 | 257 | 258 | // *************************** PLAYLIST PAGE ********************************** 259 | /* 260 | Purpose: Create a playlist of 5 (tbd) songs based on selected genre and grab 3 randomly selected related artists and insert it into the playlist 261 | Params: genre -> the selected genre 262 | Return: return the updated playlist with related artists 263 | */ 264 | getRelatedArtists: function(genre, callback) { 265 | pool.query(`select * from songs s where (s.genre -> '${genre}') is not null order by random() limit 5`, function (err, result) { 266 | if (err) { 267 | return console.log(err) 268 | } 269 | 270 | var returnPlaylist = JSON.parse(JSON.stringify(result.rows)) 271 | 272 | let artistsId = [] 273 | 274 | for (let i = 0; i < result.rowCount; i++) { 275 | artistsId.push(result.rows[i].artistid) 276 | } 277 | let counter = 0; 278 | 279 | async.eachLimit(artistsId, 1, function (artist, callback) { 280 | counter += 1 281 | let relatedArtists = [] 282 | spotify.request(`https://api.spotify.com/v1/artists/${artist}/related-artists`, function (err, res) { 283 | if (err) { 284 | return console.log(err) 285 | } 286 | 287 | let relatedArtistsPool = [] 288 | 289 | // related artists names 290 | for (let j = 0; j < res.artists.length; j++) { 291 | relatedArtistsPool.push(res.artists[j].name) 292 | } 293 | 294 | // select 3 random related artists 295 | for (let k = 0; k < 3; k++) { 296 | relatedArtists.push(relatedArtistsPool.splice(Math.random() * (relatedArtistsPool.length - 1), 1).pop()) 297 | } 298 | //add new key value pair to its respective location in the playlist 299 | returnPlaylist[counter - 1].related_artists = relatedArtists 300 | callback() 301 | }) 302 | }, 303 | function (err) { 304 | callback(returnPlaylist) 305 | }) 306 | 307 | }) 308 | }, 309 | 310 | /* 311 | Purpose: grab 3 randomly selected related songs based off of related artists to be used as wrong answers and insert it into the playlist 312 | Params: relatedArtists -> related artists of selected artist in playlist 313 | Return: return the updated playlist with related songs 314 | */ 315 | getRelatedSongs: function(playlist, callback) { 316 | let returnPlaylist = JSON.parse(JSON.stringify(playlist)) 317 | 318 | let artistId = [] 319 | 320 | for (let i = 0; i < returnPlaylist.length; i++) { 321 | artistId.push(returnPlaylist[i].artistid) 322 | } 323 | 324 | let counter = 0 325 | 326 | async.eachLimit(artistId, 1, function (artist, callback) { 327 | counter += 1 328 | let relatedSongs = [] 329 | 330 | spotify.request(`https://api.spotify.com/v1/artists/${artist}/top-tracks?country=CA`, function (err, res) { 331 | if (err) { 332 | return console.log(err); 333 | } 334 | 335 | // store top tracks names into pool (array) 336 | let relatedSongsPool = [] 337 | 338 | for (let j = 0; j < res.tracks.length; j++) { 339 | relatedSongsPool.push({ name: res.tracks[j].name, id: res.tracks[j].id }); 340 | } 341 | 342 | relatedSongsPool = relatedSongsPool.filter(function (song) { 343 | return ((song.id != returnPlaylist[counter-1].songid) && !(song.name.includes(returnPlaylist[counter-1].songname))) 344 | }) 345 | 346 | // randomly select 3 of those top tracks 347 | for (let i = 0; i < 3; i++) { 348 | randomRelatedSong = relatedSongsPool.splice(Math.random() * relatedSongsPool.length - 1, 1).pop() 349 | relatedSongs.push(randomRelatedSong.name) 350 | } 351 | 352 | // place related songs into playlist to return 353 | returnPlaylist[counter-1].related_songs = relatedSongs; 354 | callback() 355 | }) 356 | }, 357 | function (err) { 358 | callback(returnPlaylist) 359 | }) 360 | } 361 | } -------------------------------------------------------------------------------- /tests/testRoutes.js: -------------------------------------------------------------------------------- 1 | /***************************************************************************************************/ 2 | /* This test page just ensures when we are visiting different pages, we get the desired pages */ 3 | /***************************************************************************************************/ 4 | 5 | var expect = require('chai').expect; 6 | var app = require('../index'); 7 | var request = require('supertest'); 8 | 9 | /***************************************************************************************************/ 10 | /* set up the data we need to pass to the login method */ 11 | /***************************************************************************************************/ 12 | 13 | const userCredentials = { 14 | username: 'userone', 15 | password: '12345', 16 | }; 17 | 18 | /***************************************************************************************************/ 19 | /* this test says: make a POST to the /sign_in route with the username: userone, password: 12345 */ 20 | /* after the POST has completed, make sure the status code is 200 */ 21 | /* also make sure that the user has been directed to the /play page */ 22 | /***************************************************************************************************/ 23 | 24 | var authenticatedUser = request.agent(app); 25 | before(function(done) { 26 | authenticatedUser.post('/users/sign_in').send(userCredentials).end(function(err, response) { 27 | expect(response.statusCode).to.equal(302); 28 | expect('Location', '/play'); 29 | done(); 30 | }); 31 | }); 32 | 33 | /***************************************************************************************************/ 34 | /* check to see if pages are being successfully rendered when user is signed in */ 35 | /***************************************************************************************************/ 36 | 37 | //ensure user is logged in 38 | describe('Ensure signed in', function() { 39 | //checks to see if we are signed in 40 | it('Checks to see if we are signed in', function(done) { 41 | request(app).post('/users/sign_in').send(userCredentials).end(function(err, response) { 42 | expect(response.statusCode).to.equal(302); 43 | 44 | done(); 45 | }); 46 | }); 47 | }); 48 | //check to see if profile page is being rendered when when are signed in 49 | describe('testing /users/profile', function(done) { 50 | //if the user is logged in we should get a 200 status code, because we render the profile page 51 | it('should return a 200 response if the user is logged in', function(done) { 52 | authenticatedUser.get('/users/profile').expect(200, done); 53 | }); 54 | //if the user is not logged in we should get a 302 response code and be directed to the /login page 55 | it('should return a 302 response and redirect to /users/login', function(done) { 56 | request(app).get('/users/profile').expect('Location', '/users/login').expect(302, done); 57 | }); 58 | }); 59 | 60 | //check to see if leaderboard page is being rendered when when are signed in 61 | describe('testing /users/leaderboard', function(done) { 62 | //if the user is logged in we should get a 200 status code, because we render the leaderboard page 63 | it('should return a 200 response if the user is logged in', function(done) { 64 | authenticatedUser.get('/users/leaderboard').expect(200, done); 65 | }); 66 | //if the user is not logged in we should get a 302 response code and be directed to the /login page 67 | it('should return a 302 response and redirect to /users/login', function(done) { 68 | request(app).get('/users/profile').expect('Location', '/users/login').expect(302, done); 69 | }); 70 | }); 71 | 72 | //check to see if reset page is being rendered when we are signed in 73 | describe('testing /users/reset', function(done) { 74 | //if the user is logged in we should get a 200 status code, because we render the reset page 75 | it('should return a 200 response if the user is logged in', function(done) { 76 | authenticatedUser.get('/users/reset').expect(200, done); 77 | }); 78 | //if the user is not logged in we should get a 302 response code and be directed to the /profile page 79 | it('should return a 302 response and redirect to /users/login', function(done) { 80 | request(app).get('/users/reset').expect('Location', '/users/profile').expect(302, done); 81 | }); 82 | }); 83 | 84 | //check to see if signup page gets rendered 85 | describe('testing /users/signup', function(done) { 86 | //if the user is logged in we should get a 200 status code, because we render the signup page 87 | it('should return a 200 response if users clicks signup', function(done) { 88 | authenticatedUser.get('/users/reset').expect(200, done); 89 | }); 90 | }); 91 | 92 | //check to see if login page gets rendered 93 | describe('testing /users/login', function(done) { 94 | //if the user is logged in we should get a 200 status code, because we render the login page 95 | it('should return a 200 response if users clicks login', function(done) { 96 | authenticatedUser.get('/users/login').expect(200, done); 97 | }); 98 | }); 99 | 100 | //check to see if we are redirected after logging out 101 | describe('testing /users/logout', function(done) { 102 | //if the user is logged in we should get a 302 status code, because we redirect to the home page 103 | it('should return a 302 response if the user is logged in', function(done) { 104 | authenticatedUser.get('/users/logout').expect(302, done); 105 | }); 106 | //if the user is not logged in we should get a 302 response code and be directed to the /login page 107 | it('should return a 302 response and redirect to /users/login', function(done) { 108 | request(app).get('/users/logout').expect('Location', '/users/login').expect(302, done); 109 | }); 110 | }); 111 | 112 | //check to see if we can access the multi player page 113 | describe('testing /play/multiplayer', function(done) { 114 | //if the user is not logged in we should get a 302 response code and be directed to the /login page 115 | it('should return a 302 response and redirect to /users/login if you are not logged in', function(done) { 116 | request(app).get('/play/playtype/multiplayer').expect('Location', '/users/login').expect(302, done); 117 | }); 118 | }); 119 | 120 | //check to see if we can access the multi player create room page 121 | describe('testing /play/multiplayer/create', function(done) { 122 | //if the user is not logged in we should get a 302 response code and be directed to the /login page 123 | it('should return a 302 response and redirect to /users/login if you are not logged in', function(done) { 124 | request(app).get('/play//multiplayer/create').expect('Location', '/users/login').expect(302, done); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /tests/testSignup.js: -------------------------------------------------------------------------------- 1 | /***************************************************************************************************/ 2 | /* This test page checks username and password constraints */ 3 | /***************************************************************************************************/ 4 | 5 | var expect = require('chai').expect; 6 | var app = require('../index'); 7 | var request = require('supertest'); 8 | 9 | /*******************************************************************************************/ 10 | /* set up the data we need to pass to the login method */ 11 | /*******************************************************************************************/ 12 | 13 | const userCredentials = { 14 | username: 'userone', 15 | password: '12345', 16 | }; 17 | 18 | /***************************************************************************************************/ 19 | /* this test says: make a POST to the /sign_in route with the username: userone, password: 12345 */ 20 | /* after the POST has completed, make sure the status code is 200 */ 21 | /* also make sure that the user has been directed to the /play page */ 22 | /***************************************************************************************************/ 23 | 24 | var authenticatedUser = request.agent(app); 25 | before(function(done) { 26 | authenticatedUser.post('/users/sign_in').send(userCredentials).end(function(err, response) { 27 | expect(response.statusCode).to.equal(302); 28 | expect('Location', '/play'); 29 | done(); 30 | }); 31 | }); 32 | 33 | /***************************************************************************************************/ 34 | /* BELOW ARE THE TEST CASES FOR ACCOUNTS THAT WILL HAVE ERRORS */ 35 | /***************************************************************************************************/ 36 | 37 | const confirm_pass_wrong = { 38 | username: 'userone2', 39 | password: '12345', 40 | confirmPassword: '123456', 41 | }; 42 | const username_exists = { 43 | username: 'userone', 44 | password: '12345', 45 | confirmPassword: '12345', 46 | }; 47 | const pass_short = { 48 | username: 'userone3', 49 | password: '123', 50 | confirmPassword: '123', 51 | }; 52 | const user_short = { 53 | username: 'user', 54 | password: '12345', 55 | confirmPassword: '12345', 56 | }; 57 | const user_long = { 58 | username: 'user_lengthlongerthan15', 59 | password: '12345', 60 | confirmPassword: '12345', 61 | }; 62 | const user_lower_case = { 63 | username: 'UseROne', 64 | password: '12345', 65 | confirmPassword: '12345', 66 | }; 67 | 68 | /***************************************************************************************************/ 69 | /* BELOW ARE THE TEST CASES FOR ERRORS WE SHOULD GET IF WE HAVE INCORRECT USERNAME AND/OR PASSWORD */ 70 | /***************************************************************************************************/ 71 | 72 | describe('Sign up and Sign in testing', function() { 73 | //checks to see if we get passwords do not match error if we create an account and entire fields incorrectly 74 | it('Checks to see if password matches confirmPassword', function(done) { 75 | request(app).post('/users/sign_up').send(confirm_pass_wrong).end(function(err, response) { 76 | expect(response.statusCode).to.equal(400); 77 | expect(response.text).to.include('Passwords do not match'); 78 | done(); 79 | }); 80 | }); 81 | 82 | //checks to see if we get username already exists if we create an account which already uses that username 83 | it('Checks to see if username is valid', function(done) { 84 | request(app).post('/users/sign_up').send(username_exists).end(function(err, response) { 85 | expect(response.statusCode).to.equal(400); 86 | expect(response.text).to.include('Username is already in use'); 87 | done(); 88 | }); 89 | }); 90 | 91 | //checks to see if we get password is too short if we create an account with a password with length less than 5 92 | it('Checks to see if password is to short', function(done) { 93 | request(app).post('/users/sign_up').send(pass_short).end(function(err, response) { 94 | expect(response.statusCode).to.equal(400); 95 | expect(response.text).to.include('password is too short'); 96 | done(); 97 | }); 98 | }); 99 | 100 | //checks to see if we get username is too short if we create an account with a username with length less than 5 101 | it('Checks to see if username is to short', function(done) { 102 | request(app).post('/users/sign_up').send(user_short).end(function(err, response) { 103 | expect(response.statusCode).to.equal(400); 104 | expect(response.text).to.include('username is too short'); 105 | done(); 106 | }); 107 | }); 108 | 109 | //checks to see if we get username is too long if we create an account with a username with length greater than 15 110 | it('Checks to see if username is to long', function(done) { 111 | request(app).post('/users/sign_up').send(user_long).end(function(err, response) { 112 | expect(response.statusCode).to.equal(400); 113 | expect(response.text).to.include('username is too long'); 114 | done(); 115 | }); 116 | }); 117 | 118 | //signing UP with an existing account but using uppercase instead for username 119 | it('Checks to see if case insensitive username signs us up', function(done) { 120 | request(app).post('/users/sign_up').send(user_lower_case).end(function(err, response) { 121 | expect(response.statusCode).to.equal(400); 122 | expect(response.text).to.include('Username is already in use'); 123 | done(); 124 | }); 125 | }); 126 | 127 | //signing IN with an existing account but using uppercase for some letters in username 128 | it('Checks to see if case insensitive username logs us in', function(done) { 129 | request(app).post('/users/sign_in').send(user_lower_case).end(function(err, response) { 130 | expect(response.statusCode).to.equal(302); 131 | 132 | done(); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /views/pages/game.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Play Now 8 | 9 | 10 | 11 | 12 | <% include ../partials/nav.ejs %> 13 |
    14 | 15 |

    16 |

    17 | Guess the Song! 18 |

    19 |

    20 |

    Correct: 3/5

    21 | 22 |
    23 | 24 |
    25 |
    28 |
    29 | 30 |

    31 |

    32 | 33 |
    34 | 37 |
    38 | 39 |
    40 | 41 |
    42 | 43 |
    44 | 45 | 46 | 47 | 48 |
    49 | 50 |
    51 | Return to Home 52 | Select a different genre 53 | Play again 54 |
    55 | 56 |
    57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /views/pages/game_mc.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Play Now 8 | 9 | 10 | 11 | 12 | <% include ../partials/game_nav.ejs %> 13 |
    14 | 15 |

    16 |

    17 | Guess the Song! 18 |

    19 |

    20 | 21 |
    22 | 23 |
    24 |
    27 |
    28 | 29 |

    30 |   31 |

    32 | 33 |
    34 | 37 |
    38 | 39 |
    40 | 41 |
    42 | 43 |
    44 |
      45 |
      46 | 47 |
      48 |
      49 | 50 |
      51 |
      52 | 53 |
      54 |
      55 | 56 |
      57 |
      58 | 59 |
      60 |
      61 | 62 |
      63 | Return to Home 64 | Select a different genre 65 | Play again 66 |
      67 | 68 |
      69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /views/pages/game_multi.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Play Now 8 | 9 | 10 | 11 | 12 | <% include ../partials/game_nav.ejs %> 13 |
      14 | 15 |
      16 |
      17 |

      Players

      18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
      NameScore
      ttrinh2000
      bvtrinh1900
      andy1500
      kevin1000
      44 | 45 |
      46 |
      47 | 48 |

      49 |

      50 | Guess the Song! 51 |

      52 | 53 |
      54 | 55 |
      56 |
      59 |
      60 | 61 |

      62 |   63 |

      64 | 65 |
      66 | 69 |
      70 | 71 |
      72 | 73 |
      74 | 75 |
      76 |
      77 | 78 |
      79 |
      80 | 81 |
      82 |
      83 | 84 |
      85 |
      86 | 87 |
      88 |
      89 |
      90 | 91 | 92 |
      93 | 94 |
      95 | Return to Home 96 | Select a different genre 97 | Play again 98 |
      99 | 100 |
      101 | 102 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /views/pages/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | 8 | Home 9 | 10 | 11 | 12 | 13 | <% include ../partials/nav.ejs %> 14 | 15 |
      16 | 17 | 47 | 48 |
      49 |
      50 |
      51 |

      What is Tuning?

      52 |

      53 | Tuning is a party game, where the players are groups of friends or strangers that compete against one 54 | another in a fast-paced game of guessing the song title, artist, or missing lyrics of the music clip that 55 | plays. 56 | Perfect for game nights with friends, trivia competitions, bars, and many more occasions. Also, gives you 57 | the opportunity 58 | to socialize and create closer bonds among friends or help you make new friends! 59 |

      60 |
      61 | 62 |
      63 | 64 |
      65 | 66 |
      67 | 68 |
      69 | 70 |
      71 |
      72 | 127 |
      128 | 129 |
      130 |

      How do I play?

      131 |
        132 |
      • Step 1: Create an account
      • 133 |
      • Step 2: Login
      • 134 |
      • Step 3: Select game mode, single player or multiplayer
      • 135 |
      • Step 4: Listen to the music clip
      • 136 |
      • Step 5: Select/enter the right answer fast for more points
      • 137 |
      138 | 147 |
      148 |
      149 | 150 |
      151 | 152 |
      153 |

      Developers

      154 |
      155 | 156 |

      Here's the link to the repo!

      157 | 158 |
      159 |
      160 | 161 |

      Moses Lee

      162 |
      163 | 164 |
      165 | 166 |

      Andy Liu

      167 |
      168 | 169 |
      170 | 171 |

      Kevin Gandham

      172 |
      173 | 174 |
      175 | 176 |

      Trevor Grabham

      177 |
      178 | 179 |
      180 | 181 |

      Tyler Trinh

      182 |
      183 |
      184 |
      185 | 186 | 187 |
      188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /views/pages/landing.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Play Now 8 | 9 | 10 | 11 | 12 | <% include ../partials/nav.ejs %> 13 |
      14 |
      15 |
      16 | Tuning 17 |
      18 | 19 |
      20 |
      21 | 22 |
      23 |
      24 | 25 |
      26 |
      27 |
      28 |
      29 | 30 |
      31 |
      32 |
      33 |
      34 | 35 |
      36 |
      37 | 38 |
      39 |
      40 | 41 |
      42 |
      43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /views/pages/leaderboard.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Leaderboard 8 | 9 | 10 | 11 | 25 | 26 | <% include ../partials/nav.ejs %> 27 |
      28 |
      29 |
      30 |

      Leaderboard

      31 |
      32 |
      33 | 39 |
      40 |
      41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | <% if(data){ 52 | var i=1; 53 | for(var d of data){ %> 54 | 55 | 56 | 57 | 58 | 59 | <% i++; %> 60 | 61 | <% }} %> 62 | 63 |
      RankScoreUsernameGenre
      <%= i %><%= d.score %><%= d.username %><%= d.genre %>
      64 |
      65 |
      66 |
      67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /views/pages/lobby.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | Lobby 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 38 | 39 | 41 | 42 | 43 | 44 | <% include ../partials/nav.ejs %> 45 |
      46 |
      47 |
      48 | 49 |
      50 | Lobby 51 |
      52 | 53 |
      54 | 55 |
      56 |
      57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
      NameReady/Unready
      namehi
      72 |
      73 | 74 |
      75 | 76 |
      77 | 78 | 79 | 90 |
      91 |
      92 | 93 | 94 |
      95 | Room code:
      96 | 1A1A1A 97 |
      98 | 99 | 100 |
      101 |
      102 | 103 |
      104 |
      105 |
      106 |
      107 |
      108 | 109 |
      110 | 111 |
      112 | 113 |
      114 |
      115 |

      Chat

      116 |
      117 |
      118 |
      119 | 121 | 122 |
      123 |
      124 |
      125 | 126 |
      127 | 128 |
      129 | 130 | 132 |
      133 | 134 |
      135 | 136 |
      137 | 138 |
      139 |
      140 | 141 |
      142 | 143 |
      144 | 145 |
      146 |
      147 | 148 |
      149 |
      150 | 151 | 152 |
      153 |
      154 | 155 |
      156 |
      157 |

      Players

      158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 |
      NameScore
      168 | 169 |
      170 |
      171 | 172 |

      173 |

      174 | Guess the Song! 175 |

      176 | 177 |
      178 | 179 |
      180 |
      182 |
      183 | 184 | 185 |

      186 |   187 |

      188 | 189 | 190 | 191 |
      192 | 195 |
      196 | 197 |
      198 | 199 |
      200 | 201 |
      202 |
      203 | 204 |
      205 |
      206 | 207 |
      208 |
      209 | 210 |
      211 |
      212 | 213 |
      214 |
      215 |
      216 |
      217 |
      218 | 219 |
      220 | 221 | 222 |
      223 | 224 |
      225 | 226 |

      Results

      227 | 228 |
      229 |
      230 | 231 |

      Ranking

      232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 |
      NameScore
      name50
      246 |
      247 |
      248 | 249 |

      Song List

      250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 |
      RoundSong NameArtist
      1hi
      2hi
      3hi
      4hi
      5hi
      286 |
      287 |
      288 | 289 |
      290 |
      291 | 292 | 293 | 294 | 295 |
      296 |
      297 | 298 |
      299 | 300 |
      301 |
      302 |
      303 | 304 |
      305 | 306 |
      307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /views/pages/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../partials/header.ejs %> 5 | 6 | Login 7 | 8 | 9 | 10 |
      11 |
      12 |
      13 | 43 |
      44 |
      45 |
      46 | 47 | 48 | -------------------------------------------------------------------------------- /views/pages/multiplayer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Multiplayer 8 | 9 | 10 | 11 | 12 | 13 | <% include ../partials/nav.ejs %> 14 | 15 |
      16 |
      17 | 18 |
      19 | Multiplayer 20 |
      21 | 22 |
      23 | 24 |
      25 |
      26 |
      27 | 28 |
      29 |
      30 |
      31 |
      32 | 33 | 34 |
      35 |
      36 |
      37 |
      38 | 39 |
      40 | 41 |
      42 | 43 |
      44 | <% if (errors != null) { %> 45 | 52 | <% } %> 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /views/pages/playlists.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Single Player 8 | 9 | 10 | 11 | 12 | 13 | <% include ../partials/nav.ejs %> 14 | 15 |
      16 |
      17 | 18 |
      19 | Playlists 20 |
      21 | 22 |
      23 | 24 |
      25 |
      26 |
      27 | 28 |
      29 |
      30 |
      31 |
      32 | 33 |
      34 |
      35 |
      36 |
      37 | 38 |
      39 |
      40 |
      41 | 42 |
      43 |
      44 |
      45 | 46 |
      47 |
      48 |
      49 |
      50 | 51 |
      52 |
      53 |
      54 |
      55 | 56 |
      57 |
      58 |
      59 | 60 |
      61 |
      62 | 63 |
      64 | 65 | 66 | -------------------------------------------------------------------------------- /views/pages/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Profile 8 | 22 | 23 | 24 | 25 | <% include ../partials/nav.ejs %> 26 | 27 |
      28 |
      29 |
      30 |

      <%=username%>

      31 |
      32 |
      33 | 40 |
      41 |
      42 | 43 | 44 | 45 | <% if(selected == "Recent Scores"){ %> 46 | 47 | 48 | 49 | 50 | <% } else { %> 51 | 52 | 53 | 54 | <% } %> 55 | 56 | 57 | 58 | <% if(selected == "Recent Scores"){ 59 | for (let i=0; i < results.length; i++){ %> 60 | 61 | 62 | 63 | 64 | 65 | 66 | <% }} else { 67 | for (let i=0; i < results.length; i++){ %> 68 | 69 | 70 | 71 | 72 | 73 | <% }}%> 74 | 75 |
      DateScoreGenreModeTotal ScoreGames PlayedAverage Score
      <%=results[i].dateplayed.toString().substr(0,results[i].dateplayed.toString().indexOf(' GMT'))%><%=results[i].score%><%=results[i].genre%><%=results[i].mode%>
      <%=results[i].total%><%=results[i].games%><%=Math.round(results[i].total/results[i].games)%>
      76 |
      77 |
      78 |
      79 |
      80 | 81 |
      82 |
      83 |
      84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /views/pages/reset.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% include ../partials/header.ejs %> 6 | 7 | Change Password 8 | 9 | 10 | 11 | <% include ../partials/nav.ejs %> 12 | 13 | <
      14 |
      15 |
      16 | 46 |
      47 |
      48 |
      49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /views/pages/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../partials/header.ejs %> 5 | 6 | Sign Up 7 | 8 | 9 | 10 |
      11 |
      12 |
      13 | 49 |
      50 |
      51 |
      52 | 53 | 54 | -------------------------------------------------------------------------------- /views/partials/game_nav.ejs: -------------------------------------------------------------------------------- 1 | 2 | 50 | -------------------------------------------------------------------------------- /views/partials/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /views/partials/nav.ejs: -------------------------------------------------------------------------------- 1 | 2 | 39 | --------------------------------------------------------------------------------