File

src/app/models/quizz/quizz.ts

Constructor

constructor(page: any, randomMode: boolean, scenario: ScenarioQuizz)

Methods

Public runQuizz
runQuizz()
Returns: void
buildQuestionsObjects
buildQuestionsObjects()
Returns: void
Public newQuestion
newQuestion(firstTime: boolean, idFromDev: number)

quizz new question loop - game lifecyle

Parameters :
  • firstTime

    true if first question

  • idFromDev

    question id given in dev mode for testing

Returns: void
isEndOfQuizz
isEndOfQuizz()

Test if end of quizz

Returns: boolean
endQuizz
endQuizz()

Execute end of quizz action

Returns: void
checkIfJoker
checkIfJoker(userAnswerArray: any)

check if the user answer is "I don't know"

Parameters :
  • userAnswerArray

    user answer array (cause multi choice answers)

Returns: void
nextQuestion
nextQuestion()
Returns: void
updateVariables
updateVariables()
Returns: void
Public getNextQuestion
getNextQuestion(idFromDev: number)

get the next quizz question from this.questions depending on currentIndex (in normal mode)

Parameters :
  • idFromDev

    question id for dev mode

Returns: void
buildCurrentQuestion
buildCurrentQuestion()
Returns: void
checkIfGoodResult
checkIfGoodResult(answersIds: any[])
Returns: QuizzResult
createProcessedAnswers
createProcessedAnswers(quizzResult: QuizzResult)
Returns: void
getKeyByValue
getKeyByValue(object: any, value: any)
Returns: void
manageAnswer
manageAnswer(userAnswer: any, joker: boolean)
Parameters :
  • userAnswer

    processes user's answer depending of its status (remediation if false) + update thermometer score

  • joker

    for "je ne sais pas" type of answer

Returns: any
displayAndReadFeedback
displayAndReadFeedback()

displays feedback from fiche component

Returns: void
manageLRS
manageLRS(result: ResultType)
Returns: void
endActivityStatement
endActivityStatement()

Statement object created at the end of the activity

Returns: Statement[]

Statement[] contains one or two elements depending if it's the last exercise of journey (ex + journey) or a simple exercise(1)

startActivityStatement
startActivityStatement()

Statement object created at the beginning of the activity.

Returns: Statement[]

Statement[] contains one or two elements depending if it's a journey(ex + journey) or a simple exercise(1)

Properties

allQuestions
allQuestions: QuizzQuestion[]
allQuestionsMode
allQuestionsMode: boolean
answerPromise
answerPromise: any
answerSubject
answerSubject: any
currentIndex
currentIndex: number
Default value: 0
currentQuestion
currentQuestion: QuizzQuestion
difficulty
difficulty: Difficulty
gaugeType
gaugeType: any
goodAnswers
goodAnswers: QuizzAnswer[]
goodAnswersIds
goodAnswersIds: number[]
group
group: string
id
id: string | number
Public lrsFormatParticipantAnswer
lrsFormatParticipantAnswer: { id: string; name: string; }[]
maxQuestions
maxQuestions: number
page
page: any
questions
questions: number[]
questionsObjects
questionsObjects: QuizzQuestion[]
quizzAnswersProcessed
quizzAnswersProcessed: any[]
randomMode
randomMode: boolean
skill
skill: any
theme
theme: string
timerUserResponse
timerUserResponse: number
title
title: string
userAnswer
userAnswer: number[]
import { AppUtils } from "src/app/app-utils";
import { QuizzPage } from "src/app/page/quizz/quizz.page";
import { QuizzAnswer, QuizzQuestion } from "./quizz-question";
import { ScenarioQuizz } from "./scenario-quizz";
import { LrsUtils } from "../lrs/lrsUtils";
import { Subject } from "rxjs";
import { Statement } from "../statement";
import { XapiObject, XObjectType } from "../lrs/xapiobject";
import { XapiContext, XapiExtensions } from "../lrs/xapicontext";
import { LrsVerbs } from "../lrs/xapiverbs";
import { XApiResult } from "../lrs/xapiresult";
import { Details } from "src/app/page/territoire/territoire.page";
import { OseDifficulty } from "../enums/oseDifficulty";
import { environment } from "src/environments/environment";

export enum Difficulty {
	easy = 1,
	medium = 2,
	hard = 3
}

export enum ResultType {
	success,
	successOnRetry,
	fail,
	failOnFirstAttempt,
	joker
}

export enum QuizzAnswerStatus {
	valid,
	rejected,
	missing,
	notValidAndUntouched
}

export enum ThermometerColor {
	hell = "hell",
	hot = "hot",
	medium = "medium",
	low = "low"
}

