File

src/app/models/exercices-solides.ts

Constructor

constructor(cabri: CabriIntegrationSolides, page: SolidesPage, scenario: ScenarioSolides, cabriService: CabriDataService, lmsService: LmsService, globalService: GlobalService)

Methods

updateVariables
updateVariables()
Returns: void
addQCMFalseChoice
addQCMFalseChoice(nb: number)
Returns: void
createQCMNumberChoice
createQCMNumberChoice()
Returns: void
runExercice
runExercice()
Returns: void
chooseRandomSolid
chooseRandomSolid()
Returns: void
chooseRandomSubject
chooseRandomSubject()
Returns: void
chooseRandomSolidRepresentation
chooseRandomSolidRepresentation()

Choose one representation for the current solid

Returns: void

id of the choosen solid representation

chooseRandomSolidPattern
chooseRandomSolidPattern()

Choose one pattern for the current solid

Returns: void

id of the choosen solid representation

displaySolid
displaySolid()
Returns: void
displayPattern
displayPattern()
Returns: void
extractRandomElementFromArray
extractRandomElementFromArray(array: any)
Returns: void
Public runExerciceNaming
runExerciceNaming()
Returns: void
Public runExerciceRecognize
runExerciceRecognize()
Returns: void
runExerciceDescribe
runExerciceDescribe()
Returns: void
endRemediationDescribe
endRemediationDescribe()
Returns: void
runExercicePattern
runExercicePattern()
Returns: void
runRemediation
runRemediation()
Returns: void
runRemediationDescribe
runRemediationDescribe()
Returns: void
runRemediationName
runRemediationName()
Returns: void
runRemediationRecognize
runRemediationRecognize()
Returns: void
runRemediationPattern
runRemediationPattern()
Returns: void
chooseRandomIndexInArray
chooseRandomIndexInArray(array: any, oldIndex: any)
Returns: void
manageLRS
manageLRS(result: ResultType)
Returns: void
getCurrentSolidName
getCurrentSolidName()
Returns: void
updateExercice
updateExercice(firstTime: boolean)
Returns: void
foundQuestionValid
foundQuestionValid(seconds: any, previousConsigne: any)
Returns: void
spacedStudentRepetition
spacedStudentRepetition()
Returns: void

true if the current question could be asked

Private debug
debug(message: any, on: boolean)
Returns: void

Properties

allowedSolidPatterns
allowedSolidPatterns: any[]
allowedSolidRepresentations
allowedSolidRepresentations: any[]
allowedSolids
allowedSolids: any
allowedSubjects
allowedSubjects: any
Public answerSubscription
answerSubscription: any
cabri
cabri: CabriIntegrationSolides
cabriService
cabriService: CabriDataService
Public consigne
consigne: any
consignSolidId
consignSolidId: any
exoType
exoType: any
globalService
globalService: GlobalService
goodResult
goodResult: any
Private grade
grade: any
lastExoType
lastExoType: any
lastSolidId
lastSolidId: number

Id du solide affiché à la question précédente.

lastSolidPatternId
lastSolidPatternId: any
lastSolidRepresentationId
lastSolidRepresentationId: any
lastSubject
lastSubject: any
lmsService
lmsService: LmsService
page
page: SolidesPage
patternId
patternId: any
Public previousConsigne
previousConsigne: any
Public qcm
qcm: any
randomSolidTypeIndex
randomSolidTypeIndex: number
recognizeMode
recognizeMode: string
remediationRunning
remediationRunning: any
Public reponseNumérique
reponseNumérique: boolean
Default value: false
saveDefaultInputMethod
saveDefaultInputMethod: any
scenario
scenario: ScenarioSolides
Public solideTypes
solideTypes: any
solidId
solidId: any

Id du solide à afficher.

solidRepresentationId
solidRepresentationId: any
spacedRepetitonDate
spacedRepetitonDate: any
subject
subject: any
import { addWeeks, format, parseISO } from "date-fns";
import { first, Subscription } from "rxjs";
import { AppUtils } from "../app-utils";
import { SolidesPage } from "../page/solides/solides.page";
import { CabriDataService } from "../services/cabri-data.service";
import { GlobalService } from "../services/global.service";
import { LmsService } from "../services/lms.service";
import { AnswerStatus } from "./activity-answer";
import { CabriIntegrationSolides } from "./cabri-integration-solides";
import { LrsUtils } from "./lrs/lrsUtils";
import { ScenarioSolides } from "./scenario-solides";
import { AwardsType } from "./enums/awards";

declare var window: {
	store: any;
	document: any;
	innerWidth: any;
	innerHeight: any;
	outerWidth;
	outerHeight;
	dispatchEvent: any;
	CabriSceneBuilder: any;
	SceneUpdater: any;
	Hud: any;
	Globals: any;
	params: any;
	webGLCleanup: any;
	URL: any;
};

export enum ExerciseType {
	name = 0,
	recognize = 1,
	describe = 2,
	pattern = 3
}

export enum SolideType {
	CP = "1, 2, 4",
	CE1 = "1, 2, 4, 3, 5",
	CE2 = "1, 2, 4, 3, 5, 6"
}
export enum SolideIds {
	cube = 1,
	pave = 2,
	cone = 3,
	boule = 4,
	pyramide = 5,
	cylindre = 6
}
export enum SolideSubjects {
	faceNombre = "faceNombre",
	faceForme = "faceForme",
	arete = "arete",
	sommet = "sommet"
}

