├── .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 | #  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 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Submit
47 |
48 |
49 |
50 |
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 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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 | Name
22 | Score
23 |
24 |
25 |
26 |
27 | ttrinh
28 | 2000
29 |
30 |
31 | bvtrinh
32 | 1900
33 |
34 |
35 | andy
36 | 1500
37 |
38 |
39 | kevin
40 | 1000
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Guess the Song!
51 |
52 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
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 |
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 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
Step 1
85 |
Create an account
86 |
87 |
88 |
89 |
90 |
91 |
Step 2
92 |
Login
93 |
94 |
95 |
96 |
97 |
98 |
Step 3
99 |
Select game mode, single player or multiplayer
100 |
101 |
102 |
103 |
104 |
105 |
Step 4
106 |
Listen to the music clip
107 |
108 |
109 |
110 |
111 |
112 |
Step 5
113 |
Select/enter the right answer fast for more points
114 |
115 |
116 |
117 |
118 |
119 |
120 | Previous
121 |
122 |
123 |
124 | Next
125 |
126 |
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 |
26 |
27 |
28 |
31 |
32 |
33 |
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 |
32 |
33 |
34 | selected <%}%> >Overall
35 | <% genres.forEach(function(genre) { %>
36 | <% if(selected == genre){ %>selected <%}%>><%=genre%>
37 | <% }); %>
38 |
39 |
40 |
41 |
42 |
43 |
44 | Rank
45 | Score
46 | Username
47 | Genre
48 |
49 |
50 |
51 | <% if(data){
52 | var i=1;
53 | for(var d of data){ %>
54 |
55 | <%= i %>
56 | <%= d.score %>
57 | <%= d.username %>
58 | <%= d.genre %>
59 | <% i++; %>
60 |
61 | <% }} %>
62 |
63 |
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 | Name
62 | Ready/Unready
63 |
64 |
65 |
66 |
67 | name
68 | hi
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Genre
82 | Pop
83 | Rap
84 | Country
85 | Hip Hop
86 | Rock
87 | Trap
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Room code:
96 | 1A1A1A
97 |
98 |
99 |
100 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
Chat
116 |
117 |
118 |
119 |
121 | Send
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Ready
132 |
133 |
134 |
135 |
136 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
Players
158 |
159 |
160 |
161 | Name
162 | Score
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | Guess the Song!
175 |
176 |
177 |
178 |
179 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
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 | Name
236 | Score
237 |
238 |
239 |
240 |
241 | name
242 | 50
243 |
244 |
245 |
246 |
247 |
248 |
249 |
Song List
250 |
251 |
252 |
253 | Round
254 | Song Name
255 | Artist
256 |
257 |
258 |
259 |
260 | 1
261 | hi
262 |
263 |
264 |
265 | 2
266 | hi
267 |
268 |
269 |
270 | 3
271 | hi
272 |
273 |
274 |
275 | 4
276 | hi
277 |
278 |
279 |
280 | 5
281 | hi
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 | Play Again
294 |
295 |
296 |
297 |
298 |
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 |
14 |
15 |
16 |
Sign In
17 |
18 | <% if(errors){
19 | for(var e of errors){ %>
20 | <%= e.msg %>
21 | <% }} %>
22 |
23 |
41 |
42 |
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 |
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 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
52 |
53 |
54 |
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 |
32 |
33 |
34 | selected <%}%>>Recent Scores
35 | selected <%}%>>Overall Stats
36 | <% genres.forEach(function(genre) { %>
37 | <% if(selected == genre){ %>selected <%}%>><%=genre%>
38 | <% }); %>
39 |
40 |
41 |
42 |
43 |
44 |
45 | <% if(selected == "Recent Scores"){ %>
46 | Date
47 | Score
48 | Genre
49 | Mode
50 | <% } else { %>
51 | Total Score
52 | Games Played
53 | Average Score
54 | <% } %>
55 |
56 |
57 |
58 | <% if(selected == "Recent Scores"){
59 | for (let i=0; i < results.length; i++){ %>
60 |
61 | <%=results[i].dateplayed.toString().substr(0,results[i].dateplayed.toString().indexOf(' GMT'))%>
62 | <%=results[i].score%>
63 | <%=results[i].genre%>
64 | <%=results[i].mode%>
65 |
66 | <% }} else {
67 | for (let i=0; i < results.length; i++){ %>
68 |
69 | <%=results[i].total%>
70 | <%=results[i].games%>
71 | <%=Math.round(results[i].total/results[i].games)%>
72 |
73 | <% }}%>
74 |
75 |
76 |
77 |
78 |
79 |
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 |
17 |
18 |
Reset Password
19 |
20 | <% if(errors){
21 | for(var e of errors){ %>
22 | <%= e.msg %>
23 | <% }} %>
24 |
25 |
44 |
45 |
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 |
14 |
15 |
Sign Up
16 |
17 |
18 | <% if(errors){
19 | for(var e of errors){ %>
20 | <%= e.msg %>
21 | <% }} %>
22 |
23 |
24 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/views/partials/game_nav.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tuning
7 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 | <%=genre%>
23 |
24 |
25 |
26 |
28 |
29 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/views/partials/header.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/views/partials/nav.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tuning
7 |
9 |
10 |
11 |
12 |
13 |
18 |
19 | <% if (typeof username !== 'undefined') { %>
20 |
21 |
22 | <%=username%>
23 |
24 |
25 | Logout
26 |
27 | <% } else { %>
28 |
29 | Sign Up
30 |
31 |
32 | Login
33 |
34 | <% } %>
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------