interface QuizzResult {
	allValidated: boolean;
	rejected: Array<number>;
	validated: Array<number>;
	missing: Array<number>;
}

export class Quizz {
	id: number | string;
	questions: Array<number>;
	currentQuestion: QuizzQuestion;
	skill: any;
	group: string;
	theme: string;
	currentIndex = 0;
	maxQuestions: number;
	gaugeType: any;
	difficulty: Difficulty;

	// execution:
	goodAnswersIds: number[];
	userAnswer: number[];
	goodAnswers: QuizzAnswer[];
	answerSubject: Subject<boolean>;
	quizzAnswersProcessed: Array<any>;
	allQuestions: Array<QuizzQuestion>;
	allQuestionsMode: boolean;
	title: string;
	questionsObjects: Array<QuizzQuestion>;
	answerPromise: Promise<number[]>;

	// lrs
	timerUserResponse: number;

	constructor(public page: QuizzPage, public randomMode: boolean, private scenario: ScenarioQuizz) {
		this.id = null;
		this.randomMode = randomMode;
	}

	public async runQuizz() {
		if (this.randomMode) {
			if (this.allQuestionsMode) {
				AppUtils.shuffleArray(this.allQuestions);
			} else {
				AppUtils.shuffleArray(this.questions);
			}
		}
		this.buildQuestionsObjects();
		this.updateVariables();
		if (!this.page.isComponent && !this.page.inJourney && this.id !== 36843 && this.id !== 36844) {
			await this.scenario.playIntro();
		}
		this.newQuestion(true);
		const statement = this.startActivityStatement();
		if(statement){
			this.page.lrs.send(statement);
		}
	}

	buildQuestionsObjects() {
		this.questionsObjects = new Array();
		this.questions.forEach(qId => {
			this.allQuestions.forEach(question => {
				if (Number(qId) === Number(question.id)) {
					this.questionsObjects.push(question);
				}
			});
		});
	}

	/**
	 * quizz new question loop - game lifecyle
	 * @param firstTime true if first question
	 * @param idFromDev question id given in dev mode for testing
	 */
	public async newQuestion(firstTime?: boolean, idFromDev?: number) {
		if (idFromDev) {
			this.page.answerSubscription?.complete();
			this.page.answerSubscription = new Subject<any>();
			this.answerPromise = null;
		}
		this.page.ficheSkipped = false;
		this.currentQuestion = this.getNextQuestion(idFromDev);
		if (this.currentQuestion.background) {
			this.page.updateBackground(this.currentQuestion.background);
		}
		// AppUtils.debug("currentQuestion = ", this.currentQuestion);
		if (!this.page.isComponent){
			await AppUtils.timeOut(1000);
		}
		if (this.page.currentTeam?.length > 1) {
			await this.page.nextPlayer(firstTime);
		}
		this.answerPromise = this.page.waitUserAnswer();
		const skipQuestion = (this.page.isComponent && !this.page.quizid) && this.page.difficultyWasteSelected === OseDifficulty.easy ? true : false
		this.page.toggleQuizzPad(!skipQuestion);
		this.buildCurrentQuestion();
		this.page.displayConsigne(!skipQuestion);
		const skipTTS = this.page.wasteObject && this.page.difficultyWasteSelected === OseDifficulty.easy
		const askCurrentQuestionAndReadAnswersPromise = this.scenario.askCurrentQuestionAndReadAnswers(skipTTS);
		this.page.padLocked = false;
		this.timerUserResponse = Date.now();
		if(skipQuestion){
			this.page.receiveAnswer({code: this.goodAnswersIds,event: "skipMode"});
		}
		await this.answerPromise.then(async userAnswerArray => {
			console.log(userAnswerArray);
			this.userAnswer = userAnswerArray;
			this.page.padLocked = true;
			if (this.page.ttsService.isplaying) {
				await this.page.skip();
			}
			await askCurrentQuestionAndReadAnswersPromise;
			this.page.displayConsigne(false);
			// debugger;
			await this.manageAnswer(userAnswerArray, this.checkIfJoker(userAnswerArray));
			await AppUtils.timeOut(1000);
			if (this.isEndOfQuizz()) {
				this.endQuizz();
			} else {
				if (this.id !== 36843 && this.id !== 36844 && this.page.currentTeam.length > 1) {
					await this.page.nextPlayer();
				}
				this.newQuestion();
			}
		});
	}

	/**
	 * Test if end of quizz
	 */
	isEndOfQuizz(): boolean {
		let end = this.currentIndex === this.questionsObjects.length ;
		if(!environment.production){
			// end = this.currentIndex === 1 ;
		}
		return end;
	}

