diff --git a/src/activities/programmingMaze/ActivityInfo.qml b/src/activities/programmingMaze/ActivityInfo.qml --- a/src/activities/programmingMaze/ActivityInfo.qml +++ b/src/activities/programmingMaze/ActivityInfo.qml @@ -1,6 +1,7 @@ /* GCompris - ActivityInfo.qml * * Copyright (C) 2015 Siddhesh Suthar + * Copyright (C) 2018 Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,11 +26,28 @@ demo: false title: qsTr("Programming Maze") description: qsTr("This activity teaches the kid to program the Tux to its goal using -simple instructions like move forward, turn left etc") + simple instructions like move forward, turn left etc") goal: qsTr("Tux is hungry. Help him find fish by programming him to the correct ice spot.") prerequisite: qsTr("Can read instructions. Thinking of the path logically") manual: qsTr("Choose the instructions from given menu. Arrange the instruction in an -order so that they can make the tux reach to its goal") + order so that they can make the tux reach to its goal.

") + + qsTr("Keyboard Controls:

") + + qsTr("1. To navigate through instructions in the current code area having keyboard focus:") + + qsTr("
") + + qsTr("2. To append an instruction from instruction area to the main/procedure code area:") + + qsTr("
") + + qsTr("3. To add an instruction at any particular position in the main/procedure code area:") + + qsTr("
") + + qsTr("4. To delete the current navigated instruction in the main/procedure code area:") + + qsTr("
") + + qsTr("5. To edit an instruction in the main/procedure code area:") + + qsTr("
") + + qsTr("6. To run the code or reset Tux when it fails to reach the fish:") + + qsTr("") credit: "" section: "fun" + createdInVersion: 9500 } diff --git a/src/activities/programmingMaze/AnswerSheet.qml b/src/activities/programmingMaze/AnswerSheet.qml deleted file mode 100644 --- a/src/activities/programmingMaze/AnswerSheet.qml +++ /dev/null @@ -1,292 +0,0 @@ -/* GCompris - programmingMaze.js - * - * Copyright (C) 2015 Siddhesh Suthar - * - * Authors: - * "Siddhesh Suthar" (Qt Quick port) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ -import QtQuick 2.2 -import GCompris 1.0 - -import "programmingMaze.js" as Activity - -GridView { - id: answerSheet - property Item background - property ListModel currentModel - property int buttonWidth: background.width / 10 - property int buttonHeight: background.height / 10 - - width: background.width * 0.4 - height: background.height * 0.4 - 5 * ApplicationInfo.ratio - cellWidth: background.buttonWidth - cellHeight: background.buttonHeight - - interactive: false - model: currentModel - clip: true - - highlight: Rectangle { - width: buttonWidth - height: buttonHeight - color: "lightsteelblue" - border.width: 5 * ApplicationInfo.ratio - border.color: "purple" - opacity: 0.5 - z: 11 - Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } - } - highlightFollowsCurrentItem: true - highlightMoveDuration: Activity.moveAnimDuration - keyNavigationWraps: false - focus: true - - property int draggedItemIndex: -1 - property int possibleDropIndex: -1 - property int possibleDropRemoveIndex: -1 - property int xCoordinateInPossibleDrop: -1 - property int yCoordinateInPossibleDrop: -1 - - Item { - id: dropPosIndicator - visible: false - height: answerSheet.cellHeight - width: 5 * ApplicationInfo.ratio - - Rectangle { - visible: parent.visible - anchors.centerIn: parent - width: 5 * ApplicationInfo.ratio - height: parent.height - 5 * ApplicationInfo.ratio - color: "lightsteelblue" - } - - states: [ - State { - name: "shown" - when: answerSheet.possibleDropIndex != -1 - PropertyChanges { - target: dropPosIndicator - visible: true - x: Math.floor(answerSheet.xCoordinateInPossibleDrop/answerSheet.cellWidth) * - answerSheet.cellWidth - 5 * ApplicationInfo.ratio - y: Math.floor(answerSheet.yCoordinateInPossibleDrop/answerSheet.cellHeight) * - answerSheet.cellHeight - 2 * ApplicationInfo.ratio - } - } - ] - } - Item { - id: dropPosRemoveIndicator - visible: false - width: background.buttonWidth - height: background.buttonHeight - - Rectangle { - visible: parent.visible - anchors.fill: parent - radius: 5 * ApplicationInfo.ratio - color: "transparent" - border.width: 5 * ApplicationInfo.ratio - border.color: "lightsteelblue" - opacity: 1 - } - - states: [ - State { - name: "shown" - when: answerSheet.possibleDropRemoveIndex != -1 - PropertyChanges { - target: dropPosRemoveIndicator - visible: true - x: Math.floor(answerSheet.xCoordinateInPossibleDrop/answerSheet.cellWidth) * - answerSheet.cellWidth - 5 * ApplicationInfo.ratio - y: Math.floor(answerSheet.yCoordinateInPossibleDrop/answerSheet.cellHeight) * - answerSheet.cellHeight - 2 * ApplicationInfo.ratio - } - } - ] - } - Item { - id: dndContainer - anchors.fill: parent - } - - MouseArea { - id: coords - anchors.fill: parent - onPressed: { - answerSheet.draggedItemIndex = answerSheet.indexAt(mouseX,mouseY) - } - onReleased: { - if(answerSheet.draggedItemIndex != -1) { - var draggedIndex = answerSheet.draggedItemIndex - var dropIndex = answerSheet.indexAt(mouseX,mouseY) - answerSheet.draggedItemIndex = -1 - var calculatedX = Math.floor(answerSheet.xCoordinateInPossibleDrop/answerSheet.cellWidth) * - answerSheet.cellWidth - var diff = Math.floor(mouseX - calculatedX) - var insertEnd = answerSheet.cellWidth / 2 - if(answerSheet.indexAt(mouseX,mouseY) == -1) - currentModel.remove(draggedIndex) - else { - if(diff <= insertEnd) { - if(dropIndex <= draggedIndex) { - //moving box from right to left - currentModel.move(draggedIndex, answerSheet.indexAt(mouseX,mouseY), 1) - } - else { - //moving box from left to right - currentModel.move(draggedIndex, answerSheet.indexAt(mouseX,mouseY)-1, 1) - } - } - else { - currentModel.set(dropIndex, currentModel.get(draggedIndex), 1) - currentModel.remove(draggedIndex) - } - } - answerSheet.possibleDropRemoveIndex = -1 - answerSheet.possibleDropIndex = -1 - } - } - onPositionChanged: { - var newPos = answerSheet.indexAt(mouseX, mouseY) - answerSheet.xCoordinateInPossibleDrop = mouseX - answerSheet.yCoordinateInPossibleDrop = mouseY - var calculatedX = Math.floor(answerSheet.xCoordinateInPossibleDrop/answerSheet.cellWidth) * - answerSheet.cellWidth - var diffX = Math.floor(mouseX - calculatedX) - var insertEndX = answerSheet.cellWidth / 2 - if(diffX <= insertEndX) { - answerSheet.possibleDropIndex = newPos - answerSheet.possibleDropRemoveIndex = -1 - } - else { - answerSheet.possibleDropRemoveIndex = newPos - answerSheet.possibleDropIndex = -1 - } - } - } - - - delegate: Column { - Item { - id: itemParent - width: background.buttonWidth - height: background.buttonHeight - - Rectangle { - id: circlePlaceholder - width: 30 * ApplicationInfo.ratio - height: width - radius: width - anchors.centerIn: parent - color: "#cecece" - opacity: 0 - } - - Item { - id: item - width: background.buttonWidth - 5 * ApplicationInfo.ratio - height: background.buttonHeight - 5 * ApplicationInfo.ratio - state: "inactive" - opacity: 1 - - Behavior on width { NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } - Behavior on height { NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } - Behavior on opacity {NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } - - - states: [ - State { - name: "inDrag" - when: index == answerSheet.draggedItemIndex - PropertyChanges { target: circlePlaceholder; opacity: 1} - PropertyChanges { target: imageBorder; opacity: 1 } - PropertyChanges { target: item; parent: dndContainer } - PropertyChanges { target: item; width: background.buttonWidth * 0.80 } - PropertyChanges { target: item; height: background.buttonHeight * 0.80 } - PropertyChanges { target: item; anchors.centerIn: undefined } - PropertyChanges { target: item; x: coords.mouseX - item.width / 2 } - PropertyChanges { target: item; y: coords.mouseY - item.height / 2 } - }, - State { - name: "greyedOut" - when: (answerSheet.draggedItemIndex != -1) &&(answerSheet.draggedItemIndex != index) - PropertyChanges { target: item; opacity: 0.7 } - }, - State { - name: "inactive" - when: (answerSheet.draggedItemIndex == -1) || (answerSheet.draggedItemIndex == index) - PropertyChanges { target: item; opacity: 1.0 } - } - ] - - transitions: [ - Transition { - from: "inDrag" - to: "*" - PropertyAnimation { - target: item - properties: "scale, opacity" - easing.overshoot: 1.5 - easing.type: "OutBack" - from: 0.0 - to: 1.0 - duration: 500 - } - } - ] - - Rectangle { - id: answerRect - anchors.fill: parent - color: "#005B9A" - opacity: 1 - } - - Image { - source: "qrc:/gcompris/src/core/resource/button.svg" - sourceSize { height: parent.height; width: parent.width } - width: sourceSize.width - height: sourceSize.height - } - - Image { - id: answer - source: Activity.url + name + ".svg" - sourceSize { width: parent.width; height: parent.height } - width: sourceSize.width - height: sourceSize.height - anchors.centerIn: parent - smooth: false - - Rectangle { - id: imageBorder - width: background.buttonWidth + 5 * ApplicationInfo.ratio - height: background.buttonHeight + 5 * ApplicationInfo.ratio - anchors.fill: parent - radius: 5 * ApplicationInfo.ratio - color: "transparent" - border.width: 5 * ApplicationInfo.ratio - border.color: "#ffffff" - opacity: 0 - } - } - } - } - } -} diff --git a/src/activities/programmingMaze/CMakeLists.txt b/src/activities/programmingMaze/CMakeLists.txt --- a/src/activities/programmingMaze/CMakeLists.txt +++ b/src/activities/programmingMaze/CMakeLists.txt @@ -1 +1 @@ -GCOMPRIS_ADD_RCC(activities/programmingMaze *.qml *.svg *.js resource/*) +GCOMPRIS_ADD_RCC(activities/programmingMaze *.qml *.svg *.js resource/* instructions/*) diff --git a/src/activities/programmingMaze/CodeArea.qml b/src/activities/programmingMaze/CodeArea.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/CodeArea.qml @@ -0,0 +1,343 @@ +/* GCompris - CodeArea.qml + * + * Copyright (C) 2015 Siddhesh Suthar + * Copyright (C) 2018 Aman Kumar Gupta + * + * Authors: + * Siddhesh Suthar + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 + +import "programmingMaze.js" as Activity + +GridView { + id: codeArea + z: 1 + width: background.width * 0.4 + height: background.height * 0.29 + cellWidth: background.buttonWidth + cellHeight: background.buttonHeight + + interactive: false + model: currentModel + clip: true + + highlight: Rectangle { + width: buttonWidth + height: buttonHeight + color: "lightsteelblue" + border.width: 3.5 * ApplicationInfo.ratio + border.color: "purple" + opacity: 0.5 + z: 11 + radius: width / 18 + } + highlightFollowsCurrentItem: true + highlightMoveDuration: 500 + keyNavigationWraps: true + focus: true + + property ListModel currentModel + property int draggedItemIndex: -1 + property int possibleDropIndex: -1 + property int possibleDropRemoveIndex: -1 + property int xCoordinateInPossibleDrop: -1 + property int yCoordinateInPossibleDrop: -1 + + /** + * Stores the index of the item which is clicked to edit. + * + * If the index of the item on 2nd click is same as initialEditItemIndex , then the indicator will become invisible, as it means that initially wanted to edit that instruction, but now we want to deselect it. + * + * If the index of the item on 2nd click is different from initialEditItemIndex, the edit indicator moves to the new item as we now want to edit that one. + */ + property int initialEditItemIndex: -1 + + // Tells if any instruction is selected for editing. + property bool isEditingInstruction: false + + signal spaceKeyPressed + signal tabKeyPressed + signal deleteKeyPressed + + /** + * There can be three possibilities here: + * + * 1. We want to insert an instruction at the currentIndex position. + * 2. We want to select an instruction to edit, or deselect it. + * 3. We want to append an instruction. + */ + onSpaceKeyPressed: { + if(currentIndex != -1) { + if(instructionArea.instructionToInsert && (items.numberOfInstructionsAdded < items.maxNumberOfInstructionsAllowed)) { + var isInstructionInserted = appendInstruction() + if(isInstructionInserted) + currentModel.move(currentModel.count - 1, currentIndex, 1) + } + else { + if((initialEditItemIndex == currentIndex) || (initialEditItemIndex == -1 && currentIndex != -1)) { + codeArea.isEditingInstruction = !codeArea.isEditingInstruction + } + + if(!codeArea.isEditingInstruction) + codeArea.initialEditItemIndex = -1 + else + initialEditItemIndex = currentIndex + + var calculatedX = (initialEditItemIndex % 4) * codeArea.cellWidth + var calculatedY = Math.floor(initialEditItemIndex / 4) * codeArea.cellHeight + editInstructionIndicator.x = calculatedX + 1.5 * ApplicationInfo.ratio + editInstructionIndicator.y = calculatedY + 1.5 * ApplicationInfo.ratio + } + } + else if((items.numberOfInstructionsAdded < items.maxNumberOfInstructionsAllowed) && instructionArea.instructionToInsert) + appendInstruction() + } + + onDeleteKeyPressed: { + if(currentIndex != -1) { + currentModel.remove(currentIndex) + items.numberOfInstructionsAdded-- + } + resetEditingValues() + } + + function appendInstruction() { + if(background.insertIntoMain || (instructionArea.instructionToInsert != "call-procedure")) { + currentModel.append({ "name": instructionArea.instructionToInsert }) + items.numberOfInstructionsAdded++ + instructionArea.instructionToInsert = "" + return true + } + return false + } + + function resetEditingValues() { + initialEditItemIndex = -1 + isEditingInstruction = false + } + + Item { + id: dropPositionIndicator + visible: false + height: background.buttonHeight + width: 3 * ApplicationInfo.ratio + + Rectangle { + visible: parent.visible + anchors.centerIn: parent + width: parent.width + height: parent.height - 3 * ApplicationInfo.ratio + color: "blue" + radius: width + } + + states: [ + State { + name: "shown" + when: codeArea.possibleDropIndex != -1 + PropertyChanges { + target: dropPositionIndicator + visible: true + x: Math.floor(codeArea.xCoordinateInPossibleDrop / codeArea.cellWidth) * + codeArea.cellWidth - 1.5 * ApplicationInfo.ratio + y: Math.floor(codeArea.yCoordinateInPossibleDrop / codeArea.cellHeight) * + codeArea.cellHeight + 1.5 * ApplicationInfo.ratio + } + } + ] + } + + Rectangle { + id: editInstructionIndicator + visible: codeArea.isEditingInstruction && codeArea.count != 0 + width: background.buttonWidth - 3 * ApplicationInfo.ratio + height: background.buttonHeight - 3 * ApplicationInfo.ratio + color: "red" + border.color: "black" + border.width: 1.5 * ApplicationInfo.ratio + opacity: 0.2 + radius: width / 18 + } + + MouseArea { + id: codeAreaMouse + anchors.fill: parent + enabled: items.isTuxMouseAreaEnabled || items.isRunCodeEnabled + onPressed: { + codeArea.draggedItemIndex = codeArea.indexAt(mouseX,mouseY) + if(codeArea.draggedItemIndex === -1) { + constraintInstruction.changeConstraintInstructionOpacity() + codeArea.isEditingInstruction = false + } + else if(!codeArea.isEditingInstruction) + codeArea.initialEditItemIndex = codeArea.draggedItemIndex + } + + onPositionChanged: { + var newPos = codeArea.indexAt(mouseX, mouseY) + var calculatedX = Math.floor(mouseX / codeArea.cellWidth) * codeArea.cellWidth + var previousIndexPosition = codeArea.indexAt(calculatedX - 1, mouseY) + + // If the user want to move an item to the end, then the new position will be after the last instruction. + if(newPos == -1 && previousIndexPosition != -1) + newPos = previousIndexPosition + 1 + + codeArea.isEditingInstruction = false + codeArea.xCoordinateInPossibleDrop = mouseX + codeArea.yCoordinateInPossibleDrop = mouseY + codeArea.possibleDropIndex = newPos + } + + onReleased: { + if(codeArea.draggedItemIndex != -1) { + var draggedIndex = codeArea.draggedItemIndex + var dropIndex = codeArea.indexAt(mouseX,mouseY) + var calculatedX = Math.floor(mouseX / codeArea.cellWidth) * codeArea.cellWidth + var calculatedY = Math.floor(mouseY / codeArea.cellHeight) * codeArea.cellHeight + codeArea.draggedItemIndex = -1 + + if(dropIndex == -1) { + var previousIndexPosition = codeArea.indexAt(calculatedX - 1, mouseY) + if(previousIndexPosition == -1) { + currentModel.remove(draggedIndex) + items.numberOfInstructionsAdded-- + } + else { + currentModel.append(currentModel.get(draggedIndex)) + currentModel.remove(draggedIndex) + } + codeArea.initialEditItemIndex = -1 + } + else if(draggedIndex != dropIndex) { + if(dropIndex <= draggedIndex) { + // Moving instruction from right to left + currentModel.move(draggedIndex, dropIndex, 1) + } + else { + // Moving instruction from left to right + currentModel.move(draggedIndex, dropIndex - 1, 1) + } + codeArea.initialEditItemIndex = -1 + } + else { + /** + * If the index of the initially selected instruction (if any) is same as the currently selected instruction, + * deselect it for editing, else make the current instruction as the initially editable item and move the edit indicator to that position. + */ + if(codeArea.initialEditItemIndex == dropIndex) { + codeArea.isEditingInstruction = !codeArea.isEditingInstruction + if(!codeArea.isEditingInstruction) + codeArea.initialEditItemIndex = -1 + } + else + codeArea.initialEditItemIndex = draggedIndex + + editInstructionIndicator.x = calculatedX + 1.5 * ApplicationInfo.ratio + editInstructionIndicator.y = calculatedY + 1.5 * ApplicationInfo.ratio + } + codeArea.possibleDropIndex = -1 + } + } + } + + delegate: Item { + id: itemParent + width: background.buttonWidth + height: background.buttonHeight + + Rectangle { + id: circlePlaceholder + width: 30 * ApplicationInfo.ratio + height: width + radius: width + anchors.centerIn: parent + color: "#cecece" + opacity: 0 + } + + Item { + id: item + width: background.buttonWidth + height: background.buttonHeight + state: "inactive" + opacity: 1 + + Behavior on width { NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } + Behavior on height { NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } + Behavior on opacity {NumberAnimation { duration: 300; easing.type: Easing.InOutQuad } } + + states: [ + State { + name: "inDrag" + when: index == codeArea.draggedItemIndex + + PropertyChanges { target: circlePlaceholder; opacity: 1 } + PropertyChanges { target: item; parent: codeArea } + PropertyChanges { target: item; width: background.buttonWidth * 0.80 } + PropertyChanges { target: item; height: background.buttonHeight * 0.80 } + PropertyChanges { target: item; anchors.centerIn: undefined } + PropertyChanges { target: item; x: codeAreaMouse.mouseX - item.width / 2 } + PropertyChanges { target: item; y: codeAreaMouse.mouseY - item.height / 2 } + }, + State { + name: "greyedOut" + when: (codeArea.draggedItemIndex != -1) && (codeArea.draggedItemIndex != index) + + PropertyChanges { target: item; opacity: 0.7 } + }, + State { + name: "inactive" + when: (codeArea.draggedItemIndex == -1) || (codeArea.draggedItemIndex == index) + + PropertyChanges { target: item; opacity: 1.0 } + } + ] + + transitions: [ + Transition { + from: "inDrag" + to: "*" + PropertyAnimation { + target: item + properties: "scale, opacity" + from: 0.7 + to: 1.0 + duration: 200 + } + } + ] + + Rectangle { + width: parent.width - 3 * ApplicationInfo.ratio + height: parent.height - 3 * ApplicationInfo.ratio + border.width: 1.2 * ApplicationInfo.ratio + border.color: "black" + anchors.centerIn: parent + radius: width / 18 + + Image { + id: codeAreaIcon + source: Activity.url + name + ".svg" + sourceSize { width: parent.width / 1.2; height: parent.height / 1.2 } + anchors.centerIn: parent + } + } + } + } +} diff --git a/src/activities/programmingMaze/Dataset.qml b/src/activities/programmingMaze/Dataset.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/Dataset.qml @@ -0,0 +1,248 @@ +/* GCompris - Dataset.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Authors: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 + +QtObject { + property var levels: [ + // Level one + { + "map": [ + {'x': 1, 'y': 2}, + {'x': 2, 'y': 2}, + {'x': 3, 'y': 2} + ], + "fish": {'x': 3, 'y': 2}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right" + ], + "maxNumberOfInstructions": 4 + }, + // Level two + { + "map": [ + {'x': 1, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 2, 'y': 2}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 1} + ], + "fish": {'x': 3, 'y': 1}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right" + ], + "maxNumberOfInstructions": 8 + }, + // Level three + { + "map": [ + {'x': 0, 'y': 3}, + {'x': 0, 'y': 2}, + {'x': 0, 'y': 1}, + {'x': 1, 'y': 1}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 1}, + {'x': 3, 'y': 2}, + {'x': 3, 'y': 3}, + {'x': 2, 'y': 3} + ], + "fish": {'x': 2, 'y': 3}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right" + ], + "maxNumberOfInstructions": 14 + }, + // Level four + { + "map": [ + {'x': 0, 'y': 1}, + {'x': 1, 'y': 1}, + {'x': 1, 'y': 0}, + {'x': 2, 'y': 0}, + {'x': 3, 'y': 0}, + {'x': 4, 'y': 0}, + {'x': 1, 'y': 2}, + {'x': 1, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 3, 'y': 3}, + {'x': 4, 'y': 3}, + {'x': 4, 'y': 2} + ], + "fish": {'x': 4, 'y': 2}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right" + ], + "maxNumberOfInstructions": 14 + }, + // Level five + { + "map": [ + {'x': 0, 'y': 1}, + {'x': 0, 'y': 0}, + {'x': 1, 'y': 0}, + {'x': 2, 'y': 0}, + {'x': 3, 'y': 0}, + {'x': 4, 'y': 0}, + {'x': 0, 'y': 2}, + {'x': 0, 'y': 3}, + {'x': 1, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 3, 'y': 3}, + {'x': 4, 'y': 3}, + {'x': 2, 'y': 1}, + {'x': 2, 'y': 2}, + {'x': 4, 'y': 2} + ], + "fish": {'x': 4, 'y': 2}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right" + ], + "maxNumberOfInstructions": 15 + }, + // Level six + { + "map": [ + {'x': 1, 'y': 1}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 1}, + {'x': 3, 'y': 2}, + {'x': 3, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 1, 'y': 3} + ], + "fish": {'x': 1, 'y': 3}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right", + "call-procedure" + ], + "maxNumberOfInstructions": 7 + }, + // Level seven + { + "map": [ + {'x': 0, 'y': 3}, + {'x': 1, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 2, 'y': 2}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 1}, + {'x': 4, 'y': 1}, + {'x': 4, 'y': 2}, + {'x': 4, 'y': 3} + ], + "fish": {'x': 4, 'y': 3}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right", + "call-procedure" + ], + "maxNumberOfInstructions": 10 + }, + // Level eight + { + "map": [ + {'x': 0, 'y': 3}, + {'x': 1, 'y': 3}, + {'x': 1, 'y': 2}, + {'x': 2, 'y': 2}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 1}, + {'x': 3, 'y': 0}, + {'x': 4, 'y': 0} + ], + "fish": {'x': 4, 'y': 0}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right", + "call-procedure" + ], + "maxNumberOfInstructions": 12 + }, + // Level nine + { + "map": [ + {'x': 1, 'y': 1}, + {'x': 0, 'y': 0}, + {'x': 1, 'y': 0}, + {'x': 2, 'y': 0}, + {'x': 2, 'y': 1}, + {'x': 3, 'y': 0}, + {'x': 4, 'y': 0}, + {'x': 4, 'y': 1}, + {'x': 4, 'y': 2}, + {'x': 4, 'y': 3}, + {'x': 3, 'y': 3}, + {'x': 2, 'y': 3}, + {'x': 1, 'y': 3}, + {'x': 0, 'y': 3}, + {'x': 0, 'y': 2} + ], + "fish": {'x': 0, 'y': 2}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right", + "call-procedure" + ], + "maxNumberOfInstructions": 14 + }, + // Level ten + { + "map": [ + {'x': 0, 'y': 3}, + {'x': 0, 'y': 2}, + {'x': 0, 'y': 1}, + {'x': 0, 'y': 0}, + {'x': 1, 'y': 0}, + {'x': 2, 'y': 0}, + {'x': 2, 'y': 1}, + {'x': 2, 'y': 2}, + {'x': 2, 'y': 3}, + {'x': 3, 'y': 3}, + {'x': 4, 'y': 3}, + {'x': 4, 'y': 2}, + {'x': 4, 'y': 1}, + {'x': 4, 'y': 0} + ], + "fish": {'x': 4, 'y': 0}, + "instructions": [ + "move-forward", + "turn-left", + "turn-right", + "call-procedure" + ], + "maxNumberOfInstructions": 15 + } + ] +} diff --git a/src/activities/programmingMaze/HeaderArea.qml b/src/activities/programmingMaze/HeaderArea.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/HeaderArea.qml @@ -0,0 +1,63 @@ +/* GCompris - HeaderArea.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Authors: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../core" + +Rectangle { + id: header + width: background.width * 0.4 + height: background.height / 10 + border.width: 2 * ApplicationInfo.ratio + border.color: "black" + color: "transparent" + + property real headerOpacity + property string headerText + + signal clicked + + Image { + width: parent.width - 2 * parent.border.width + height: parent.height - 2 * parent.border.width + anchors.centerIn: parent + source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW02.svg" + opacity: header.headerOpacity + + GCText { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + height: parent.height + fontSizeMode: Font.DemiBold + minimumPointSize: 7 + fontSize: mediumSize + wrapMode: Text.WordWrap + color: "white" + text: header.headerText + } + } + + MouseArea { + anchors.fill: parent + onClicked: header.clicked() + } +} diff --git a/src/activities/programmingMaze/InstructionArea.qml b/src/activities/programmingMaze/InstructionArea.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/InstructionArea.qml @@ -0,0 +1,182 @@ +/* GCompris - InstructionArea.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Authors: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../core" + +import "programmingMaze.js" as Activity + +GridView { + id: instructionArea + width: parent.width * 0.5 + height: parent.height * 0.17 + cellWidth: background.buttonWidth + cellHeight: background.buttonHeight + + anchors.left: parent.left + anchors.top: mazeModel.bottom + anchors.topMargin: background.height * 0.4 + + interactive: false + model: instructionModel + + header: HeaderArea { + width: instructionArea.width + height: background.height / 11 + headerOpacity: 1 + headerText: qsTr("Choose the instructions") + } + + property string instructionToInsert + + signal spaceKeyPressed + signal tabKeyPressed + + onSpaceKeyPressed: { + if(instructionArea.currentIndex != -1) + instructionArea.currentItem.selectCurrentInstruction() + } + + onTabKeyPressed: { + instructionArea.currentIndex = -1 + background.areaWithKeyboardInput = mainFunctionCodeArea + } + + highlight: Rectangle { + width: buttonWidth - 3 * ApplicationInfo.ratio + height: buttonHeight * 1.18 - 3 * ApplicationInfo.ratio + color: "lightsteelblue" + border.width: 3.5 * ApplicationInfo.ratio + border.color: "purple" + z: 2 + radius: width / 18 + opacity: 0.6 + visible: activity.keyboardNavigationVisible + } + highlightFollowsCurrentItem: true + keyNavigationWraps: true + + delegate: Item { + id: instructionItem + width: background.buttonWidth + height: background.buttonHeight * 1.18 + + Rectangle { + id: imageHolder + width: parent.width - 3 * ApplicationInfo.ratio + height: parent.height - 3 * ApplicationInfo.ratio + border.width: 1.2 * ApplicationInfo.ratio + border.color: "black" + anchors.centerIn: parent + radius: width / 18 + + Image { + id: icon + source: Activity.url + name + ".svg" + sourceSize { + width: parent.width / 1.2 + height: parent.height / 1.2 + } + anchors.centerIn: parent + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: (items.isTuxMouseAreaEnabled || items.isRunCodeEnabled) && ((items.numberOfInstructionsAdded < items.maxNumberOfInstructionsAllowed) || procedureCodeArea.isEditingInstruction || mainFunctionCodeArea.isEditingInstruction) + + onPressed: instructionItem.checkModelAndInsert() + } + + function selectCurrentInstruction() { + if(!mainFunctionCodeArea.isEditingInstruction && !procedureCodeArea.isEditingInstruction) { + instructionArea.instructionToInsert = name + playClickedAnimation() + } + else { + if(mainFunctionCodeArea.isEditingInstruction) + insertIntoModel(mainFunctionModel, mainFunctionCodeArea) + if(procedureCodeArea.isEditingInstruction && (name != Activity.CALL_PROCEDURE)) + insertIntoModel(procedureModel, procedureCodeArea) + } + } + + function checkModelAndInsert() { + if(items.constraintInstruction.opacity) + items.constraintInstruction.hide() + + if(!background.insertIntoMain && (name != Activity.CALL_PROCEDURE)) + insertIntoModel(procedureModel, procedureCodeArea) + else if(background.insertIntoMain) + insertIntoModel(mainFunctionModel, mainFunctionCodeArea) + } + + /** + * If we are adding an instruction, append it to the model if number of instructions added is less than the maximum number of instructions allowed. + * If editing, replace it with the selected instruction in the code area. + */ + function insertIntoModel(model, area) { + if(!area.isEditingInstruction) { + if(items.numberOfInstructionsAdded >= items.maxNumberOfInstructionsAllowed) + constraintInstruction.changeConstraintInstructionOpacity() + else { + playClickedAnimation() + model.append({ "name": name }) + items.numberOfInstructionsAdded++ + } + } + else { + playClickedAnimation() + model.set(area.initialEditItemIndex, {"name": name}, 1) + area.resetEditingValues() + } + } + + /** + * If two successive clicks on the same icon are made very fast, stop the ongoing animation and set the scale back to 1. + * Then start the animation for next click. + * This gives proper feedback of multiple clicks. + */ + function playClickedAnimation() { + clickedAnimation.stop() + icon.scale = 1 + clickedAnimation.start() + } + + SequentialAnimation { + id: clickedAnimation + PropertyAnimation { + target: imageHolder + property: "scale" + to: 0.8 + duration: 150 + } + + PropertyAnimation { + target: imageHolder + property: "scale" + to: 1 + duration: 150 + } + } + } +} diff --git a/src/activities/programmingMaze/ProgrammingMaze.qml b/src/activities/programmingMaze/ProgrammingMaze.qml --- a/src/activities/programmingMaze/ProgrammingMaze.qml +++ b/src/activities/programmingMaze/ProgrammingMaze.qml @@ -1,9 +1,11 @@ -/* GCompris - programmingMaze.qml +/* GCompris - ProgrammingMaze.qml * * Copyright (C) 2015 Siddhesh Suthar + * Copyright (C) 2018 Aman Kumar Gupta * * Authors: - * Siddhesh Suthar (Qt Quick port) + * Siddhesh Suthar + * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,13 +20,12 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ -import QtQuick 2.2 +import QtQuick 2.6 import GCompris 1.0 import "../../core" import "programmingMaze.js" as Activity - ActivityBase { id: activity @@ -43,17 +44,23 @@ oldHeight = height } - pageComponent: Rectangle { + property bool keyboardNavigationVisible: false + property string mode: "basic" + property string datasetUrl: "qrc:/gcompris/src/activities/programmingMaze/Dataset.qml" + + pageComponent: Image { id: background - anchors.fill: parent - color: "#8C8984" + source: "qrc:/gcompris/src/activities/programmingMaze/resource/background.svg" + fillMode: Image.PreserveAspectCrop + sourceSize.width: parent.width signal start signal stop - property bool keyNavigation: false property bool insertIntoMain: true - property bool insertIntoProcedure: false + property alias items: items + property int buttonWidth: background.width / 10 + property int buttonHeight: background.height / 15.3 Component.onCompleted: { activity.start.connect(start) @@ -70,48 +77,71 @@ property GCAudio audioEffects: activity.audioEffects property alias mazeModel: mazeModel property alias instructionModel: instructionModel - property alias answerModel: answerModel - property alias answerSheet: answerSheet + property alias mainFunctionModel: mainFunctionModel + property alias mainFunctionCodeArea: mainFunctionCodeArea property alias procedureModel: procedureModel - property alias procedure: procedure + property alias procedureCodeArea: procedureCodeArea + property alias instructionArea: instructionArea property alias player: player - property alias runCodeImage: runCode.source + property alias constraintInstruction: constraintInstruction + property alias tutorialImage: tutorialImage + property alias fish: fish + property alias dataset: dataset + property bool isRunCodeEnabled: true + property bool isTuxMouseAreaEnabled: false + property bool currentLevelContainsProcedure + property int maxNumberOfInstructionsAllowed + property int numberOfInstructionsAdded } - onStart: { - Activity.start(items) - keyNavigation = false + // This function catches the signal emitted after completion of movement of Tux after executing each instruction. + function checkSuccessAndExecuteNextInstruction() { + Activity.checkSuccessAndExecuteNextInstruction() } - onStop: { Activity.stop() } - - Keys.onRightPressed: { - keyNavigation = true - instruction.moveCurrentIndexRight() - } - Keys.onLeftPressed: { - keyNavigation = true - instruction.moveCurrentIndexLeft() + // This function catches the signal emitted after finding a dead-end in any of the executing instruction. + function deadEnd() { + Activity.deadEnd() } - Keys.onDownPressed: { - keyNavigation = true - instruction.moveCurrentIndexDown() - } - Keys.onUpPressed: { - keyNavigation = true - instruction.moveCurrentIndexUp() - } - Keys.onSpacePressed: { - keyNavigation = true - instruction.currentItem.mouseAreaInstruction.clicked() + + Loader { + id: dataset } - Keys.onEnterPressed: { - keyNavigation = true - instruction.currentItem.mouseAreaInstruction.clicked() + + onStart: { Activity.start(items, mode, datasetUrl) } + onStop: { Activity.stop() } + + property var areaWithKeyboardInput: instructionArea + + onAreaWithKeyboardInputChanged: activeCodeAreaIndicator.changeActiveCodeAreaIndicator(areaWithKeyboardInput) + + Keys.enabled: items.isTuxMouseAreaEnabled || items.isRunCodeEnabled + Keys.onPressed: { + activity.keyboardNavigationVisible = true + if(event.key === Qt.Key_Left) + areaWithKeyboardInput.moveCurrentIndexLeft() + if(event.key === Qt.Key_Right) + areaWithKeyboardInput.moveCurrentIndexRight() + if(event.key === Qt.Key_Up) + areaWithKeyboardInput.moveCurrentIndexUp() + if(event.key === Qt.Key_Down) + areaWithKeyboardInput.moveCurrentIndexDown() + if(event.key === Qt.Key_Space) + areaWithKeyboardInput.spaceKeyPressed() + if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) + runCodeOrResetTux() + if(event.key === Qt.Key_Tab) + areaWithKeyboardInput.tabKeyPressed() + if(event.key === Qt.Key_Delete && activeCodeAreaIndicator.top != instructionArea.top) { + areaWithKeyboardInput.deleteKeyPressed() + } } - Keys.onReturnPressed: { - keyNavigation = true - instruction.currentItem.mouseAreaInstruction.clicked() + + function runCodeOrResetTux() { + if(!Activity.deadEndPoint) + runCodeMouseArea.executeCode() + else + Activity.initLevel() } ListModel { @@ -119,372 +149,240 @@ } ListModel { - id: answerModel + id: mainFunctionModel } ListModel { id: procedureModel } + Rectangle { + id: constraintInstruction + anchors.left: parent.left + anchors.bottom: runCode.top + width: parent.width / 2.3 + height: parent.height / 8.9 + radius: 10 + z: 3 + color: "#E8E8E8" //paper white + border.width: 3 * ApplicationInfo.ratio + border.color: "#87A6DD" //light blue + + Behavior on opacity { PropertyAnimation { duration: 200 } } + + function changeConstraintInstructionOpacity() { + if(opacity) + constraintInstruction.hide() + else + constraintInstruction.show() + } + + function show() { + if(instructionText.text) + opacity = 0.8 + } + function hide() { + opacity = 0 + } + + GCText { + id: instructionText + anchors.fill: parent + anchors.margins: parent.border.width + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + fontSizeMode: Text.Fit + wrapMode: Text.WordWrap + + readonly property string resetTuxInstructionText: qsTr("Click on Tux or press Enter key to reset it or RELOAD button to reload the level.") + readonly property string constraintInstructionText: qsTr("Reach the fish in less than %1 instructions.").arg(items.maxNumberOfInstructionsAllowed) + + text: items.isTuxMouseAreaEnabled ? resetTuxInstructionText : constraintInstructionText + } + } + + MouseArea { + anchors.fill: parent + onClicked: constraintInstruction.changeConstraintInstructionOpacity() + } + Repeater { id: mazeModel - model: Activity.mazeBlocks[0] anchors.left: parent.left anchors.top: parent.top Image { - x: modelData[0] * width - y: modelData[1] * height + x: modelData.x * width + y: modelData.y * height width: background.width / 10 - height: (background.height - background.height/10) / 10 + height: (background.height - background.height / 10) / 10 source: Activity.reverseCountUrl + "iceblock.svg" - - Image { - id: fish - anchors.centerIn: parent - sourceSize.width: background.width / 12 - source: (modelData[0] == Activity.mazeBlocks[Activity.currentLevel][1][0][0] && modelData[1] == Activity.mazeBlocks[Activity.currentLevel][1][0][1]) ? Activity.reverseCountUrl + "blue-fish.svg" : "" - z: 5 - } } } + Image { + id: fish + sourceSize.width: background.width / 12 + source: Activity.reverseCountUrl + "blue-fish.svg" + } + Image { id: player source: "qrc:/gcompris/src/activities/maze/resource/tux_top_south.svg" sourceSize.width: background.width / 12 - x: 0; y: 0; z: 11 + z: 1 property int duration: 1000 - property bool tuxIsBusy: false - - signal init + readonly property real playerCenterX: x + width / 2 + readonly property real playerCenterY: y + height / 2 - onInit: { - player.rotation = Activity.EAST - } - - onTuxIsBusyChanged: { - Activity.playerRunningChanged() - } - - Behavior on x { - SmoothedAnimation { - duration: player.duration - reversingMode: SmoothedAnimation.Immediate - onRunningChanged: { - player.tuxIsBusy = !player.tuxIsBusy - } + MouseArea { + id: tuxMouseArea + anchors.fill: parent + enabled: items.isTuxMouseAreaEnabled + onClicked: { + Activity.initLevel() } } + } - Behavior on y { - SmoothedAnimation { - duration: player.duration - reversingMode: SmoothedAnimation.Immediate - onRunningChanged: { - player.tuxIsBusy = !player.tuxIsBusy - } - } - } + Rectangle { + id: activeCodeAreaIndicator + opacity: 0.5 + visible: activity.keyboardNavigationVisible - Behavior on rotation { - RotationAnimation { - duration: player.duration / 2 - direction: RotationAnimation.Shortest - onRunningChanged: { - player.tuxIsBusy = !player.tuxIsBusy - } - } + function changeActiveCodeAreaIndicator(activeArea) { + anchors.top = activeArea.top + anchors.fill = activeArea } + } + InstructionArea { + id: instructionArea } - property int buttonWidth: background.width / 10 - property int buttonHeight: background.height / 10 + HeaderArea { + id: mainFunctionHeader + headerText: qsTr("Main") + headerOpacity: background.insertIntoMain ? 1 : 0.5 + onClicked: background.insertIntoMain = true + anchors.top: parent.top + anchors.right: parent.right + } - GridView { - id: instruction - width: parent.width * 0.5 - height: parent.height * 0.3 + 25 * ApplicationInfo.ratio - cellWidth: background.buttonWidth - cellHeight: background.buttonHeight + CodeArea { + id: mainFunctionCodeArea + currentModel: mainFunctionModel + anchors.right: parent.right + anchors.top: mainFunctionHeader.bottom - anchors.left: parent.left - anchors.bottom: runCode.top - anchors.top: mazeModel.bottom - anchors.topMargin: background.buttonHeight * 4 - anchors.bottomMargin: runCode.height / 2 - - interactive: false - model: instructionModel - - header: instructionHeaderComponent - - highlight: Rectangle { - width: buttonWidth - height: buttonHeight - color: "lightsteelblue" - border.width: 3 - border.color: "black" - visible: background.keyNavigation - x: instruction.currentItem.x - Behavior on x { SpringAnimation { spring: 3; damping: 0.2 } } - } - highlightFollowsCurrentItem: false - focus: true - keyNavigationWraps: true - - delegate: Column { - spacing: 10 * ApplicationInfo.ratio - property alias mouseAreaInstruction: mouseAreaInstruction - - Item { - width: background.buttonWidth - height: background.buttonHeight - - Rectangle { - id: rect - width: parent.width - height: parent.height - anchors.fill: parent - gradient: Gradient { - GradientStop { position: 0.0; color: "#0191C8" } - GradientStop { position: 1.0; color: "#005B9A" } - } - opacity: 0.5 - } - - Image { - id: icon - source: Activity.url + name + ".svg" - sourceSize {width: parent.width; height: parent.height} - width: sourceSize.width - height: sourceSize.height - anchors.horizontalCenter: parent.horizontalCenter - } - - - Image { - source: "qrc:/gcompris/src/core/resource/button.svg" - sourceSize { height: parent.height; width: parent.width } - width: sourceSize.width - height: sourceSize.height - } - - MouseArea { - id: mouseAreaInstruction - anchors.fill: parent - signal clicked - onClicked: { - insertIntoModel() - } - onPressed: { - insertIntoModel() - } - function insertIntoModel() { - clickedAnim.start() - if(background.insertIntoProcedure && name != Activity.CALL_PROCEDURE) { - procedureModel.append({"name": name}) - } - if(background.insertIntoMain) { - answerModel.append({"name": name}) - } - } - } - SequentialAnimation { - id: clickedAnim - PropertyAnimation { - target: rect - property: "opacity" - to: "1" - duration: 300 - } - - PropertyAnimation { - target: rect - property: "opacity" - to: "0.5" - duration: 300 - } - } + onTabKeyPressed: { + mainFunctionCodeArea.currentIndex = -1 + if(!items.currentLevelContainsProcedure) { + background.areaWithKeyboardInput = instructionArea + instructionArea.currentIndex = 0 + } + else { + background.areaWithKeyboardInput = procedureCodeArea + background.insertIntoMain = false } } } - // insert data upon clicking the list items into this answerData - // and then process it to run the code - - AnswerSheet { - id: answerSheet - background: background - currentModel: answerModel + HeaderArea { + id: procedureHeader + headerText: qsTr("Procedure") + headerOpacity: !background.insertIntoMain ? 1 : 0.5 + visible: procedureCodeArea.visible + onClicked: background.insertIntoMain = false + anchors.top: mainFunctionCodeArea.bottom anchors.right: parent.right - anchors.top: answerHeaderComponent.bottom } - AnswerSheet { - id: procedure - background: background + CodeArea { + id: procedureCodeArea currentModel: procedureModel anchors.right: parent.right - anchors.bottom: parent.bottom - visible: bar.level > 2 + anchors.top: procedureHeader.bottom + visible: items.currentLevelContainsProcedure + + property alias procedureIterator: procedureCodeArea.currentIndex + + onTabKeyPressed: { + procedureCodeArea.currentIndex = -1 + background.areaWithKeyboardInput = instructionArea + instructionArea.currentIndex = 0 + background.insertIntoMain = true + } } Image { id: runCode width: background.width / 10 height: background.height / 10 - anchors.right: instruction.right + anchors.right: instructionArea.right anchors.bottom: bar.top anchors.margins: 10 * ApplicationInfo.ratio source:"qrc:/gcompris/src/core/resource/bar_ok.svg" fillMode: Image.PreserveAspectFit - MouseArea { id: runCodeMouseArea anchors.fill: parent - hoverEnabled: true + hoverEnabled: ApplicationInfo.isMobile ? false : (!items.isRunCodeEnabled ? false : true) + enabled: items.isRunCodeEnabled + + signal executeCode + onEntered: runCode.scale = 1.1 - onClicked: { - // todo add a condition to disable it if code is running - // either the execution hasn't started or stopped because of deadEndPoint - if(Activity.codeIterator == 0 || Activity.deadEndPoint) { - console.log(Activity.codeIterator +" value of codeIterator") - Activity.runCode() - } + onExecuteCode: { + if(mainFunctionModel.count) + startCodeExecution() } + onClicked: executeCode() onExited: runCode.scale = 1 - } - } - - - Component { - id: instructionHeaderComponent - Rectangle { - id: headerRect - width: instruction.width - height: 25 * ApplicationInfo.ratio - color: "#005B9A" + function startCodeExecution() { + runCodeClickAnimation.start() + Activity.resetCodeAreasIndices() - Image { - source: "qrc:/gcompris/src/core/resource/button.svg" - sourceSize { height: parent.height; width: parent.width } - width: sourceSize.width - height: sourceSize.height - } + if(constraintInstruction.opacity) + constraintInstruction.hide() - GCText { - id: headerText - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignHCenter - width: parent.width - height: parent.height - fontSizeMode: Font.DemiBold - minimumPointSize: 7 - fontSize: mediumSize - wrapMode: Text.WordWrap - color: "white" - text: qsTr("Choose the instructions") + Activity.runCode() } } - } - Item { - id: answerHeaderComponent - width: answerSheet.width - height: 50 * ApplicationInfo.ratio - anchors.left: answerSheet.left - anchors.top: parent.top - - Rectangle { - id: answerHeaderRect - anchors.fill: parent - color: "#005B9A" - opacity: background.insertIntoMain ? 1 : 0.5 - - MouseArea { - anchors.fill: parent - onClicked: { - background.insertIntoMain = true - background.insertIntoProcedure = false - } - } - - Image { - source: "qrc:/gcompris/src/core/resource/button.svg" - sourceSize { height: parent.height; width: parent.width } - width: sourceSize.width - height: sourceSize.height - } - - GCText { - id: answerHeaderText - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - height: parent.height - fontSizeMode: Font.DemiBold - minimumPointSize: 7 - fontSize: mediumSize - wrapMode: Text.WordWrap - color: "white" - text: qsTr("Your Code") - } + SequentialAnimation { + id: runCodeClickAnimation + NumberAnimation { target: runCode; property: "scale"; to: 0.8; duration: 100 } + NumberAnimation { target: runCode; property: "scale"; to: 1.0; duration: 100 } } } - Item{ - id: procedureHeaderComponent - width: procedure.width - height: 50 * ApplicationInfo.ratio - anchors.left: procedure.left - anchors.bottom: procedure.top - visible: procedure.visible - Rectangle { - id: procedureHeaderRect - anchors.fill: parent - color: "#005B9A" - opacity: background.insertIntoProcedure ? 1 : 0.5 - - MouseArea { - anchors.fill: parent - onClicked: { - background.insertIntoMain = false - background.insertIntoProcedure = true - } - } - - Image { - source: "qrc:/gcompris/src/core/resource/button.svg" - sourceSize { height: parent.height; width: parent.width } - width: sourceSize.width - height: sourceSize.height - } - - GCText { - id: procedureHeaderText - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - height: parent.height - fontSizeMode: Font.DemiBold - minimumPointSize: 7 - fontSize: mediumSize - wrapMode: Text.WordWrap - color: "white" - text: qsTr("Your procedure") + Image { + id: tutorialImage + source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" + anchors.fill: parent + z: 5 + visible: true + + property bool shownProcedureTutorialInstructions: false + + Tutorial { + id:tutorialSection + tutorialDetails: bar.level <= 2 ? Activity.mainTutorialInstructions : Activity.procedureTutorialInstructions + onSkipPressed: { + Activity.initLevel() + tutorialImage.visible = false + tutorialNumber = 0 } } + onVisibleChanged: { + if(tutorialImage.visible && tutorialImage.shownProcedureTutorialInstructions) + tutorialSection.visible = true + } } DialogHelp { @@ -494,7 +392,7 @@ Bar { id: bar - content: BarEnumContent { value: help | home | level | reload } + content: BarEnumContent { value: tutorialImage.visible ? help | home : help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } @@ -509,5 +407,4 @@ Component.onCompleted: win.connect(Activity.nextLevel) } } - } diff --git a/src/activities/programmingMaze/instructions/Instruction.qml b/src/activities/programmingMaze/instructions/Instruction.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/instructions/Instruction.qml @@ -0,0 +1,51 @@ +/* GCompris - Instruction.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Author: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 + +import "../programmingMaze.js" as Activity + +Item { + id: instruction + + //stores the movement animation duration of Tux when an instruction is executed. + property real movementAnimationDuration + + /** + * This signal is emitted after the execution of current instruction is complete. + * + * The signal will be caught by ProgrammingMaze.qml/Procedure.qml depending on with whom the connection is made, + * and it will check if Tux has reached the fish(level is complete) or will execute the next instruction. + */ + signal executionComplete + + /** + * This signal is emitted if Tux cannot move according to the current executed instruction. + * + * It will be caught by deadEnd() in its parent file. + */ + signal foundDeadEnd + + function setCodeAreaHighlightMoveDuration() { + Activity.items.mainFunctionCodeArea.highlightMoveDuration = movementAnimationDuration + Activity.items.procedureCodeArea.highlightMoveDuration = movementAnimationDuration + } +} diff --git a/src/activities/programmingMaze/instructions/MoveForward.qml b/src/activities/programmingMaze/instructions/MoveForward.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/instructions/MoveForward.qml @@ -0,0 +1,104 @@ +/* GCompris - MoveForward.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Author: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 + +import "../programmingMaze.js" as Activity + +Instruction { + id: moveForward + movementAnimationDuration: 1000 + + property double playerXCoordinate: 0 + property double playerYCoordinate: 0 + + //If there has been an x-axis movement, x co-ordinate will be animated without any effect on y-axis movement and same vice-versa. + ParallelAnimation { + id: movementAnimation + SmoothedAnimation { + target: Activity.items.player + property: 'x' + to: playerXCoordinate + duration: moveForward.movementAnimationDuration + reversingMode: SmoothedAnimation.Immediate + } + SmoothedAnimation { + target: Activity.items.player + property: 'y' + to: playerYCoordinate + duration: moveForward.movementAnimationDuration + reversingMode: SmoothedAnimation.Immediate + } + onStopped: executionComplete() + } + + function nextPositionExists(playerCenterX, playerCenterY) { + var playerNextPositionX = Math.floor(playerCenterX / Activity.stepX) + var playerNextPositionY = Math.floor(playerCenterY / Activity.stepY) + var currentLevelCoordinates = Activity.mazeBlocks[Activity.currentLevel].map + for(var i = 0; i < currentLevelCoordinates.length; i++) { + if(currentLevelCoordinates[i].x == playerNextPositionX && currentLevelCoordinates[i].y == playerNextPositionY) + return true + } + return false + } + + //Function to check if the current movement is possible or not and then process the instruction accordingly + function checkAndExecuteMovement() { + var currentRotation = Activity.getPlayerRotation() + var playerCenterX = Activity.items.player.playerCenterX + var playerCenterY = Activity.items.player.playerCenterY + var nextTileExists = false + + moveForward.playerXCoordinate = Activity.items.player.x + moveForward.playerYCoordinate = Activity.items.player.y + + if(currentRotation === Activity.EAST) { + playerCenterX += Activity.stepX + moveForward.playerXCoordinate += Activity.stepX + } + + else if(currentRotation === Activity.WEST) { + playerCenterX -= Activity.stepX + moveForward.playerXCoordinate -= Activity.stepX + } + + else if(currentRotation === Activity.SOUTH) { + playerCenterY -= Activity.stepY + moveForward.playerYCoordinate -= Activity.stepY + } + + else if(currentRotation === Activity.NORTH) { + playerCenterY += Activity.stepY + moveForward.playerYCoordinate += Activity.stepY + } + + nextTileExists = nextPositionExists(playerCenterX, playerCenterY) + + setCodeAreaHighlightMoveDuration() + + if(nextTileExists) { + movementAnimation.start() + } + else + foundDeadEnd() + } +} diff --git a/src/activities/programmingMaze/instructions/Procedure.qml b/src/activities/programmingMaze/instructions/Procedure.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/instructions/Procedure.qml @@ -0,0 +1,67 @@ +/* GCompris - Procedure.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Author: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 + +import "../programmingMaze.js" as Activity + +Instruction { + id: callProcedure + + property alias procedureCode: procedureCode + + //Stores the list of instructions to be executed in procedure code area + ListModel { + id: procedureCode + } + + function checkAndExecuteMovement() { + if(!Activity.deadEndPoint && parent.items.procedureCodeArea.procedureIterator < callProcedure.procedureCode.count - 1) { + parent.items.procedureCodeArea.procedureIterator++ + var currentInstruction = procedureCode.get(parent.items.procedureCodeArea.procedureIterator).name + Activity.procedureInstructionObjects[currentInstruction].checkAndExecuteMovement() + } + else { + parent.items.procedureCodeArea.procedureIterator = -1 + executionComplete() + } + } + + function deadEnd() { + foundDeadEnd() + } + + function checkSuccessAndExecuteNextInstruction() { + var fishX = Activity.mazeBlocks[Activity.currentLevel].fish.x + var fishY = Activity.mazeBlocks[Activity.currentLevel].fish.y + + var tuxX = Math.floor(Activity.items.player.playerCenterX / Activity.stepX) + var tuxY = Math.floor(Activity.items.player.playerCenterY / Activity.stepY) + + if(tuxX === fishX && tuxY === fishY) { + Activity.codeIterator = 0 + parent.items.bonus.good("tux") + } + else { + checkAndExecuteMovement() + } + } +} diff --git a/src/activities/programmingMaze/instructions/TurnLeftOrRight.qml b/src/activities/programmingMaze/instructions/TurnLeftOrRight.qml new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/instructions/TurnLeftOrRight.qml @@ -0,0 +1,55 @@ +/* GCompris - TurnLeftOrRight.qml + * + * Copyright (C) 2018 Aman Kumar Gupta + * + * Author: + * Aman Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 + +import "../programmingMaze.js" as Activity + +Instruction { + id: turnLeftOrRight + movementAnimationDuration: 500 + + property real finalRotation: 0 + property string turnDirection + + RotationAnimation { + id: movementAnimation + target: Activity.items.player + to: finalRotation + duration: turnLeftOrRight.movementAnimationDuration + direction: RotationAnimation.Shortest + onStopped: executionComplete() + } + + function checkAndExecuteMovement() { + var currentRotation = Activity.getPlayerRotation() + + if(turnLeftOrRight.turnDirection === "turn-left") + Activity.changedRotation = (currentRotation - 90) % 360 + else + Activity.changedRotation = (currentRotation + 90) % 360 + + setCodeAreaHighlightMoveDuration() + + turnLeftOrRight.finalRotation = Activity.changedRotation + movementAnimation.start() + } +} diff --git a/src/activities/programmingMaze/programmingMaze.js b/src/activities/programmingMaze/programmingMaze.js --- a/src/activities/programmingMaze/programmingMaze.js +++ b/src/activities/programmingMaze/programmingMaze.js @@ -1,9 +1,11 @@ /* GCompris - programmingMaze.js * * Copyright (C) 2015 Siddhesh Suthar + * Copyright (C) 2018 Aman Kumar Gupta * * Authors: - * "Siddhesh Suthar" (Qt Quick port) + * Siddhesh Suthar + * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +21,7 @@ * along with this program; if not, see . */ .pragma library -.import QtQuick 2.0 as Quick +.import QtQuick 2.6 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo // possible instructions @@ -27,81 +29,48 @@ var TURN_LEFT = "turn-left" var TURN_RIGHT = "turn-right" var CALL_PROCEDURE = "call-procedure" -var START_PROCEDURE = "start-procedure" -var END_PROCEDURE = "end-procedure" - -var mazeBlocks = [ - //level one - [ - //maze blocks - [[1,2],[2,2],[3,2]], - //fish index - [[3,2]], - //instruction set - [MOVE_FORWARD, - TURN_LEFT, - TURN_RIGHT] - ], - //level two - [ - [[1,3],[2,3],[2,2],[2,1],[3,1]], - //fish index - [[3,1]], - //instruction set - [MOVE_FORWARD, - TURN_LEFT, - TURN_RIGHT] - ], - //level three - [ - [[1,1],[2,1],[3,1],[3,2],[3,3],[2,3],[1,3]], - [[1,3]], - //instruction set - [MOVE_FORWARD, - TURN_LEFT, - TURN_RIGHT, - CALL_PROCEDURE] - ], - //level four - [ - [[0,3],[1,3],[1,2],[2,2],[2,1],[3,1]], - [[3,1]], - //instruction set - [MOVE_FORWARD, - TURN_LEFT, - TURN_RIGHT, - CALL_PROCEDURE] - ], - //level five - [ - [[0,3],[0,2],[0,1],[0,0],[1,0],[2,0],[2,1], - [2,2],[2,3],[3,3],[4,3],[4,2],[4,1],[4,0]], - [[4,0]], - //instruction set - [MOVE_FORWARD, - TURN_LEFT, - TURN_RIGHT, - CALL_PROCEDURE] - ] - ] +var mazeBlocks + +// Length of 1 step along x-axis var stepX + +// Length of 1 step along y-axis var stepY -var playerCode = [] -var tuxIceBlockNumber -var changedX -var changedY + +/** + * Lookup tables of instruction objects for main and procedure areas which will be stored here on creation and can be + * accessed when required to execute. + */ +var mainInstructionObjects = [] +var procedureInstructionObjects = [] + +// New rotation of Tux on turning. var changedRotation + +// Indicates if there is a dead-end var deadEndPoint = false + +// Stores the index of mainInstructionObjects[] which is going to be processed var codeIterator = 0 -var reset = false -var procedureBlocks -var runningProcedure + +/** + * Stores if the reset is done only when Tux is clicked. + * + * If resetTux is true, initLevel() is called and the instruction areas are not cleared. + * + * Else, it means that initLevel() is called to reset the entire level and the instruction areas are cleared as well. + */ +var resetTux = false + +// Duration of movement of highlight in the execution area. var moveAnimDuration + +//Stores the currrent instruction which is going to be processed +var currentInstruction + var url = "qrc:/gcompris/src/activities/programmingMaze/resource/" var reverseCountUrl = "qrc:/gcompris/src/activities/reversecount/resource/" -var okImage = "qrc:/gcompris/src/core/resource/bar_ok.svg" -var reloadImage = "qrc:/gcompris/src/core/resource/bar_reload.svg" var currentLevel = 0 var numberOfLevel var items @@ -111,64 +80,188 @@ var SOUTH = 180 var EAST = 270 -var BLOCKS_DATA_INDEX = 0 -var BLOCKS_FISH_INDEX = 1 -var BLOCKS_INSTRUCTION_INDEX = 2 +/** + * Stores the qml file components of all the instructions used in the activity. + * + * To add a new instruction, add its component here and add the instruction name in "instructionList" inside createInstructionObjects() along with the other instructions. + */ +var instructionComponents = { + "move-forward": Qt.createComponent("qrc:/gcompris/src/activities/programmingMaze/instructions/MoveForward.qml"), + "turn-left": Qt.createComponent("qrc:/gcompris/src/activities/programmingMaze/instructions/TurnLeftOrRight.qml"), + "turn-right": Qt.createComponent("qrc:/gcompris/src/activities/programmingMaze/instructions/TurnLeftOrRight.qml"), + "call-procedure": Qt.createComponent("qrc:/gcompris/src/activities/programmingMaze/instructions/Procedure.qml") +} + +var mainTutorialInstructions = [ + { + "instruction": qsTr("Instruction Area:" + + "There are 3 instructions which you have to use to code and make Tux reach the fish:" + + "
  • 1. Move forward: Moves Tux one step forward in the direction it is facing.
  • " + + "
  • 2. Turn left: Turns Tux in the left direction from where it is facing.
  • " + + "
  • 3. Turn right: Turns Tux in the right direction from where it is facing.
  • "), + "instructionImage": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial1.png" + }, + { + "instruction": qsTr("Main Function:" + + "
  • -The execution of code starts here on running.
  • " + + "
  • -Click on any instruction in the instruction area to add them to the Main Function
  • " + + "
  • -The instructions will execute in order until there's none left, dead-end or Tux reaches the fish.
  • "), + "instructionImage": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial2.png" + }, + ] + +var procedureTutorialInstructions = [ + { + "instruction": qsTr("Procedure:" + + "
  • -Procedure is a reusable set of instructions which can be used in a code by calling it where needed.
  • " + + "
  • -To switch between the Procedure area and Main Function area to add your code, click on the label Procedure or Main Function.
  • "), + "instructionImage": "qrc:/gcompris/src/activities/programmingMaze/resource/tutorial3.png" + }, + ] + +// Mode of the activity: basic or loop +var activityMode -function start(items_) { +function start(items_, mode_, datasetUrl_) { items = items_ + items.dataset.source = datasetUrl_ + activityMode = mode_ currentLevel = 0 + mazeBlocks = items.dataset.item.levels numberOfLevel = mazeBlocks.length - reset = false + resetTux = false initLevel() } function stop() { + destroyInstructionObjects() +} + +/** + * This function creates and populate instruction objects for main as well as procedure area. + * + * These are stored in the lookup table, provided in the parameter as "instructionObjects". + * The instructions are then connected to the slots of their code area (main or procedure), provided as "instructionCodeArea" in the parameter. + * + * The instructions can now be obtained from the look-up tables and executed when called. + * + * This saves the process of re-creating all the instruction objets, connecting them to their parent's slot and destroying + * them everytime for each instruction call which will be very redundant and quite memory consuming on devices with + * less RAM, weak processing power and slow performance specially for "loops" mode. + * + * 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. + */ +function createInstructionObjects(instructionObjects, instructionCodeArea) { + var instructionList = [MOVE_FORWARD, TURN_LEFT, TURN_RIGHT] + for(var i = 0; i < instructionList.length; i++) + createInstruction(instructionObjects, instructionList[i], instructionCodeArea) +} + +function createInstruction(instructionObjects, instructionName, instructionCodeArea) { + if(instructionName == TURN_LEFT || instructionName == TURN_RIGHT) + instructionObjects[instructionName] = instructionComponents[instructionName].createObject(instructionCodeArea, { "turnDirection": instructionName }) + else + instructionObjects[instructionName] = instructionComponents[instructionName].createObject(instructionCodeArea) + + instructionObjects[instructionName].foundDeadEnd.connect(instructionCodeArea.deadEnd) + instructionObjects[instructionName].executionComplete.connect(instructionCodeArea.checkSuccessAndExecuteNextInstruction) +} + +// Destroy instruction objects from the look-up tables +function destroyInstructionObjects() { + var instructionList = Object.keys(mainInstructionObjects) + for(var i = 0; i < instructionList.length; i++) + mainInstructionObjects[instructionList[i]].destroy() + + instructionList = Object.keys(procedureInstructionObjects) + for(var i = 0; i < instructionList.length; i++) + procedureInstructionObjects[instructionList[i]].destroy() + + mainInstructionObjects = [] + procedureInstructionObjects = [] } function initLevel() { if(!items || !items.bar) - return; + return items.bar.level = currentLevel + 1 - items.mazeModel.model = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX] + destroyInstructionObjects() + + var levelInstructions = mazeBlocks[currentLevel].instructions + + if(levelInstructions.indexOf(CALL_PROCEDURE) != -1) + items.currentLevelContainsProcedure = true + else + items.currentLevelContainsProcedure = false + + // Create, populate and connect signals of instructions for main function code area and store them in mainInstructionObjects. + createInstructionObjects(mainInstructionObjects, items.background) - if(!reset && !deadEndPoint) { - items.answerModel.clear() + if(items.currentLevelContainsProcedure) { + if(!items.tutorialImage.shownProcedureTutorialInstructions) { + items.tutorialImage.shownProcedureTutorialInstructions = true + items.tutorialImage.visible = true + } + + // Create procedure object in the main look-up table ,if the level has procedure/loop, to execute it for procedure/loop calls from the main code area. + createInstruction(mainInstructionObjects, CALL_PROCEDURE, items.background) + + // Create, populate and connect signals of instructions for procedure code area if the level has procedure/loop. + createInstructionObjects(procedureInstructionObjects, mainInstructionObjects[CALL_PROCEDURE]) + } + + // Stores the co-ordinates of the tile blocks in the current level + var currentLevelBlocksCoordinates = mazeBlocks[currentLevel].map + + items.mazeModel.model = currentLevelBlocksCoordinates + + if(!resetTux) { + items.mainFunctionModel.clear() items.procedureModel.clear() + items.numberOfInstructionsAdded = 0 } stepX = items.mazeModel.itemAt(0).width stepY = items.mazeModel.itemAt(0).height items.instructionModel.clear() - var levelInstructions = mazeBlocks[currentLevel][BLOCKS_INSTRUCTION_INDEX] - for (var i = 0; i < levelInstructions.length ; i++) { - items.instructionModel.append({"name":levelInstructions[i]}); - } + + for (var i = 0; i < levelInstructions.length; i++) + items.instructionModel.append({"name":levelInstructions[i]}) // Center Tux in its first case - items.player.x = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][0][0] * stepX + (stepX-items.player.width)/2 - items.player.y = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][0][1] * stepY + (stepY-items.player.height)/2 + items.player.x = currentLevelBlocksCoordinates[0].x * stepX + (stepX - items.player.width) / 2 + items.player.y = currentLevelBlocksCoordinates[0].y * stepY + (stepY - items.player.height) / 2 + items.player.rotation = EAST + + // Center fish at it's co-ordinate + items.fish.x = mazeBlocks[currentLevel].fish.x * stepX + (stepX - items.fish.width) / 2 + items.fish.y = mazeBlocks[currentLevel].fish.y * stepY + (stepY - items.fish.height) / 2 - tuxIceBlockNumber = 0 changedRotation = EAST deadEndPoint = false - procedureBlocks = 0 - runningProcedure = false moveAnimDuration = 1000 items.background.insertIntoMain = true - items.background.insertIntoProcedure = false - items.answerSheet.currentIndex = -1 - items.procedure.currentIndex = -1 - items.answerSheet.highlightMoveDuration = moveAnimDuration - items.procedure.highlightMoveDuration = moveAnimDuration - items.runCodeImage = okImage - items.player.tuxIsBusy = false + items.mainFunctionCodeArea.highlightMoveDuration = moveAnimDuration / 2 + items.procedureCodeArea.highlightMoveDuration = moveAnimDuration / 2 + items.isTuxMouseAreaEnabled = false + items.isRunCodeEnabled = true + items.maxNumberOfInstructionsAllowed = mazeBlocks[currentLevel].maxNumberOfInstructions + items.constraintInstruction.show() + items.mainFunctionCodeArea.resetEditingValues() + items.procedureCodeArea.resetEditingValues() + items.background.areaWithKeyboardInput = items.instructionArea + resetCodeAreasIndices() + resetTux = false codeIterator = 0 - playerCode = [] +} - items.player.init() +function resetCodeAreasIndices() { + items.instructionArea.currentIndex = -1 + items.mainFunctionCodeArea.currentIndex = -1 + items.procedureCodeArea.currentIndex = -1 + items.instructionArea.instructionToInsert = '' } function getPlayerRotation() { @@ -176,153 +269,63 @@ } function runCode() { - if(items.runCodeImage == reloadImage) { - playerCode = [] - items.answerSheet.highlightFollowsCurrentItem = false - initLevel() - } - else { - items.answerSheet.highlightFollowsCurrentItem = true - //initialize code - playerCode = [] - items.player.tuxIsBusy = false - procedureBlocks = items.procedureModel.count - for(var i = 0; i < items.answerModel.count; i ++) { - if(items.answerModel.get([i]).name == CALL_PROCEDURE) { - playerCode.push(START_PROCEDURE) - for(var j = 0; j < items.procedureModel.count; j++) { - if(items.procedureModel.get([j]).name != END_PROCEDURE) - playerCode.push(items.procedureModel.get([j]).name) - } - playerCode.push(END_PROCEDURE) - } - else { - playerCode.push(items.answerModel.get([i]).name) - } - } + items.mainFunctionCodeArea.resetEditingValues() + items.procedureCodeArea.resetEditingValues() - if(!items.player.tuxIsBusy) { - executeNextInstruction() - } - } -} + var instructionName - -function playerRunningChanged() { - if(!items.player.tuxIsBusy) { - if(deadEndPoint) { - console.log("it was a dead end") - } - else{ - executeNextInstruction() - } + // Append all the procedure instructions to the procedure area object. + for(var j = 0; j < items.procedureModel.count; j++) { + instructionName = items.procedureModel.get(j).name + mainInstructionObjects[CALL_PROCEDURE].procedureCode.append({ "name" : instructionName }) } + + items.isRunCodeEnabled = false + if(items.mainFunctionModel.count > 0) + executeNextInstruction() + else + deadEnd() } function executeNextInstruction() { - var currentInstruction = playerCode[codeIterator] - - if(!items.player.tuxIsBusy && codeIterator < playerCode.length && !deadEndPoint - && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { - changedX = items.player.x - changedY = items.player.y - var currentRotation = getPlayerRotation() - - var currentBlock = tuxIceBlockNumber - var nextBlock = tuxIceBlockNumber + 1 - - var currentX = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][currentBlock][0] - var currentY = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][currentBlock][1] - var nextX = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][nextBlock][0] - var nextY = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][nextBlock][1] - - if(currentInstruction == MOVE_FORWARD) { - ++tuxIceBlockNumber; - items.answerSheet.highlightMoveDuration = moveAnimDuration - items.procedure.highlightMoveDuration = moveAnimDuration - if (nextX - currentX > 0 && currentRotation == EAST) { - changedX += stepX - } - else if(nextX - currentX < 0 && currentRotation == WEST) { - changedX -= stepX - } - else if(nextY - currentY < 0 && currentRotation == SOUTH) { - changedY -= stepY - } - else if(nextY - currentY > 0 && currentRotation == NORTH) { - changedY += stepY - } - else { - // add an animation to indicate that its not possible - deadEndPoint = true - items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav") - deadEnd() - } - items.player.x = changedX - items.player.y = changedY - } - else if(currentInstruction == TURN_LEFT) { - changedRotation = (currentRotation - 90) % 360 - items.player.rotation = changedRotation - items.answerSheet.highlightMoveDuration = moveAnimDuration / 2 - items.procedure.highlightMoveDuration = moveAnimDuration / 2 - } - else if(currentInstruction == TURN_RIGHT) { - changedRotation = (currentRotation + 90) % 360 - items.player.rotation = changedRotation - items.answerSheet.highlightMoveDuration = moveAnimDuration / 2 - items.procedure.highlightMoveDuration = moveAnimDuration / 2 - } - - codeIterator ++ - items.player.tuxIsBusy = true - if(runningProcedure && procedureBlocks > 0 - && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { - procedureBlocks-- - items.procedure.moveCurrentIndexRight() - } - if(!runningProcedure - && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { - items.answerSheet.moveCurrentIndexRight() - } - checkSuccess() - } - else if(currentInstruction == START_PROCEDURE) { - runningProcedure = true - items.answerSheet.currentIndex += 1 - items.procedure.currentIndex = -1 - codeIterator ++ - executeNextInstruction() - } - else if(currentInstruction == END_PROCEDURE) { - runningProcedure = false - procedureBlocks = items.procedureModel.count - codeIterator ++ - executeNextInstruction() + if((codeIterator < items.mainFunctionModel.count) && !deadEndPoint) { + items.mainFunctionCodeArea.currentIndex += 1 + var instructionToExecute = items.mainFunctionModel.get(codeIterator).name + mainInstructionObjects[instructionToExecute].checkAndExecuteMovement() } } function deadEnd() { deadEndPoint = true - items.runCodeImage = reloadImage + resetTux = true + items.isTuxMouseAreaEnabled = true + items.constraintInstruction.show() + items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav") + items.bonus.bad("tux") } -function checkSuccess() { - var fishX = mazeBlocks[currentLevel][BLOCKS_FISH_INDEX][0][0]; - var fishY = mazeBlocks[currentLevel][BLOCKS_FISH_INDEX][0][1]; - var tuxX = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][tuxIceBlockNumber][0] - var tuxY = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][tuxIceBlockNumber][1] +function checkSuccessAndExecuteNextInstruction() { + var fishX = mazeBlocks[currentLevel].fish.x + var fishY = mazeBlocks[currentLevel].fish.y + + var tuxX = Math.floor(items.player.playerCenterX / stepX) + var tuxY = Math.floor(items.player.playerCenterY / stepY) if(tuxX === fishX && tuxY === fishY) { - playerCode = [] codeIterator = 0 - items.player.tuxIsBusy = false - items.bonus.good("smiley") + items.bonus.good("tux") + } + else if(codeIterator === (items.mainFunctionModel.count - 1)) { + deadEnd() + } + else { + codeIterator++ + executeNextInstruction() } } function nextLevel() { - reset = false + resetTux = false if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } @@ -330,7 +333,7 @@ } function previousLevel() { - reset = false + resetTux = false if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } @@ -338,20 +341,18 @@ } function repositionObjectsOnWidthChanged(factor) { - reset = true + resetTux = true if(items) initLevel() } function repositionObjectsOnHeightChanged(factor) { - reset = true + resetTux = true if(items) initLevel() } function reloadLevel() { - if(deadEndPoint) { - playerCode = [] - } + resetTux = false initLevel() } diff --git a/src/activities/programmingMaze/resource/README b/src/activities/programmingMaze/resource/README new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/resource/README @@ -0,0 +1,5 @@ +1. turn-left.svg: https://upload.wikimedia.org/wikipedia/commons/5/50/Simpleicons_Interface_undo-circular-arrow.svg By SimpleIcon http://www.simpleicon.com/ (http://www.flaticon.com/packs/simpleicon-interface) [CC BY 3.0 (http://creativecommons.org/licenses/by/3.0)] + +2. turn-right.svg: https://upload.wikimedia.org/wikipedia/commons/b/b5/Simpleicons_Interface_redo-circular-arrow.svg By SimpleIcon http://www.simpleicon.com/ (http://www.flaticon.com/packs/simpleicon-interface) [CC BY 3.0 (http://creativecommons.org/licenses/by/3.0)] + +3. move-forward.svg: https://www.flaticon.com/free-icon/arrow-pointing-to-up_66757 By Freepik \ No newline at end of file diff --git a/src/activities/programmingMaze/resource/background.svg b/src/activities/programmingMaze/resource/background.svg new file mode 100644 --- /dev/null +++ b/src/activities/programmingMaze/resource/background.svg @@ -0,0 +1,822 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/activities/programmingMaze/resource/call-procedure.svg b/src/activities/programmingMaze/resource/call-procedure.svg --- a/src/activities/programmingMaze/resource/call-procedure.svg +++ b/src/activities/programmingMaze/resource/call-procedure.svg @@ -1,8 +1,5 @@ - - - - - - - - - - - - - - - - - + width="400" + height="285.625" + style="display: block;" + inkscape:version="0.91 r13725" + sodipodi:docname="call-procedure.svg"> + id="metadata13"> image/svg+xml - + - - - - CALL P - - + + + diff --git a/src/activities/programmingMaze/resource/move-forward.svg b/src/activities/programmingMaze/resource/move-forward.svg --- a/src/activities/programmingMaze/resource/move-forward.svg +++ b/src/activities/programmingMaze/resource/move-forward.svg @@ -1,225 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - Openclipart - - - Indian road sign - Compulsory ahead only - 2012-09-10T00:52:00 - created by swecha developer and contributer Srujana. - https://openclipart.org/detail/172253/indian-road-sign---compulsory-ahead-only-by-ksrujana96-172253 - - - ksrujana96 - - - - - Compulsory ahead only - indian road sign - road sign - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/programmingMaze/resource/turn-left.svg b/src/activities/programmingMaze/resource/turn-left.svg --- a/src/activities/programmingMaze/resource/turn-left.svg +++ b/src/activities/programmingMaze/resource/turn-left.svg @@ -1,170 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - Openclipart - - - - 2012-09-10T00:55:46 - created by swecha developer and contributer Srujana. - https://openclipart.org/detail/172256/indian-road-sign---compulsory-turn-left-by-ksrujana96-172256 - - - ksrujana96 - - - - - Compulsory turn left - indian road sign - road sign - - - - - - - - - - - + \ No newline at end of file diff --git a/src/activities/programmingMaze/resource/turn-right.svg b/src/activities/programmingMaze/resource/turn-right.svg --- a/src/activities/programmingMaze/resource/turn-right.svg +++ b/src/activities/programmingMaze/resource/turn-right.svg @@ -1,234 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - Openclipart - - - Indian road sign - Compulsory turn right - 2012-09-10T00:56:48 - created by swecha developer and contributer Srujana. - https://openclipart.org/detail/172257/indian-road-sign---compulsory-turn-right-by-ksrujana96-172257 - - - ksrujana96 - - - - - Compulsory turn right - indian road sign - road sign - - - - - - - - - - - + \ No newline at end of file diff --git a/src/activities/programmingMaze/resource/tutorial1.png b/src/activities/programmingMaze/resource/tutorial1.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@