diff --git a/src/activities/programmingMaze/ProgrammingMaze.qml b/src/activities/programmingMaze/ProgrammingMaze.qml index 18a2079eb..b8559ccfa 100644 --- a/src/activities/programmingMaze/ProgrammingMaze.qml +++ b/src/activities/programmingMaze/ProgrammingMaze.qml @@ -1,513 +1,513 @@ /* GCompris - programmingMaze.qml * * 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 "../../core" import "programmingMaze.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property int oldWidth: width onWidthChanged: { Activity.repositionObjectsOnWidthChanged(width / oldWidth) oldWidth = width } property int oldHeight: height onHeightChanged: { Activity.repositionObjectsOnHeightChanged(height / oldHeight) oldHeight = height } pageComponent: Rectangle { id: background anchors.fill: parent color: "#8C8984" signal start signal stop property bool keyNavigation: false property bool insertIntoMain: true property bool insertIntoProcedure: false Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property GCAudio audioEffects: activity.audioEffects property alias mazeModel: mazeModel property alias instructionModel: instructionModel property alias answerModel: answerModel property alias answerSheet: answerSheet property alias procedureModel: procedureModel property alias procedure: procedure property alias player: player - property alias fish: fish property alias runCodeImage: runCode.source } onStart: { Activity.start(items) keyNavigation = false } onStop: { Activity.stop() } Keys.onRightPressed: { keyNavigation = true instruction.moveCurrentIndexRight() } Keys.onLeftPressed: { keyNavigation = true instruction.moveCurrentIndexLeft() } Keys.onDownPressed: { keyNavigation = true instruction.moveCurrentIndexDown() } Keys.onUpPressed: { keyNavigation = true instruction.moveCurrentIndexUp() } Keys.onSpacePressed: { keyNavigation = true instruction.currentItem.mouseAreaInstruction.clicked() } Keys.onEnterPressed: { keyNavigation = true instruction.currentItem.mouseAreaInstruction.clicked() } Keys.onReturnPressed: { keyNavigation = true instruction.currentItem.mouseAreaInstruction.clicked() } ListModel { id: instructionModel } ListModel { id: answerModel } ListModel { id: procedureModel } Repeater { id: mazeModel model: Activity.mazeBlocks[0] anchors.left: parent.left anchors.top: parent.top Image { - x: modelData[0] * background.width / 10 - y: modelData[1] * (background.height - background.height/10) / 10 + x: modelData[0] * width + y: modelData[1] * height width: background.width / 10 - height: background.height / 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: player source: "qrc:/gcompris/src/activities/maze/resource/tux_top_south.svg" sourceSize.width: background.width / 12 x: 0; y: 0; z: 11 property int duration: 1000 property bool tuxIsBusy: false signal init onInit: { player.rotation = Activity.EAST } onTuxIsBusyChanged: { Activity.playerRunningChanged() } Behavior on x { SmoothedAnimation { duration: player.duration reversingMode: SmoothedAnimation.Immediate onRunningChanged: { player.tuxIsBusy = !player.tuxIsBusy } } } Behavior on y { SmoothedAnimation { duration: player.duration reversingMode: SmoothedAnimation.Immediate onRunningChanged: { player.tuxIsBusy = !player.tuxIsBusy } } } Behavior on rotation { RotationAnimation { duration: player.duration / 2 direction: RotationAnimation.Shortest onRunningChanged: { player.tuxIsBusy = !player.tuxIsBusy } } } } - Image { - id: fish - source: Activity.reverseCountUrl + "blue-fish.svg" - sourceSize.width: background.width / 12 - x: 0; y: 0; z: 5 - } - property int buttonWidth: background.width / 10 property int buttonHeight: background.height / 10 GridView { id: instruction width: parent.width * 0.5 height: parent.height * 0.3 + 25 * ApplicationInfo.ratio cellWidth: background.buttonWidth cellHeight: background.buttonHeight 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 } } } } } // 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 anchors.right: parent.right anchors.top: answerHeaderComponent.bottom } AnswerSheet { id: procedure background: background currentModel: procedureModel anchors.right: parent.right anchors.bottom: parent.bottom visible: bar.level > 2 } Image { id: runCode width: background.width / 10 height: background.height / 10 anchors.right: instruction.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 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() } } onExited: runCode.scale = 1 } } Component { id: instructionHeaderComponent Rectangle { id: headerRect width: instruction.width height: 25 * ApplicationInfo.ratio color: "#005B9A" Image { source: "qrc:/gcompris/src/core/resource/button.svg" sourceSize { height: parent.height; width: parent.width } width: sourceSize.width height: sourceSize.height } 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") } } } 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") } } } 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") } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reloadLevel() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/programmingMaze/programmingMaze.js b/src/activities/programmingMaze/programmingMaze.js index ee785d515..1443e38ea 100644 --- a/src/activities/programmingMaze/programmingMaze.js +++ b/src/activities/programmingMaze/programmingMaze.js @@ -1,358 +1,357 @@ /* 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 . */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo +// possible instructions +var MOVE_FORWARD = "move-forward" +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"] + [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"] + [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"] + [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"] + [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"] + [MOVE_FORWARD, + TURN_LEFT, + TURN_RIGHT, + CALL_PROCEDURE] ] ] -//[1,3],[2,3],[2,2],[2,1],[3,1] -//[1,1],[2,1],[3,1],[3,2],[3,3],[2,3],[1,3] -//[0,3],[1,3],[1,2],[2,2],[2,1],[3,1] -var countOfMazeBlocks + var stepX var stepY var playerCode = [] -var currentInstruction var tuxIceBlockNumber var changedX var changedY -var currentRotation var changedRotation var deadEndPoint = false var codeIterator = 0 var reset = false var procedureBlocks var runningProcedure var moveAnimDuration 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 var NORTH = 0 var WEST = 90 var SOUTH = 180 var EAST = 270 var BLOCKS_DATA_INDEX = 0 var BLOCKS_FISH_INDEX = 1 var BLOCKS_INSTRUCTION_INDEX = 2 -var MOVE_FORWARD = "move-forward" -var TURN_LEFT = "turn-left" -var TURN_RIGHT = "turn-right" -var CALL_PROCEDURE = "call-procedure" -var END_PROCEDURE = "end-procedure" - function start(items_) { items = items_ currentLevel = 0 numberOfLevel = mazeBlocks.length reset = false initLevel() } function stop() { } function initLevel() { if(!items || !items.bar) return; items.bar.level = currentLevel + 1 items.mazeModel.model = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX] if(!reset && !deadEndPoint) { items.answerModel.clear() items.procedureModel.clear() } - countOfMazeBlocks = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX].length - stepX = items.background.width / 10 - stepY = (items.background.height - items.background.height/10) / 10 + 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]}); } - items.player.x = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][0][0] * stepX - items.player.y = mazeBlocks[currentLevel][BLOCKS_DATA_INDEX][0][1] * stepY - items.fish.x = mazeBlocks[currentLevel][BLOCKS_FISH_INDEX][0][0] * stepX - items.fish.y = mazeBlocks[currentLevel][BLOCKS_FISH_INDEX][0][1] * stepY + // 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 + tuxIceBlockNumber = 0 - currentRotation = EAST 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 codeIterator = 0 playerCode = [] items.player.init() } function getPlayerRotation() { return ((changedRotation % 360) + 360) % 360 } 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") + 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") + playerCode.push(END_PROCEDURE) } else { playerCode.push(items.answerModel.get([i]).name) } } if(!items.player.tuxIsBusy) { executeNextInstruction() } } } function playerRunningChanged() { if(!items.player.tuxIsBusy) { if(deadEndPoint) { console.log("it was a dead end") } else{ executeNextInstruction() } } } function executeNextInstruction() { - currentInstruction = playerCode[codeIterator] + var currentInstruction = playerCode[codeIterator] if(!items.player.tuxIsBusy && codeIterator < playerCode.length && !deadEndPoint - && currentInstruction != "start-procedure" && currentInstruction != "end-procedure") { + && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { changedX = items.player.x changedY = items.player.y - currentRotation = getPlayerRotation() + 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 = currentX * stepX + stepX + changedX += stepX } else if(nextX - currentX < 0 && currentRotation == WEST) { - changedX = currentX * stepX - stepX + changedX -= stepX } else if(nextY - currentY < 0 && currentRotation == SOUTH) { - changedY = currentY * stepY - stepY + changedY -= stepY } else if(nextY - currentY > 0 && currentRotation == NORTH) { - changedY = currentY * stepY + stepY + 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") - console.log("dead end") 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 = codeIterator + 1 + codeIterator ++ items.player.tuxIsBusy = true if(runningProcedure && procedureBlocks > 0 - && currentInstruction != "start-procedure" && currentInstruction != "end-procedure") { + && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { procedureBlocks-- items.procedure.moveCurrentIndexRight() } if(!runningProcedure - && currentInstruction != "start-procedure" && currentInstruction != "end-procedure") { + && currentInstruction != START_PROCEDURE && currentInstruction != END_PROCEDURE) { items.answerSheet.moveCurrentIndexRight() } checkSuccess() } - else if(currentInstruction == "start-procedure") { + else if(currentInstruction == START_PROCEDURE) { runningProcedure = true items.answerSheet.currentIndex += 1 items.procedure.currentIndex = -1 - codeIterator = codeIterator + 1 + codeIterator ++ executeNextInstruction() } - else if(currentInstruction == "end-procedure") { + else if(currentInstruction == END_PROCEDURE) { runningProcedure = false procedureBlocks = items.procedureModel.count - codeIterator = codeIterator + 1 + codeIterator ++ executeNextInstruction() } } function deadEnd() { deadEndPoint = true items.runCodeImage = reloadImage } function checkSuccess() { - if(changedX === items.fish.x && changedY === items.fish.y) { + 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] + + if(tuxX === fishX && tuxY === fishY) { playerCode = [] codeIterator = 0 items.player.tuxIsBusy = false items.bonus.good("smiley") } } function nextLevel() { reset = false if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { reset = false if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function repositionObjectsOnWidthChanged(factor) { reset = true if(items) initLevel() } function repositionObjectsOnHeightChanged(factor) { reset = true if(items) initLevel() } function reloadLevel() { if(deadEndPoint) { playerCode = [] } initLevel() }