// TuxMath JS
//
// Author: Julien Marin - projet Afrikalan
// Contact: julien.marin@afrikalan.org
// Licence: AGPL-3.0-or-later, see COPYING file
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
const LEVEL_CARDS_NUM=40;
const NOT_ANSWERED_REPEAT=2;
const NB_CORRECT_ANSWER_BONUS=10;
class Game
{
constructor(objJqContainer)
{
this.objJqContainer=objJqContainer;
this.init();
this.running=false;
}
getObjJqContainer() { return this.objJqContainer; }
init()
{
this.tuxConsole=new TuxConsole(this);
this.keyboardManager=new KeyboardManager();
this.keyboardManager.fctCallBackKeyStroke.push(this.tuxConsole.updateText.bind(this.tuxConsole));
this.keyboardManager.fctCallBackKeyStroke.push(this.tuxConsole.evtTyping.bind(this.tuxConsole));
this.keyboardManager.fctCallBackKeyStroke.push(MusicPlayer.giveUserInputEvent.bind(MusicPlayer));
this.keyboardManager.fctCallBackEntryValidated.push(this.evtUserValidatedAnswer.bind(this));
this.keyboardManager.fctCallBackPauseUnpause.push(this.evtPauseUnpause.bind(this));
}
evtUserValidatedAnswer(answer)
{
if ((this.runningWave) && (this.runningWave.evtUserValidatedAnswer))
{
let boolCorrect=this.runningWave.evtUserValidatedAnswer(answer);
if (boolCorrect)
this.totalAnswersGood++;
else
{
this.totalAnswersBad++;
this.checkGameOver();
}
this.updateCountersDisplay();
}
}
startGame(levelId)
{
this.running=true;
this.paused=false;
this.waveNum=0;
this.sameLevelWaveNum=0;
this.totalAnswersGood=0;
this.totalAnswersBad=0;
this.nextBonusAt=NB_CORRECT_ANSWER_BONUS;
this.loadLevel(levelId);
this.startWave();
this.initIgloos();
MusicPlayer.play();
}
loadLevel(levelId)
{
this.sameLevelWaveNum=0;
this.levelId=levelId;
this.mathCardsCollection=new MathCardsCollection();
this.mathCardsCollection.genCards(this.levelId, LEVEL_CARDS_NUM);
}
getWaveComets(waveNum)
{
return(4+waveNum);
}
startWave()
{
this.waveNum++;
this.sameLevelWaveNum++;
this.objJqContainer.find(".js-background").css("background-image", "url('"+tmGlob_objTheme.chooseBackground()+"')");
let waveCards=this.mathCardsCollection.takeCards(this.getWaveComets(this.sameLevelWaveNum));
if (waveCards.length==0)
{
this.gameFinished(true);
return false;
}
this.runningWave=new GameWave(this, waveCards);
this.updateCountersDisplay();
//Launching programming wave start animation events
let startOffset=0;
if (this.waveNum>1) startOffset=2000;
setTimeout(this.tuxConsole.setTuxImage.bind(this.tuxConsole, CONSOLE_TUX_PRESTART), 100); //overridden if called directly, override check not simple to implement...
setTimeout(function(){
tmGlob_objSfxPlayer.playSfx(SFX_LEVELSTART)
this.tuxConsole.yellowBlink();
}.bind(this), startOffset+2000);
setTimeout(function(){
this.tuxConsole.setTuxImage(CONSOLE_TUX_ANIMSTART);
}.bind(this), startOffset+3000);
setTimeout(this.runningWave.startWave.bind(this.runningWave), startOffset+4000);
}
initIgloos()
{
this.arIgloos=[]
for (let i=1; i<=GAME_COLUMNS_NUMBER; i++)
this.arIgloos.push(new Igloo(this, i));
}
//get random alive igloo
getAliveIgloo()
{
let rndMap=[];
for (let i in this.arIgloos)
rndMap[i]=i;
array_shuffle(rndMap);
for (let i in this.arIgloos)
if (! this.arIgloos[rndMap[i]].isDestroyed())
return this.arIgloos[rndMap[i]];
}
evtPauseUnpause()
{
this.paused=!this.paused;
$("body").toggleClass("gamePaused", this.paused);
if (this.paused)
MusicPlayer.pause();
else
MusicPlayer.unPause();
}
ispaused() { return this.paused; }
gameFinished(boolSuccess)
{
this.running=false;
this.cleanUp();
if (boolSuccess)
{
gold_star_add(this.levelId);
new MessageGameEnd(GAME_END_WIN, this.keyboardManager);
for (let i in this.arIgloos)
this.arIgloos[i].evtWin();
}
else
new MessageGameEnd(GAME_END_LOOSE, this.keyboardManager);
}
evtNotAnswered(cometId, mathCard, colNumber) //comet reached bottom and exploded...
{
this.arIgloos[colNumber-1].evtGotHit();
this.tuxConsole.setTuxImage(CONSOLE_TUX_IGLOODESTROYED);
this.checkGameOver();
this.mathCardsCollection.addMathCard(mathCard, NOT_ANSWERED_REPEAT);
this.runningWave.evtNotAnswered(cometId, mathCard, colNumber);
this.updateCountersDisplay();
}
cleanUp()
{
this.runningWave.cleanUp();
}
checkGameOver()
{
let gameOver=true;
for (let i in this.arIgloos)
if (! this.arIgloos[i].isDestroyed())
gameOver=false;
if (gameOver)
this.gameFinished(false);
}
evtWaveFinished()
{
if (tmGlob_Options.get(OPT_AUTOLEVEL)=="1")
{
let computedLJ=this.runningWave.computeLevelJump();
if (computedLJ!=AL_NOJUMP)
{
if (computedLJthis.nextBonusAt)
{
this.nextBonusAt=this.totalAnswersGood+NB_CORRECT_ANSWER_BONUS;
return true;
}
else return false;
}
evtBonusCometDestroyed()
{
let mostDestroyedId=-1;
let mostDestroyedHealth=IGLOO_HEALTH_OK;
for (let i in this.arIgloos)
if (this.arIgloos[i].health a + b, 0) / this.answersGoodTimes.length;
console.log({ "answGood": this.answersGood, "answBad": this.answersBad, "meanTime": meanSecondsAnswersGoodTime, "unanswered": this.answersNotAnswered });
if ((this.answersNotAnswered*2>=this.answersGood*3) && (this.answersNotAnswered>2))
return AL_JUMP_BACKWARD_BIG;
if ((this.answersNotAnswered>=this.answersGood) && (this.answersNotAnswered>1))
return AL_JUMP_BACKWARD_SMALL;
if ((meanSecondsAnswersGoodTime=0; i--) //process array reverse order, to prevent index skip bugs
if (this.arLaunchedCommets[i].answer==answer)
{
cometsMatched++;
this.answersGoodTimes.push(this.arLaunchedCommets[i].getSecondsSinceLaunch());
this.arLaunchedCommets[i].destroy();
new Laser(this.objGame, this.arLaunchedCommets[i]);
this.removeComet(this.arLaunchedCommets[i].getId()); //remove at end of iteration!
}
if (cometsMatched>1)
tmGlob_objSfxPlayer.playSfx(SFX_COMETDESTROY_MULTIPLE);
if (cometsMatched>0)
{
this.answersGood++;
this.checkWaveFinished();
}
else
{
this.answersBad++;
tmGlob_objSfxPlayer.playSfx(SFX_ANSWER_BAD);
this.objGame.tuxConsole.redBlink();
if (this.answersBad%NB_ANSWER_BAD_DESTROY_IGLOO==0)
{ //to many bad anwser -> Destroy igloo
let targetIgloo=this.objGame.getAliveIgloo();
new Laser(this.objGame, targetIgloo.getCenterCoords());
targetIgloo.evtGotHitByLaser();
}
else
{
new Laser(this.objGame, this.searchFalseLocation());
}
}
return (cometsMatched>0); //return true if user writen validen answer, false otherwise.
}
searchFalseLocation()
{
let originJqObject=this.objGame.getObjJqContainer().find(".js-laser-origin");
let br=originJqObject[0].getBoundingClientRect();
let originCoords=[Math.round(br.x+br.width/2), Math.round(br.y+br.height/2)];
let origLaserObj={x: originCoords[0], y: originCoords[1]};
tryLoop: for (let triesLeft=FALSE_LOCATION_SEARCH_TRIES; triesLeft>0; triesLeft--)
{
//let targetCoords=[getRandomInt(0, this.objGame.getObjJqContainer().width()-1), getRandomInt(0,this.objGame.getObjJqContainer().height()*.8-1)]; //*.8 to prevent laser going down
//target random plot of screen is not good, we shoud olny target borders
let border=getRandomInt(0, 2);
let targetCoords;
if (border==0) targetCoords=[0, getRandomInt(0,this.objGame.getObjJqContainer().height()*.8-1)];
if (border==1) targetCoords=[getRandomInt(0,this.objGame.getObjJqContainer().width()-1), 0];
if (border==2) targetCoords=[this.objGame.getObjJqContainer().width(), getRandomInt(0,this.objGame.getObjJqContainer().height()*.8-1)];
let targetLaserObj={x: targetCoords[0], y: targetCoords[1]};
for (let i in this.arLaunchedCommets)
{
let commetCoords=this.arLaunchedCommets[i].getCenterCoords();
let objCommetCoords={x: commetCoords[0], y: commetCoords[1]};
if (distToSegment(objCommetCoords, origLaserObj, targetLaserObj)