	/**
	 * Execute end of quizz action
	 */
	async endQuizz() {
		if (this.id == 36843 || this.id == 36844) {
			this.page.outputEvent.next({ success: true, mesh: null});
		} else if (!this.page.isComponent && !this.page.inJourney) {
			await this.scenario.endOfActivity();
		}
		if(!this.page.isComponent && this.page.oseJourneyService.currentJourney){
			if(this.page.oseJourneyService.currentJourney.isQuizzTypeStarted){
				this.page.oseJourneyService.currentJourney.isQuizzTypeCompleted =  true;
			}
		}
		const endStatement = this.endActivityStatement();
		if(endStatement){
			this.page.lrs
			.send(endStatement)
			.then(() => {
				if(this.page.isComponent){
					if (this.page.isComponent) {
						this.page.outputEvent.next({ success: this.page.currentUser.awardsCurrent.shooting > 0, mesh: this.page.wasteObject?.mesh});
					}
				} else {
					this.page.oseJourneyService.exerciseEnd.next();
				}
			})
			.catch(error => {
				console.error("error last statement not send", error);
				this.page.oseJourneyService.exerciseEnd.next(true);
			});
		}else{
			if (this.page.isComponent) {
				this.page.outputEvent.next({ success: this.page.currentUser.awardsCurrent.shooting > 0, mesh: this.page.wasteObject?.mesh});
			}
		}
	
	}

	/**
	 * check if the user answer is "I don't know"
	 * @param userAnswerArray user answer array (cause multi choice answers)
	 */
	checkIfJoker(userAnswerArray) {
		const dontKnowAnswer = this.currentQuestion.answers.find(a => {
			return a.text.toLowerCase() === "je ne sais pas";
		});
		let userDontKnow = false;
		if (dontKnowAnswer) {
			userDontKnow = userAnswerArray.some(a => dontKnowAnswer.id === a);
		}
		return userDontKnow;
	}

	// for dev
	async nextQuestion() {
		await this.page.skip();
		this.newQuestion();
	}

	updateVariables() { }

	/**
	 * get the next quizz question from this.questions depending on currentIndex (in normal mode)
	 * @param idFromDev question id for dev mode
	 */
	public getNextQuestion(idFromDev?: number) {
		let questionObject;
		if (this.allQuestionsMode) {
			questionObject = this.allQuestions[idFromDev ? idFromDev : this.currentIndex];
		} else {
			questionObject = AppUtils.find(this.allQuestions, q => {
				return Number(q.id) === Number(idFromDev ? idFromDev : this.questions[this.currentIndex]);
			});
		}
		// remove empty answers:
		questionObject.answers.forEach((answer, index) => {
			if (answer.text === "") {
				questionObject.answers.splice(index, 1);
			}
		});
		if (!idFromDev) {
			this.currentIndex++;
		}
		// AppUtils.debug("questionObject ", questionObject);
		const question = Object.assign(new QuizzQuestion(), questionObject);
		if ((questionObject as any).feedback_html) {
			question.feedback = (questionObject as any).feedback_html;
		}
		question.answers.forEach((answer, index) => {
			question.answers[index] = Object.assign(new QuizzAnswer(), answer);
		});
		this.page.cabriService.currentOseActivity = { ...this.page.cabriService.currentOseActivity, ...question, id: this.page.cabriService.currentOseActivity.id };
		return question;
	}

	buildCurrentQuestion() {
		this.goodAnswersIds = new Array();
		this.goodAnswers = new Array();
		this.page.updateBackground(this.currentQuestion.background);
		this.currentQuestion.answers.forEach(answer => {
			if (answer.isValid) {
				this.goodAnswersIds.push(answer.id);
				this.goodAnswers.push(answer);
			}
		});
		// AppUtils.debug("this.goodAnswersIds = ", this.goodAnswersIds);
	}

	checkIfGoodResult(answersIds: any[]): QuizzResult {
		const validated = new Array();
		const rejected = new Array();
		this.quizzAnswersProcessed = new Array();
		let missing = this.goodAnswersIds;
		answersIds.forEach(answerId => {
			if (this.goodAnswersIds.indexOf(answerId) === -1) {
				rejected.push(answerId);
			} else {
				validated.push(answerId);
				missing = missing.filter(answer => {
					return answer !== answerId;
				});
			}
		});
		let allValidated: boolean;
		if (answersIds.length > 0 && validated.length === this.goodAnswersIds.length && validated.length === answersIds.length) {
			// AppUtils.debug("all good !");
			allValidated = true;
		} else if (answersIds.length > 0 && validated.length === answersIds.length) {
			// AppUtils.debug("answered good but missing other good answers ! ", missing);
		} else if (validated.length > 0) {
			// AppUtils.debug(`answered good but rejected other good answers ! validated : ${validated}, rejected : ${rejected}`);
		} else {
			// AppUtils.debug("only rejected = ", rejected);
		}
		const result: QuizzResult = {
			allValidated,
			rejected,
			validated,
			missing
		};
		this.createProcessedAnswers(result);
		return result;
	}

