diff --git a/db.json b/db.json index bb690b6..4ca9439 100644 --- a/db.json +++ b/db.json @@ -1,39 +1,106 @@ { "theTriviaApi": [ { - "category": "society_and_culture", - "id": "622a1c367cc59eab6f950408", - "correctAnswer": "Rosa Parks", + "category": "sport_and_leisure", + "id": "62417da50f96c4efe8d773db", + "correctAnswer": "San Francisco 49ers", "incorrectAnswers": [ - "Angela Davis", - "Dorothy Cotton", - "Fanny Lou Hamer" + "San Francisco Jaguars", + "San Francisco Pistons", + "San Francisco Predators" ], "question": { - "text": "Which civil right activist is famous for refusing to give up her seat on a bus to make way for a white person?" + "text": "Which of these is an American Football team based in San Francisco?" }, "tags": [ - "general_knowledge", - "people", - "society_and_culture" + "sport" ], "type": "text_choice", "difficulty": "medium", "regions": [], "isNiche": false + }, + { + "category": "history", + "id": "622a1c367cc59eab6f9503ac", + "correctAnswer": "Germany", + "incorrectAnswers": [ + "Switzerland", + "Denmark", + "France" + ], + "question": { + "text": "Which country was first to operate an old age pension scheme?" + }, + "tags": [ + "history" + ], + "type": "text_choice", + "difficulty": "hard", + "regions": [], + "isNiche": false + }, + { + "category": "arts_and_literature", + "id": "649b4f4f828109028d236251", + "correctAnswer": "A Spider", + "incorrectAnswers": [ + "A Pig", + "A Sheep", + "A Horse" + ], + "question": { + "text": "In E.B. White's classic children's book \"Charlotte's Web\", what kind of animal is Charlotte?" + }, + "tags": [ + "animals", + "childrens_literature", + "arts_and_literature", + "literature" + ], + "type": "text_choice", + "difficulty": "easy", + "regions": [], + "isNiche": false } ], "openTriviaDb": { "response_code": 0, "results": [ { - "type": "Ym9vbGVhbg==", + "type": "bXVsdGlwbGU=", + "difficulty": "bWVkaXVt", + "category": "SGlzdG9yeQ==", + "question": "V2hlbiBkaWQgdGhlIENyaXNpcyBvZiB0aGUgVGhpcmQgQ2VudHVyeSBiZWdpbj8=", + "correct_answer": "MjM1IEFE", + "incorrect_answers": [ + "MjM1IEJD", + "MjQyIEFE", + "MjEwIEFE" + ] + }, + { + "type": "bXVsdGlwbGU=", + "difficulty": "bWVkaXVt", + "category": "RW50ZXJ0YWlubWVudDogVmlkZW8gR2FtZXM=", + "question": "V2hpY2ggb2YgdGhlc2UgQ291bnRlci1TdHJpa2UgbWFwcyBpcyBhIGJvbWIgZGVmdXNlIHNjZW5hcmlvPw==", + "correct_answer": "UHJvZGlneQ==", + "incorrect_answers": [ + "NzQ3", + "SGF2YW5h", + "T2lscmln" + ] + }, + { + "type": "bXVsdGlwbGU=", "difficulty": "bWVkaXVt", - "category": "RW50ZXJ0YWlubWVudDogSmFwYW5lc2UgQW5pbWUgJiBNYW5nYQ==", - "question": "VGhlIGFuaW1hdGVkIGZpbG0gIlNwaXJpdGVkIEF3YXkiIHdvbiB0aGUgQWNhZGVteSBBd2FyZCBmb3IgQmVzdCBBbmltYXRlZCBGZWF0dXJlIGF0IHRoZSA3NXRoIEFjYWRlbXkgQXdhcmRzIGluIDIwMDMu", - "correct_answer": "VHJ1ZQ==", + "category": "RW50ZXJ0YWlubWVudDogVmlkZW8gR2FtZXM=", + "question": "V2hhdCBpcyB0aGUgd29ybGQncyBmaXJzdCB2aWRlbyBnYW1lIGNvbnNvbGU/", + "correct_answer": "TWFnbmF2b3ggT2R5c3NleQ==", "incorrect_answers": [ - "RmFsc2U=" + "Q29sZWNvIFRlbHN0YXI=", + "TmludGVuZG8gQ29sb3IgVFYgR2FtZQ==", + "QXRhcmkgMjYwMA==" ] } ] @@ -70,6 +137,71 @@ ], "category": "Code", "difficulty": "Easy" + }, + { + "id": 1, + "question": "How to delete a directory in Linux?", + "description": "delete folder", + "answers": { + "answer_a": "ls", + "answer_b": "delete", + "answer_c": "remove", + "answer_d": "rmdir", + "answer_e": null, + "answer_f": null + }, + "multiple_correct_answers": "false", + "correct_answers": { + "answer_a_correct": "false", + "answer_b_correct": "false", + "answer_c_correct": "false", + "answer_d_correct": "true", + "answer_e_correct": "false", + "answer_f_correct": "false" + }, + "explanation": "rmdir deletes an empty directory", + "tip": null, + "tags": [], + "category": "linux", + "difficulty": "Easy" + }, + { + "id": 3, + "question": "How to check the current disk usage on Linux?", + "description": "check current disk usage", + "answers": { + "answer_a": "df", + "answer_b": "usage", + "answer_c": "uptime", + "answer_d": "free", + "answer_e": null, + "answer_f": null + }, + "multiple_correct_answers": "false", + "correct_answers": { + "answer_a_correct": "true", + "answer_b_correct": "false", + "answer_c_correct": "false", + "answer_d_correct": "false", + "answer_e_correct": "false", + "answer_f_correct": "false" + }, + "correct_answer": "answer_a", + "explanation": "df shows you the current disk usage", + "tip": "df", + "tags": [ + { + "name": "Linux" + }, + { + "name": "BASH" + }, + { + "name": "cmd" + } + ], + "category": "uncategorized", + "difficulty": "easy" } ] } diff --git a/package.json b/package.json index 0eed12e..4237d7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xpquiz.github.io", - "version": "1.4.1", + "version": "1.5.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/about-window/about-window.component.html b/src/app/about-window/about-window.component.html index 3995a74..921446f 100644 --- a/src/app/about-window/about-window.component.html +++ b/src/app/about-window/about-window.component.html @@ -1,12 +1,13 @@
- + + href="https://opentdb.com/">Open Trivia Database and QuizAPI @@ -14,7 +15,45 @@ href="https://www.deviantart.com/marchmountain/art/Windows-XP-High-Resolution-Icon-Pack-916042853">marchmountain at DeviantArt - +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+
+
+
+ diff --git a/src/app/about-window/about-window.component.sass b/src/app/about-window/about-window.component.sass index 7ebffdb..4546310 100644 --- a/src/app/about-window/about-window.component.sass +++ b/src/app/about-window/about-window.component.sass @@ -1,15 +1,69 @@ -.window-body - display: flex - flex-direction: column - align-items: center - justify-content: center +.window + margin: 10px - label - margin: 3px + &-body + display: flex + flex-direction: column + align-items: center + justify-content: center - &.main - margin-bottom: 10px + label + margin: 3px + + &.main + margin-bottom: 10px + font-style: italic + + &_buttons + display: flex + flex-direction: row + align-items: center + justify-content: center + margin: 5px + + app-icon-button + margin: 5px + + label.window-body_feedback-title font-style: italic + margin-bottom: 10px + + &_feedback-inputs + display: flex + flex-direction: column + align-items: center + justify-content: center + margin: 10px + width: 300px + + .field-row + width: 100% + + textarea + border: 1px solid #7f9db9 + + label + width: 25% + display: block + text-align: end + align-self: flex-start + + input, textarea + width: 75% + + &_feedback-inputs-error + color: red + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_feedback-buttons + display: flex + flex-direction: row + align-items: center + justify-content: center + margin: 5px - app-icon-button - margin-top: 15px + app-icon-button + margin: 5px diff --git a/src/app/about-window/about-window.component.ts b/src/app/about-window/about-window.component.ts index ec3d417..f8c112b 100644 --- a/src/app/about-window/about-window.component.ts +++ b/src/app/about-window/about-window.component.ts @@ -1,6 +1,7 @@ import {Component} from '@angular/core'; import {Router} from "@angular/router"; import {PathsEnum} from "../../model/enums/PathsEnum"; +import {AbstractControl, FormBuilder, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-about-window', @@ -9,12 +10,42 @@ import {PathsEnum} from "../../model/enums/PathsEnum"; }) export class AboutWindowComponent { + protected readonly PathsEnum = PathsEnum; + + public showFeedbackWindow: boolean = false; + public feedbackForm: FormGroup = this.formBuilder.group({ + name: ['', Validators.required], + message: [undefined, Validators.required] + }); + constructor( - private readonly router: Router + public readonly router: Router, + public readonly formBuilder: FormBuilder ) { } - public async returnHome(): Promise { - await this.router.navigateByUrl(PathsEnum.HOME); + protected toggleFeedbackWindow(): void { + this.feedbackForm.reset(); + this.showFeedbackWindow = !this.showFeedbackWindow; + } + + public async onSubmit(): Promise { + const subject: string = `XPQuiz - Suggestions from ${this.feedbackForm.controls['name'].value}`; + const body: string = encodeURI(this.feedbackForm.controls['message'].value) + + window.location.href = `mailto:xpquiz.github.io@gmail.com?subject=${subject}&body=${body}`; + } + + public shouldDisableSendButton(): boolean { + return !this.feedbackForm.valid; + } + + public shouldShowErrorMessage(formControlName: string): boolean { + const formControl: AbstractControl = this.feedbackForm.controls[formControlName]; + + if (!this.feedbackForm.touched && !formControl.touched) + return false; + + return !formControl.valid; } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4a8fb88..0d41511 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,6 +7,8 @@ import {PathsEnum} from "../model/enums/PathsEnum"; import {CorrectAnswerWindowComponent} from "./correct-answer-window/correct-answer-window.component"; import {WrongAnswerWindowComponent} from "./wrong-answer-window/wrong-answer-window.component"; import {AboutWindowComponent} from "./about-window/about-window.component"; +import {GameModeWindowComponent} from "./game-mode-window/game-mode-window.component"; +import {QuestionTrifectaWindowComponent} from "./question-trifecta-window/question-trifecta-window.component"; const routes: Routes = [ { @@ -27,15 +29,23 @@ const routes: Routes = [ component: ScoreWindowComponent }, { - path: PathsEnum.QUIZ, + path: PathsEnum.GAME_MODE, + component: GameModeWindowComponent, + }, + { + path: `${PathsEnum.QUIZ_NORMAL}`, component: QuestionWindowComponent, }, { - path: `${PathsEnum.CORRECT_ANSWER}/:result`, + path: `${PathsEnum.QUIZ_TRIFECTA}`, + component: QuestionTrifectaWindowComponent, + }, + { + path: `${PathsEnum.CORRECT_ANSWER}/:mode/:result`, component: CorrectAnswerWindowComponent, }, { - path: `${PathsEnum.WRONG_ANSWER}/:result`, + path: `${PathsEnum.WRONG_ANSWER}/:mode/:result`, component: WrongAnswerWindowComponent } ] diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 220cfc0..11a3c98 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,6 +14,10 @@ import { CorrectAnswerWindowComponent } from './correct-answer-window/correct-an import { WrongAnswerWindowComponent } from './wrong-answer-window/wrong-answer-window.component'; import { AboutWindowComponent } from './about-window/about-window.component'; import {CopyClipboardDirective} from "./directives/CopyClipboardDirective"; +import { GameModeWindowComponent } from './game-mode-window/game-mode-window.component'; +import { IconTextButtonComponent } from './common/icon-text-button/icon-text-button.component'; +import { QuestionTrifectaWindowComponent } from './question-trifecta-window/question-trifecta-window.component'; +import {ReactiveFormsModule} from "@angular/forms"; @NgModule({ declarations: [ @@ -26,13 +30,17 @@ import {CopyClipboardDirective} from "./directives/CopyClipboardDirective"; CorrectAnswerWindowComponent, WrongAnswerWindowComponent, AboutWindowComponent, - CopyClipboardDirective + CopyClipboardDirective, + GameModeWindowComponent, + IconTextButtonComponent, + QuestionTrifectaWindowComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule, - NgOptimizedImage + NgOptimizedImage, + ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/common/icon-text-button/icon-text-button.component.html b/src/app/common/icon-text-button/icon-text-button.component.html new file mode 100644 index 0000000..423d56c --- /dev/null +++ b/src/app/common/icon-text-button/icon-text-button.component.html @@ -0,0 +1,7 @@ + diff --git a/src/app/common/icon-text-button/icon-text-button.component.sass b/src/app/common/icon-text-button/icon-text-button.component.sass new file mode 100644 index 0000000..f82da82 --- /dev/null +++ b/src/app/common/icon-text-button/icon-text-button.component.sass @@ -0,0 +1,13 @@ +button + display: flex + flex-direction: column + align-items: center + justify-content: center + height: 130px + width: 170px + + img + margin: 5px + + label + font-weight: bold diff --git a/src/app/common/icon-text-button/icon-text-button.component.spec.ts b/src/app/common/icon-text-button/icon-text-button.component.spec.ts new file mode 100644 index 0000000..753ad7b --- /dev/null +++ b/src/app/common/icon-text-button/icon-text-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IconTextButtonComponent } from './icon-text-button.component'; + +describe('IconTextButtonComponent', () => { + let component: IconTextButtonComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [IconTextButtonComponent] + }); + fixture = TestBed.createComponent(IconTextButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/icon-text-button/icon-text-button.component.ts b/src/app/common/icon-text-button/icon-text-button.component.ts new file mode 100644 index 0000000..2ab149e --- /dev/null +++ b/src/app/common/icon-text-button/icon-text-button.component.ts @@ -0,0 +1,27 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; + +@Component({ + selector: 'app-icon-text-button', + templateUrl: './icon-text-button.component.html', + styleUrls: ['./icon-text-button.component.sass'] +}) +export class IconTextButtonComponent { + + @Input() + public iconPath: string = ''; + @Input() + public title: string = ''; + @Input() + public description: string = ''; + @Input() + public disabled: boolean = false; + @Output() + public onButtonClick: EventEmitter = new EventEmitter(); + + public readonly iconSize: number = 30; + + public onClick(): void { + this.onButtonClick.emit(); + } + +} diff --git a/src/app/correct-answer-window/correct-answer-window.component.html b/src/app/correct-answer-window/correct-answer-window.component.html index c2826ee..68afc07 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.html +++ b/src/app/correct-answer-window/correct-answer-window.component.html @@ -3,14 +3,14 @@
- +
+ (onButtonClick)="this.router.navigateByUrl(PathsEnum.HOME);">
diff --git a/src/app/correct-answer-window/correct-answer-window.component.ts b/src/app/correct-answer-window/correct-answer-window.component.ts index c0bfdbf..d38d57e 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.ts +++ b/src/app/correct-answer-window/correct-answer-window.component.ts @@ -4,7 +4,8 @@ import {PathsEnum} from "../../model/enums/PathsEnum"; import {AppStorageService} from "../../service/app-storage.service"; import {EncryptionService} from "../../service/encryption.service"; import {TemplateService} from "../../service/template.service"; -import {QuestionResultTemplateParams, TemplateEnum} from "../../model/Template"; +import {QuestionResultTemplateParams, QuestionResultTrifectaTemplateParams} from "../../model/Template"; +import {GameMode} from "../../model/enums/GameModesEnum"; @Component({ selector: 'app-correct-answer-window', @@ -13,6 +14,8 @@ import {QuestionResultTemplateParams, TemplateEnum} from "../../model/Template"; }) export class CorrectAnswerWindowComponent implements OnInit { + protected readonly PathsEnum = PathsEnum; + public questionScore: number = 0; public clipboardText: string = ''; public displayClipboardMessage: boolean = false; @@ -20,7 +23,7 @@ export class CorrectAnswerWindowComponent implements OnInit { private correctAnswerSound: HTMLAudioElement = new Audio('assets/sounds/tada.wav'); constructor( - private readonly router: Router, + protected readonly router: Router, private readonly route: ActivatedRoute, private readonly encryptionService: EncryptionService, private readonly templateService: TemplateService, @@ -32,7 +35,7 @@ export class CorrectAnswerWindowComponent implements OnInit { await this.retrieveRouteParams(); if (!this.appStorageService.canQuizBeAnswered()) { - await this.returnHome(); + await this.router.navigateByUrl(PathsEnum.HOME); return; } @@ -40,10 +43,6 @@ export class CorrectAnswerWindowComponent implements OnInit { this.saveCurrentScore(); } - public async returnHome(): Promise { - await this.router.navigateByUrl(PathsEnum.HOME); - } - public async showClipboardMessage(): Promise { this.displayClipboardMessage = true; @@ -53,15 +52,17 @@ export class CorrectAnswerWindowComponent implements OnInit { } private saveCurrentScore(): void { - this.appStorageService.saveAnswer(true, this.questionScore); + this.appStorageService.saveAnswer(true, this.questionScore, 3); } private async retrieveRouteParams(): Promise { + const routeGameModeTitle: string = this.route.snapshot.paramMap.get('mode')!; const encryptedQuestionResult: string = this.route.snapshot.paramMap.get('result')!; const decryptedQuestionResult: string = this.encryptionService.decrypt(encryptedQuestionResult); - const questionResult: QuestionResultTemplateParams = JSON.parse(decryptedQuestionResult); - const questionResultText: string = await this.templateService.render(TemplateEnum.QUESTION_RESULT, questionResult); + const questionResult: QuestionResultTemplateParams | QuestionResultTrifectaTemplateParams = JSON.parse(decryptedQuestionResult); + const mode: GameMode = GameMode.getByTitle(routeGameModeTitle); + const questionResultText: string = await this.templateService.render(mode.templateEnum, questionResult); this.questionScore = questionResult.questionPoints!; this.clipboardText = questionResultText; diff --git a/src/app/game-mode-window/game-mode-window.component.html b/src/app/game-mode-window/game-mode-window.component.html new file mode 100644 index 0000000..89b2eb4 --- /dev/null +++ b/src/app/game-mode-window/game-mode-window.component.html @@ -0,0 +1,21 @@ +
+ +
+ + +
+ + + +
+ + +
+
diff --git a/src/app/game-mode-window/game-mode-window.component.sass b/src/app/game-mode-window/game-mode-window.component.sass new file mode 100644 index 0000000..fa65a9d --- /dev/null +++ b/src/app/game-mode-window/game-mode-window.component.sass @@ -0,0 +1,17 @@ +.window-body + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_modes + display: inherit + flex-direction: row + align-items: center + justify-content: center + + app-icon-text-button + margin: 5px + + app-icon-button + margin: 5px diff --git a/src/app/game-mode-window/game-mode-window.component.spec.ts b/src/app/game-mode-window/game-mode-window.component.spec.ts new file mode 100644 index 0000000..091c4c3 --- /dev/null +++ b/src/app/game-mode-window/game-mode-window.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GameModeWindowComponent } from './game-mode-window.component'; + +describe('GameModeWindowComponent', () => { + let component: GameModeWindowComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [GameModeWindowComponent] + }); + fixture = TestBed.createComponent(GameModeWindowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/game-mode-window/game-mode-window.component.ts b/src/app/game-mode-window/game-mode-window.component.ts new file mode 100644 index 0000000..169eb1f --- /dev/null +++ b/src/app/game-mode-window/game-mode-window.component.ts @@ -0,0 +1,26 @@ +import {Component} from '@angular/core'; +import {Router} from "@angular/router"; +import {PathsEnum} from "../../model/enums/PathsEnum"; +import {GameMode} from "../../model/enums/GameModesEnum"; + +@Component({ + selector: 'app-game-mode-window', + templateUrl: './game-mode-window.component.html', + styleUrls: ['./game-mode-window.component.sass'] +}) +export class GameModeWindowComponent { + + protected readonly PathsEnum = PathsEnum; + + protected readonly gameModes: GameMode[] = [ + GameMode.NORMAL, + GameMode.TRIFECTA, + GameMode.TIME_RUSH, + ]; + + constructor( + protected readonly router: Router + ) { + } + +} diff --git a/src/app/main-window/main-window.component.html b/src/app/main-window/main-window.component.html index 0dcd1a5..016681c 100644 --- a/src/app/main-window/main-window.component.html +++ b/src/app/main-window/main-window.component.html @@ -12,12 +12,12 @@
+ (onButtonClick)="this.router.navigateByUrl(PathsEnum.SCORES)"> + (onButtonClick)="this.router.navigateByUrl(PathsEnum.ABOUT)">
diff --git a/src/app/main-window/main-window.component.ts b/src/app/main-window/main-window.component.ts index e8f2fe2..62cf31a 100644 --- a/src/app/main-window/main-window.component.ts +++ b/src/app/main-window/main-window.component.ts @@ -18,7 +18,7 @@ export class MainWindowComponent implements OnInit { protected readonly PathsEnum = PathsEnum; constructor( - private readonly router: Router, + protected readonly router: Router, private readonly appStorageService: AppStorageService ) { } @@ -30,10 +30,6 @@ export class MainWindowComponent implements OnInit { this.startCountdown(); } - public async redirectTo(route: PathsEnum): Promise { - await this.router.navigateByUrl(route); - } - private async startCountdown(): Promise { const appStorage: AppStorage = this.appStorageService.retrieveAppStorage(); diff --git a/src/app/question-trifecta-window/question-trifecta-window.component.html b/src/app/question-trifecta-window/question-trifecta-window.component.html new file mode 100644 index 0000000..c87a810 --- /dev/null +++ b/src/app/question-trifecta-window/question-trifecta-window.component.html @@ -0,0 +1,85 @@ +
+
+ +
+ +
+ +
+
+ + +
+
+ +
+ + + +
+ +
    +
    + + +
    + + + +
    +
    +
+
+
+ + +
+
+
+ +
+ +
+
+ + + +
+
+ + +
+ + +
+ + + + +
+
+
+
diff --git a/src/app/question-trifecta-window/question-trifecta-window.component.sass b/src/app/question-trifecta-window/question-trifecta-window.component.sass new file mode 100644 index 0000000..77ebefb --- /dev/null +++ b/src/app/question-trifecta-window/question-trifecta-window.component.sass @@ -0,0 +1,126 @@ +.host + display: flex + flex-direction: column + align-items: center + justify-content: center + + .window + margin: 10px + + &_question + &_body + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_questions + display: flex + flex-direction: column + align-items: center + justify-content: center + margin: 5px + + &_total-points + font-weight: bold + + &_sum-points + font-style: italic + + &_tri + display: flex + align-items: stretch + justify-content: center + + @media(200px < width < 1000px) + flex-direction: column + + @media(width > 1000px) + flex-direction: row + + ul + margin: 5px + + &_hint + font-style: italic + + &_loading + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_title + margin: 5px + text-align: center + + &_progress + margin: 5px + + &_image + width: 30% + display: flex + align-items: initial + justify-content: center + + &_loaded + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_title + text-align: center + margin: 15px + font-weight: bold + max-width: 250px + + &_points + font-style: italic + margin: 0px 5px 5px 5px + + &_list + display: flex + flex-direction: column + align-items: center + justify-content: center + width: 100% + + &_option-button + margin: 3px + width: 100% + + &_bold + font-weight: bold + + &_check-answer + &_body + display: flex + flex-direction: row + align-items: center + justify-content: center + + &_image + width: 30% + display: flex + align-items: initial + justify-content: center + + &_question + display: flex + flex-direction: column + align-items: center + justify-content: center + + &_title + margin: 5px + text-align: center + + &_buttons + display: flex + flex-direction: row + align-items: center + justify-content: center + + app-icon-button + margin: 3px diff --git a/src/app/question-trifecta-window/question-trifecta-window.component.spec.ts b/src/app/question-trifecta-window/question-trifecta-window.component.spec.ts new file mode 100644 index 0000000..d4c9f94 --- /dev/null +++ b/src/app/question-trifecta-window/question-trifecta-window.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuestionTrifectaWindowComponent } from './question-trifecta-window.component'; + +describe('QuestionTrifectaWindowComponent', () => { + let component: QuestionTrifectaWindowComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [QuestionTrifectaWindowComponent] + }); + fixture = TestBed.createComponent(QuestionTrifectaWindowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/question-trifecta-window/question-trifecta-window.component.ts b/src/app/question-trifecta-window/question-trifecta-window.component.ts new file mode 100644 index 0000000..e8267b6 --- /dev/null +++ b/src/app/question-trifecta-window/question-trifecta-window.component.ts @@ -0,0 +1,176 @@ +import {Component} from '@angular/core'; +import {Question} from "../../model/questions/Question"; +import {TriviaService} from "../../service/trivia.service"; +import {Router} from "@angular/router"; +import {EncryptionService} from "../../service/encryption.service"; +import {AppStorageService} from "../../service/app-storage.service"; +import {PathsEnum} from "../../model/enums/PathsEnum"; +import {QuestionResultTrifectaTemplateParams} from "../../model/Template"; +import {GameMode} from "../../model/enums/GameModesEnum"; + +@Component({ + selector: 'app-question-trifecta-window', + templateUrl: './question-trifecta-window.component.html', + styleUrls: ['./question-trifecta-window.component.sass'] +}) +export class QuestionTrifectaWindowComponent { + + public questions: Question[] = []; + public selectedAnswers: string[] | undefined[] = [undefined, undefined, undefined + ]; + public showQuestions: boolean = false; + public confirmedAnswers: boolean = false; + + private questionLoaded: boolean = false; + private questionAmount: number = 3; + + public loadingProgressBar: number = 0; + public answerProgressBar: number = 0; + public progressBarMax: number = 100; + + private questionReadySound: HTMLAudioElement = new Audio('assets/sounds/logon.wav'); + private confirmAnswerSound: HTMLAudioElement = new Audio('assets/sounds/exclamation.wav'); + + constructor( + private readonly triviaService: TriviaService, + private readonly router: Router, + private readonly encryptionService: EncryptionService, + private readonly appStorageService: AppStorageService + ) { + } + + public async ngOnInit(): Promise { + if (!this.appStorageService.canQuizBeAnswered()) { + await this.router.navigateByUrl(PathsEnum.HOME); + return; + } + + this.startLoadingProgressBar(); + await this.loadQuestions(); + } + + private async loadQuestions(): Promise { + this.questions = await this.triviaService.fetchQuestion(this.questionAmount); + this.questionLoaded = true; + } + + private async startLoadingProgressBar(): Promise { + let revertProgressBar: boolean = false; + + while (true) { + if (this.loadingProgressBar === 100) { + revertProgressBar = true; + + if (this.questionLoaded) { + this.showQuestions = true; + await this.questionReadySound.play(); + break; + } + } else if (this.loadingProgressBar === 0) { + revertProgressBar = false; + } + + this.loadingProgressBar += revertProgressBar ? -10 : 10; + + await new Promise(f => setTimeout(f, 300)); + } + } + + public async onClickAnswer(index: number, answer: string): Promise { + if (this.selectedAnswers[index] !== undefined) + this.selectedAnswers[index] = undefined; + else + this.selectedAnswers[index] = answer; + + if (this.selectedAllAnswers()) + await this.confirmAnswerSound.play(); + } + + public shouldDisableAnswer(index: number, answer: string): boolean { + const selectedAnswer = this.selectedAnswers[index]; + + return (selectedAnswer !== undefined && selectedAnswer !== answer) || this.confirmedAnswers; + } + + public getTotalPointsLabel(): string { + return `This trifecta is worth ${(this.questions[0].points + this.questions[1].points + this.questions[2].points) * 3} points!`; + } + + public getSumPointsLabel(): string { + const firstQuestionPoints: number = this.questions[0].points; + const secondQuestionPoints: number = this.questions[1].points; + const thirdQuestionPoints: number = this.questions[2].points; + const totalPoints: number = firstQuestionPoints + secondQuestionPoints + thirdQuestionPoints; + + return `(${firstQuestionPoints} + ${secondQuestionPoints} + ${thirdQuestionPoints}) = ${totalPoints} * 3 = ${totalPoints * 3}`; + } + + public getAnswerLabel(index: number, answer: string): string { + return this.selectedAnswers[index] === answer ? `> ${answer} <` : answer; + } + + public async confirmAnswers(): Promise { + this.confirmedAnswers = true; + + while (true) { + await new Promise(f => setTimeout(f, 400)); + + if (this.answerProgressBar === this.progressBarMax) + break; + + this.answerProgressBar += 10; + } + + await this.redirectFromAnswer(); + } + + private async redirectFromAnswer(): Promise { + let correctAnswers: boolean = true; + let totalPoints: number = 0; + + for (let i = 0; i <= 2; i++) { + if (this.selectedAnswers[i] !== this.questions[i].correctAnswer) { + correctAnswers = false; + break; + } + } + + const questionResultTrifecta: QuestionResultTrifectaTemplateParams = { + questions: this.questions.map(question => question.question), + correctAnswers: this.questions.map(question => question.correctAnswer), + selectedAnswers: this.selectedAnswers.map((answer, index) => { + const question = this.questions[index]; + const correctAnswer: boolean = answer === question.correctAnswer + + totalPoints += question.points * 3; + + return { + icon: correctAnswer ? '🟩' : '🟥', + answer: answer!, + points: correctAnswer ? `(${question.points} * 3) = ${question.points * 3}` : '0' + } + }), + questionPoints: correctAnswers ? totalPoints : 0, + }; + + const questionResultTrifectaData: string = this.encryptionService.encrypt(JSON.stringify(questionResultTrifecta)); + + await this.router.navigate([(correctAnswers ? PathsEnum.CORRECT_ANSWER : PathsEnum.WRONG_ANSWER), GameMode.TRIFECTA.title, questionResultTrifectaData]); + } + + public validateAnswers(): void { + this.selectedAnswers = []; + } + + public isSelectingAnswers(): boolean { + return this.selectedAnswers[0] === undefined || + this.selectedAnswers[1] === undefined || + this.selectedAnswers[2] === undefined; + } + + public selectedAllAnswers(): boolean { + return this.selectedAnswers[0] !== undefined && + this.selectedAnswers[1] !== undefined && + this.selectedAnswers[2] !== undefined; + } +} diff --git a/src/app/question-window/question-window.component.html b/src/app/question-window/question-window.component.html index 04fe100..c8cb0f3 100644 --- a/src/app/question-window/question-window.component.html +++ b/src/app/question-window/question-window.component.html @@ -15,10 +15,10 @@
- - + +
- + diff --git a/src/app/question-window/question-window.component.ts b/src/app/question-window/question-window.component.ts index 35cdd59..8dc14a1 100644 --- a/src/app/question-window/question-window.component.ts +++ b/src/app/question-window/question-window.component.ts @@ -7,6 +7,7 @@ import {QuestionResultTemplateParams} from "../../model/Template"; import {EncryptionService} from "../../service/encryption.service"; import {Subscription} from "rxjs"; import {Question} from "../../model/questions/Question"; +import {GameMode} from "../../model/enums/GameModesEnum"; @Component({ selector: 'app-question-window', @@ -15,20 +16,19 @@ import {Question} from "../../model/questions/Question"; }) export class QuestionWindowComponent implements OnInit, OnDestroy { - public questionLoaded: boolean = false; + public question: Question | undefined; + + private questionLoaded: boolean = false; public showQuestion: boolean = false; - public question: string = ''; - public questionPoints: number = 0; - public answers: string[] = []; public selectedAnswer: string = ''; public confirmedAnswer: boolean = false; + public progressBarMax: number = 100; public answerProgressBar: number = 0; public loadingProgressBar: number = 0; private questionReadySound: HTMLAudioElement = new Audio('assets/sounds/logon.wav'); private confirmAnswerSound: HTMLAudioElement = new Audio('assets/sounds/exclamation.wav'); - private correctAnswer: string = ''; private getQuizzesSubscription: Subscription | undefined; constructor( @@ -41,7 +41,7 @@ export class QuestionWindowComponent implements OnInit, OnDestroy { public async ngOnInit(): Promise { if (!this.appStorageService.canQuizBeAnswered()) { - await this.returnHome(); + await this.router.navigateByUrl(PathsEnum.HOME); return; } @@ -65,17 +65,8 @@ export class QuestionWindowComponent implements OnInit, OnDestroy { } private async loadQuestion(): Promise { - const questions: Question[] = await this.triviaService.fetchQuestion(); - const singleQuestion: Question = questions[0]; - - this.question = singleQuestion.question; - this.correctAnswer = singleQuestion.correctAnswer; - this.answers = [singleQuestion.correctAnswer, ...singleQuestion.incorrectAnswers] - .map((value) => ({value, sort: Math.random()})) - .sort((a, b) => a.sort - b.sort) - .map(({value}) => value); - - this.questionPoints = this.sumQuestionPoints(singleQuestion.difficulty, singleQuestion.isNiche); + const questions: Question[] = await this.triviaService.fetchQuestion(1); + this.question = questions[0]; this.questionLoaded = true; } @@ -99,30 +90,17 @@ export class QuestionWindowComponent implements OnInit, OnDestroy { } private async redirectFromAnswer(): Promise { - const correctAnswer: boolean = this.selectedAnswer === this.correctAnswer; + const correctAnswer: boolean = this.selectedAnswer === this.question!.correctAnswer; const questionResult: QuestionResultTemplateParams = { - question: this.question, - questionPoints: correctAnswer ? this.questionPoints : null, + question: this.question!.question, + questionPoints: correctAnswer ? this.question!.points : null, selectedAnswer: `${correctAnswer ? '🟩' : '🟥'} ${this.selectedAnswer}`, - rightAnswer: this.correctAnswer, - wrongAnswers: this.answers.filter(value => value !== this.correctAnswer) + rightAnswer: this.question!.correctAnswer, + wrongAnswers: this.question!.answers.filter(value => value !== this.question!.correctAnswer) }; const questionResultData: string = this.encryptionService.encrypt(JSON.stringify(questionResult)); - await this.router.navigate([correctAnswer ? PathsEnum.CORRECT_ANSWER : PathsEnum.WRONG_ANSWER, questionResultData]); - } - - private sumQuestionPoints(questionDifficulty: string, isNiche: boolean): number { - const difficultyPointsMap: Map = new Map([ - ['easy', 1], - ['medium', 3], - ['hard', 5] - ]); - - const questionPoints: number = difficultyPointsMap.get(questionDifficulty)!; - const nichePoints: number = isNiche ? 10 : 0; - - return questionPoints + nichePoints; + await this.router.navigate([(correctAnswer ? PathsEnum.CORRECT_ANSWER : PathsEnum.WRONG_ANSWER), GameMode.NORMAL.title, questionResultData]); } private async startLoadingProgressBar(): Promise { @@ -146,8 +124,4 @@ export class QuestionWindowComponent implements OnInit, OnDestroy { await new Promise(f => setTimeout(f, 300)); } } - - private async returnHome() { - await this.router.navigateByUrl(PathsEnum.HOME); - } } diff --git a/src/app/score-window/score-window.component.html b/src/app/score-window/score-window.component.html index d6d6e07..97de466 100644 --- a/src/app/score-window/score-window.component.html +++ b/src/app/score-window/score-window.component.html @@ -67,7 +67,7 @@ + (onButtonClick)="this.router.navigateByUrl(PathsEnum.HOME)">
diff --git a/src/app/score-window/score-window.component.ts b/src/app/score-window/score-window.component.ts index 3841ce4..fb8754d 100644 --- a/src/app/score-window/score-window.component.ts +++ b/src/app/score-window/score-window.component.ts @@ -22,7 +22,7 @@ export class ScoreWindowComponent implements OnInit { public displayClipboardMessage: boolean = false; constructor( - private readonly router: Router, + protected readonly router: Router, private readonly templateService: TemplateService, private readonly appStorageService: AppStorageService ) { @@ -33,10 +33,6 @@ export class ScoreWindowComponent implements OnInit { await this.assembleClipboardText(); } - public async returnHome(): Promise { - await this.router.navigateByUrl(PathsEnum.HOME); - } - public async showClipboardMessage(): Promise { this.displayClipboardMessage = true; @@ -75,4 +71,6 @@ export class ScoreWindowComponent implements OnInit { this.currentYear = currentYear; this.currentWeek = currentWeek; } + + protected readonly PathsEnum = PathsEnum; } diff --git a/src/app/wrong-answer-window/wrong-answer-window.component.html b/src/app/wrong-answer-window/wrong-answer-window.component.html index 54b42e5..32c68fd 100644 --- a/src/app/wrong-answer-window/wrong-answer-window.component.html +++ b/src/app/wrong-answer-window/wrong-answer-window.component.html @@ -2,15 +2,16 @@
- - + + +
+ (onButtonClick)="this.router.navigateByUrl(PathsEnum.HOME)">
diff --git a/src/app/wrong-answer-window/wrong-answer-window.component.ts b/src/app/wrong-answer-window/wrong-answer-window.component.ts index 0f008d1..e29e542 100644 --- a/src/app/wrong-answer-window/wrong-answer-window.component.ts +++ b/src/app/wrong-answer-window/wrong-answer-window.component.ts @@ -2,9 +2,10 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; import {PathsEnum} from "../../model/enums/PathsEnum"; import {AppStorageService} from "../../service/app-storage.service"; -import {QuestionResultTemplateParams, TemplateEnum} from "../../model/Template"; +import {QuestionResultTemplateParams, QuestionResultTrifectaTemplateParams} from "../../model/Template"; import {EncryptionService} from "../../service/encryption.service"; import {TemplateService} from "../../service/template.service"; +import {GameMode} from "../../model/enums/GameModesEnum"; @Component({ selector: 'app-wrong-answer-window', @@ -13,14 +14,15 @@ import {TemplateService} from "../../service/template.service"; }) export class WrongAnswerWindowComponent implements OnInit { - public correctAnswer: string | null = ''; + public correctAnswers: string[] = []; public clipboardText: string = ''; public displayClipboardMessage: boolean = false; + public hoursToPlayAgain: number = 0; private wrongAnswerSound: HTMLAudioElement = new Audio('assets/sounds/critical_stop.wav'); constructor( - private readonly router: Router, + protected readonly router: Router, private readonly route: ActivatedRoute, private readonly encryptionService: EncryptionService, private readonly templateService: TemplateService, @@ -32,7 +34,7 @@ export class WrongAnswerWindowComponent implements OnInit { await this.retrieveRouteParams(); if (!this.appStorageService.canQuizBeAnswered()) { - await this.returnHome(); + await this.router.navigateByUrl(PathsEnum.HOME); return; } @@ -40,10 +42,6 @@ export class WrongAnswerWindowComponent implements OnInit { this.saveCurrentScore(); } - public async returnHome(): Promise { - await this.router.navigateByUrl(PathsEnum.HOME); - } - public async showClipboardMessage(): Promise { this.displayClipboardMessage = true; @@ -53,16 +51,22 @@ export class WrongAnswerWindowComponent implements OnInit { } private saveCurrentScore() { - this.appStorageService.saveAnswer(false); + this.appStorageService.saveAnswer(false, undefined, this.hoursToPlayAgain); } private async retrieveRouteParams(): Promise { + const routeGameModeTitle: string = this.route.snapshot.paramMap.get('mode')!; const encryptedQuestionResult: string = this.route.snapshot.paramMap.get('result')!; const decryptedQuestionResult: string = this.encryptionService.decrypt(encryptedQuestionResult); - const questionResult: QuestionResultTemplateParams = JSON.parse(decryptedQuestionResult); + const questionResult: QuestionResultTemplateParams | QuestionResultTrifectaTemplateParams = JSON.parse(decryptedQuestionResult); + const gameMode: GameMode = GameMode.getByTitle(routeGameModeTitle); - this.correctAnswer = questionResult.rightAnswer; - this.clipboardText = await this.templateService.render(TemplateEnum.QUESTION_RESULT, questionResult); + this.hoursToPlayAgain = gameMode === GameMode.TRIFECTA ? 24 : 3; + + this.correctAnswers = 'questions' in questionResult ? questionResult.correctAnswers : [questionResult.rightAnswer]; + this.clipboardText = await this.templateService.render(gameMode.templateEnum, questionResult); } + + protected readonly PathsEnum = PathsEnum; } diff --git a/src/assets/icons/20x20/contact.png b/src/assets/icons/20x20/contact.png new file mode 100644 index 0000000..210166a Binary files /dev/null and b/src/assets/icons/20x20/contact.png differ diff --git a/src/assets/icons/20x20/send.png b/src/assets/icons/20x20/send.png new file mode 100644 index 0000000..f493361 Binary files /dev/null and b/src/assets/icons/20x20/send.png differ diff --git a/src/assets/icons/30x30/normal.png b/src/assets/icons/30x30/normal.png new file mode 100644 index 0000000..88c28b2 Binary files /dev/null and b/src/assets/icons/30x30/normal.png differ diff --git a/src/assets/icons/30x30/time-rush.png b/src/assets/icons/30x30/time-rush.png new file mode 100644 index 0000000..85d31c1 Binary files /dev/null and b/src/assets/icons/30x30/time-rush.png differ diff --git a/src/assets/icons/30x30/trifecta.png b/src/assets/icons/30x30/trifecta.png new file mode 100644 index 0000000..d6d417c Binary files /dev/null and b/src/assets/icons/30x30/trifecta.png differ diff --git a/src/assets/icons/trifecta.png b/src/assets/icons/trifecta.png new file mode 100644 index 0000000..b9173b2 Binary files /dev/null and b/src/assets/icons/trifecta.png differ diff --git a/src/assets/templates/question_result_trifecta.mustache b/src/assets/templates/question_result_trifecta.mustache new file mode 100644 index 0000000..bd9512f --- /dev/null +++ b/src/assets/templates/question_result_trifecta.mustache @@ -0,0 +1,24 @@ +{{#questions}} +👉 {{{.}}} +{{/questions}} + +The answers were... + +{{#correctAnswers}} + 🟩 {{{.}}} +{{/correctAnswers}} + +I picked... + +{{#selectedAnswers}} + {{{icon}}} {{{answer}}} - {{{points}}} points +{{/selectedAnswers}} + +{{#questionPoints}} +Earned {{questionPoints}} points in total! +{{/questionPoints}} +{{^questionPoints}} +Need some luck next time... +{{/questionPoints}} + +🤔 XPQuiz - xpquiz.github.io ❔ diff --git a/src/model/Template.ts b/src/model/Template.ts index d5acb8d..9767f69 100644 --- a/src/model/Template.ts +++ b/src/model/Template.ts @@ -1,11 +1,14 @@ export enum TemplateEnum { WEEK_SCORE = "/assets/templates/week_score.mustache", - QUESTION_RESULT = "/assets/templates/question_result.mustache" + QUESTION_RESULT = "/assets/templates/question_result.mustache", + QUESTION_RESULT_TRIFECTA = "/assets/templates/question_result_trifecta.mustache" } export interface TemplateParams { } +// Score + export interface WeekScoreTemplateParams extends TemplateParams { year: number, week: number, @@ -14,6 +17,8 @@ export interface WeekScoreTemplateParams extends TemplateParams { totalScore: number } +// Normal question + export interface QuestionResultTemplateParams extends TemplateParams { question: string, questionPoints: number | null, @@ -21,3 +26,18 @@ export interface QuestionResultTemplateParams extends TemplateParams { rightAnswer: string, wrongAnswers: string[] } + +// Trifecta + +export interface QuestionResultTrifectaTemplateParams extends TemplateParams { + questions: string[], + correctAnswers: string[], + selectedAnswers: TrifectaSelectedAnswer[], + questionPoints: number +} + +interface TrifectaSelectedAnswer { + icon: string, + answer: string, + points: string +} diff --git a/src/model/enums/GameModesEnum.ts b/src/model/enums/GameModesEnum.ts new file mode 100644 index 0000000..119f148 --- /dev/null +++ b/src/model/enums/GameModesEnum.ts @@ -0,0 +1,27 @@ +import {TemplateEnum} from "../Template"; + +export class GameMode { + static readonly NORMAL = new GameMode('normal', 'Standard mode. Answer a question, wait 3 hours to play again. Simple as that.', 1, TemplateEnum.QUESTION_RESULT); + static readonly TRIFECTA = new GameMode('trifecta', 'Three questions for three times the score! At the risk of waiting 24 hours to play again.', 3, TemplateEnum.QUESTION_RESULT_TRIFECTA); + static readonly TIME_RUSH = new GameMode('time-rush', 'Coming soon...', -1, TemplateEnum.QUESTION_RESULT); + + private constructor(public readonly title: string, + public readonly description: string, + public readonly questions: number, + public readonly templateEnum: TemplateEnum) { + } + + public static getByTitle(title: string): GameMode { + switch(title) { + case 'normal': + return GameMode.NORMAL; + case 'trifecta': + return GameMode.TRIFECTA; + case 'time-rush': + return GameMode.TIME_RUSH; + default: + return GameMode.NORMAL; + } + } +} + diff --git a/src/model/enums/PathsEnum.ts b/src/model/enums/PathsEnum.ts index b642f28..bef3279 100644 --- a/src/model/enums/PathsEnum.ts +++ b/src/model/enums/PathsEnum.ts @@ -2,7 +2,9 @@ export enum PathsEnum { HOME = 'home', SCORES = 'scores', ABOUT = 'about', - QUIZ = 'quiz', + GAME_MODE = 'game-mode', + QUIZ_NORMAL = 'quiz-normal', + QUIZ_TRIFECTA = 'quiz-trifecta', CORRECT_ANSWER = 'correct-answer', WRONG_ANSWER = 'wrong-answer', } diff --git a/src/model/questions/Question.ts b/src/model/questions/Question.ts index b1e14e6..bfa8ea1 100644 --- a/src/model/questions/Question.ts +++ b/src/model/questions/Question.ts @@ -1,7 +1,10 @@ export interface Question { question: string; + answers: string[]; correctAnswer: string; - incorrectAnswers: string[]; - difficulty: 'easy' | 'medium' | 'hard'; + points: number; + difficulty: DifficultyType; isNiche: boolean; } + +export type DifficultyType = 'easy' | 'medium' | 'hard'; diff --git a/src/service/app-storage.service.ts b/src/service/app-storage.service.ts index 6622963..6e9989e 100644 --- a/src/service/app-storage.service.ts +++ b/src/service/app-storage.service.ts @@ -27,7 +27,7 @@ export class AppStorageService { } } - public saveAnswer(correctAnswer: boolean, questionScore?: number | null): void { + public saveAnswer(correctAnswer: boolean, questionScore: number | undefined, hoursToPlayAgain: number): void { const currentYear: number = moment().year(); const currentWeek: number = moment().isoWeek(); @@ -67,7 +67,9 @@ export class AppStorageService { this.storageService.save( { ...appStorage, - lastQuizResponseDate: moment().toISOString(), + // using - 3 because i fucked up no the main window, should have been saving something + // like "nextQuizResponseDate" instead of this shit + lastQuizResponseDate: moment().add(hoursToPlayAgain - 3, 'hours').toISOString(), yearScoreMap: yearScoreMap } ); @@ -88,22 +90,6 @@ export class AppStorageService { return this.storageService.get()??this.createNewAppStorage(); } - public clearWeek(currentYear: number, currentWeek: number) { - const appStorage: AppStorage = this.storageService.get()!; - - appStorage.yearScoreMap.get(currentYear)?.delete(currentWeek); - - this.storageService.save(appStorage); - } - - public clearYear(currentYear: number) { - const appStorage: AppStorage = this.storageService.get()!; - - appStorage.yearScoreMap.delete(currentYear); - - this.storageService.save(appStorage); - } - public retrieveScoreForYear(currentYear: number): Map { const appStorage: AppStorage = this.retrieveAppStorage(); diff --git a/src/service/storage.service.ts b/src/service/storage.service.ts index c7f7af3..e330aa5 100644 --- a/src/service/storage.service.ts +++ b/src/service/storage.service.ts @@ -1,5 +1,4 @@ import {Injectable} from '@angular/core'; -import {AES, enc} from 'crypto-js'; import {AppStorage} from "../model/AppStorage"; import {EncryptionService} from "./encryption.service"; diff --git a/src/service/trivia.service.ts b/src/service/trivia.service.ts index baa1dca..4cd732a 100644 --- a/src/service/trivia.service.ts +++ b/src/service/trivia.service.ts @@ -3,7 +3,7 @@ import {HttpClient} from "@angular/common/http"; import {firstValueFrom} from "rxjs"; import {TheTriviaApiResponse} from "../model/questions/TheTriviaApiResponse"; import {OpenTriviaDBResponse} from "../model/questions/OpenTriviaDBResponse"; -import {Question} from "../model/questions/Question"; +import {DifficultyType, Question} from "../model/questions/Question"; import { QuizAPIResponse, QuizAPIResponseAnswers, @@ -11,12 +11,18 @@ import { } from "../model/questions/QuizAPIResponse"; import {environment} from "../environments/environment"; + @Injectable({ providedIn: 'root' }) export class TriviaService { - private readonly questionLimit: number = 1; + private readonly difficultyPointsMap: Map = new Map([ + ['easy', 1], + ['medium', 3], + ['hard', 5] + ]); + private readonly questionMethods: Function[] = [ this.getQuestionsTheTriviaApi, this.getQuestionsOpenTriviaDB, @@ -28,13 +34,13 @@ export class TriviaService { ) { } - public async fetchQuestion(): Promise { - while(true) { + public async fetchQuestion(questionNumber: number): Promise { + while (true) { const randomNumber: number = this.randomIntFromInterval(0, this.questionMethods.length - 1); const randomQuestionMethod: Function = this.questionMethods[randomNumber]; - const questions: Question[] = await randomQuestionMethod.call(this); + const questions: Question[] = await randomQuestionMethod.call(this, questionNumber); - if(questions.length !== 0) return questions; + if (questions.length !== 0) return questions; } } @@ -42,44 +48,58 @@ export class TriviaService { return Math.floor(Math.random() * (max - min + 1) + min) } - private async getQuestionsTheTriviaApi(): Promise { - const url: string = `${environment.theTriviaApiUrl}?limit=${this.questionLimit}`; - + private async getQuestionsTheTriviaApi(questionNumber: number): Promise { + const url: string = `${environment.theTriviaApiUrl}?limit=${questionNumber}`; const response: TheTriviaApiResponse[] = await firstValueFrom( this.httpClient.get(url) ); return response.map(res => { + const answers = [res.correctAnswer, ...res.incorrectAnswers] + .map((value) => ({value, sort: Math.random()})) + .sort((a, b) => a.sort - b.sort) + .map(({value}) => value); + return { question: res.question.text, + answers, correctAnswer: res.correctAnswer, - incorrectAnswers: res.incorrectAnswers, + points: this.difficultyPointsMap.get(res.difficulty)!, difficulty: res.difficulty, isNiche: res.isNiche, } }); } - private async getQuestionsOpenTriviaDB(): Promise { - const url: string = `${environment.openTriviaDBUrl}?amount=${this.questionLimit}&encode=base64`; + private async getQuestionsOpenTriviaDB(questionNumber: number): Promise { + const url: string = `${environment.openTriviaDBUrl}?amount=${questionNumber}&encode=base64`; const response: OpenTriviaDBResponse = await firstValueFrom( this.httpClient.get(url) ); return response.results.map(res => { + const correctAnswer: string = atob(res.correct_answer) + + const answers = [correctAnswer, ...res.incorrect_answers.map(r => atob(r))] + .map((value) => ({value, sort: Math.random()})) + .sort((a, b) => a.sort - b.sort) + .map(({value}) => value); + const difficulty: DifficultyType = atob(res.difficulty) as DifficultyType; + return { question: atob(res.question), - correctAnswer: atob(res.correct_answer), - incorrectAnswers: res.incorrect_answers.map(r => atob(r)), - difficulty: atob(res.difficulty) as 'easy' | 'medium' | 'hard', + correctAnswer, + answers, + points: this.difficultyPointsMap.get(difficulty)!, + difficulty, isNiche: false, } }); } - private async getQuestionsQuizAPI(): Promise { - const url: string = `${environment.quizAPIUrl}?limit=${this.questionLimit}`; + private async getQuestionsQuizAPI(questionNumber: number): Promise { + const url: string = `${environment.quizAPIUrl}?limit=${questionNumber}`; const response: QuizAPIResponse[] = await firstValueFrom( this.httpClient.get(url, { headers: {'X-Api-Key': environment.quizAPIKey} @@ -87,6 +107,7 @@ export class TriviaService { ); return response.map(res => { + const difficulty: DifficultyType = res.difficulty.toLowerCase() as DifficultyType; let incorrectAnswers: string[] = []; let correctAnswer: string | undefined = undefined; @@ -107,11 +128,17 @@ export class TriviaService { incorrectAnswers.push(answer); } + const answers = [correctAnswer!, ...incorrectAnswers] + .map((value) => ({value, sort: Math.random()})) + .sort((a, b) => a.sort - b.sort) + .map(({value}) => value); + return { question: res.question, correctAnswer: correctAnswer!, - incorrectAnswers: incorrectAnswers, - difficulty: res.difficulty.toLowerCase() as 'easy' | 'medium' | 'hard', + answers, + points: this.difficultyPointsMap.get(difficulty)!, + difficulty, isNiche: false } });