generated from Code-Institute-Org/gitpod-full-template
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscript.js
583 lines (513 loc) · 19.8 KB
/
script.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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
// Global variables
// variable to hold the chosen operators – solution by tutor Roo
let currentChosenOperators = [];
// counter for streak without errors
let streak = 0;
// variable to store and return eval with an indirect call
// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
const geval = eval;
// constants (HTML elements)
// Welcome screen
// the welcome screen `div`
const welcomeSection = document.getElementById('welcome-section');
// the `div` where the gameplay rules are displayed
const gameplayRulesSection = document.getElementById('gameplay-rules-section');
// Start Game button
const startGameButton = document.getElementById('start-game-button');
// the button for showing the score `div`
const scoreButton = document.getElementById('score-button');
// the `div` where the scoring rules are displayed
const scoreRulesSection = document.getElementById('score-rules-section');
// the button for showing the gameplay `div`
const gameplayRulesButton = document.getElementById('gameplay-rules-button');
// Gameplay
// the button for showing the welcome screen again
const welcomeScreenButton = document.getElementById('welcome-screen-button');
// the `div` where the solution/congratulation text is displayed
const solutionDiv = document.getElementById('solution');
// the Check Answer/Submit button
const submitButton = document.getElementById('submit-button');
// the New Game button
const newGameButton = document.getElementById('new-game-button');
// the streak counter at the bottom of the page
const streakCounter = document.getElementById('streak-counter');
// the div containing the perfect streak text and counter at the bottom of the page
const streakDiv = document.getElementById('streak-div');
// Milestones
const milestoneSection = document.getElementById('milestone-section');
// Continue Game button
const continueGameButton = document.getElementById('continue-game-button');
// Event Listeners
// Welcome screen
// when the button is clicked, the `scoreRulesSection` should replace `gameplayRulesSection` on the screen
scoreButton.addEventListener('click',showScoreRulesDiv);
// when the button is clicked, the `gameplayRulesSection` should replace `scoreRulesSection` on the screen
gameplayRulesButton.addEventListener('click',showGameplayRulesDiv);
// hide the Welcome screen
startGameButton.addEventListener('click',startGame);
// Gameplay
// New Game button should start a new game
// add event listener to New Game button to run the `newGame()` function
newGameButton.addEventListener('click',newGame);
// add event listener to Submit button to check the user asnwer
submitButton.addEventListener('click',checkAnswer);
// show the Welcome screen
welcomeScreenButton.addEventListener('click',showWelcomeScreen);
// when the operand selector is changed, add the correcponding number of operand+operator pairs
let operandSelector = document.getElementById('number-selector');
operandSelector.addEventListener('change',setOperandOperatorCount);
// get all operator select forms
let operatorSelectors = document.getElementsByClassName('operator-selector');
// event listeners for `operatorSelectors` are triggered inside `setOperandOperatorCount()`
// Milestones
// hide the Milestone "popup" screen
continueGameButton.addEventListener('click',continueGame);
//Functions
/**
* Hide the `gameplayRulesSection` and show the the `scoreRulesSection`
*/
function showScoreRulesDiv() {
gameplayRulesSection.style.display = 'none';
scoreRulesSection.style.display = 'flex';
}
/**
* Hide the `scoreRulesSection` and show the the `gameplayRulesSection`
*/
function showGameplayRulesDiv() {
scoreRulesSection.style.display = 'none';
gameplayRulesSection.style.display = 'flex';
}
/**
* Hide the `welcomeSection` and show the `welcomeScreenButton`
*/
function startGame() {
welcomeSection.style.display = 'none';
welcomeScreenButton.style.display = 'inline';
}
/**
* Show the `welcomeSection` and hide the `welcomeScreenButton`
*/
function showWelcomeScreen() {
welcomeSection.style.display = 'flex';
welcomeScreenButton.style.display = 'none';
}
// generating and displaying operands (numbers)
/**
*
* Generate an array of random integers, with the array length equal to the lenth of the array generated by `getOperands()`
* @param {number} `bound` the upper bound of the ingeters to be generated (excluding `bound`)
* @return {array} an array of random integers between 0 and `bound`-1
*/
function generateRandomNumbers(bound) {
let randomNumbers = [];
let length = getOperands().length;
for (let i = 0; i<length; i++) {
// generate random integer between 0 and `bound-1`
let randomNumber = Math.floor(Math.random()*bound);
// add the random integer to the array `randomNumbers`
randomNumbers.push(randomNumber);
}
return randomNumbers;
}
/**
*
* Eliminate zero from an array and replace it with a random integer
* @param {array} `array` the input array
* @param {number} `bound` the upper bound of the random integer(s) to be generated (excluding `bound`)
* @return {array} `array` the input array modified not to include zeros
*/
function eliminateZero(array,bound) {
// make sure the number is not 0
for (let i in array) {
if (array.hasOwnProperty(i)) { //`if` statement suggested by mentor to eliminate JSHint warning. Does not change functionality.
while (array[i] === 0) {
array[i] = Math.floor(Math.random()*bound);
}
}
}
return array;
}
/**
* Replace the integers shown on the site with ones from an array of random integers between 0 and `bound` (excl.)
* @param {number} `bound` the upper bound of the range where integers are selected from (not included in the range)
* @return {array} the array of random integers
*/
function showRandomOperands(bound) {
// generate an array of random integers between 1 and `bound`-1
// array length is equal to the number of operands shown on the page
let operandsComputed = eliminateZero(generateRandomNumbers(bound),bound);
let operands = document.getElementsByClassName('operand');
// each operand shown on the page is replaced by a random integer from the array
for (let i = 0; i < operandsComputed.length; i++) { //with the help of tutor Lewis
operands[i].innerHTML = operandsComputed[i]; //with the help of tutor Lewis
}
return operandsComputed;
}
/**
*
* Get the array of operand values from the HTML code.
* This is a separate function because its output is used by multiple other functions, so it needs to be unchanged.
* Getting the output of `showRandomOperands()` would result in a different random array each time it's called
* @return {array} HTML collection https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection
*/
function getOperands() {
// empty array of operand values
let operandValues = [];
// get all elements with the class 'operand'
let operands = document.getElementsByClassName('operand');
// add the values of operands from the HTML to the array `operandValues`
for (let operand of operands) {
operandValues.push(operand.innerHTML);
}
return operandValues;
}
// end of generating and displaying operands (numbers)
/**
* Pick random operators from an array.
* These are not shown to the user.
* @return {array} an array random operators with the length equal to the length of the array of operands
*/
function generateRandomOperators() {
// generate an array of random integers between 0 and 3 (inclusive)
let randomOperatorNumbers = generateRandomNumbers(4);
// the operators the program can chose from
let operators = ["+","-","*","/"];
// the array that will be filled with the randomly chosen operators at each game
let chosenOperators = [];
// get the length of array of operands, and generate 1 less `chosenOperators`
// there should always be 1 less operators than operands
let length = getOperands().length-1;
for (let i = 0; i<length; i++) {
// add an element from `operators`based on the random number
chosenOperators.push(operators[randomOperatorNumbers[i]]);
}
return chosenOperators;
}
/**
* Alternately concatenate the operands shown with an array of operators.
* @param {array} operators the array of operators chosen.
* The operators can be the ones chosen by the user or the randomly generated ones.
* @returns {string} a concatenated string of the operands shown and the operators specified
*/
function concatenateWithOperands(operators) {
// define empty string for user answer
let concatenatedString = '';
// add a dummy operator at the end of the operator array
// to make its length equal to the array of operators
operators.push('X');
// alternately add a number and an operator to the string
let operands = getOperands();
let length = operands.length;
for (let i = 0; i<length; i++) {
concatenatedString += operands[i];
concatenatedString += operators[i];
}
// cut off the dummy operator from the end of the string
concatenatedString = concatenatedString.slice(0, -1);
return concatenatedString;
}
/**
* Show the expected result in the question area
* @return {number} The correct solution of the puzzle
*/
function showResult() {
let result = document.getElementById('result');
result.innerHTML = geval(concatenateWithOperands(currentChosenOperators));
return result.innerHTML;
}
/**
* Get the operators chosen by the user
* @return {array} the array of operators chosen by the user
*/
function getUserOperators() {
// get the array of all operators selected by the user
let userOperators = document.getElementsByClassName('operator-selector');
// empty array to add operator values to
let userOperatorValues = [];
// add each operator value selected by the user to the empty array
for (let i=0; i<userOperators.length; i++) {
userOperatorValues.push(userOperators[i].value);
}
return userOperatorValues;
}
/**
* Check the answer submitted by the user
*/
function checkAnswer() {
// concatenate the operands with the operators selected by the user
let userOperators = getUserOperators();
concatenateWithOperands(userOperators);
// calculate the user answer
checkUserGuess();
// refresh the perfect streak counter
refreshStreakCounter();
// Trigger extra events at certain perfect streak milestones
showMilestones();
}
/**
* Display the result of the user's incorrect guess
*/
function showUserGuess() {
// get the concatenated string of operands and user-selected operators
let userOperators = getUserOperators();
let guess = concatenateWithOperands(userOperators);
// in the text displayed to the user, change "-","*" and "/"
// to "&minus", "×" and "÷", respectively
// ("+" is the same character as "+"", so doesn't need to be changed")
// start out with the concatenated string of operators and operands
let formattedGuess = guess;
// change each character that is "-","*" or "/"
for (let i =0; i < formattedGuess.length; i++) {
formattedGuess = formattedGuess.replace("-", "−");
formattedGuess = formattedGuess.replace("*", "×");
formattedGuess = formattedGuess.replace("/", "÷");
}
// show the user's guess and its result
solutionDiv.innerHTML = `Not quite! Your result is:
<br>
<span class="hilight">${formattedGuess}=${geval(guess)}</span>
<br>
Try changing an operator!`;
}
/**
* Check the user's guess against the correct solution
*/
function checkUserGuess() {
// the user guess based on the operands shown and the operators selected by the user
let userGuess = geval(concatenateWithOperands(getUserOperators()));
// the correct result based on the operands shown and the randomly generated operators
let correctSolution = geval(concatenateWithOperands(currentChosenOperators));
// the `div` containing the solution/congratulation message
if (userGuess === correctSolution) {
// display congratulations message
solutionDiv.innerHTML = `Congratulations, that is correct!
<br>
🎉🎉🎉🎉
`;
// disable Submit button
submitButton.disabled = true;
// increase perfect streak counter
streak += 1;
} else {
// show the result of the user's guess
showUserGuess();
// change text on Submit button
submitButton.textContent = 'Check Again';
// enable Submit button
submitButton.disabled = true;
// reset perfect streak
streak = 0;
}
}
/**
* Clear the `solution` div
*/
function clearSolutionText() {
// set the contents of the `solution` `div` to empty
solutionDiv.innerHTML = '';
}
/**
* Enable the Submit button
* Made into a separate function so that an event listener could trigger it
*/
function enableSubmitButton() {
submitButton.disabled = false;
}
/**
* Refresh the perfect streak counter in the HTML
*/
function refreshStreakCounter() {
// set its content to the value of the global variable `streak`
streakCounter.textContent=streak;
return streakCounter;
}
/**
* Show the `milestoneSection` and hide the `welcomeScreenButton`
*/
function showMilestone() {
milestoneSection.style.display = 'flex';
welcomeScreenButton.style.display = 'none';
}
/**
* Hide the `milestoneSection` and show the `welcomeScreenButton`
*/
function continueGame() {
milestoneSection.style.display = 'none';
welcomeScreenButton.style.display = 'inline';
}
/**
* Trigger extra events at certain perfect streak milestones
*/
function showMilestones() {
// get the message on the milestone div
let streakText = milestoneSection.children[0];
// get the `span` for the streak count
let streakDisplay = milestoneSection.children[0].children[0];
// color variable for the streakText background color and the color of streakText
let color;
// color variable for the Continue Game button
let buttonColor;
switch (streak) {
case 0:
//change the color of the streak div back to default
streakDiv.style.backgroundColor='var(--white)';
break;
case 3:
// set streak milestone color
color = 'var(--med-blue)';
buttonColor = 'var(--light-orange)';
// show the `milestoneSection`
showMilestone();
// show streak count on `milestoneSection`
streakDisplay.textContent = streak;
// change background color of the milestone "popup" text
streakText.style.backgroundColor=color;
// change background color of Continue button
continueGameButton.style.backgroundColor = buttonColor;
// change background background color of the streak div
streakDiv.style.backgroundColor = buttonColor;
// run new game after milestone "popup"
newGame();
break;
case 5:
// set streak milestone color
color = 'var(--red)';
buttonColor = 'var(--light-blue)';
// show the `milestoneSection`
showMilestone();
// show streak count on `milestoneSection`
streakDisplay.textContent = streak;
// change background color of the milestone "popup" text
streakText.style.backgroundColor=color;
// change color of Continue button
continueGameButton.style.backgroundColor = buttonColor;
// change background color of the streak div
streakDiv.style.backgroundColor = buttonColor;
// run new game after milestone "popup"
newGame();
break;
case 10:
// set streak milestone color
color = 'var(--med-orange)';
buttonColor = 'var(--peach)';
// show the `milestoneSection`
showMilestone();
// show streak count on `milestoneSection`
streakDisplay.textContent = streak;
// change background color of the milestone "popup" text
streakText.style.backgroundColor=color;
// change background color of Continue button
continueGameButton.style.backgroundColor = buttonColor;
// change background color of the streak div
streakDiv.style.backgroundColor = buttonColor;
// run new game after milestone "popup"
newGame();
break;
}
}
/**
* Get the number of operands from the drop-down selector
* @returns {number}
*/
function getOperandNumber() {
let operandNumber = document.getElementById('number-selector').value;
return operandNumber;
}
/**
* Adjust the instruction text in the game area depending on the number of operands:
* singular "operator" for 2 operands, plural "operators" for 3 or more operands
*/
function adjustInstructions() {
// get the `game-area` from the HTML
let gameArea = document.getElementById('game-area');
// the first paragraph within it
let instructions = gameArea.children[0];
// the number of operands chosen by the user
let num = getOperandNumber();
if (num == 2) { // this needs to be `==`, not `====`, type conversion needed
instructions.innerHTML = 'Choose the correct operator to get the result shown';
} else {
instructions.innerHTML = 'Choose the correct operators to get the result shown';
}
}
/**
* Adjust the number of operators and operands based on the value of the operand drop-down selector
*/
function setOperandOperatorCount() {
let questionDiv = document.getElementById('question-area');
// the first operand shown
let operand1 = questionDiv.children[0];
// the first operator drop-down shown
let operator1 = questionDiv.children[1];
// the value of the operand selector
let num = getOperandNumber();
while (getOperands().length < num){
// 2 operands appear by default, so:
// iterate 2 times less than the number specified by the drop-down
for (let i=2; i<num; i++) {
// clone the first operand node
let newOperand = operand1.cloneNode(true);
// clone the first operator node
let newOperator = operator1.cloneNode(true);
// add operator to the beginning of the div
// has to be added before adding the operand
questionDiv.insertBefore(newOperator, questionDiv.children[0]);
// add operand to the beginning of the div
// has to be added after adding the operator
questionDiv.insertBefore(newOperand, questionDiv.children[0]);
}
}
// if the number of operands is larger than the value of the operand selector
while (getOperands().length > num) {
// remove the first operator drop-down
questionDiv.children[1].remove();
// remove the first operand
questionDiv.children[0].remove();
}
// adjust the instruction text based on the number of operands
adjustInstructions();
// start a new game after changing the number of operands
newGame();
}
/**
* Run a new game
*/
function newGame() {
// clear the `solution` text
clearSolutionText();
// enable Submit button again
submitButton.disabled = false;
// Submit button should have initial text
submitButton.textContent='Check Answer';
// add event listener to each operator select form:
// when any of them are changed, the solution text is cleared
// IMPORTANT: this has to be here instead of the beginning of the file,
// otherwise the event listeners are only added to the operator drop-downs that are present in the HTML
for (let i of operatorSelectors) {
i.addEventListener('change',clearSolutionText);
i.addEventListener('change',enableSubmitButton);
}
// set operator menus back to '+'
for (let i of operatorSelectors) {
i.value = ('+');
}
// show operands in the question area
showRandomOperands(11);
// reset operator numbers
currentChosenOperators = [];// with the help of tutor Roo (refactored)
currentChosenOperators = generateRandomOperators(); // with the help of tutor Roo
// show the operators in the console log for development purposes
// ASSESSMENT uncomment the next line for testing/assessment
// console.log(currentChosenOperators);
// show the expected result of the calculation in the question area
showResult();
// show the correct result in the console log for development purposes
// ASSESSMENT uncomment the next line for testing/assessment
// console.log('the correct solution:',concatenateWithOperands(currentChosenOperators)+'='+geval(concatenateWithOperands(currentChosenOperators)));
// the result should not have more than 2 decimal places
while (Number.isInteger(showResult()*100) === false) {
newGame();
}
}
// run a new game when refreshing the page
newGame();