export enum ResultType {
	success,
	successOnRetry,
	fail,
	failOnFirstAttempt
}
export class ExercicesSolides {
	public consigne;
	public previousConsigne;
	public qcm: any;
	public reponseNumérique = false;
	randomSolidTypeIndex: number;
	// public exercise;
	public solideTypes: Map<string, Array<number>>;
	allowedSolids: any;
	allowedSolidRepresentations = [];
	allowedSolidPatterns = [];

	/** Id du solide affiché à la question précédente. */
	lastSolidId: number;
	/** Id du solide à afficher. */
	solidId: any;
	solidRepresentationId: any;

	private grade;
	lastSolidRepresentationId: any;
	exoType: any;
	goodResult;

	public answerSubscription: Subscription;
	allowedSubjects: any;
	subject: any;
	lastSolidPatternId: any;
	patternId: any;
	remediationRunning: any;
	saveDefaultInputMethod: any;
	lastSubject: any;
	lastExoType: any;
	spacedRepetitonDate: any;
	recognizeMode: string;
	consignSolidId: any;
	constructor(
		public cabri: CabriIntegrationSolides,
		public page: SolidesPage,
		public scenario: ScenarioSolides,
		public cabriService: CabriDataService,
		public lmsService: LmsService,
		public globalService: GlobalService
	) {
		const solidsByClass = [
			{ grade: "CP", solids: [1, 2, 4] },
			{ grade: "CE1", solids: [1, 2, 4, 3, 5] },
			{ grade: "CE2", solids: [1, 2, 4, 3, 5, 6] }
		];
		this.solideTypes = new Map(solidsByClass.map(item => [item.grade, item.solids]));
	}

	// 1   Manipulation libre
	// 2   Manipulation libre
	// 3   Manipulation libre
	// 1   Reconnaître les solides simples parmi des objets du quotidien
	// 2   Reconnaître les solides usuels parmi des objets du quotidien
	// 1   Nommer les solides simples parmi des objets du quotidien
	// 2   Nommer les solides usuels parmi des objets du quotidien
	// 3   Nommer les solides usuels parmi des objets du quotidien
	// 1   Décrire un cube/pavé
	// 2   Décrire un cube/pavé/pyramide
	// 3   Décrire un cube/pavé/pyramide/solide quelconque
	// 2   Patrons du cube
	// 3   Patrons du cube/pavé/pyramide

