File indexing completed on 2024-05-05 15:53:11

0001 /* GCompris - learn_decimals.js
0002  *
0003  * SPDX-FileCopyrightText: 2021 Mariam Fahmy <mariamfahmy66@gmail.com>
0004  *
0005  * Authors:
0006  *   Mariam Fahmy <mariamfahmy66@gmail.com>
0007  *   Timothée Giet <animtim@gmail.com>
0008  *
0009  * SPDX-License-Identifier: GPL-3.0-or-later
0010  *
0011  */
0012 .pragma library
0013 .import QtQuick 2.12 as Quick
0014 .import GCompris 1.0 as GCompris
0015 .import "qrc:/gcompris/src/core/core.js" as Core
0016 
0017 var numberOfLevel;
0018 var items;
0019 var dataset;
0020 var generatedNumber;
0021 var minimumValue;
0022 var maximumValue;
0023 var firstNumber;
0024 var secondNumber = 0;
0025 var squaresNumber = 10;
0026 var correctAnswer;
0027 var lastBarSquareUnits;
0028 var firstNumberList;
0029 var numberOfPossibleQuestions;
0030 
0031 var tutorialInstructions = [
0032             {
0033                 "instruction": qsTr("A decimal number is displayed. The bar with the arrow represents a full unit, and each square in it represents one tenth of this unit."),
0034                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial1.qml"
0035             },
0036             {
0037                 "instruction": qsTr("Drag the arrow to select a part of the bar, and drag the selected part of the bar to the empty area. Repeat these steps until the number of dropped bars corresponds to the displayed decimal number. Then click on the OK button to validate your answer."),
0038                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial2.qml"
0039             }
0040         ];
0041 
0042 var subtractionInstructions = [
0043             {
0044                 "instruction": qsTr("A subtraction with two decimal numbers is displayed. Below it, the first number from the subtraction is represented with bars. One bar represents a full unit, and each square in it represents one tenth of this unit."),
0045                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial3.qml"
0046             },
0047             {
0048                 "instruction": qsTr("Click on the squares to subtract them and display the result of the operation, and click on the OK button to validate your answer."),
0049                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial4.qml"
0050             },
0051             {
0052                 "instruction": qsTr("If the answer is correct, type the corresponding result, and click on the OK button to validate your answer."),
0053                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial5.qml"
0054             }
0055         ];
0056 
0057 var additionInstructions = [
0058             {
0059                 "instruction": qsTr("An addition with two decimal numbers is displayed. The bar with the arrow represents a full unit, and each square in it represents one tenth of this unit."),
0060                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial6.qml"
0061             },
0062             {
0063                 "instruction": qsTr("Drag the arrow to select a part of the bar, and drag the selected part of the bar to the empty area. Repeat these steps until the number of dropped bars corresponds to the result of the addition, and click on the OK button to validate your answer."),
0064                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial7.qml"
0065             },
0066             {
0067                 "instruction": qsTr("If the answer is correct, type the corresponding result, and click on the OK button to validate your answer."),
0068                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial8.qml"
0069             }
0070         ];
0071 
0072 var quantityInstructions = [
0073             {
0074                 "instruction": qsTr("A quantity is requested. The arrow allows to select up to 10 oranges."),
0075                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial1.qml"
0076             },
0077             {
0078                 "instruction": qsTr("Drag the arrow to select a number of oranges, and drag the selected oranges to the empty area. Repeat these steps until the number of oranges corresponds to the requested quantity. Then click on the OK button to validate your answer."),
0079                 "instructionQml": "qrc:/gcompris/src/activities/learn_decimals/resource/tutorial2.qml"
0080             }
0081         ];
0082 
0083 function start(items_) {
0084     items = items_;
0085     dataset = items.levels;
0086     numberOfLevel = dataset.length;
0087     items.currentLevel = Core.getInitialLevel(numberOfLevel);
0088     items.score.currentSubLevel = 0;
0089     firstNumberList = [];
0090 
0091     if(!items.tutorialImage.visible) {
0092         initLevel();
0093     }
0094 }
0095 
0096 function stop() {
0097 }
0098 
0099 function initLevel() {
0100     items.errorRectangle.resetState();
0101     var data = dataset[items.currentLevel];
0102     items.score.numberOfSubLevels = data.numberOfSubLevels;
0103     items.draggedItems.clear();
0104     items.droppedItems.clear();
0105     items.largestNumberRepresentation.clear();
0106     items.typeResult = false;
0107     minimumValue = dataset[items.currentLevel].minValue;
0108     maximumValue = dataset[items.currentLevel].maxValue;
0109 
0110     checkAvailableQuestions();
0111 
0112     displayDecimalNumberQuestion()
0113 
0114     if(!items.isSubtractionMode) {
0115         //resetting the selected bar to 0.1 (the least draggable part)
0116         items.draggedItems.append({"selectedSquareNumbers" : 1 });
0117 
0118         if(items.background.horizontalLayout) {
0119             items.scrollBar.arrowX = items.scrollBar.arrowOrigin;
0120         }
0121         else {
0122             items.scrollBar.arrowY = items.scrollBar.arrowOrigin;
0123         }
0124         items.scrollBar.currentStep = 0;
0125     }
0126     else {
0127         var largestNumber = firstNumber * squaresNumber;
0128         while(largestNumber > 0) {
0129             if(largestNumber > squaresNumber) {
0130                 items.largestNumberRepresentation.append({"selectedSquareNumbers" : squaresNumber });
0131             }
0132             else {
0133                 items.largestNumberRepresentation.append({"selectedSquareNumbers" : largestNumber });
0134                 lastBarSquareUnits = largestNumber;
0135             }
0136             largestNumber -= squaresNumber;
0137         }
0138     }
0139     items.buttonsBlocked = false;
0140 }
0141 
0142 function nextLevel() {
0143     items.score.stopWinAnimation();
0144     items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel);
0145     items.score.currentSubLevel = 0;
0146     initLevel();
0147 }
0148 
0149 function previousLevel() {
0150     items.score.stopWinAnimation();
0151     items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel);
0152     items.score.currentSubLevel = 0;
0153     initLevel();
0154 }
0155 
0156 function nextSubLevel() {
0157     if(items.score.currentSubLevel >= items.score.numberOfSubLevels) {
0158         items.bonus.good('flower');
0159     } else {
0160         items.droppedItems.clear();
0161 
0162         // In case number of sublevels are greater than the number of possibilities of the current level.
0163         checkAvailableQuestions();
0164 
0165         displayDecimalNumberQuestion();
0166         items.buttonsBlocked = false;
0167     }
0168 }
0169 
0170 function checkAvailableQuestions() {
0171     // In case all possible values from the provided range in the dataset are all displayed.
0172     var isAllDisplayed = true;
0173     numberOfPossibleQuestions = 0;
0174 
0175     for(var i = minimumValue; i <= maximumValue; i += items.unit) {
0176         numberOfPossibleQuestions += 1;
0177         // i += items.unit can sometimes return weird numbers like 0.30000000000000004 or 0.7999999999999999
0178         // so rounding it is needed for a safe check
0179         var j = Math.round(i * 10) / 10;
0180         if(firstNumberList.indexOf(j) == -1) {
0181             isAllDisplayed = false;
0182         }
0183     }
0184 
0185     if(isAllDisplayed) {
0186         firstNumberList = [];
0187     }
0188 }
0189 
0190 function organizeDroppedBars() {
0191     var totalSquareUnits = 0;
0192     for(var i = 0; i < items.droppedItems.count; i++) {
0193         totalSquareUnits += items.droppedItems.get(i).selectedSquareNumbers;
0194     }
0195 
0196     items.droppedItems.clear();
0197 
0198     while(totalSquareUnits > 0) {
0199         if(totalSquareUnits >= squaresNumber) {
0200             items.droppedItems.append({"selectedSquareNumbers" : squaresNumber });
0201         }
0202         else {
0203             items.droppedItems.append({"selectedSquareNumbers" : totalSquareUnits });
0204         }
0205         totalSquareUnits -= squaresNumber;
0206     }
0207 }
0208 
0209 function generateFirstNumber() {
0210     if(items.isAdditionMode) {
0211         maximumValue -= minimumValue;
0212     }
0213     generatedNumber = generateDecimalNumbers(minimumValue, maximumValue);
0214     var loopCheck = 0;
0215     // if the number has already been asked, try to get a new one
0216     while(firstNumberList.indexOf(generatedNumber) !== -1) {
0217         generatedNumber = generateDecimalNumbers(minimumValue, maximumValue);
0218         //safety check to avoid stuck loop in case of js bugs
0219         loopCheck += 1;
0220         if(loopCheck > numberOfPossibleQuestions)
0221             firstNumberList = []
0222     }
0223 
0224     return generatedNumber;
0225 }
0226 
0227 function generateSecondNumber() {
0228     if(items.isAdditionMode) {
0229         maximumValue = dataset[items.currentLevel].maxValue;
0230         maximumValue -= generatedNumber;
0231     }
0232 
0233     do {
0234         generatedNumber = generateDecimalNumbers(minimumValue, maximumValue);
0235     }
0236     while(generatedNumber === firstNumber && items.isSubtractionMode);
0237 
0238     return generatedNumber;
0239 }
0240 
0241 function displayDecimalNumberQuestion() {
0242     firstNumber = generateFirstNumber();
0243 
0244     if(items.isAdditionMode || items.isSubtractionMode)
0245         secondNumber = generateSecondNumber();
0246 
0247     // The first number must be greater than the second number to avoid having negative results.
0248     if(items.isSubtractionMode) {
0249         if(firstNumber < secondNumber) {
0250             var temp = firstNumber;
0251             firstNumber = secondNumber;
0252             secondNumber = temp;
0253         }
0254     }
0255 
0256     // Storing the first decimal number in a list to avoid displaying the same number again for the rest of the levels.
0257     firstNumberList.push(firstNumber);
0258 
0259     if(items.isQuantityMode)
0260         items.largestNumber = firstNumber.toString();
0261     else {
0262         items.largestNumber = toDecimalLocaleNumber(firstNumber);
0263         items.smallestNumber = toDecimalLocaleNumber(secondNumber);
0264     }
0265 }
0266 
0267 function verifyNumberRepresentation() {
0268     items.buttonsBlocked = true;
0269     var i;
0270     var sum = 0;
0271 
0272     //calculating the correct answer.
0273     calculateCorrectAnswer();
0274 
0275     //calculating the displayed units in the answerZone.
0276     if(!items.isSubtractionMode) {
0277         for(i = 0; i < items.droppedItems.count; i++) {
0278             sum += items.droppedItems.get(i).selectedSquareNumbers;
0279         }
0280     }
0281     else {
0282         for(i = 0; i < items.largestNumberRepresentation.count; i++) {
0283             sum += items.largestNumberRepresentation.get(i).selectedSquareNumbers;
0284         }
0285     }
0286 
0287     if(!items.isQuantityMode)
0288         sum /= squaresNumber;
0289 
0290     if(sum === correctAnswer) {
0291         if(items.isSubtractionMode || items.isAdditionMode) {
0292             items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0293             items.numpad.resetText();
0294             items.typeResult = true;
0295             items.buttonsBlocked = false;
0296         }
0297         else {
0298             items.score.currentSubLevel += 1;
0299             items.score.playWinAnimation();
0300             items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0301         }
0302     }
0303     else {
0304         items.errorRectangle.startAnimation();
0305         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav");
0306     }
0307 }
0308 
0309 function calculateCorrectAnswer() {
0310     if(items.isSubtractionMode) {
0311         correctAnswer = (firstNumber * squaresNumber - secondNumber * squaresNumber) / squaresNumber;
0312     }
0313     else if(items.isAdditionMode) {
0314         correctAnswer = (firstNumber * squaresNumber + secondNumber * squaresNumber) / squaresNumber;
0315     }
0316     else {
0317         correctAnswer = firstNumber;
0318     }
0319 }
0320 
0321 function verifyNumberTyping(typedAnswer) {
0322     items.buttonsBlocked = true;
0323     typedAnswer = typedAnswer.replace("," , ".");
0324     if(parseFloat(typedAnswer) === parseFloat(correctAnswer)) {
0325         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0326         items.bonus.good('flower')
0327     }
0328     else {
0329         items.errorRectangle.startAnimation();
0330         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav");
0331     }
0332 }
0333 
0334 function changeSingleBarVisibility(currentSquareNumber) {
0335     items.draggedItems.setProperty(0, "selectedSquareNumbers", currentSquareNumber);
0336 }
0337 
0338 function changeMultiBarVisibility(barIndex, currentSquareNumber) {
0339     items.largestNumberRepresentation.setProperty(barIndex, "selectedSquareNumbers", currentSquareNumber);
0340 }
0341 
0342 function generateDecimalNumbers(minValue, maxValue) {
0343     var generatedNumber = Math.random() * (maxValue - minValue) + minValue;
0344     if(items.isQuantityMode)
0345         return Math.round(generatedNumber);
0346     else
0347         return Math.round((generatedNumber + Number.EPSILON) * squaresNumber) / squaresNumber;
0348 }
0349 
0350 function toDecimalLocaleNumber(decimalNumber) {
0351     var locale = GCompris.ApplicationSettings.locale;
0352     if(locale === "system") {
0353         locale = Qt.locale().name === "C" ? "en_US" : Qt.locale().name;
0354     }
0355     var decimalLocale = Core.convertNumberToLocaleString(decimalNumber, locale, 'f', 1);
0356     return decimalLocale;
0357 }