-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsocket.js
309 lines (267 loc) · 11.7 KB
/
socket.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
Author: UP805717 (springjben)
Current Version: 3.2.1 (<major>.<minor>.<maintenance>)
Documentation:
To start a quiz: http://127.0.0.1:8000/quiz/start/{PIN} (use pin 9876 for demonstration purposes)
To finish a quiz: http://127.0.0.1:8000/quiz/finish/{PIN} (use pin 9876 for demonstration purposes)
Version Control:
3.2.1 - (Commit on March 15th) Maintenace, added documentation and cleaned up the code.
3.2.0 - More minor code added to allow the users to answer questions properly and without cheating.
3.1.0 - Minor bugs fixed, timer slider property, scoreboard scores work
3.0.0 - (Commit on March 3rd) Another major update, fixed a big timing issue, multiple users can play the quiz at the same time.
2.0.0 - (Commit on March 2nd) Major updates, the server shows all the questions to the user and redirects them at the end of the quiz. Quiz "states" implemented.
1.1.0 - (Commit on Feb 15th) Minor updates, database and node server can pass data to each other, the questions are passed to the client.
1.0.0 - (Commit on Feb 12th) Major update, server and client can speak to each other dynamically, database queries successfully get the correct quiz data
0.1.2 - (Commit on Feb 8th) Bugs fixed, such as PHP events firing off.
0.1.2 - Minor update, built the structure of the document (got sockets working) and got node communicating witht the MySQL database. (app/Socket/database.js)
0.0.1 - All files added, all packages installed (Redis, Socket.io and Express) and updated.
This version control part has only been added on the last commit as it was originally on a google document (but I moved it here so you can see it easier).
*/
/*
Required librays that are needed
*/
//Used for passing data from Laravel framework to a node server
var Redis = require('ioredis');
var redis = new Redis();
//Used to create a basic HTTP server in node
var express = require('express');
var app = express();
//Used to dynamically communicate with the users in the quiz (using web sockets)
var socketIO = require('socket.io')
let server = app.listen(3000)
var io = socketIO(server)
//Getting a file I wrote with methods to communicate with the MySQL database
let database = require('./app/Sockets/database.js')
database.init()
/*
Global server variables
*/
let users = []
let quizData = {}
let index = 0
const secondsPerQuestion = 7 //How many seconds to show per quiz (Change back to 15 seconds!)
const serverOffset = 6 //This means after the quiz has ended
//Relative arrays containing objects from the database
let quiz = {
pin: 0,
state: "Not_Playing", //States: Not_Playing, Lobby, Playing, Ended
questions : [],
answers: []
}
/*
Main server method and functionality
*/
//This gets redis to subscrive to the "questions" channel. A PHP event gets fired off and communicates to node through this channel.
redis.subscribe('questions', function(err, count) {
console.log('Listening for questions')
});
let loop; //A global variable for the main game loop
let resetGame = false //This variable is used to reset the client side info
//If an event is fired (from the questions redis channel) and is apart of the message room, it will output the messages sent here.
redis.on('message', async function(channel, message) {
message = JSON.parse(message);
//Determines if the quiz host starts of stops the game
quiz.pin = message.data.questionData.pin
let method = message.data.questionData.method
if (method == "start") {
quiz.state = "Playing"
//If the quiz host has started the game, get all quiz questions and answers and redirect all the users.
database.getQuizData(quiz.pin).then(result => {
reset() //Reset all the relevant information
quizData = result
io.emit('showQuestion', quizData[index]['data']) //Using sockets to dynamically communcate with the users.
loop=setInterval(passQuestion, ((secondsPerQuestion+serverOffset) * 1000)); //This loops every configured second
})
io.emit('redirect', '/question') //Once the quiz host starts the game, it redirects everyone from the 'splash' page to the 'question' page
} else if (method == "finish") {
//If the quiz host wants to finish the game it updates the quiz information and redirects all its users to the homepage.
quiz.state = "Ended"
quiz.questions = []
quiz.answers = []
users = [] //Removing all users from lobby
io.emit('redirect', '/')
}
});
/*
This is for the main functionality of the quizzes using web sockets.
If the user connects to the server (happens automatically once visiting the /splash or /questions page), validates the user.
*/
io.on('connection', function(client) {
//Upon joining they will be shown the latest question
if (quizData.length > 0 || quiz.state == "Playing") {
runQuestion(users)
}
//If the users browser (using sockets) makes a call to the server, it will reply with the leaderboard results.
client.on('getLeaderboards', function(data) {
client.emit('displayLeaderBoards', {users: users})
})
//If the users browser requests the results for the current question, it will reply with the results for the question
client.on('getResults', function(data) {
client.emit('showResults', {users: users, correct: currentCorrect})
})
//When a user answers a question within the quiz, it gets logged server side.
client.on('answer', function(data) {
userAnswer(data.answer, data.id)
})
//If a new user joins the quiz (client-side), it will add their details to a global users array.
client.on('addUser', async function(data) {
let newUser = {
id: data.id,
username: data.name, //User different usernames. This needs some validation
answeredQuestion: false,
questionsCorrect: 0,
questionsIncorrect: 0,
timeAnsweredIn: 0,
lastQuestionCorrect: false
}
users.push(newUser)
let info = await getUserInfo(users, data.id) //This gets the users relevent information.
client.emit('userInfo', {allUsers: info.allUsernames, username: info.yourUsername}) //Once the user has been added, the server gives the relevent information back to the user
})
/*
This is some validation to ensure the user has been added to the server users array.
If they have not been added, it will force the user to pick a username (otherwise they can't play the quiz).
The client gets a cookie created to make sure that our product remembers them. Its the way I am validating the user every time.
*/
client.on('validateUser', async function(data) {
let info = await getUserInfo(users, data.id) //Has the user already played before and is in the users array?
if (info.validUser == false) {
client.emit('signUserUp', {}) //If they have no entered their information, force them to
} else {
client.emit('userInfo', {allUsers: info.allUsernames, username: info.yourUsername}) //If the user has played before and we have their information, simply pass the information back to the client
}
})
})
let timer; //Stores the time left for a question to be answered, this stops users cheating the system and getting more time.
/*
This method is called when a new question needs to be displayed to the user.
Once the timer is up, it moves onto the next question left in the database.
*/
function passQuestion() {
quiz.state = "Playing"
io.emit('resetQuestion', {})
resetUserQuestion()
//If its the last question, clear the interval.
timer = new Date();
timer.setSeconds(timer.getSeconds() + secondsPerQuestion);
index++
//If quiz questions are just 1 or more if will display, even if the user refreshes the page.
//This checks to see if its the last question in in the quiz. If it is, it will terminate the loop and reset everything.
console.log("length", quizData.length, index)
if ((quizData.length-1) <= (index)) {
console.log("2 No more questions, the game is over.")
runQuestion()
reset()
outputQuizResults()
} else if (quizData.length > 1) {
runQuestion()
} else {
reset()
console.log("1 No more questions, the game is over.")
}
}
let currentCorrect; //Stores the correct answer for each quiz question
/*
This method just renders all the correct information to the user connecting.
If the user refreshes the page (or joins late), it will show the user the correct question (the same for every user).
This is also a way to stop users cheating or accidentally refreshing the page.
*/
function runQuestion() {
//A try and catch incase anything goes wrong with the questions.
try {
//If the timer hasn't been created, it gets created and passed to all users connected.
if (typeof timer == "undefined") {
timer = new Date();
timer.setSeconds(timer.getSeconds() + secondsPerQuestion);
}
quizData[index]['data'].timer = timer
currentCorrect = quizData[index]['ans-correct']
io.emit('showQuestion', quizData[index]['data']) //Passes the relevant question information to the user
} catch(e) {
//If a problem does occur, it needs to reset the quiz data and report the error.
console.log("Problem with indexing ", e)
reset()
}
}
/*
This method displays all of the final results to the user
*/
function outputQuizResults() {
//Display all the results
setTimeout(function(){
//io.emit('displayScoreBoard', users)
io.emit('redirect', '/leaderboards')
}, (secondsPerQuestion+(serverOffset-1)) * 1000);
//After 10 seconds from showing the user the scores, reset the game entirly (the data only gets removed twhen the user refreshes their page)
setTimeout(function(){
users = []
quiz.state = "Not_Playing"
}, ((secondsPerQuestion+(serverOffset-1))+ 10) * 1000); //Times out 10 seconds after redirect
}
/*
This method resets all of the games information, ready for the next quiz that wants to be played
*/
function reset() {
console.log("Resetting the game...")
clearInterval(loop) //Clears the loop.
quizData = {}
index = 0
timer = undefined
quiz.state = "Ended" //Sets quiz state to ended
}
/*
This method is called when the user answers a question on the quiz.
It updates the users score and determines if they have the answer correct or not.
*/
function userAnswer(answer, id) {
for (let i = 0; i < users.length; i++) {
if (users[i].id == id && users[i].answeredQuestion == false) {
if (answer == currentCorrect) {
users[i].questionsCorrect += 1
users[i].lastQuestionCorrect = true
} else {
users[i].questionsIncorrect += 1
}
users[i].answeredQuestion = true
}
}
}
/*
This method is called when a new question in the quiz is shown.
It resets the users last played answer.
This is ensures a user cannot cheat by not answering a question. If they do not answer before the timer is up, it will mark them down as getting the question incorrect.
*/
function resetUserQuestion() {
for (let i = 0; i < users.length; i++) {
if (users[i].answeredQuestion == false) {
users[i].questionsIncorrect += 1
} else {
users[i].answeredQuestion = false
users[i].lastQuestionCorrect = false
}
}
}
/*
This method retrives a particular users current information
After is has got their data, it creates an object and returns that object.
*/
async function getUserInfo(users, id) {
let validUser = false;
let allUsernames = [];
let yourUsername;
//Retrive relevant user (by their ID which is stored and passed from their cookie server side)
for (let i = 0; i < users.length; i++) {
allUsernames.push(users[i].username)
if (users[i].id == id) {
validUser = true
yourUsername = users[i].username
}
}
//New object is created with their relevant information and is returned
let returnVal = {
validUser: validUser,
allUsernames: allUsernames,
yourUsername: yourUsername
}
return returnVal
}