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 }