	updateVariables() {
		this.debug(this.cabriService.getCurrentExercice());
		this.lastExoType = this.exoType;

		// security for variables JSON decoding
		if (typeof this.cabriService.getCurrentExercice().variables === "string") {
			this.cabriService.getCurrentExercice().variables = JSON.parse(this.cabriService.getCurrentExercice().variables);
		}

		this.grade = this.cabriService.getCurrentExercice().classe;
		this.exoType = this.cabriService.getCurrentExercice().variables.type;

		this.debug(this.grade);
		this.debug(this.exoType);
		// default values for testing
		// this.grade = "CE2";
		// const randomExo = Math.floor(Math.random() * 4);
		// this.exoType = randomExo; // ExerciseType.describe;
		// this.exoType = ExerciseType.describe;

		// clean qcm choices.
		this.qcm = new Array();

		// clean representationId
		this.solidRepresentationId = null;
		// ExerciseType.name;
		// ExerciseType.recognize;
		// ExerciseType.describe;
		// ExerciseType.pattern;
		// ExerciseType.discovery;
		switch (this.exoType) {
			case ExerciseType.name:
				this.debug("ExerciseType.name");

				// choisir le solide (random)
				this.solidId = this.chooseRandomSolid();

				// choisir sa representation (cube de base, dés etc)
				this.solidRepresentationId = this.chooseRandomSolidRepresentation();
				this.debug("this.representation = " + this.page.SOLIDES[this.solidId].values[this.solidRepresentationId]);
				// ecrire consigne "quel est ce solide?"
				this.consigne = $localize`Quel est ce solide ?`;

				// set goodResult
				this.goodResult = this.page.SOLIDES[this.solidId].name;
				this.debug("this.goodResult = " + this.goodResult);

				// QCM pour numpad
				this.qcm = [
					{
						text: this.page.SOLIDES[this.solidId].qcmValue,
						value: this.page.SOLIDES[this.solidId].name
					}
				];
				this.addQCMFalseChoice();
				AppUtils.shuffleArray(this.qcm);
				break;
			case ExerciseType.recognize:
				// choisir le solide (random)
				this.solidId = this.chooseRandomSolid();

				// choisir mode OUI/NON ou QCM
				if (Math.random() > 0.5) {
					// QCM
					this.recognizeMode = "qcm";
					this.consigne = $localize`Quel est ce solide ?`;
					this.goodResult = this.page.SOLIDES[this.solidId].name;
					this.qcm = [
						{
							text: this.page.SOLIDES[this.solidId].qcmValue,
							value: this.page.SOLIDES[this.solidId].name
						}
					];
					// choisir les intrus
					this.addQCMFalseChoice();
					AppUtils.shuffleArray(this.qcm);
				} else {
					// oui/non
					this.recognizeMode = "ouinon";
					this.qcm = [
						{
							text: $localize`OUI`,
							value: $localize`:participantAnswereYes:oui`
						},
						{
							text: $localize`NON`,
							value: $localize`:participantAnswereNo:non`
						}
					];
					const solids = this.solideTypes.get(this.grade);
					const solidId2 = this.extractRandomElementFromArray(solids);
					this.consignSolidId = solidId2;
					if (solidId2 === this.solidId) {
						this.goodResult = $localize`:participantAnswereYes:oui`;
					} else {
						this.goodResult = $localize`:participantAnswereNo:non`;
					}
					const solidName = String(this.page.SOLIDES[solidId2].qcmValue).toLowerCase();
					this.consigne = $localize`Ce solide est-il ${solidName}:aSolidName: ?`;
				}

				// lequel de ces solides est un (solidType)
				break;
			case ExerciseType.describe:
				// choisir face (nombre ou forme) arretes sommets
				this.subject = this.chooseRandomSubject();

				// for testing
				// this.subject = SolideSubjects.faceNombre;

				// choisir le solide (random)
				this.solidId = this.chooseRandomSolid();

				// choisir sa representation (cube de base, dés etc)
				this.solidRepresentationId = this.chooseRandomSolidRepresentation();
				// question:
				switch (this.subject) {
					case SolideSubjects.faceForme:
						this.goodResult = this.page.SOLIDES[this.solidId].facetShape;
						this.consigne = $localize`Quelle forme ont les faces de ce solide ?`;
						this.qcm = [
							{
								text: this.page.SOLIDES[this.solidId].facetShape
							}
						];
						this.addQCMFalseChoice();
						AppUtils.shuffleArray(this.qcm);
						break;
					case SolideSubjects.faceNombre:
						this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbFacets;
						this.consigne = $localize`Combien de faces à ce solide ?`;
						this.createQCMNumberChoice();
						break;
					case SolideSubjects.arete:
						this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbEdges;
						this.consigne = $localize`Combien d’arêtes à ce solide ?`;
						this.createQCMNumberChoice();
						break;
					case SolideSubjects.sommet:
						this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbVertices;
						this.consigne = $localize`Combien de sommets à ce solide ?`;
						this.createQCMNumberChoice();
						break;
				}
				break;
			case ExerciseType.pattern:
				// choisir le solide (random)
				this.solidId = this.chooseRandomSolid();

				// choisir le patron
				this.patternId = this.chooseRandomSolidPattern();

				// question:
				this.consigne = $localize`De quel solide est-ce le patron ?`;

				// set goodResult
				this.goodResult = this.page.SOLIDES[this.solidId].name;

				// build qcm
				this.qcm = [
					{
						text: this.page.SOLIDES[this.solidId].qcmValue,
						value: this.page.SOLIDES[this.solidId].name
					}
				];
				this.addQCMFalseChoice();
				AppUtils.shuffleArray(this.qcm);

				// quel patron represente un cube? (multi patrons)
				break;

			default:
				break;
		}
	}
	addQCMFalseChoice(nb = 4) {
		let allowedSolids = new Array();
		switch (this.exoType) {
			case ExerciseType.name:
			case ExerciseType.recognize:
				// choose allowed solids : all
				this.solideTypes.get(this.grade).forEach(sId => {
					if (sId !== this.solidId) {
						allowedSolids.push(sId);
					}
				});

				// build qcm values : solide name
				while (allowedSolids.length > 0 && this.qcm.length < nb) {
					const qcmChoice = this.extractRandomElementFromArray(allowedSolids);
					allowedSolids = allowedSolids.filter(item => item !== qcmChoice);
					this.qcm.push({
						text: this.page.SOLIDES[qcmChoice].qcmValue,
						value: this.page.SOLIDES[qcmChoice].name
					});
				}
				break;
			case ExerciseType.describe:
				allowedSolids = new Array();

				// choose allowed solids : hasFacets
				this.solideTypes.get(this.grade).forEach(sId => {
					if (sId !== this.solidId && this.page.SOLIDES[sId].hasFacets) {
						allowedSolids.push(sId);
					}
				});

				// build qcm values : facet shape
				while (allowedSolids.length > 0 && this.qcm.length < nb) {
					const qcmChoice = this.extractRandomElementFromArray(allowedSolids);
					allowedSolids = allowedSolids.filter(item => item !== qcmChoice);
					this.qcm.push({
						text: this.page.SOLIDES[qcmChoice].facetShape
					});
				}
				break;
			case ExerciseType.pattern:
				allowedSolids = new Array();

				// choose allowed solids : hasPatron
				this.solideTypes.get(this.grade).forEach(sId => {
					if (sId !== this.solidId && this.page.SOLIDES[sId].hasPatron) {
						allowedSolids.push(sId);
					}
				});

				// build qcm values : solide name
				while (allowedSolids.length > 0 && this.qcm.length < nb) {
					const qcmChoice = this.extractRandomElementFromArray(allowedSolids);
					allowedSolids = allowedSolids.filter(item => item !== qcmChoice);
					this.qcm.push({
						text: this.page.SOLIDES[qcmChoice].qcmValue,
						value: this.page.SOLIDES[qcmChoice].name
					});
				}
				break;
			default:
				break;
		}
	}
	createQCMNumberChoice() {
		this.qcm = [{ text: 5 }, { text: 6 }, { text: 8 }, { text: 12 }];
		AppUtils.shuffleArray(this.qcm);
	}
	runExercice() {
		switch (this.exoType) {
			case ExerciseType.name:
				this.debug("ExerciseType.name");
				this.runExerciceNaming();
				break;
			case ExerciseType.recognize:
				this.debug("ExerciseType.recognize");
				this.runExerciceRecognize();
				break;
			case ExerciseType.describe:
				this.debug("ExerciseType.describe");
				this.runExerciceDescribe();
				break;
			case ExerciseType.pattern:
				this.debug("ExerciseType.pattern");
				this.runExercicePattern();
				break;
			default:
				break;
		}
	}