	// updates numpad answers with result statuses
	createProcessedAnswers(quizzResult: QuizzResult) {
		this.currentQuestion.answers.forEach(answer => {
			const answerStatus = this.getKeyByValue(quizzResult, answer.id) ? this.getKeyByValue(quizzResult, answer.id) : "untouched";
			this.quizzAnswersProcessed.push({ id: answer.id, status: answerStatus });
		});
		// AppUtils.debug("this.quizzAnswersProcessed = ", this.quizzAnswersProcessed);
	}

	getKeyByValue(object, value) {
		const keyFound = Object.keys(object).find(key => {
			if (key !== "allValidated") {
				const key2 = object[key];
				return key2.includes(value);
			}
		});
		return keyFound ? keyFound : null;
	}

	/**
	 * 
	 * @param userAnswer processes user's answer depending of its status (remediation if false) + update thermometer score
	 * @param joker for "je ne sais pas" type of answer
	 */
	manageAnswer(userAnswer, joker: boolean): Promise<void> {
		return new Promise(async (resolve, reject) => {
			// this.page.needInitializeZoneResponse = true;
			const timestampDiff = Date.now() - this.timerUserResponse;
			const millis = Math.floor(timestampDiff / 1000);
			LrsUtils.responseTime = millis;
			this.page.displayBubble(false);
			const result: QuizzResult = this.checkIfGoodResult(userAnswer);
			if (result.allValidated || this.id == 36843 || this.id == 36844) {
				// juste, passer à la question suivante
				this.page.quizzPad?.clearAnswer();
				this.page.toggleQuizzPad(false);
				this.manageLRS(ResultType.success);
				if (this.id !== 36843 && this.id !== 36844) {
					this.page.updatePlayerAndTeamStarboards("shooting");
					this.page.updateThermometer();
					await this.scenario.goodResponseShooting();
				} else {
					resolve();
					return;
				}
				await AppUtils.timeOut(500);
			} else if (joker) {
				// the user doesn't know
				await this.scenario.dontKnowShowResults(this.goodAnswers.length > 1, false, 5000);
				this.manageLRS(ResultType.joker);
				this.page.updatePlayerAndTeamStarboards("normal");
				this.page.updateThermometer();
				this.page.quizzPad?.showPadResults(false);
				this.page.quizzPad?.clearAnswer();
				this.page.toggleQuizzPad(false);
			} else {
				this.manageLRS(ResultType.fail);
				this.page.updatePlayerAndTeamStarboards("moon");
				this.page.updateThermometer();
				if (this.goodAnswers.length > 1) {
					// bad answer - there was several good answers
					if (result.validated.length > 0 && result.rejected.length === 0) {
						await this.scenario.badAnswerCustomAndShowResults(
							$localize`Oui ! Mais il y avait plusieurs bonnes réponses, regarde !`,
							false,
							5000
						);
					} else if (result.validated.length > 0 && result.rejected.length > 0) {
						await this.scenario.badAnswerCustomAndShowResults(
							$localize`Oups ! les bonnes réponses étaient celles-ci, regarde !`,
							false,
							5000
						);
					} else {
						await this.scenario.badAnswerCustomAndShowResults(
							$localize`Oups ! Il y avait plusieurs bonnes réponses, regarde !`,
							false,
							5000
						);
					}
				} else {
					// bad answer - there was only one good answer
					await this.scenario.badAnswerCustomAndShowResults(
						$localize`Oups ! La bonne réponse était celle-ci, regarde !`,
						false,
						5000
					);
				}
				this.page.quizzPad?.showPadResults(false);
				this.page.quizzPad?.clearAnswer();
				this.page.toggleQuizzPad(false);
			}
			if(!this.page.nofeedback){
				await this.displayAndReadFeedback();
			}
			resolve();
		});
	}

	/**
	 * displays feedback from fiche component
	 */
	async displayAndReadFeedback() {
		this.page.displayFeedback(true);
		await this.scenario.readFeedbackFiche(this.currentQuestion.feedback);
		this.page.displayFeedback(false);
	}

