File indexing completed on 2024-05-12 15:21:21

0001 /* GCompris - graduated_line_read.js
0002  *
0003  * SPDX-FileCopyrightText: 2023 Bruno ANSELME <be.root@free.fr>
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  *
0006  */
0007 .pragma library
0008 .import QtQuick 2.12 as Quick
0009 .import GCompris 1.0 as GCompris // for ApplicationInfo
0010 .import "qrc:/gcompris/src/core/core.js" as Core
0011 
0012 var numberOfLevel
0013 var items
0014 var activityMode
0015 var maxSolutionSize = 0
0016 var mapToPad = {}       // Maps keyboard charcodes to numPad's indexes to animate graphics from computer's numpad
0017 var segmentThickness = 2
0018 var exercices = []
0019 
0020 function randInt(max) { return Math.floor(Math.random() * max) }
0021 
0022 function longInt(val) {     // Format number to string (avoid exponential notation)
0023     val = Number(val)
0024     var str = ""
0025     while (val !== 0) {
0026         var remainder = val % 10
0027         str = String(remainder) + str
0028         val = Math.floor(val / 10)
0029     }
0030     if (str === "") str = "0"
0031     return str
0032 }
0033 
0034 function createExercice(idx, rules, s) {
0035     var nbSeg = 2
0036     var step = rules.steps[s]
0037     var maxOffset = 0
0038     if (rules.fitLimits) {
0039         nbSeg = Math.floor((rules.range[1] - rules.range[0]) / step) + 1
0040     } else {
0041         nbSeg = 1 + rules.segments[0] + randInt(1 + rules.segments[1] - rules.segments[0])
0042         maxOffset = rules.range[1] - (nbSeg * step)
0043         while (maxOffset < 0) {             // if data is not valid, reduce the number of segment
0044             nbSeg--
0045             maxOffset =  rules.range[1] - (nbSeg * step)
0046         }
0047         maxOffset = rules.range[1] - ((nbSeg - 1) * step)
0048     }
0049     if (rules.fitLimits) {
0050         idx = 1 + (idx % (nbSeg - 2))
0051     } else {
0052         idx = randInt(nbSeg - 2) + 1
0053     }
0054     var start = rules.range[0] + randInt(maxOffset)
0055     return {
0056         "solution": idx,
0057         "step": step,
0058         "nbSeg": nbSeg,
0059         "start": start,
0060         "rangeMin": rules.range[0],
0061         "rangeMax": rules.range[1]
0062     }
0063 }
0064 
0065 function createLevel() {    // Create an array of exercice
0066     exercices = []
0067     var levelRules = items.levels[items.currentLevel].rules
0068     for(var s = 0; s < levelRules.steps.length; s++) {
0069         var nbTicks = Math.floor((levelRules.range[1] - levelRules.range[0]) / levelRules.steps[s]) -1
0070         for(var i = 0; i < nbTicks; i++) {
0071             var exo = createExercice(i, levelRules, s)
0072             exercices.push(exo)
0073         }
0074     }
0075 }
0076 
0077 function buildRuler() {     // Read from exercices with currentSubLevel index
0078     items.solutionGrad = 0
0079     items.rulerModel.clear()
0080     if (items.currentSubLevel % exercices.length === 0) // if first time or all exercices already done
0081         Core.shuffle(exercices)                         //    shuffle exercices before restarting
0082     var exo = exercices[items.currentSubLevel % exercices.length]
0083     var start = exo.start
0084     var thickStep = Math.floor(exo.rangeMax / 10)
0085     if (thickStep < 10)
0086         thickStep = 10
0087     var i = 0
0088     for (i = 0; i < exo.nbSeg; i++) {           // Create rulerModel
0089         var thick = segmentThickness
0090         if (start % thickStep === 0)
0091             thick = 2 * segmentThickness
0092         if ((i === 0) || (i === exo.nbSeg - 1))
0093             thick = 3 * segmentThickness
0094         items.rulerModel.append({ "value_": start
0095                                 , "thickness_": thick })
0096         start += exo.step
0097     }
0098     maxSolutionSize = start.toString().length
0099     var min = (items.orientation === Qt.LeftToRight) ? 0 : items.rulerModel.count -1
0100     var max = (items.orientation === Qt.LeftToRight) ? items.rulerModel.count -1 : 0
0101     items.leftLimit.text = longInt(items.rulerModel.get(min).value_)
0102     items.rightLimit.text = longInt(items.rulerModel.get(max).value_)
0103 
0104     items.solutionGrad = exo.solution
0105     items.answer = items.rulerModel.get(items.solutionGrad).value_.toString()
0106     if (activityMode === "number2tick")   // Choose an other starting tick
0107         items.solutionGrad = randInt(exo.nbSeg - 2) + 1
0108 }
0109 
0110 function createRuler() {
0111     var levelRules = items.levels[items.currentLevel].rules
0112     items.numberOfSubLevel = levelRules.nbOfQuestions
0113     buildRuler()
0114     var title = items.levels[items.currentLevel].title
0115     if (!levelRules.fitLimits)
0116         title = title.substr(0, title.length - 1) + " " + qsTr("(variable boundaries)") + title.substr(title.length - 1)
0117 }
0118 
0119 function moveLeft() {
0120     if (items.score.isWinAnimationPlaying || items.buttonsBlocked)
0121         return
0122     if (items.solutionGrad > 1) {
0123         items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/audioclick.wav')
0124         items.solutionGrad--
0125     }
0126 }
0127 
0128 function moveRight() {
0129     if (items.score.isWinAnimationPlaying || items.buttonsBlocked)
0130         return
0131     if (items.solutionGrad < items.rulerModel.count - 2) {
0132         items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/audioclick.wav')
0133         items.solutionGrad++
0134     }
0135 }
0136 
0137 function checkResult() {
0138     if (items.score.isWinAnimationPlaying || items.buttonsBlocked)
0139         return
0140     items.buttonsBlocked = true;
0141     var success = false;
0142     switch (activityMode) {
0143     case "tick2number":
0144         success = (items.cursor.children[items.solutionGrad].textValue === items.answer);
0145         break
0146     case "number2tick":
0147         success = (items.rulerModel.get(items.solutionGrad).value_.toString() === items.answer);
0148         if (success)
0149             items.cursor.children[items.solutionGrad].textValue = items.answer;
0150         break
0151     }
0152     if (success) {
0153         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0154         items.currentSubLevel ++;
0155         items.score.playWinAnimation();
0156     } else {
0157         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav")
0158         items.buttonsBlocked = true;
0159         items.errorRectangle.startAnimation();
0160     }
0161 }
0162 
0163 function start(items_, activityMode_) {
0164     items = items_;
0165     activityMode = activityMode_
0166     items.orientation = (Core.isLeftToRightLocale(GCompris.ApplicationSettings.locale)) ?  Qt.LeftToRight : Qt.RightToLeft
0167 //    items.orientation = Qt.RightToLeft  // Force RightToLeft here
0168     // Make sure numberOfLevel is initialized before calling Core.getInitialLevel
0169     numberOfLevel = items.levels.length
0170     items.currentLevel = Core.getInitialLevel(numberOfLevel)
0171     initLevel();
0172 }
0173 
0174 function stop() {
0175 }
0176 
0177 function initLevel() {
0178     items.errorRectangle.resetState();
0179     items.buttonsBlocked = false;
0180     items.bar.level = items.currentLevel + 1;
0181     items.currentSubLevel = 0;
0182     createLevel();
0183     createRuler();
0184  }
0185 
0186 function nextLevel() {
0187     items.score.stopWinAnimation()
0188     items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel);
0189     initLevel();
0190 }
0191 
0192 function previousLevel() {
0193     items.score.stopWinAnimation()
0194     items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel);
0195     initLevel();
0196 }
0197 
0198 function nextSubLevel() {
0199     if(items.currentSubLevel >= items.numberOfSubLevel)
0200         items.bonus.good("sun");
0201     else {
0202         items.buttonsBlocked = false;
0203         createRuler(); // initLevel();
0204     }
0205 }
0206 
0207 function previousSubLevel() {
0208     if( --items.currentSubLevel < 0)
0209         previousLevel();
0210     else {
0211         items.buttonsBlocked = false;
0212         createRuler(); // initLevel();
0213     }
0214 }
0215 
0216 function handleKeys(key) {
0217     if (items.score.isWinAnimationPlaying || items.buttonsBlocked)
0218         return
0219     if (items.orientation === Qt.RightToLeft) {
0220         switch (key) {
0221         case Qt.Key_Left:
0222             key = Qt.Key_Right
0223             break
0224         case Qt.Key_Right:
0225             key = Qt.Key_Left
0226             break
0227         }
0228     }
0229     switch (key) {
0230     case Qt.Key_Space:
0231     case Qt.Key_Return:
0232     case Qt.Key_Enter:
0233         if (activityMode === "tick2number") {
0234             if (items.cursor.children[items.solutionGrad].textValue !== "")
0235                 checkResult()
0236         } else {
0237             checkResult()
0238         }
0239 
0240         break
0241     case Qt.Key_Left:
0242         if (activityMode === "number2tick")
0243             moveLeft()
0244         break
0245     case Qt.Key_Right:
0246         if (activityMode === "number2tick")
0247             moveRight()
0248         break
0249     case Qt.Key_0:
0250     case Qt.Key_1:
0251     case Qt.Key_2:
0252     case Qt.Key_3:
0253     case Qt.Key_4:
0254     case Qt.Key_5:
0255     case Qt.Key_6:
0256     case Qt.Key_7:
0257     case Qt.Key_8:
0258     case Qt.Key_9:
0259         if (activityMode === "number2tick")
0260             return
0261         if (items.cursor.children[items.solutionGrad].textValue.length < maxSolutionSize) {
0262             items.cursor.children[items.solutionGrad].textValue += key - '0'.charCodeAt(0)
0263             items.numPad.currentIndex = mapToPad[key]
0264             items.numPad.currentItem.state = "pressed"
0265         } else {
0266             items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav')
0267         }
0268 
0269         break
0270     case Qt.Key_Backspace:
0271         if (activityMode === "number2tick")
0272             return
0273         items.cursor.children[items.solutionGrad].textValue = items.cursor.children[items.solutionGrad].textValue.slice(0, -1)
0274         items.numPad.currentIndex = mapToPad[key]
0275         items.numPad.currentItem.state = "pressed"
0276         break
0277     case Qt.Key_Delete:
0278         if (activityMode === "number2tick")
0279             return
0280         items.cursor.children[items.solutionGrad].textValue = ""
0281         items.numPad.currentIndex = mapToPad[key]
0282         items.numPad.currentItem.state = "pressed"
0283         break
0284     }
0285 }
0286 
0287 function handleEvents(event) {
0288     handleKeys(event.key)
0289 }