	chooseRandomSolid() {
		if (
			!this.allowedSolids ||
			this.allowedSolids.length === 0 ||
			(this.exoType === ExerciseType.describe && this.subject !== this.lastSubject) ||
			this.exoType !== this.lastExoType
		) {
			switch (this.exoType) {
				case ExerciseType.describe:
					this.allowedSolids = new Array();
					switch (this.subject) {
						case SolideSubjects.faceForme:
						case SolideSubjects.faceNombre:
							this.solideTypes.get(this.grade).forEach(sId => {
								if (this.page.SOLIDES[sId].hasFacets) {
									this.allowedSolids.push(sId);
								}
							});
							break;
						case SolideSubjects.arete:
							this.solideTypes.get(this.grade).forEach(sId => {
								if (this.page.SOLIDES[sId].hasEdges) {
									this.allowedSolids.push(sId);
								}
							});
							break;
						case SolideSubjects.sommet:
							this.solideTypes.get(this.grade).forEach(sId => {
								if (this.page.SOLIDES[sId].hasVertices) {
									this.allowedSolids.push(sId);
								}
							});
							break;
					}

					break;
				case ExerciseType.pattern:
					this.allowedSolids = new Array();

					this.solideTypes.get(this.grade).forEach(sId => {
						if (this.page.SOLIDES[sId].hasPatron) {
							this.allowedSolids.push(Number(sId));
						}
					});
					break;
				default:
					this.allowedSolids = [...this.solideTypes.get(this.grade)];
			}
		}
		const solidId = this.extractRandomElementFromArray(this.allowedSolids);
		this.allowedSolids = this.allowedSolids.filter(item => item !== solidId);

		this.debug("solidId = " + solidId);
		if (this.lastSolidId !== solidId) {
			return solidId;
		} else {
			return this.chooseRandomSolid();
		}
	}
	chooseRandomSubject() {
		if (!this.allowedSubjects || this.allowedSubjects.length === 0) {
			this.allowedSubjects = new Array(
				SolideSubjects.faceForme,
				SolideSubjects.faceNombre,
				SolideSubjects.arete,
				SolideSubjects.sommet
			);
		}
		const subject = this.extractRandomElementFromArray(this.allowedSubjects);
		this.allowedSubjects = this.allowedSubjects.filter(item => item !== subject);
		if (this.lastSubject !== subject) {
			this.lastSubject = subject;
			return subject;
		} else {
			return this.chooseRandomSubject();
		}
	}
	/**
	 * Choose one representation for the current solid
	 * @returns id of the choosen solid representation
	 */
	chooseRandomSolidRepresentation() {
		if (!this.allowedSolidRepresentations[this.solidId] || this.allowedSolidRepresentations[this.solidId].length === 0) {
			this.allowedSolidRepresentations[this.solidId] = new Array();
			for (const representationId in this.page.SOLIDES[this.solidId].values) {
				if (this.page.SOLIDES[this.solidId].values.hasOwnProperty(representationId)) {
					this.allowedSolidRepresentations[this.solidId].push(Number(representationId));
				}
			}
		}
		this.debug(this.allowedSolidRepresentations[this.solidId]);
		const solidRepresentationId = this.extractRandomElementFromArray(this.allowedSolidRepresentations[this.solidId]);
		this.allowedSolidRepresentations[this.solidId] = this.allowedSolidRepresentations[this.solidId].filter(
			item => item !== solidRepresentationId
		);
		if (this.lastSolidRepresentationId !== solidRepresentationId) {
			this.lastSolidRepresentationId = solidRepresentationId;
			return solidRepresentationId;
		} else {
			return this.chooseRandomSolidRepresentation();
		}
	}

