import {BehaviorSubject} from 'rxjs'; import {Injectable} from '@angular/core'; import {GameActionsService} from './game-actions.service'; import {SubLevelService} from './sub-level.service'; import {PreloaderOxService} from 'ox-core'; import {ExerciseOx} from 'ox-core'; import {ExpandableInfo, shuffle} from 'ox-types'; @Injectable({providedIn: 'root'}) export abstract class ChallengeService { private usedDataExercises: Exercise[]; // Todo re-check this. Is for a pilot in yaci with worker. protected cachedExercises: ExerciseOx[]; public currentExercise: BehaviorSubject> = new BehaviorSubject(undefined); protected currentSubLevelPregeneratedExercisesNeeded = 2; protected exerciseIndex!:number; public waitingForExerciseResources: ExerciseOx; resources: Map = new Map(); protected constructor(private gameActions: GameActionsService, private subLevelService: SubLevelService, protected preloader: PreloaderOxService) { this.cachedExercises = []; this.usedDataExercises = []; this.gameActions.showNextChallenge.subscribe(_ => { this.nextChallenge(); this.assignCurrentExerciseFromCache(); }); this.gameActions.restartGame.subscribe(answer => { this.cachedExercises = []; this.currentExercise.next(undefined); }); this.gameActions.microLessonCompleted.subscribe(e => { this.cachedExercises = []; this.currentExercise.next(undefined); }); this.gameActions.exitFromGame.subscribe(e => { this.cachedExercises = []; this.currentExercise.next(undefined); }); } protected abstract generateNextChallenge(subLevel: number): ExerciseOx; protected abstract equalsExerciseData(exerciseData: Exercise, exerciseDoneData: Exercise): boolean; public nextChallenge(): void { let checkingRepeatedCount = 0; let possibleChallenge; // the goal is to have pre cached possible exercises for every possible sub level const toAddSubLevels = this.getPossibleNextSubLevels(this.getCurrentSubLevel(), this.getMaxSubLevel(), this.cachedExercises, this.currentSubLevelPregeneratedExercisesNeeded); for (let i = 0; i < toAddSubLevels.length; i++) { if (this.cachedExercises.filter(x => x.subLevel === toAddSubLevels[i]).length > 3) { continue; } do { possibleChallenge = this.generateNextChallenge(toAddSubLevels[i]); checkingRepeatedCount++; if (checkingRepeatedCount >= 50) { this.removeHalfRepeatedExercises(); } if (checkingRepeatedCount >= 2000) { // console.log('loop in generate next challenge', this.usedDataExercises); throw new Error('Loop in generate next challenge'); } } while (this.isRepeatedExercise(possibleChallenge.exerciseData)); this.cachedExercises.push(possibleChallenge); } this.loadExerciseResources(); /* this.exercisesReadyToUse.next(this.cachedExercises.splice(0, 1)[0]); */ } private loadExerciseResources() { this.cachedExercises.forEach(exercise => { if (!this.preloader.areLoad(exercise.requiredResources.map(x => x.path))) { this.preloader.loadResourceList(exercise.requiredResources).subscribe(x => { if (this.itsTheSameAsTheOneWaiting(exercise)) { this.resourcesLoadedForCurrentWaitingExercise(); } }); } else if (this.itsTheSameAsTheOneWaiting(exercise)) { this.resourcesLoadedForCurrentWaitingExercise(); } }); } private resourcesLoadedForCurrentWaitingExercise() { this.newCurrentExercise(this.waitingForExerciseResources); this.waitingForExerciseResources = undefined; // TODO great event } private itsTheSameAsTheOneWaiting(exercise) { return this.waitingForExerciseResources && exercise.subLevel === this.waitingForExerciseResources.subLevel && this.equalsExerciseData(exercise.exerciseData, this.waitingForExerciseResources.exerciseData) && this.waitingForExerciseResources.requiredResources.every((z, i) => exercise.requiredResources[i].path === z.path); } protected removeHalfRepeatedExercises() { const countToRemove = this.usedDataExercises.length / 2; this.usedDataExercises = this.usedDataExercises.length > 1 ? this.usedDataExercises.slice(countToRemove) : []; // this.usedDataExercises; } protected isRepeatedExercise(exerciseData: Exercise): boolean { return this.usedDataExercises.some(x => this.equalsExerciseData(exerciseData, x)); } public assignCurrentExerciseFromCache() { const possibles = this.cachedExercises.filter(x => x.subLevel === this.getCurrentSubLevel()); if (possibles.length === 0) { this.nextChallenge(); throw new Error(' Request for a non set sublevel exercise. Please check pre-exercises generation for a solution.'); } const nextChallenge = shuffle(possibles).sort((a, b) => this.anyNotLoadFor(a) ? -1 : 1)[0]; // const nextChallenge = shuffle(possibles).sort((a, b) => this.anyNotLoadFor(a) ? -1 : 1)[0]; // const nextChallenge = possibles[Math.floor(Math.random() * possibles.length)]; // remove next challenge with splice from exercises cache const nextChallengeIndex = this.cachedExercises.findIndex(e => this.equalsExerciseData(e.exerciseData, nextChallenge.exerciseData)); const nextChallengeArray = this.cachedExercises.splice(nextChallengeIndex, 1); if (nextChallengeIndex === -1) { console.warn('There was not next challenge index.' + '\nCannot find the selected exercise in cached.' + '\nOrobably your equalExerciseData method is not working as expected..'); } const newExercise = nextChallengeArray[0]; // this.currentExercise.next(nextChallengeArray[0]); // this.usedDataExercises.push(this.currentExercise.value.exerciseData); if (this.getPossibleNextSubLevels(this.getCurrentSubLevel(), this.getMaxSubLevel(), this.cachedExercises, this.currentSubLevelPregeneratedExercisesNeeded).length > 1) { this.nextChallenge(); } if (this.anyNotLoadFor(newExercise)) { this.waitingForExerciseResources = newExercise; // forkJoin(this.currentExercise.value.requiredResources.map(e => this.preloader.loadResource(e, true))).subscribe(value => { // this.waitingForResources = false; // // this.viewOxService.nextChallengeSet(); // }); } else { // TODO don't now if this must be here // } else if (!this.preloader.anyNotLoadFor(ScreenTypeOx.Game)) { // TODO don't now if this must be here, BIG ERROR MUST NOT BE HERE. this.newCurrentExercise(newExercise); // this.waitingForExerciseResources = undefined; // this.waitingForResources = false; // this.viewOxService.nextChallengeSet(); } } private anyNotLoadFor(exercise: ExerciseOx) { return !this.preloader.areLoad(exercise.requiredResources.map(x => x.path)); } private newCurrentExercise(exercise: ExerciseOx) { this.currentExercise.next(exercise); this.usedDataExercises.push(this.currentExercise.value.exerciseData); } protected getPossibleNextSubLevels(currentSubLevel: number, maxSubLevel: number, cacheExercises: ExerciseOx[], preGeneratedExercisesNeeded: number): number[] { const possibleSubLevels: number[] = []; const previousSubLevel = currentSubLevel - 1; // Add one of previous sublevel if (currentSubLevel > 1 && !cacheExercises.some(x => x.subLevel === previousSubLevel)) { possibleSubLevels.push(previousSubLevel); } // Add pre generated count of current sublevel exercises while (cacheExercises.filter(x => x.subLevel === currentSubLevel).length + possibleSubLevels.filter(x => x === currentSubLevel).length < preGeneratedExercisesNeeded) { possibleSubLevels.push(currentSubLevel); } const nextSubLevel = currentSubLevel + 1; // if there is a higher subelevel add one of these to if (nextSubLevel <= maxSubLevel && !cacheExercises.some(x => x.subLevel === nextSubLevel)) { possibleSubLevels.push(nextSubLevel); } return possibleSubLevels; } private getCurrentSubLevel(): number { return this.subLevelService.currentSublevel.value; } private getMaxSubLevel() { return this.subLevelService.getMaxSubLevel(); } public beforeStartGame(): void { } public inWumbox(resources: { name: string, value: string }[]): void { resources.forEach(md => { this.resources.set(md.name, md.value as string); }); } public abstract getMetricsInitialExpandableInfo(): ExpandableInfo; public onRestartGame() { } } // { // exercisesData: [], // exerciseMetadata: {}, // globalStatement: [], // timeSettings: { // timeMode: 'no-time', // } // }