File indexing completed on 2024-04-28 15:07:59

0001 /* GCompris - programmingMaze.js
0002  *
0003  * SPDX-FileCopyrightText: 2015 Siddhesh Suthar <siddhesh.it@gmail.com>
0004  * SPDX-FileCopyrightText: 2018 Aman Kumar Gupta <gupta2140@gmail.com>
0005  *
0006  * Authors:
0007  *   Siddhesh Suthar <siddhesh.it@gmail.com>
0008  *   Aman Kumar Gupta <gupta2140@gmail.com>
0009  *   Timothée Giet <animtim@gcompris.net> (Layout and graphics rework)
0010  *
0011  *   SPDX-License-Identifier: GPL-3.0-or-later
0012  */
0013 .pragma library
0014 .import QtQuick 2.12 as Quick
0015 .import GCompris 1.0 as GCompris //for ApplicationInfo
0016 .import "qrc:/gcompris/src/core/core.js" as Core
0017 
0018 // possible instructions
0019 var MOVE_FORWARD = "move-forward"
0020 var TURN_LEFT = "turn-left"
0021 var TURN_RIGHT = "turn-right"
0022 var CALL_PROCEDURE = "call-procedure"
0023 var EXECUTE_LOOPS = "execute-loop"
0024 
0025 var mazeBlocks
0026 
0027 // Length of 1 step along x-axis
0028 var stepX
0029 
0030 // Length of 1 step along y-axis
0031 var stepY
0032 
0033 /**
0034  * Lookup tables of instruction objects for main, procedure and loop areas which will be stored here on creation and can be
0035  * accessed when required to execute.
0036  */
0037 var mainInstructionObjects = []
0038 var procedureInstructionObjects = []
0039 var loopInstructionObjects = []
0040 
0041 // New rotation of Tux on turning.
0042 var changedRotation
0043 
0044 // Indicates if there is a dead-end
0045 var deadEndPoint = false
0046 
0047 // Stores the index of mainInstructionObjects[] which is going to be processed
0048 var codeIterator = 0
0049 
0050 // Stores the number of loops needed
0051 var loopsNumber
0052 
0053 /**
0054  * Stores if the reset is done only when Tux is clicked.
0055  *
0056  * If resetTux is true, initLevel() is called and the instruction areas are not cleared.
0057  *
0058  * Else, it means that initLevel() is called to reset the entire level and the instruction areas are cleared as well.
0059  */
0060 var resetTux = false
0061 
0062 //Stores the currrent instruction which is going to be processed
0063 var currentInstruction
0064 
0065 var url = "qrc:/gcompris/src/activities/programmingMaze/resource/"
0066 var reverseCountUrl = "qrc:/gcompris/src/activities/reversecount/resource/"
0067 var numberOfLevel
0068 var items
0069 
0070 var NORTH = 0
0071 var WEST = 90
0072 var SOUTH = 180
0073 var EAST = 270
0074 
0075 //Used to increment or decrement the loop counter, and display one as an initial value for the loop counter in the tutorial base.
0076 var LoopEnumValues = {
0077     PLUS_SIGN : "\u002B",
0078     MINUS_SIGN : "\u2212"
0079 }
0080 
0081 /**
0082  * Stores the qml file components of all the instructions used in the activity.
0083  *
0084  * To add a new instruction, add its component here and add the instruction name in "instructionList" inside createInstructionObjects() along with the other instructions.
0085  */
0086 var instructionComponents = {
0087     "move-forward": Qt.createComponent(url + "instructions/MoveForward.qml"),
0088     "turn-left": Qt.createComponent(url + "instructions/TurnLeftOrRight.qml"),
0089     "turn-right": Qt.createComponent(url + "instructions/TurnLeftOrRight.qml"),
0090     "call-procedure": Qt.createComponent(url + "instructions/Procedure.qml"),
0091     "execute-loop": Qt.createComponent(url + "instructions/Loop.qml")
0092 }
0093 
0094 var mainTutorialInstructions = [
0095             {
0096                 "instruction": "<b><h7>" + qsTr("Instruction Area:") + "</h7></b>" +
0097                                     qsTr("There are 3 instructions which you can use to code and lead Tux to the fish:") + "<li>" +
0098                                     qsTr("<b>1. Move forward:</b> Moves Tux one step forward in the direction it is facing.") + "</li><li>" +
0099                                     qsTr("<b>2. Turn left:</b> Turns Tux to the left.") + "</li><li>" +
0100                                     qsTr("<b>3. Turn right:</b> Turns Tux to the right.") + "</li>",
0101                 "instructionQml": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial1.qml"
0102             },
0103             {
0104                 "instruction": "<b><h7>" + qsTr("Main Function:") + "</h7></b>" +
0105                                     qsTr("The execution of the code starts here.") + "<li>" +
0106                                     qsTr("-Click on any instruction in the <b>instruction area</b> to add it to the <b>Main Function</b>.") + "</li><li>" +
0107                                     qsTr("-The instructions will execute in order until there's none left, or until a dead-end, or when Tux reaches the fish.") + "</li>",
0108                 "instructionQml": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial2.qml"
0109             },
0110         ]
0111 
0112 var procedureTutorialInstructions = [
0113             {
0114                 "instruction": "<b><h7>" + qsTr("Procedure:") + "</h7></b>" +
0115                                     qsTr("<b>Procedure</b> is a reusable set of instructions which can be <b>used in the code by calling it where needed</b>.") + "<li>" +
0116                                     qsTr("-To <b>switch</b> between the <b>Procedure area</b> and the <b>Main Function area</b> to add your code, click on the <b>Procedure</b> or <b>Main Function</b> label.") + "</li>",
0117                 "instructionQml": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial3.qml"
0118             },
0119         ]
0120 
0121 var loopTutorialInstructions = [
0122             {
0123                 "instruction": "<b><h7>" + qsTr("Loop:") + "</h7></b>" +
0124                                     qsTr("<b>Loop</b> is a sequence of instructions that is <b>continually repeated the number of times defined by the number inside it</b>.") + "<li>" +
0125                                     qsTr("-To <b>switch</b> between the <b>Loop area</b> and the <b>Main Function area</b> to add your code, click on the <b>Loop</b> or <b>Main Function</b> label.") + "</li>",
0126                 "instructionQml": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial4.qml"
0127             }
0128         ]
0129 
0130 function start(items_) {
0131     items = items_
0132     mazeBlocks = items.levels
0133     numberOfLevel = mazeBlocks.length
0134     items.currentLevel = Core.getInitialLevel(numberOfLevel)
0135     resetTux = false
0136     initLevel()
0137 }
0138 
0139 function stop() {
0140     items.activityStopped = true
0141     destroyInstructionObjects()
0142 }
0143 
0144 /**
0145  * This function creates and populate instruction objects for main as well as procedure area.
0146  *
0147  * These are stored in the lookup table, provided in the parameter as "instructionObjects".
0148  * The instructions are then connected to the slots of their code area (main or procedure/loops), provided as "instructionCodeArea" in the parameter.
0149  *
0150  * The instructions can now be obtained from the look-up tables and executed when called.
0151  *
0152  * This saves the process of re-creating all the instruction objets, connecting them to their parent's slot and destroying
0153  * them everytime for each instruction call which will be very redundant and quite memory consuming on devices with
0154  * less RAM, weak processing power and slow performance specially for "loops" mode.
0155  *
0156  * Hence these look-up table objects will be created and destroyed only once in each level (depending on the need) and can be accessed when needed.
0157  */
0158 function createInstructionObjects(instructionObjects, instructionCodeArea) {
0159     var instructionList = [MOVE_FORWARD, TURN_LEFT, TURN_RIGHT]
0160     for(var i = 0; i < instructionList.length; i++)
0161         createInstruction(instructionObjects, instructionList[i], instructionCodeArea)
0162 }
0163 
0164 function createInstruction(instructionObjects, instructionName, instructionCodeArea) {
0165     if(instructionName === TURN_LEFT || instructionName === TURN_RIGHT) {
0166         instructionObjects[instructionName] = instructionComponents[instructionName].createObject(instructionCodeArea, { "turnDirection": instructionName })
0167     }
0168     else {
0169         instructionObjects[instructionName] = instructionComponents[instructionName].createObject(instructionCodeArea)
0170     }
0171 
0172     instructionObjects[instructionName].foundDeadEnd.connect(instructionCodeArea.deadEnd)
0173     instructionObjects[instructionName].executionComplete.connect(instructionCodeArea.checkSuccessAndExecuteNextInstruction)
0174 }
0175 
0176 // Destroy instruction objects from the look-up tables
0177 function destroyInstructionObjects() {
0178     var i
0179     var instructionList = Object.keys(mainInstructionObjects)
0180     for(i = 0; i < instructionList.length; i++) {
0181         mainInstructionObjects[instructionList[i]].destroy()
0182     }
0183 
0184     instructionList = Object.keys(procedureInstructionObjects)
0185     for(i = 0; i < instructionList.length; i++) {
0186         procedureInstructionObjects[instructionList[i]].destroy()
0187     }
0188 
0189     instructionList = Object.keys(loopInstructionObjects)
0190     for(i = 0; i < instructionList.length; i++) {
0191         loopInstructionObjects[instructionList[i]].destroy()
0192     }
0193 
0194 
0195     mainInstructionObjects = []
0196     procedureInstructionObjects = []
0197     loopInstructionObjects = []
0198 }
0199 
0200 function initLevel() {
0201     if(!items)
0202         return
0203 
0204     loopsNumber = 1
0205     destroyInstructionObjects()
0206 
0207     var levelInstructions = mazeBlocks[items.currentLevel].instructions
0208 
0209     if(levelInstructions.indexOf(CALL_PROCEDURE) !== -1)
0210         items.currentLevelContainsProcedure = true
0211     else
0212         items.currentLevelContainsProcedure = false
0213 
0214     if(levelInstructions.indexOf(EXECUTE_LOOPS) !== -1)
0215         items.currentLevelContainsLoop = true
0216     else
0217         items.currentLevelContainsLoop = false
0218 
0219     // Create, populate and connect signals of instructions for main function code area and store them in mainInstructionObjects.
0220     createInstructionObjects(mainInstructionObjects, items.background)
0221 
0222     if(items.currentLevelContainsProcedure) {
0223         if(!items.tutorialImage.shownProcedureTutorialInstructions) {
0224             items.tutorialImage.shownProcedureTutorialInstructions = true
0225             items.tutorialImage.visible = true
0226         }
0227 
0228         // Create procedure object in the main look-up table ,if the level has procedure, to execute it for procedure/loop calls from the main code area.
0229         createInstruction(mainInstructionObjects, CALL_PROCEDURE, items.background)
0230 
0231         // Create, populate and connect signals of instructions for procedure code area if the level has procedure.
0232         createInstructionObjects(procedureInstructionObjects, mainInstructionObjects[CALL_PROCEDURE])
0233     }
0234 
0235     if(items.currentLevelContainsLoop) {
0236         if(!items.tutorialImage.shownLoopTutorialInstructions) {
0237             items.tutorialImage.shownLoopTutorialInstructions = true
0238             items.tutorialImage.visible = true
0239         }
0240 
0241         if(resetTux) {
0242             loopsNumber = items.loopCounterSelection.loopNumber
0243         }
0244 
0245         createLoopObjectAndInstructions()
0246     }
0247 
0248     // Stores the co-ordinates of the tile blocks in the current level
0249     var currentLevelBlocksCoordinates = mazeBlocks[items.currentLevel].map
0250 
0251     items.mazeModel.model = currentLevelBlocksCoordinates
0252 
0253     if(!resetTux) {
0254         items.mainFunctionModel.clear()
0255         items.procedureModel.clear()
0256         items.numberOfInstructionsAdded = 0
0257         items.loopCounterSelection.loopNumber = loopsNumber;
0258     }
0259 
0260     stepX = items.mazeModel.itemAt(0).width
0261     stepY = items.mazeModel.itemAt(0).height
0262 
0263     items.instructionModel.clear()
0264 
0265     for (var i = 0; i < levelInstructions.length; i++)
0266         items.instructionModel.append({"name":levelInstructions[i]})
0267 
0268     // Center Tux in its first case
0269     items.player.x = currentLevelBlocksCoordinates[0].x * stepX + (stepX - items.player.width) / 2
0270     items.player.y = currentLevelBlocksCoordinates[0].y * stepY + (stepY - items.player.height) / 2
0271     items.player.rotation = EAST
0272 
0273     // Center fish at it's co-ordinate
0274     items.fish.x = mazeBlocks[items.currentLevel].fish.x * stepX + (stepX - items.fish.width) / 2
0275     items.fish.y = mazeBlocks[items.currentLevel].fish.y * stepY + (stepY - items.fish.height) / 2
0276 
0277     changedRotation = EAST
0278     deadEndPoint = false
0279     items.isRunCodeEnabled = true
0280     items.maxNumberOfInstructionsAllowed = mazeBlocks[items.currentLevel].maxNumberOfInstructions
0281     items.constraintInstruction.show()
0282     items.mainFunctionCodeArea.resetEditingValues()
0283     items.procedureCodeArea.resetEditingValues()
0284     items.background.areaWithKeyboardInput = items.instructionArea
0285     resetCodeAreasIndices()
0286     resetTux = false
0287     codeIterator = 0
0288 }
0289 
0290 function createLoopObjectAndInstructions() {
0291     // Create loop object in the main look-up table ,if the activity mode is loops, to execute it loops from the main code area.
0292     createInstruction(mainInstructionObjects, EXECUTE_LOOPS, items.background)
0293 
0294     // Create, populate and connect signals of instructions for loop code area itself.
0295     createInstructionObjects(loopInstructionObjects, mainInstructionObjects[EXECUTE_LOOPS])
0296 }
0297 
0298 function resetCodeAreasIndices() {
0299     items.instructionArea.currentIndex = -1
0300     items.mainFunctionCodeArea.currentIndex = -1
0301     items.procedureCodeArea.currentIndex = -1
0302     items.instructionArea.instructionToInsert = ''
0303 }
0304 
0305 function getPlayerRotation() {
0306     return ((changedRotation % 360) + 360) % 360
0307 }
0308 
0309 function runCode() {
0310     items.mainFunctionCodeArea.resetEditingValues()
0311     items.procedureCodeArea.resetEditingValues()
0312 
0313     var instructionName
0314 
0315     // Append all the procedure instructions to the procedure area object in basic mode.
0316     // Append all the loop instructions to the loop area object in loops mode.
0317     for(var j = 0; j < items.procedureModel.count; j++) {
0318         instructionName = items.procedureModel.get(j).name
0319         if(items.currentLevelContainsProcedure) {
0320             mainInstructionObjects[CALL_PROCEDURE].procedureCode.append({ "name" : instructionName })
0321         }
0322         else {
0323             mainInstructionObjects[EXECUTE_LOOPS].loopCode.append({ "name" : instructionName })
0324         }
0325     }
0326 
0327     items.isRunCodeEnabled = false
0328     if(items.mainFunctionModel.count > 0)
0329         executeNextInstruction()
0330     else
0331         deadEnd()
0332 }
0333 
0334 function executeNextInstruction() {
0335     if((codeIterator < items.mainFunctionModel.count) && !deadEndPoint) {
0336         items.mainFunctionCodeArea.currentIndex += 1
0337         var instructionToExecute = items.mainFunctionModel.get(codeIterator).name
0338         mainInstructionObjects[instructionToExecute].checkAndExecuteMovement()
0339     }
0340 }
0341 
0342 function deadEnd() {
0343     deadEndPoint = true
0344     items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav")
0345     items.bonus.bad("tux")
0346 }
0347 
0348 function resetTuxPosition() {
0349     resetTux = true
0350     if(!items.activityStopped) {
0351         initLevel()
0352     }
0353 }
0354 
0355 function checkSuccessAndExecuteNextInstruction() {
0356     var fishX = mazeBlocks[items.currentLevel].fish.x
0357     var fishY = mazeBlocks[items.currentLevel].fish.y
0358 
0359     var tuxX = Math.floor(items.player.playerCenterX / stepX)
0360     var tuxY = Math.floor(items.player.playerCenterY / stepY)
0361 
0362     if(tuxX === fishX && tuxY === fishY) {
0363         codeIterator = 0
0364         items.bonus.good("tux")
0365     }
0366     else if(codeIterator === (items.mainFunctionModel.count - 1)) {
0367         deadEnd()
0368     }
0369     else {
0370         codeIterator++
0371         executeNextInstruction()
0372     }
0373 }
0374 
0375 function nextLevel() {
0376     resetTux = false
0377     items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel);
0378     initLevel();
0379 }
0380 
0381 function previousLevel() {
0382     resetTux = false
0383     items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel);
0384     initLevel();
0385 }
0386 
0387 function repositionObjectsOnWidthChanged(factor) {
0388     resetTux = true
0389     if(items && !items.activityStopped)
0390         initLevel()
0391 }
0392 
0393 function repositionObjectsOnHeightChanged(factor) {
0394     resetTux = true
0395     if(items && !items.activityStopped)
0396         initLevel()
0397 }
0398 
0399 function reloadLevel() {
0400     resetTux = false
0401     initLevel()
0402 }