	/**
	 * Choose one pattern for the current solid
	 * @returns id of the choosen solid representation
	 */
	chooseRandomSolidPattern() {
		if (!this.allowedSolidPatterns[this.solidId] || this.allowedSolidPatterns[this.solidId].length === 0) {
			this.allowedSolidPatterns[this.solidId] = new Array();
			for (let i = 1; i <= this.page.PATRON[this.solidId].max; i++) {
				this.allowedSolidPatterns[this.solidId].push(i);
			}
		}
		const solidPatternId = this.extractRandomElementFromArray(this.allowedSolidPatterns[this.solidId]);
		this.allowedSolidPatterns[this.solidId] = this.allowedSolidPatterns[this.solidId].filter(item => item !== solidPatternId);
		if (this.lastSolidPatternId !== solidPatternId) {
			this.lastSolidPatternId = solidPatternId;
			return solidPatternId;
		} else {
			return this.chooseRandomSolidPattern();
		}
	}
	async displaySolid() {
		await this.page.startLoader();
		if (this.page.cabri.patron || this.page.currentSolid !== this.solidId) {
			await this.page.changeSolidType(this.solidId);
			await this.page.cabri.getCabriMeshAsync(this.solidId + "-1");
			await AppUtils.timeOut(100);
			if (this.globalService.isIos) {
				await AppUtils.timeOut(2000);
			}
		}
		if (this.page.currentRepresentation !== this.solidRepresentationId) {
			await this.page.changeRepresentation(null, this.solidRepresentationId);
			await this.page.cabri.getCabriMeshAsync(this.solidId + "-" + this.solidRepresentationId);
		}
		await this.page.stopLoader();
	}

	async displayPattern() {
		this.page.startLoader();
		await this.page.changeSolidType(this.solidId);
		await this.page.cabri.getCabriMeshAsync(this.solidId + "-1");
		await AppUtils.timeOut(100);
		await this.page.patron(this.patternId);
		this.page.stopLoader();
	}

	extractRandomElementFromArray(array) {
		const randomIndex = Math.floor(Math.random() * array.length);
		const randomNumber = array[randomIndex];
		return randomNumber;
	}

	// nommer le solide affiché
	public async runExerciceNaming() {
		this.page.checkIfSonicMode();
		// afficher le solide
		if (!this.page.failed) {
			await this.displaySolid();
		}

		// poser la question
		await this.scenario.readConsigne(this.consigne);

		// attendre la réponse
		this.answerSubscription = this.page.waitUserAnswer(this.cabriService.inputMethod).subscribe(async answer => {
			const responseTimeInMs = Date.now() - LrsUtils.currentUserResponseTime;
			const responseTimeInSeconds = Math.floor(responseTimeInMs / 1000);
			LrsUtils.responseTime = responseTimeInSeconds;
			// traiter la réponse
			this.debug("participantAnswer = " + answer);

			if (answer === this.goodResult) {
				// si juste, passer à la question suivante
				this.page.audioService.playStarSound();
				this.answerSubscription.unsubscribe();
				if (this.page.failed) {
					this.manageLRS(ResultType.successOnRetry);
					await this.scenario.goodResponse(AnswerStatus.VALID2ND, responseTimeInSeconds, AwardsType.normal);
				} else {
					this.manageLRS(ResultType.success);
					await this.scenario.goodResponse(AnswerStatus.VALID1ST, responseTimeInSeconds, AwardsType.shooting);
				}
				this.page.failed = false;
				this.page.newQuestion();
			} else {
				this.page.audioService.playAwardMoonSound();
				await this.page.timeOut(500);
				if (!this.page.failed) {
					// si faux, lancer remédiation
					await this.scenario.badAnswer(AnswerStatus.ERROR1ST, responseTimeInSeconds);
					this.manageLRS(ResultType.failOnFirstAttempt);
					this.page.failed = true;
					this.runRemediation()
						.pipe(first())
						.subscribe(async e => {
							this.remediationRunning = false;
							this.page.waitUserAnswer(this.cabriService.inputMethod);
						});
				} else {
					this.page.failed = false;
					this.answerSubscription.unsubscribe();
					this.manageLRS(ResultType.fail);
					await this.scenario.readConsigne($localize`Oups ! La bonne réponse était : ${this.goodResult}:goodResult:`);
					this.page.newQuestion();
				}
			}
		});
	}
	// nommer le solide affiché
	public async runExerciceRecognize() {
		this.page.checkIfSonicMode();
		// afficher le solide
		if (!this.page.failed) {
			await this.displaySolid();
		}

		// poser la question
		await this.scenario.readConsigne(this.consigne);

		// attendre la réponse
		this.answerSubscription = this.page.waitUserAnswer(this.cabriService.inputMethod).subscribe(async answer => {
			const responseTimeInMs = Date.now() - LrsUtils.currentUserResponseTime;
			const responseTimeInSeconds = Math.floor(responseTimeInMs / 1000);
			LrsUtils.responseTime = responseTimeInSeconds;
			// traiter la réponse
			this.debug("participantAnswer = ", answer);
			if (answer === this.goodResult) {
				this.page.audioService.playStarSound();
				// si juste, passer à la question suivante
				this.answerSubscription.unsubscribe();
				if (this.page.failed) {
					this.manageLRS(ResultType.successOnRetry);
					await this.scenario.goodResponse(AnswerStatus.VALID2ND, responseTimeInSeconds, AwardsType.normal);
				} else {
					this.manageLRS(ResultType.success);
					await this.scenario.goodResponse(AnswerStatus.VALID1ST, responseTimeInSeconds, AwardsType.shooting);
				}
				if (this.goodResult === $localize`:participantAnswereNo:non` && !this.page.sonicAward) {
					const solidName = String(this.page.SOLIDES[this.solidId].qcmValue).toLowerCase();
					await this.scenario.readConsigne($localize`Ce solide est ${solidName}:aSolidName:.`);
				}
				this.page.failed = false;
				this.page.newQuestion();
			} else {
				this.page.audioService.playAwardMoonSound();
				await this.page.timeOut(500);
				if (!this.page.failed) {
					// si faux, lancer remédiation
					await this.scenario.badAnswer(AnswerStatus.ERROR1ST, responseTimeInSeconds);
					this.manageLRS(ResultType.failOnFirstAttempt);
					this.page.failed = true;
					this.runRemediation()
						.pipe(first())
						.subscribe(async e => {
							this.remediationRunning = false;
							this.page.waitUserAnswer(this.cabriService.inputMethod);
						});
				} else {
					this.page.failed = false;
					this.answerSubscription.unsubscribe();
					this.manageLRS(ResultType.fail);
					if (
						this.goodResult === $localize`:participantAnswereYes:oui` ||
						this.goodResult === $localize`:participantAnswereNo:non`
					) {
						await this.scenario.readConsigne($localize`Oups ! La bonne réponse était : ${this.goodResult}:goodResult:`);
						const solidName = String(this.page.SOLIDES[this.solidId].qcmValue).toLowerCase();
						await this.scenario.readConsigne($localize`Ce solide est ${solidName}:aSolidName:`);
					} else {
						const solidName = String(this.page.SOLIDES[this.solidId].qcmValue).toLowerCase();
						await this.scenario.readConsigne($localize`Oups ! Ce solide était ${solidName}:aSolidName:`);
					}
					this.page.newQuestion();
				}
			}
		});
	}