	manageLRS(result: ResultType) {
		let statement: Statement;
		switch (result) {
			case ResultType.success:
				statement = this.page.lrs.oseQuestionStatement(LrsVerbs.passed,this.lrsFormatParticipantAnswer);
				// this.page.updatePlayerAndTeamStarboards("shooting");
				break;
			case ResultType.successOnRetry:
				statement = this.page.lrs.oseQuestionStatement(LrsVerbs.passedWithHelp,this.lrsFormatParticipantAnswer);
				// this.page.updatePlayerAndTeamStarboards("normal");
				break;
			case ResultType.fail:
				statement = this.page.lrs.oseQuestionStatement(LrsVerbs.failed,this.lrsFormatParticipantAnswer);
				// this.page.updatePlayerAndTeamStarboards("moon");
				break;
			case ResultType.failOnFirstAttempt:
				statement = this.page.lrs.oseQuestionStatement(LrsVerbs.failedOnFirstAttempt,this.lrsFormatParticipantAnswer);
				break;
			case ResultType.joker:
				statement = this.page.lrs.oseQuestionStatement(LrsVerbs.skipped,this.lrsFormatParticipantAnswer);
				break;
		}

		console.log("statement = ", statement);
		this.page.lrs.send(statement).then((sendedStatement: Statement) => {
			sendedStatement.setVerb(LrsVerbs.finished);
			this.page.lrs.send(sendedStatement);
		});
	}

	public get lrsFormatParticipantAnswer(): { id: string; name: string }[] {
		return this.userAnswer.map(answerNumber => {
			const answer = this.currentQuestion.answers[answerNumber - 1];
			return { id: String(answer.id), name: answer.text }; // Convertir l'ID en chaîne
		});
	}

	/**
	 * Statement object created at the end of the activity
	 * @returns Statement[] contains one or two elements depending if it's the last exercise of journey (ex + journey) or a simple exercise(1)
	 */
	endActivityStatement(): Statement[] {
		if (this.id == 36843 || this.id == 36844) {
			return;
		} else if(!this.page.wasteObject){
			const statements = new Array();
			const userAwards = this.page.currentUser.awardsCurrent;
			const perGoodAnswers = ((userAwards.shooting + userAwards.normal) / userAwards.total) * 100;
			const perWrongAnswers = (userAwards.moons / userAwards.total) * 100;
			if(this.page.oseJourneyService.currentJourney){
				this.page.oseJourneyService.currentJourney.calculateSessionExerciseCompletionPercentage();
			}
			const object = new XapiObject(`${LrsUtils.statementUrl}/${String(this.id)}`, this.title, this.theme, XObjectType.exercise);
			const activityDuration = LrsUtils.duration8601(LrsUtils.timeStampConversion(LrsUtils.currentUserResponseTime));
			const result = new XApiResult(LrsVerbs.completed, activityDuration);
			const context = new XapiContext(this.page.accountService.team, {
				...this.page.lrs.globalActivityExtensions(),
				[XapiExtensions.pourcentageBonnesReponses]: perGoodAnswers,
				[XapiExtensions.pourcentageMauvaisesReponses]: perWrongAnswers,
				[XapiExtensions.dureeActivite]: activityDuration,
				[XapiExtensions.sequenceTermine]: !this.page.oseJourneyService.currentJourney?.isQuizzTypeCompleted ? true : false
			});
			const exerciseStatement = new Statement(this.page.currentUser?.id, LrsVerbs.completed, object, context, result);
			const endJourneyStatement = this.page.lrs.endJourneyStatement();
			if (endJourneyStatement) {
				statements.push(endJourneyStatement);
			}

			statements.push(exerciseStatement);
			return statements;
		}
	}

	/**
	 * Statement object created at the beginning of the activity.
	 * @returns Statement[] contains one or two elements depending if it's a journey(ex + journey) or a simple exercise(1)
	 */
	startActivityStatement(): Statement[] {
		if(!this.page.wasteObject){
			const statements = new Array();
			LrsUtils.idsession = LrsUtils.generateUniqueSessionId(this.page.accountService, true);
			LrsUtils.currentUserResponseTime = Date.now();
			const object = new XapiObject(`${LrsUtils.statementUrl}/exercise/${String(this.id)}`, this.title, this.theme, XObjectType.exercise);
			const context = new XapiContext(this.page.accountService.team, {
				...this.page.lrs.globalActivityExtensions()
			});

			const exerciseStatement = new Statement(this.page.currentUser?.id, LrsVerbs.initialized, object, context);
			const startJourneyStatement = this.page.lrs.startJourneyStatement();
			if (startJourneyStatement) {
				statements.push(startJourneyStatement);
			}
			statements.push(exerciseStatement);
			return statements;
		}
	}

}

results matching ""

    No results matching ""