import { PlayTTSService } from "src/app/services/play-tts.service";
import { GlobalService } from "src/app/services/global.service";
import { addWeeks, format, parseISO } from "date-fns";
import { Subscription } from "rxjs";
import { Fraction } from "../app-utils";
import { JeuJustePointPage, justePointActivity } from "../page/jeu-juste-point/jeu-juste-point.page";
import { CabriDataService } from "../services/cabri-data.service";
import { LmsService } from "../services/lms.service";
import { CabriIntegrationJeuJustePoint, ResultOnTheRoad, UserAnswer } from "./cabri-integration-jeujustepoint";
import { LrsUtils } from "./lrs/lrsUtils";
import { ScenarioJeuJustePoint } from "./scenario-jeu-juste-point";
import { AnswerNeedsHelp, AnswerStatus } from "./activity-answer";
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 ResultType {
success,
successOnRetry,
fail,
failOnFirstAttempt
}
export class ExercicesJustePoint {
exoType: justePointActivity;
public answerSubscription: Subscription;
spacedRepetitonDate: any;
consigneTTS: string;
constructor(
public cabri: CabriIntegrationJeuJustePoint,
public page: JeuJustePointPage,
public scenario: ScenarioJeuJustePoint,
public cabriService: CabriDataService,
public lmsService: LmsService,
public globalService: GlobalService,
public ttsService: PlayTTSService
) {}
updateVariables() {
this.cabriService.currentActivity.updateVariables(this.cabriService.getCurrentExercice());
this.exoType = this.cabriService.currentActivity.currentVariables.activity;
let expectedResult: any;
const nbVariablesArray = Object.entries(this.cabriService.currentActivity.currentVariables).filter(
key => key[0].includes("nb") && !key[0].includes("nbM")
);
this.cabriService.currentActivity.currentVariables.nb1 = Number(this.cabriService.currentActivity.currentVariables.nb1);
this.cabriService.currentActivity.currentVariables.unitStep = this.cabriService.currentActivity.currentVariables.pas;
if (nbVariablesArray.length > 1) {
expectedResult = [];
nbVariablesArray.forEach(key => {
expectedResult.push(Number(key[1]));
});
} else {
this.cabri.basketUnitStepPrecision = this.cabri.getStepPrecision(this.cabriService.currentActivity.currentVariables.unitStep);
if (nbVariablesArray[0][1] instanceof Fraction) {
expectedResult = nbVariablesArray[0][1];
} else {
expectedResult = Number(
Number(nbVariablesArray[0][1]).toFixed(this.cabri.displayedNumberPrecision(Number(nbVariablesArray[0][1])))
);
}
}
this.cabriService.currentActivity.currentVariables.expectedResult = expectedResult;
this.page.variable = this.cabriService.currentActivity.currentVariables;
this.page.variable.chooseExactNumber = (this.page.variable.chooseExactNumber as any) === 0 ? false : true;
if (!this.page.variable.chooseExactNumber && !this.page.variable.approximation) {
this.page.variable.approximation = 1;
}
}
public async runExerciceBasket() {
this.page.checkIfSonicMode();
this.getConsigneTTS();
this.ttsService.playTTSEventProtected(this.consigneTTS);
await this.cabri.displayBasketBoxRandomly();
// WAIT USER ANSWER
this.answerSubscription = this.cabri.waitUserAnswer.subscribe(async (userAnswer: UserAnswer) => {
await this.ttsService.killSpeech();
this.page.displayActivityElements(false);
this.cabriService.participantAnswer = userAnswer?.response;
if (userAnswer.success) {
let scenario;
const animation = this.page.runAnimSuccess();
if (this.page.failed) {
// SUCCESS 2ND TRY
this.manageLRS(ResultType.successOnRetry);
if (this.page.sonicAward) {
scenario = this.scenario.playSoundWithAward(AwardsType.normal);
} else {
this.page.audioService.playStarSound();
scenario = this.scenario.dynamicFeedback(AnswerStatus.VALID2ND, LrsUtils.responseTime, AwardsType.normal);
}
await this.execScenarioMathiaAnim(animation, scenario);
} else {
// SUCCESS 1ST TRY
this.manageLRS(ResultType.success);
if (this.page.sonicAward) {
scenario = this.scenario.playSoundWithAward(AwardsType.shooting);
} else {
this.page.audioService.playStarSound();
scenario = this.scenario.dynamicFeedback(AnswerStatus.VALID1ST, LrsUtils.responseTime, AwardsType.shooting);
}
await this.execScenarioMathiaAnim(animation, scenario);
}
this.page.failed = false;
this.page.detectChanges();
this.page.newQuestion();
} else {
this.page.audioService.playAwardMoonSound();
if (!this.page.failed) {
// ERROR 1ST TRY - NEED HELP
this.manageLRS(ResultType.failOnFirstAttempt);
const animation = this.page.runAnimError();
const scenario = this.firstBadResponseScenario(AnswerStatus.ERROR1ST, LrsUtils.responseTime, null, AnswerNeedsHelp.YES);
await this.execScenarioMathiaAnim(animation, scenario);
if (this.isChallengeMode) {
this.checkChallengeModeRules();
this.page.failed = false;
} else {
await this.runRemediationBasket();
this.page.failed = true;
}
if(!this.isChallengeMode){
this.page.displayMathia(false);
this.page.displayActivityElements(true);
this.page.detectChanges();
this.getConsigneTTS(true);
this.ttsService.playTTSEventProtected(this.consigneTTS);
}
} else {
// ERROR 2ND TRY
this.page.failed = false;
this.manageLRS(ResultType.fail);
const animation = this.page.runAnimError();
const scenario = this.page.scenario.badResponseMoonWithResultBasketDynamic(
AnswerStatus.ERROR2ND,
LrsUtils.responseTime,
AwardsType.moon
);
await this.execScenarioMathiaAnim(animation, scenario);
this.page.displayActivityElements(true);
this.page.newQuestion();
}
}
});
}
getConsigneTTS(secondTry?: boolean) {
const virgule = $localize`virgule`;
const kilometre = $localize`kilomètre`;
switch (this.exoType) {
case justePointActivity.basket:
this.consigneTTS = this.cabri
.getConsigneExerciceBasketTTS(secondTry)
.reduce((p, n) => p + "!" + n)
.replace(/\./g, " "+ virgule +" ");
break;
case justePointActivity.onTheRoad:
// injected in cabri.buildElementActivityOnTheRoad()
break;
}
}
public async runExerciceOnTheRoad() {
this.ttsService.playTTSEventProtected(this.consigneTTS);
// WAIT USER ANSWER
this.answerSubscription = this.cabri.waitUserAnswer.subscribe(async (userAnswer: UserAnswer) => {
await this.page.ttsService.killSpeech();
this.cabri.toggleGui(false);
// traiter la réponse
if (userAnswer.success) {
let waitScenario;
const promiseSuccess = this.page.runAnimSuccess();
if (this.page.failed) {
// SUCCESS 2ND TRY
this.manageLRS(ResultType.successOnRetry, false);
this.page.checkIfSonicMode();
if (this.page.sonicAward) {
waitScenario = this.scenario.playSoundWithAward(AwardsType.normal);
} else {
this.page.audioService.playStarSound();
waitScenario = this.scenario.dynamicFeedback(AnswerStatus.VALID2ND, LrsUtils.responseTime, AwardsType.normal);
}
await this.execScenarioMathiaAnim(promiseSuccess, waitScenario);
} else {
// SUCCESS 1ST TRY
this.manageLRS(ResultType.success, false);
this.page.checkIfSonicMode();
if (this.page.sonicAward) {
waitScenario = this.scenario.playSoundWithAward(AwardsType.shooting);
} else {
this.page.audioService.playStarSound();
waitScenario = this.scenario.dynamicFeedback(AnswerStatus.VALID1ST, LrsUtils.responseTime, AwardsType.shooting);
}
await this.execScenarioMathiaAnim(promiseSuccess, waitScenario);
}
this.page.failed = false;
this.page.displayCM = false;
this.page.detectChanges();
this.page.newQuestion();
} else {
this.page.audioService.playAwardMoonSound();
if (!this.page.failed) {
// ERROR 1ST TRY - NEED HELP
this.manageLRS(ResultType.failOnFirstAttempt, false);
const promiseError = this.page.runAnimError(false);
const scenario = this.firstBadResponseScenario(AnswerStatus.ERROR1ST, LrsUtils.responseTime, null, AnswerNeedsHelp.YES);
await this.execScenarioMathiaAnim(promiseError, scenario);
this.page.displayMathia(false);
await this.scenario.showErrorCar(userAnswer.response as ResultOnTheRoad);
if (this.isChallengeMode) {
this.checkChallengeModeRules();
this.page.failed = false;
} else {
if (userAnswer.reversed) {
await this.scenario.runRemediationRoadReverse();
} else {
await this.scenario.runRemediationRoad();
}
this.cabri.toggleGui(true);
this.page.cabri.highlightCar(false, userAnswer.response as ResultOnTheRoad);
this.page.failed = true;
}
this.page.detectChanges();
this.cabri.resetAllCarPosition(userAnswer.response as ResultOnTheRoad);
} else {
// ERROR 2ND TRY
this.page.failed = false;
this.manageLRS(ResultType.fail, false);
await this.scenario.badResponseMoonWithResultNextQRoadDynamic(userAnswer.response as ResultOnTheRoad,
AnswerStatus.ERROR2ND,
LrsUtils.responseTime,
AwardsType.moon);
this.page.newQuestion();
}
}
});
}
/**
* Scenario of the first bad response
*/
firstBadResponseScenario(answerStatus: AnswerStatus, responseTimeInSeconds: number, award?: string, help?: AnswerNeedsHelp) {
let currentScenario: Promise<void>;
if (Number(this.cabriService.currentActivity.variables["v-cursor-mod"]) === 2) {
// MODE D2FI
currentScenario = this.scenario.eliminationChallengeMode();
} else {
if (this.exoType === justePointActivity.basket) {
currentScenario = this.scenario.dynamicFeedback(answerStatus, responseTimeInSeconds, award, help );
} else {
currentScenario = this.scenario.dynamicFeedback(answerStatus, responseTimeInSeconds, award, help );
}
}
return currentScenario;
}
checkChallengeModeRules() {
if (this.page.currentTeam.length === 1) {
this.page.endActivity();
} else if (this.page.currentTeam.length > 1) {
this.page.currentEliminatedPlayerId = Number(this.page.currentUser.playerId);
this.page.newQuestion();
}
}
get isChallengeMode() {
return Number(this.cabriService.currentActivity.variables["v-cursor-mod"]) === 2;
}
/**
* Promises of scenario and mascotte animation
*/
execScenarioMathiaAnim(promiseAnim: Promise<any>, promiseScenario: Promise<any>, callback?): Promise<void> {
return new Promise<void>(resolve => {
Promise.all([promiseAnim, promiseScenario]).then(async () => {
if (callback) {
await callback();
}
resolve();
});
});
}
async runRemediationBasket(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
this.page.activateHelp = true;
this.page.cabriService.unitsHelp = true;
this.scenario.runResumedRemediationBasket().then(() => {
if (this.page.isActivePage()) {
this.page.validationType = "units_help_validation";
this.page.cabriService.unitsHelp = this.page.globalService.unitsHelpCMValidation = true;
this.page.validateAnswer(null, null, true, true).then(async validationData => {
this.page.cabri.resetMeshesAfterRemediation();
this.page.validationActive = false;
this.page.globalService.unitsHelpCMValidation = false;
this.page.detectChanges();
if (validationData === true) {
this.page.cabriService.hideMathia = true;
this.page.activateHelp = false;
this.cabriService.unitsHelp = false;
this.page.detectChanges();
// END
resolve();
} else if (validationData === false) {
this.page.activateHelp = true;
this.page.cabriService.hideMathia = false;
await this.scenario.runCompleteRemediationBasket();
this.page.activateHelp = false;
this.page.cabri.resetMeshesAfterRemediation();
this.page.detectChanges();
// this.toggleUnitsTutorialCore(resolve, reject, status);
}
this.page.validationType = "";
this.page.cabriService.unitsHelp = this.page.globalService.unitsHelpCMValidation = false;
resolve();
});
this.page.detectChanges();
} else {
this.page.validationActive = false;
this.page.globalService.unitsHelpCMValidation = false;
this.cabriService.unitsHelp = false;
this.page.activateHelp = false;
resolve();
}
});
});
}
/**
* Response time foor LRS
*/
calculateResponseTime() {
const timestampDiff = Date.now() - LrsUtils.currentUserResponseTime;
const millis = Math.floor(timestampDiff / 1000);
LrsUtils.responseTime = millis;
}
/**
* Show or hide mascotte (if show scene is not displayed)
*/
mathiaDisplay(show: boolean) {
if (show) {
this.cabri.displayMeshes(false);
this.page.displayExceptedResult = false;
this.page.displayCM = true;
this.cabriService.hideMathia = false;
} else {
this.cabri.displayMeshes(true);
this.page.displayExceptedResult = true;
this.page.displayCM = false;
this.cabriService.hideMathia = false;
}
this.page.detectChanges();
}
async runExercice() {
switch (this.exoType) {
case justePointActivity.basket:
await this.runExerciceBasket();
break;
case justePointActivity.onTheRoad:
this.runExerciceOnTheRoad();
}
}
manageLRS(result: ResultType, toggleStarboard = true) {
this.calculateResponseTime();
switch (result) {
case ResultType.success:
this.page.manageLrsOperation("passed");
this.page.updatePlayerAndTeamStarboards(this.getAward(AwardsType.shooting));
break;
case ResultType.successOnRetry:
this.page.manageLrsOperation("passed-with-help");
this.page.updatePlayerAndTeamStarboards(this.getAward(AwardsType.normal));
break;
case ResultType.fail:
// this.page.manageLrsOperation("failed");
this.page.updatePlayerAndTeamStarboards(this.getAward(AwardsType.moon));
break;
case ResultType.failOnFirstAttempt:
const isEliminated = this.checkRulesLrsElimination();
if (isEliminated) {
this.page.manageLrsOperation("failed-on-first-attempt", true, true);
} else {
this.page.manageLrsOperation("failed-on-first-attempt");
}
break;
}
if (result !== ResultType.failOnFirstAttempt && toggleStarboard) {
this.page.showHideAward();
}
}
/**
* Check if any user eliminated after bad response before end of the activity
*/
checkRulesLrsElimination(): boolean {
let anyUserEliminated = false;
if (this.isChallengeMode) {
anyUserEliminated = this.page.currentTeam.length > 1 && !this.page.endOfActivity();
}
return anyUserEliminated;
}
getAward(award: AwardsType): string {
let currentAward = award;
// exceptions
if (Number(this.cabriService.currentActivity.variables["v-cursor-mod"]) === 0) {
// MODE DECOUVERTE
if (award === AwardsType.shooting) {
currentAward = AwardsType.normal;
}
}
return currentAward;
}
async updateExercice(firstTime = false) {
return new Promise((resolve, reject) => {
// console.error("update exercice");
const previousConsigne = this.cabri.getCurrentOperation();
const exoType = this.updateVariables();
// force numpad mode for certain exercises
// update time for spaced repetition
const date = new Date();
const seconds = (date.getTime() - this.spacedRepetitonDate.getTime()) / 1000;
if (this.foundQuestionValid(seconds, previousConsigne)) {
this.spacedRepetitonDate = new Date();
resolve(exoType);
} else {
setTimeout(async () => {
resolve(await this.updateExercice());
}, 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();
}
}
} 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();
}
}
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);
}
}
}