	async runExerciceDescribe() {
		this.page.checkIfSonicMode();
		// si difficulté === 1 ne pas afficher le solide
		// afficher le solide
		if (!this.page.failed) {
			await this.displaySolid();
		}

		// poser la question
		await this.scenario.readConsigne(this.consigne);

		// attendre la réponse
		this.answerSubscription = this.page.waitUserAnswer(this.cabriService.inputMethod).subscribe(async answer => {
			const responseTimeInMs = Date.now() - LrsUtils.currentUserResponseTime;
			const responseTimeInSeconds = Math.floor(responseTimeInMs / 1000);
			LrsUtils.responseTime = responseTimeInSeconds;
			// traiter la réponse
			this.debug("participantAnswer = " + answer);
			this.debug("goodResult = " + this.goodResult);

			if (String(answer) === String(this.goodResult)) {
				this.page.audioService.playStarSound();
				// si juste, passer à la question suivante
				this.answerSubscription.unsubscribe();
				if (this.page.failed) {
					this.manageLRS(ResultType.successOnRetry);
					await this.scenario.goodResponse(AnswerStatus.VALID2ND, responseTimeInSeconds, AwardsType.normal);
				} else {
					this.manageLRS(ResultType.success);
					await this.scenario.goodResponse(AnswerStatus.VALID1ST, responseTimeInSeconds, AwardsType.shooting);
				}
				this.page.failed = false;
				this.page.newQuestion();
			} else {
				this.page.audioService.playAwardMoonSound();
				await this.page.timeOut(500);
				if (!this.page.failed) {
					// si faux, lancer remédiation
					await this.scenario.badAnswer(AnswerStatus.ERROR1ST, responseTimeInSeconds);
					this.manageLRS(ResultType.failOnFirstAttempt);
					this.page.failed = true;
					this.runRemediation()
						.pipe(first())
						.subscribe(async e => {
							if (this.saveDefaultInputMethod) {
								// auto switch to numpad for SolideSubjects.faceForme, so revert
								this.cabriService.inputMethod = this.saveDefaultInputMethod;
								this.saveDefaultInputMethod = null;
							}
							this.remediationRunning = false;
							await this.endRemediationDescribe();
							await this.scenario.readConsigne(this.consigne);
							this.page.remediationCountingMode = false;
							this.page.waitUserAnswer(this.cabriService.inputMethod);
						});
				} else {
					this.page.failed = false;
					this.answerSubscription.unsubscribe();
					this.manageLRS(ResultType.fail);
					await this.scenario.readConsigne($localize`Oups ! La bonne réponse était : ${this.goodResult}:goodResult:`);
					this.page.newQuestion();
				}
			}
		});
	}

	async endRemediationDescribe() {
		switch (this.subject) {
			case SolideSubjects.faceForme:
				this.goodResult = this.page.SOLIDES[this.solidId].facetShape;
				this.consigne = $localize`Alors ? Quelle forme ont les faces de ce solide ?`;
				this.qcm = [
					{
						text: this.page.SOLIDES[this.solidId].facetShape
					}
				];
				this.addQCMFalseChoice();
				AppUtils.shuffleArray(this.qcm);
				break;
			case SolideSubjects.faceNombre:
				this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbFacets;
				this.consigne = $localize`Alors ? Combien de faces à ce solide ?`;
				this.createQCMNumberChoice();
				break;
			case SolideSubjects.arete:
				this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbEdges;
				this.consigne = $localize`Alors ? Combien d’arêtes à ce solide ?`;
				this.createQCMNumberChoice();
				break;
			case SolideSubjects.sommet:
				this.goodResult = this.page.SOLIDES_DATA[this.solidId + "-1"].nbVertices;
				this.consigne = $localize`Alors ? Combien de sommets à ce solide ?`;
				this.createQCMNumberChoice();
				break;
		}
	}

