import {EventEmitter, Injectable} from '@angular/core'; import {GameActionsService} from '../game-actions.service'; import {AppInfoOxService} from '../app-info-ox.service'; import {ScoreStarsService} from './score-stars.service'; import {LevelService} from '../level.service'; import {ChallengeService} from '../challenge.service'; import {SubLevelService} from '../sub-level.service'; import {filter, take} from 'rxjs/operators'; import { MicroLessonInfoOxDefault, ExerciseData, MiniLessonMetrics, UserAnswer, PartCorrectness, sum, ExerciseSchema, SchemaData, secondsBetweenDates } from 'ox-types'; import {Subscription} from 'rxjs'; @Injectable({ providedIn: 'root' }) export class MicroLessonMetricsService { currentMetrics: MiniLessonMetrics; // currentMetrics: MicroLessonMetrics; // exercisesBySubLevel: number[]; finishedMetrics: EventEmitter = new EventEmitter(true); updateMetrics: EventEmitter = new EventEmitter(true); startedMetrics: EventEmitter = new EventEmitter(true); private timeSubs: Subscription[] = []; public inGameMetrics = true; constructor(// private timer: TimerOxService, // private answerService: AnswerService, private gameActions: GameActionsService, private appInfo: AppInfoOxService, private scoreStars: ScoreStarsService, private levelService: LevelService, private challenge: ChallengeService, private subLevelService: SubLevelService) { this.gameActions.startGame.subscribe(value => { this.gameStart(value.info, value.level); }); // this.subLevelService.currentSublevel.subscribe(sublevel => { // if (this.currentMetrics) { // this.currentMetrics.lastSubLevelReached = sublevel; // } // }); this.gameActions.restartGame.subscribe(value => { this.metricsValuesAsStart(); this.currentMetrics.level = value ? value.level : undefined; // this.currentMetrics.level = value ? value.level : undefined; // this.timer.stop(); this.startedMetrics.emit(this.currentMetrics as any); // this.startedMetrics.emit(this.currentMetrics); }); this.gameActions.surrender.subscribe(() => { // this.currentMetrics.surrender++; this.currentMetrics.surrender++; // const lastExerciseMetric = this.getLastExerciseMetric(); // lastExerciseMetric.surrender = true; // lastExerciseMetric.surrenderTime = new Date(); // lastExerciseMetric.finishTime = lastExerciseMetric.surrenderTime; const lastExerciseMetric = this.getLastExerciseMetric(); lastExerciseMetric.userInput.surrendered = true; lastExerciseMetric.finishTime = new Date(); // this.updateNetTime(); // this.timer.pause(); this.metricsUpdated(); }); this.gameActions.microLessonCompleted.subscribe(() => { // TODO remove this. Should have a end challenge service or someting like that. // const lastExerciseMetric = this.getLastExerciseMetric(); // if (lastExerciseMetric.finishTime === undefined) { // lastExerciseMetric.finishTime = new Date(); // } const lastExerciseMetric = this.getLastExerciseMetric(); if (lastExerciseMetric && lastExerciseMetric.finishTime === undefined) { lastExerciseMetric.finishTime = new Date(); } this.gameFinished(); }); this.gameActions.showHint.subscribe(() => { this.currentMetrics.hints++; // this.currentMetrics.hints++; // const lastMetric = this.getLastExerciseMetric(); // this.getLastExerciseMetric().hints.push({answersBeforeHint: lastMetric.answers.length, askTime: new Date()}); const lastMetric = this.getLastExerciseMetric(); lastMetric.userInput.requestedHints++; // .push({answersBeforeHint: lastMetric.answers.length, askTime: new Date()}); // TODO check if here must also send updated metrics event. }); this.challenge.currentExercise.pipe(filter(exercise => exercise !== undefined)).subscribe(__ => { console.log('New exercise has arrived, but metrics are not longer adding those by default, the game must call add metric with specific values'); // this.addMetric(); // this.gameActions.showNextChallenge.subscribe(__ => { // TODO validate this change. // The problem is that if metrics listen to showNextChallenge this metric is corrupted, cause // in challenge service show next chllenge triggers currentChallenge and generates a // race condition, if metrics listen the event before challenge service this will throw, // If not, all the exercises will have a wrong value for the metric cause it will be the previous one. // const currentExercise = this.challenge.currentExercise.value; // if (this.currentMetrics.exerciseMetrics.length === 0) { // this.timer.start(); // } else if (!this.timer.isRunning()) { // this.timer.resume(); // } // this.currentMetrics.exerciseMetrics.push( // { // level: this.levelService.currentLevel.value, // subLevel: currentExercise.subLevel, // initialTime: new Date(), // bonusTime: currentExercise.bonusTime, // exercise: currentExercise.exerciseData, // answers: [], // hints: [], // surrender: false, // gameSurprisesUsed: [] // }); }); // this.answerService.tryAnswer.subscribe(value => { // console.log('TODO try answer in metrics'); // console.log('TODO try answer in metrics'); // console.log('TODO try answer in metrics'); // console.log('TODO try answer in metrics'); // console.log('TODO try answer in metrics'); // // this.getLastExerciseMetric() // // .answers.push({correctness: 'indeterminate', tryDate: new Date(), answer: value}); // }); this.gameActions.checkedAnswer.subscribe((answerData: { correctness: PartCorrectness, answer: UserAnswer }) => { console.log('TODO checkedAnswer in metrics'); const lastExerciseMetric = this.getLastExerciseMetric(); lastExerciseMetric.userInput.answers.push(answerData.answer); if (answerData.correctness === 'unknown') { lastExerciseMetric.finalStatus = 'answered-without-correction'; lastExerciseMetric.finishTime = new Date(); // this.updateNetTime(); } else if (answerData.correctness === 'correct') { this.currentMetrics.rightAnswers++; lastExerciseMetric.finalStatus = 'correct'; // answerByExercise.answers[answerByExercise.answers.length - 1].correctness = 'right'; lastExerciseMetric.finishTime = new Date(); // this.updateNetTime(); } else { // this.currentMetrics.wrongAnswers++; this.currentMetrics.wrongAnswers++; lastExerciseMetric.finalStatus = 'wrong'; // answerByExercise.answers[answerByExercise.answers.length - 1].correctness = 'wrong'; } this.metricsUpdated(); }); } // private getLastExerciseMetric(): ExerciseMetrics { // return this.currentMetrics.exerciseMetrics[this.currentMetrics.exerciseMetrics.length - 1]; // } public getLastExerciseMetric(): ExerciseData { return this.currentMetrics.expandableInfo.exercisesData[this.currentMetrics.expandableInfo.exercisesData.length - 1]; } public gameStart(info: MicroLessonInfoOxDefault, level?: number): void { // TODO check if this table will be continue to be used or not. // TODO probably not cause may be in the microlesson config file, and catch there by the game score calculator. // const table = this.appInfo.getMetricsTable(info.microLessonId, level); this.currentMetrics = {} as MiniLessonMetrics; this.currentMetrics.metricsType = this.appInfo.microLessonInfo?.creatorInfo?.metricsType || 'results'; this.currentMetrics.level = level; // this.currentMetrics = {} as MicroLessonMetrics; // this.currentMetrics.metricsType = this.appInfo.microLessonInfo?.creatorInfo?.metricsType || 'results'; // this.currentMetrics.level = level; this.metricsValuesAsStart(); // TODO check this, exercise to up level is used in several games // this.currentMetrics.exercisesToUpLevel = this.appInfo.getExercisesToUpSubLevel(level); // this.startedMetrics.emit(this.currentMetrics); console.log('startedMetrics', this.currentMetrics); this.startedMetrics.emit(this.currentMetrics as any); } metricsValuesAsStart(): void { // this.currentMetrics.rightAnswers = 0; // this.currentMetrics.wrongAnswers = 0; // this.currentMetrics.hints = 0; // this.currentMetrics.netTime = 0; // this.currentMetrics.surrender = 0; // this.currentMetrics.exerciseMetrics = []; // this.currentMetrics.microLessonId = this.appInfo.microLessonInfo.microLessonId; this.destroyTimeSubs(); this.currentMetrics.rightAnswers = 0; this.currentMetrics.wrongAnswers = 0; this.currentMetrics.hints = 0; this.currentMetrics.surrender = 0; this.currentMetrics.netTime = 0; this.currentMetrics.pointsScore = 0; this.currentMetrics.bonusScore = 0; this.currentMetrics.score = 0; this.currentMetrics.stars = 0; this.currentMetrics.totalSecondsInResource = 0; this.currentMetrics.date = undefined; if (this.inGameMetrics) { this.currentMetrics.expandableInfo = this.challenge.getMetricsInitialExpandableInfo(); const aux = this.currentMetrics.expandableInfo.exerciseMetadata.exercisesQuantity; this.currentMetrics.exercises = aux !== 'infinite' ? aux : 0; } else { this.currentMetrics.exercises = 0; } this.currentMetrics.resourceUid = this.appInfo.microLessonInfo.microLessonId; // TODO check if this id is correct. ask nash } private gameFinished(): void { // this.scoreStars.calculateScore(this.currentMetrics); // this.scoreStars.calculateStars(this.currentMetrics); // this.finishedMetrics.emit(this.currentMetrics); console.log('Game finished', this.currentMetrics); this.setNetTime(); if (this.currentMetrics.metricsType === 'results') { this.scoreStars.calculateScore(this.currentMetrics); this.scoreStars.calculateStars(this.currentMetrics); } this.finishedMetrics.emit(this.currentMetrics); } private setNetTime(): void { // const aux = this.getLastExerciseMetric(); // const aux = this.getLastExerciseMetric(); this.currentMetrics.netTime = Math.round(sum(this.currentMetrics.expandableInfo.exercisesData .map(z => (+z.finishTime) - (+z.initialTime))) / 1000); // const init = aux.initialTime; // const finish = aux.finishTime; // const rest = (+finish) - (+init); // const toSeconds = rest / 1000; // // this.currentMetrics.netTime += toSeconds; // this.currentMetrics.netTime += toSeconds; } private metricsUpdated(): void { console.log('metricsUpdated', this.currentMetrics); // this.updateMetrics.emit(this.currentMetrics); this.updateMetrics.emit(this.currentMetrics); } // updateSurpriseMetric(currentSurprisesUsed: SurpriseInfo[]): void { // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // // const lastMetric = this.getLastExerciseMetric(); // // const lastMetric = this.getLastExerciseMetricNew(); // // lastMetric.gameSurprisesUsed = currentSurprisesUsed; // } // @deprecated public addMetric(exerciseData: ExerciseData): void { console.log('You are using a deprecated way to add a metric'); this.currentMetrics.expandableInfo.exercisesData.push(exerciseData); // this.sendMetricsToWumbox(); } public addMetricWithStandardValues(schemaType: ExerciseSchema, schemaData: SchemaData, increaseExerciseCount: boolean = false): void { const metric: ExerciseData = { userInput: {requestedHints: 0, answers: [], surrendered: false}, finishTime: undefined, firstInteractionTime: undefined, initialTime: new Date(), finalStatus: 'to-answer', secondsInExercise: 0, schemaData, schemaType }; this.destroyTimeSubs(); this.currentMetrics.expandableInfo.exercisesData.push(metric); this.timeSubs.push(this.gameActions.actionToAnswer.pipe(take(1)) .subscribe(z => metric.firstInteractionTime = new Date())); this.timeSubs.push(this.gameActions.checkedAnswer.pipe(take(1)) .subscribe(z => { metric.finishTime = new Date(); metric.secondsInExercise = secondsBetweenDates(metric.initialTime, metric.finishTime); })); if (increaseExerciseCount) { this.currentMetrics.exercises++; } } public startMetrics(info: MicroLessonInfoOxDefault, level?: number): void { this.currentMetrics = {} as MiniLessonMetrics; // this.currentMetrics = {} as MicroLessonMetrics; this.currentMetrics.metricsType = this.appInfo.microLessonInfo?.creatorInfo?.metricsType || 'results'; this.currentMetrics.level = level; this.metricsValuesAsStart(); // TODO poner esto en newMetrics // this.currentMetrics.exercisesToUpLevel = this.appInfo.getExercisesToUpSubLevel(level); // this.startedMetrics.emit(this.currentMetrics); console.log('startedMetrics', this.currentMetrics); this.startedMetrics.emit(this.currentMetrics); } private destroyTimeSubs() { this.timeSubs.forEach( z => z.unsubscribe()); this.timeSubs = []; } } // ******************************************************************************************************************* // ******************************************************************************************************************* // ******************************************** OLD METRICS ********************************************************** // ******************************************************************************************************************* // ******************************************************************************************************************* // import {EventEmitter, Injectable} from '@angular/core'; // import {GameActionsService} from '../game-actions.service'; // import {AppInfoOxService} from '../app-info-ox.service'; // import {ScoreStarsService} from './score-stars.service'; // import {AnswerService} from '../answer.service'; // import {ChallengeService} from '../challenge.service'; // import {SubLevelService} from '../sub-level.service'; // import {LevelService} from '../level.service'; // import { // MicroLessonMetrics, MicroLessonInfoOxDefault, SurpriseInfo // } from 'ox-types'; // import {filter} from 'rxjs/operators'; // import {ExerciseData, MetricsPerResource} from '../../models/new-metrics'; // import {ExerciseMetrics} from '../../../../../ox-types/src/lib/models/all-communication-protocols'; // @Injectable({ // providedIn: 'root' // }) // export class MicroLessonMetricsService { // currentMetrics: MetricsPerResource; // currentMetrics: MicroLessonMetrics; // tempMetric: ExerciseData; // // exercisesBySubLevel: number[]; // finishedMetrics: EventEmitter> = new EventEmitter(true); // updateMetrics: EventEmitter> = new EventEmitter(true); // startedMetrics: EventEmitter> = new EventEmitter(true); // // constructor(// private timer: TimerOxService, // private answerService: AnswerService, // private gameActions: GameActionsService, private appInfo: AppInfoOxService, // private scoreStars: ScoreStarsService, // private levelService: LevelService, // private answer: AnswerService, private challenge: ChallengeService, // private subLevelService: SubLevelService) { // this.gameActions.startGame.subscribe(value => { // this.gameStart(value.info, value.level); // }); // // this.subLevelService.currentSublevel.subscribe(sublevel => { // // if (this.currentMetrics) { // // this.currentMetrics.lastSubLevelReached = sublevel; // // } // // }); // this.gameActions.restartGame.subscribe(value => { // this.metricsValuesAsStart(); // this.currentMetrics.level = value ? value.level : undefined; // this.currentMetrics.level = value ? value.level : undefined; // // this.timer.stop(); // this.startedMetrics.emit(this.currentMetrics as any); // // this.startedMetrics.emit(this.currentMetrics); // }); // this.gameActions.surrender.subscribe(() => { // this.currentMetrics.surrender++; // this.currentMetrics.surrenderAnswers++; // const lastExerciseMetric = this.getLastExerciseMetric(); // lastExerciseMetric.surrender = true; // lastExerciseMetric.surrenderTime = new Date(); // lastExerciseMetric.finishTime = lastExerciseMetric.surrenderTime; // this.updateNetTime(); // // this.timer.pause(); // this.metricsUpdated(); // }); // this.gameActions.microLessonCompleted.subscribe(() => { // // TODO remove this. Should have a end challenge service or someting like that. // const lastExerciseMetric = this.getLastExerciseMetric(); // if (lastExerciseMetric.finishTime === undefined) { // lastExerciseMetric.finishTime = new Date(); // } // this.gameFinished(); // }); // this.gameActions.showHint.subscribe(() => { // this.currentMetrics.hints++; // this.currentMetrics.hints++; // const lastMetric = this.getLastExerciseMetric(); // this.getLastExerciseMetric().hints.push({answersBeforeHint: lastMetric.answers.length, askTime: new Date()}); // // TODO check if here must also send updated metrics event. // }); // // this.challenge.currentExercise.pipe(filter(exercise => exercise !== undefined)).subscribe(__ => { // // this.gameActions.showNextChallenge.subscribe(__ => { // TODO validate this change. // // The problem is that if metrics listen to showNextChallenge this metric is corrupted, cause // // in challenge service show next chllenge triggers currentChallenge and generates a // // race condition, if metrics listen the event before challenge service this will throw, // // If not, all the exercises will have a wrong value for the metric cause it will be the previous one. // const currentExercise = this.challenge.currentExercise.value; // // if (this.currentMetrics.exerciseMetrics.length === 0) { // // this.timer.start(); // // } else if (!this.timer.isRunning()) { // // this.timer.resume(); // // } // this.currentMetrics.exerciseMetrics.push( // { // level: this.levelService.currentLevel.value, // subLevel: currentExercise.subLevel, // initialTime: new Date(), // bonusTime: currentExercise.bonusTime, // exercise: currentExercise.exerciseData, // answers: [], // hints: [], // surrender: false, // gameSurprisesUsed: [] // }); // // this.addMetric(); // }); // // this.answerService.tryAnswer.subscribe(value => { // this.getLastExerciseMetric() // .answers.push({correctness: 'indeterminate', tryDate: new Date(), answer: value}); // }); // this.gameActions.checkedAnswer.subscribe(isCorrect => { // const answerByExercise = this.getLastExerciseMetric(); // const answerByExerciseNew = this.getLastExerciseMetricNew(); // // answerByExerciseNew.userInput.answers.push(this.answer.getCurrentUserInputAnswer()); // if (isCorrect) { // this.currentMetrics.rightAnswers++; // this.currentMetrics.rightAnswers++; // answerByExerciseNew.finalStatus = 'correct'; // answerByExercise.answers[answerByExercise.answers.length - 1].correctness = 'right'; // answerByExercise.finishTime = new Date(); // this.updateNetTime(); // } else { // this.currentMetrics.wrongAnswers++; // this.currentMetrics.wrongAnswers++; // answerByExercise.answers[answerByExercise.answers.length - 1].correctness = 'wrong'; // } // this.metricsUpdated(); // }); // } // // private getLastExerciseMetric(): ExerciseMetrics { // return this.currentMetrics.exerciseMetrics[this.currentMetrics.exerciseMetrics.length - 1]; // } // // private getLastExerciseMetricNew(): ExerciseData { // return this.currentMetrics.expandableInfo.exercisesData[this.currentMetrics.expandableInfo.exercisesData.length - 1]; // } // // public gameStart(info: MicroLessonInfoOxDefault, level?: number): void { // // TODO check if this table will be continue to be used or not. // // TODO probably not cause may be in the microlesson config file, and catch there by the game score calculator. // // const table = this.appInfo.getMetricsTable(info.microLessonId, level); // this.currentMetrics = {} as MetricsPerResource; // this.currentMetrics = {} as MicroLessonMetrics; // this.currentMetrics.metricsType = this.appInfo.microLessonInfo?.creatorInfo?.metricsType || 'results'; // this.currentMetrics.level = level; // this.currentMetrics.level = level; // this.metricsValuesAsStart(); // this.currentMetrics.exercisesToUpLevel = // this.appInfo.getExercisesToUpSubLevel(level); // // this.startedMetrics.emit(this.currentMetrics); // console.log('startedMetrics', this.currentMetrics); // this.startedMetrics.emit(this.currentMetrics as any); // } // // metricsValuesAsStart(): void { // this.currentMetrics.rightAnswers = 0; // this.currentMetrics.wrongAnswers = 0; // this.currentMetrics.hints = 0; // this.currentMetrics.netTime = 0; // this.currentMetrics.surrender = 0; // this.currentMetrics.exerciseMetrics = []; // this.currentMetrics.microLessonId = this.appInfo.microLessonInfo.microLessonId; // // this.currentMetrics.rightAnswers = 0; // this.currentMetrics.wrongAnswers = 0; // this.currentMetrics.hints = 0; // this.currentMetrics.netTime = 0; // this.currentMetrics.surrenderAnswers = 0; // this.currentMetrics.expandableInfo.exercisesData = []; // this.currentMetrics.resourceUid = this.appInfo.microLessonInfo.microLessonId; // // } // // private gameFinished(): void { // // // this.scoreStars.calculateScore(this.currentMetrics); // // this.scoreStars.calculateStars(this.currentMetrics); // // this.finishedMetrics.emit(this.currentMetrics); // console.log('Game finished', this.currentMetrics); // this.scoreStars.calculateScore(this.currentMetrics as any); // this.scoreStars.calculateStars(this.currentMetrics as any); // this.finishedMetrics.emit(this.currentMetrics as any); // } // // private updateNetTime(): void { // const aux = this.getLastExerciseMetric(); // const init = aux.initialTime; // const finish = aux.finishTime; // const rest = (+finish) - (+init); // const toSeconds = rest / 1000; // this.currentMetrics.netTime += toSeconds; // this.currentMetrics.netTime += toSeconds; // } // // private metricsUpdated(): void { // console.log('metricsUpdated', this.currentMetrics); // // this.updateMetrics.emit(this.currentMetrics); // this.updateMetrics.emit(this.currentMetrics as any); // } // // updateSurpriseMetric(currentSurprisesUsed: SurpriseInfo[]): void { // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // console.log('Fix update surprise metrics things!!'); // // const lastMetric = this.getLastExerciseMetric(); // // const lastMetric = this.getLastExerciseMetricNew(); // // lastMetric.gameSurprisesUsed = currentSurprisesUsed; // } // // }