	async runExercicePattern() {
		this.page.checkIfSonicMode();
		// si difficulté === 1 ne pas afficher le solide
		// afficher le solide
		if (!this.page.failed) {
			await this.displayPattern();
			this.page.cabri.camera.beta = Math.PI / 6;
			this.page.cabri.camera.radius = 72;
			this.page.store.getters.cps.setPageOrientation(this.cabri.camera.alpha, this.cabri.camera.beta);
		}

		// poser la question
		await this.scenario.readConsigne(this.consigne);

		// attendre la réponse
		this.answerSubscription = this.page.waitUserAnswer(this.cabriService.inputMethod).subscribe(async answer => {
			const responseTimeInMs = Date.now() - LrsUtils.currentUserResponseTime;
			const responseTimeInSeconds = Math.floor(responseTimeInMs / 1000);
			LrsUtils.responseTime = responseTimeInSeconds;
			// traiter la réponse
			this.debug("participantAnswer = " + answer);
			this.debug("goodResult = " + this.goodResult);
			if (String(answer) === String(this.goodResult)) {
				// si juste, passer à la question suivante
				this.page.audioService.playStarSound();
				this.answerSubscription.unsubscribe();
				if (this.page.failed) {
					this.manageLRS(ResultType.successOnRetry);
					await this.scenario.goodResponse(AnswerStatus.VALID2ND, responseTimeInSeconds, AwardsType.normal);
				} else {
					this.manageLRS(ResultType.success);
					await this.scenario.goodResponse(AnswerStatus.VALID1ST, responseTimeInSeconds, AwardsType.shooting);
				}
				this.page.failed = false;
				this.page.newQuestion();
			} else {
				this.page.audioService.playAwardMoonSound();
				await this.page.timeOut(500);
				if (!this.page.failed) {
					// si faux la première fois, lancer remédiation
					this.manageLRS(ResultType.failOnFirstAttempt);
					await this.scenario.badAnswer(AnswerStatus.ERROR1ST, responseTimeInSeconds);
					this.page.failed = true;
					this.runRemediation()
						.pipe(first())
						.subscribe(async e => {
							this.remediationRunning = false;
							this.consigne = $localize`Alors ? De quel solide est-ce le patron ?`;
							await this.scenario.readConsigne(this.consigne);
							this.page.waitUserAnswer(this.cabriService.inputMethod);
						});
				} else {
					this.page.failed = false;
					this.answerSubscription.unsubscribe();
					this.manageLRS(ResultType.fail);
					await this.scenario.readConsigne($localize`Oups ! La bonne réponse était : ${this.goodResult}:goodResult:`);
					this.page.newQuestion();
				}
			}
		});
	}

	runRemediation() {
		this.remediationRunning = true;
		switch (this.exoType) {
			case ExerciseType.name:
				this.runRemediationName();
				break;
			case ExerciseType.recognize:
				this.runRemediationRecognize();
				break;
			case ExerciseType.describe:
				this.runRemediationDescribe();
				break;
			case ExerciseType.pattern:
				this.runRemediationPattern();
				break;
		}
		return this.page.remediationEndSubscription;
	}
	async runRemediationDescribe() {
		if (this.page.currentRepresentation !== 1) {
			await this.page.changeRepresentation(null, 1);
		}
		switch (this.subject) {
			case SolideSubjects.faceForme:
				await this.scenario.readConsigne($localize`Regarde bien et réessaye`);
				this.saveDefaultInputMethod = this.cabriService.inputMethod;
				this.cabriService.inputMethod = "numpad";
				this.page.remediationEndSubscription.next();
				break;
			case SolideSubjects.arete:
				this.page.remediationCountingMode = true;
				this.scenario.readConsigne(
					$localize`Tu peux zoomer en utilisant le curseur en bas à droite`,
					$localize`Clique sur toutes les arêtes pour les compter`
				);
				this.page.markEdges(null, true);
				break;
			case SolideSubjects.faceNombre:
				this.page.remediationCountingMode = true;
				this.scenario.readConsigne(
					$localize`Tu peux zoomer en utilisant le curseur en bas à droite`,
					$localize`Clique sur toutes les faces pour les compter`
				);
				this.page.markFacets(null, true);
				break;
			case SolideSubjects.sommet:
				this.page.remediationCountingMode = true;
				this.scenario.readConsigne(
					$localize`Tu peux zoomer en utilisant le curseur en bas à droite`,
					$localize`Clique sur tous les sommets pour les compter`
				);
				this.page.markVertices(null, true);
				break;
		}
	}
	async runRemediationName() {
		await this.scenario.descriptionSolid();
		await this.scenario.readConsigne($localize`Qui suis-je ?`);
		this.page.remediationEndSubscription.next();
	}
	async runRemediationRecognize() {
		await this.scenario.descriptionSolid();
		let consigne: string;
		if (this.recognizeMode === "qcm") {
			consigne = $localize`Alors ? Qui suis-je ?`;
		} else {
			const solidName = String(this.page.SOLIDES[this.consignSolidId].qcmValue).toLowerCase();
			consigne = $localize`Alors ? Suis-je ${solidName}:aSolidName: ?`;
		}
		await this.scenario.readConsigne(consigne);
		this.page.remediationEndSubscription.next();
	}
	async runRemediationPattern() {
		this.scenario.introRemediationPattern();
	}
	// getRandomIndexWithoutRepetition(numberArray, oldIndex): Promise<number> {
	// 	return new Promise((resolve, reject) => {
	// 		const newIndex = AppUtils.getRandomIntInclusive(1, 7);
	// 		newIndex !== oldIndex ? resolve(newIndex) : resolve(this.getRandomIndexWithoutRepetition(min, max, oldIndex));
	// 	});
	// }

	// without repetition / without filtering array
	chooseRandomIndexInArray(array, oldIndex) {
		const randomIndex = Math.floor(Math.random() * array.length);
		const randomNumber = array[randomIndex];
		if (randomNumber !== oldIndex) {
			return randomNumber;
		} else {
			return this.chooseRandomIndexInArray(array, oldIndex);
		}
	}

	manageLRS(result: ResultType) {
		switch (result) {
			case ResultType.success:
				this.page.manageLrsOperation("passed");
				this.page.updatePlayerAndTeamStarboards(AwardsType.shooting);
				break;
			case ResultType.successOnRetry:
				this.page.manageLrsOperation("passed-with-help");
				this.page.updatePlayerAndTeamStarboards(AwardsType.normal);
				break;
			case ResultType.fail:
				this.page.manageLrsOperation("failed");
				this.page.updatePlayerAndTeamStarboards(AwardsType.moon);
				break;
			case ResultType.failOnFirstAttempt:
				this.page.manageLrsOperation("failed-on-first-attempt");
				break;
		}
		if (result !== ResultType.failOnFirstAttempt) {
			this.page.showHideAward();
		}
	}
	getCurrentSolidName() {
		return this.page.SOLIDES[this.solidId].values[this.solidRepresentationId ? this.solidRepresentationId : 1];
	}

	async updateExercice(firstTime = true) {
		return new Promise<void>((resolve, reject) => {
			// console.error("update exercice");
			const previousConsigne = this.cabri.getCurrentOperation();
			this.updateVariables();

			// force numpad mode for certain exercises
			if (
				this.cabriService.currentExercice.responseMode &&
				this.cabriService.currentExercice.responseMode === "numpad" &&
				this.cabriService.holoMode === "0"
			) {
				this.cabriService.currentActivity.setParamValue("input-method", "numpad");
			}

			this.page.inputMethodsAllowed();

			// update time for spaced repetition
			const date = new Date();
			const seconds = (date.getTime() - this.spacedRepetitonDate.getTime()) / 1000;

			if (this.foundQuestionValid(seconds, previousConsigne)) {
				this.lastSolidId = this.solidId;
				this.spacedRepetitonDate = new Date();
				resolve();
			} else {
				setTimeout(async () => {
					await this.updateExercice(false);
					resolve();
				}, 30);
			}
		});
	}

	foundQuestionValid(seconds, previousConsigne) {
		if (this.lmsService.currentUserJourney) {
			// mode parcours
			if (!this.spacedStudentRepetition() && seconds <= 3) {
				return false;
			} else {
				if (seconds >= 3 && this.lmsService.currentUserJourney.allAskedQuestions) {
					this.lmsService.currentUserJourney.allAskedQuestions = new Array();

					// ne pas reposer la question précédente
					if (String(this.cabri.getCurrentOperation()) === String(previousConsigne)) {
						return false;
					}
				}
			}
		} else {
			// mode exercice

			const posInd = this.page.indexAlreadyAskedQuestion(this.cabri.getCurrentOperation(), previousConsigne);
			if (posInd > -1 && seconds <= 3) {
				return false;
			} else if (seconds >= 3 && this.page.questionAskedDuringSession) {
				this.page.questionAskedDuringSession = new Array();
				// ne pas reposer la question précédente
				if (String(this.consigne) === String(previousConsigne)) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * @returns true if the current question could be asked
	 */
	spacedStudentRepetition() {
		let spacedRepetition = true;

		const countCorrectAnswers = this.page.checkQuestionAskedDuringSession(this.cabri.getCurrentOperation());
		if (countCorrectAnswers !== 0) {
			const questionAwards = this.lmsService.currentUserJourney.allAskedQuestions.filter(questionAward => {
				return (
					questionAward.question &&
					(questionAward.question.trim() === this.cabri.getCurrentOperation().trim() ||
						this.cabri.getCurrentOperation().trim() === questionAward.question.trim().split("").reverse().join("")) &&
					typeof questionAward.award === "string"
				);
			});
			const lastTimePassedQuestion = questionAwards[questionAwards.length - 1];
			if (!lastTimePassedQuestion) {
				spacedRepetition = true;
			} else {
				const lastAnswerDate = lastTimePassedQuestion.timestamp;
				const weekCount = Math.pow(2, countCorrectAnswers - 1);
				const recurrenceWeek: string = format(addWeeks(parseISO(lastAnswerDate), weekCount), "T");
				spacedRepetition = Number(format(new Date(), "T")) > Number(recurrenceWeek);
			}
			return spacedRepetition;
		} else {
			// question never been asked
			return true;
		}
	}
	private debug(message, on = true) {
		if (on) {
			console.error(message);
		}
	}
}

results matching ""

    No results matching ""