diff --git a/src/activities/algebra_by/Algebra.qml b/src/activities/algebra_by/Algebra.qml index ec15790df..922fc8fcf 100644 --- a/src/activities/algebra_by/Algebra.qml +++ b/src/activities/algebra_by/Algebra.qml @@ -1,179 +1,179 @@ /* GCompris - Algebra.qml * * Copyright (C) 2014 Aruna Sankaranarayanan * * 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 "algebra.js" as Activity ActivityBase { id: activity property alias operand: operand onStart: { focus = true; } pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/algebra_by/resource/background.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Item { id: coreItems property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias balloon: balloon - property alias timer:timer + property alias timer: timer property GCSfx audioEffects: activity.audioEffects } onStart: Activity.start(coreItems, otherItems, operand) onStop: Activity.stop() DialogHelp { id: dialogHelpLeftRight onClose: home() } Timer { id: timer interval: 1000 onTriggered: Activity.run() } Item { width: background.width - 60 * ApplicationInfo.ratio height: background.height Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: { Activity.previousLevel() } onNextLevelClicked: { Activity.nextLevel() } onHomeClicked: home() } } Balloon { id: balloon onTimeout: bonus.bad("smiley") } Bonus { id: bonus Component.onCompleted: { loose.connect(Activity.run) win.connect(Activity.nextLevel) } } Score { id: score x: parent.width * 0.25 y: parent.height * 0.65 anchors.right: undefined anchors.bottom: undefined currentSubLevel: 0 numberOfSubLevels: 10 } } Item { id: otherItems property alias iAmReady: iAmReady property alias firstOp: firstOp property alias secondOp: secondOp property alias numpad: numpad property int result } NumPad { id: numpad onAnswerChanged: Activity.questionsLeft() maxDigit: ('' + otherItems.result).length + 1 } ReadyButton { id: iAmReady onClicked: Activity.run() } Flow { - id:textFlow + id: textFlow x: parent.width / 2 - width / 2 y: 80 width: parent.width / 2 height: 100 anchors.margins: 4 spacing: 10 AlgebraText { id: firstOp visible: !iAmReady.visible } AlgebraText { id: operand visible: firstOp.visible } AlgebraText { id: secondOp visible: !iAmReady.visible } AlgebraText { id: equals visible: firstOp.visible text: "=" } AlgebraText { id: result visible: !iAmReady.visible text: numpad.answer } } Keys.onPressed: { numpad.updateAnswer(event.key, true); } Keys.onReleased: { numpad.updateAnswer(event.key, false); } } diff --git a/src/activities/algebra_div/AlgebraDiv.qml b/src/activities/algebra_div/AlgebraDiv.qml index ce43e3fe3..120d78d54 100644 --- a/src/activities/algebra_div/AlgebraDiv.qml +++ b/src/activities/algebra_div/AlgebraDiv.qml @@ -1,30 +1,30 @@ /* GCompris - AlgebraDiv.qml * * Copyright (C) 2015 Sayan Biswas * * Authors: * Sayan Biswas (Qt version) * * 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 "../../core" import "../algebra_by/" Algebra { - onStart:{ + onStart: { operand.text = "/" } } diff --git a/src/activities/algebra_minus/AlgebraMinus.qml b/src/activities/algebra_minus/AlgebraMinus.qml index 518bae3fa..091301fdb 100644 --- a/src/activities/algebra_minus/AlgebraMinus.qml +++ b/src/activities/algebra_minus/AlgebraMinus.qml @@ -1,32 +1,32 @@ /* GCompris - AlgebraMinus.qml * * Copyright (C) 2014 Aruna Sankaranarayanan * * Authors: * Bruno Coudoin (GTK+ version) * Aruna Sankaranarayanan (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.6 import "../../core" import "../algebra_by/" Algebra { - onStart:{ + onStart: { operand.text = "-" } } diff --git a/src/activities/align4-2players/align4.js b/src/activities/align4-2players/align4.js index 529c1a582..e57b7cd26 100644 --- a/src/activities/align4-2players/align4.js +++ b/src/activities/align4-2players/align4.js @@ -1,530 +1,530 @@ /* GCompris - align4.js * * Copyright (C) 2014 Bharath M S * * Authors: * Laurent Lacheny (GTK+ version) * Bharath M S (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.6 as Quick var currentLevel var numberOfLevel var items var url = "qrc:/gcompris/src/activities/align4-2players/resource/" var currentPiece var currentPlayer var currentLocation var twoPlayer var weight = [[100, 50, 20, 100, 50, 20], [100, 50, 20, 100, 50, 20], [110, 55, 20, 100, 50, 20], [100, 50, 20, 110, 55, 20], [100, 50, 20, 110, 55, 20], [100, 50, 20, 110, 55, 20]]; var nextColumn var depthMax var randomMiss function start(items_, twoPlayer_) { items = items_ currentLevel = 0 twoPlayer = twoPlayer_ initLevel() } function stop() { } function initLevel() { numberOfLevel = twoPlayer ? 1 : weight.length items.bar.level = currentLevel + 1 items.counter = items.nextPlayerStart+1 - if(items.nextPlayerStart == 1) { + if(items.nextPlayerStart === 1) { items.player2score.endTurn(); items.player1score.beginTurn(); } else { items.player1score.endTurn(); items.player2score.beginTurn(); if(!twoPlayer) { items.trigTuxMove.start(); } } items.gameDone = false items.pieces.clear() for(var y = 0; y < items.rows; y++) { for(var x = 0; x < items.columns; x++) { items.pieces.append({'stateTemp': "invisible"}) } } nextColumn = 3 if(currentLevel < 2) depthMax = 2 else depthMax = 4 if(currentLevel < 2) randomMiss = 1 else if(currentLevel < 4) randomMiss = 0.5 else randomMiss = 1 setPieceLocationByIndex(3) } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function reset() { // If the previous game is not won, we switch the starting player // Else, the next player is the one who lost (set in continueGame()) if(!items.gameDone) { items.nextPlayerStart = (items.nextPlayerStart == 1) ? 2 : 1; } items.trigTuxMove.stop(); items.drop.stop() // stop animation items.pieces.clear() // Clear the board initLevel() } function whichColumn(mouseX, mouseY) { for(var i = 0; i < items.columns - 1; i++) { if(mouseX < items.repeater.itemAt(i + 1).x) { return i } } return items.columns - 1 } /* To move the piece before a column is chosen */ function setPieceLocation(mouseX, mouseY) { currentLocation = whichColumn(mouseX, mouseY) items.fallingPiece.y = items.repeater.itemAt(0).y - items.cellSize items.fallingPiece.x = items.repeater.itemAt(currentLocation).x } function setPieceLocationByIndex(index) { setPieceLocation(items.repeater.itemAt(index).x, items.repeater.itemAt(0).y) } function moveCurrentIndexRight() { if(currentLocation++ > items.columns) currentLocation = 0 setPieceLocationByIndex(currentLocation) } function moveCurrentIndexLeft() { if(currentLocation-- <= 0) currentLocation = items.columns - 1 setPieceLocationByIndex(currentLocation) } function isModelEmpty(model) { var state = model.stateTemp - return (state == "1" || state == "2") ? false : true + return (state === "1" || state === "2") ? false : true } function getPieceAt(col, row) { return items.pieces.get(row * items.columns + col) } function getNextFreeStop(col) { for(var row = items.rows - 1; row >= 0; row--) { if(isModelEmpty(getPieceAt(col, row))) return row } // Full column return -1 } function handleDrop(column) { var singleDropSize = items.cellSize var nextFreeStop = getNextFreeStop(column) if(nextFreeStop >= 0) { items.drop.to = items.repeater.itemAt(nextFreeStop * items.columns).y currentPiece = nextFreeStop * items.columns + column items.drop.start() } } function setPieceState(col, row, state) { items.pieces.set(row * items.columns + col, {"stateTemp": state}) } function getPieceState(col, row) { return items.pieces.get(row * items.columns + col).stateTemp } function getBoardFromModel() { var board = [] var temp for(var i = 0; i < items.rows; i++) { temp = [] for(var j = 0; j < items.columns; j++) { temp.push(getPieceState(j, i)) } board.push(temp) } return board } function getFreeStopFromBoard(column, board) { for(var row = items.rows-1; row > -1; row--) { if(board[row][column] === "invisible") { return row } } return -1 } function alphabeta(depth, alpha, beta, player, board) { var value = evaluateBoard(player, player % 2 ? 2 : 1, board) if(depth === 0 || value === 100000 || value < -100000) { return value } if(player === 2) { var scores = []; for(var c = 0; c < items.columns; c++) { var r = getFreeStopFromBoard(c, board) if(r === -1) continue; board[r][c] = "2" alpha = Math.max(alpha, alphabeta(depth - 1, alpha, beta, 1, board)) board[r][c] = "invisible" scores[c] = alpha; if(beta <= alpha) break; } if(depth === depthMax) { var max = -10000; for(var i = 0; i < scores.length; i++) { if(scores[i] > max) { max = scores[i] nextColumn = i } } } return alpha; } else { for(var c = 0; c < items.columns; c++) { var r = getFreeStopFromBoard(c, board) if(r === -1) continue; board[r][c] = "1" beta = Math.min(beta, alphabeta(depth - 1, alpha, beta, 2, board)) board[r][c] = "invisible" if(beta <= alpha) break; } return beta; } } function doMove() { var board = getBoardFromModel() alphabeta(depthMax, -10000, 10000, 2, board) setPieceLocation(items.repeater.itemAt(nextColumn).x, items.repeater.itemAt(0).y) handleDrop(nextColumn) } function checkLine() { var score = 0 var count1, count2 // Make the game easier, forget to analyse some line depending on the level if(Math.random() > randomMiss) return 0 // Performance improvement, do not enter the processing loop // if there is nothing to look at. var gotOne = false for(var i = 2; i < (arguments.length - 1); i++) { if(arguments[i] !== "invisible") { gotOne = true break } } if(!gotOne) return 0 var player1 = arguments[0].toString() var player2 = arguments[1].toString() for(var i = 2; i < (arguments.length - 3); i++) { count1 = 0 count2 = 0 for(var j = 0; j < 4; j++) { if(arguments[i + j] === player1) { count1++ } else if( arguments[i + j] === player2) { count2++ } } if((count1 > 0) && (count2 === 0)) { if(count1 === 4) { return 10000 } score += ((count1 / 3) * weight[currentLevel][0] + (count1 / 2) * weight[currentLevel][1] + count1 * weight[currentLevel][2]) } else if((count1 === 0) && (count2 > 0)) { if(count2 === 4) { return -10000 } score -= ((count2 / 3) * weight[currentLevel][3] + (count2 / 2) * weight[currentLevel][4] + count2 * weight[currentLevel][5]) } } return score } function evaluateBoard(player1, player2, board) { var score = 0 //Horizontal for(var i = 0; i < items.rows; i++) { score += checkLine(player1, player2, board[i][0], board[i][1], board[i][2], board[i][3], board[i][4], board[i][5], board[i][6]); } //Vertical for(var i = 0; i < items.columns; i++) { score += checkLine(player1, player2, board[0][i], board[1][i], board[2][i], board[3][i], board[4][i], board[5][i]) } //Diagonal Bottom-Right score += checkLine(player1, player2, board[0][3], board[1][4], board[2][5], board[3][6]); score += checkLine(player1, player2, board[0][2], board[1][3], board[2][4], board[3][5], board[4][6]); score += checkLine(player1, player2, board[0][1], board[1][2], board[2][3], board[3][4], board[4][5], board[5][6]); score += checkLine(player1, player2, board[0][0], board[1][1], board[2][2], board[3][3], board[4][4], board[5][5]); score += checkLine(player1, player2, board[1][0], board[2][1], board[3][2], board[4][3], board[5][4]); score += checkLine(player1, player2, board[2][0], board[3][1], board[4][2], board[5][3]); //Diagonal Top-Left score += checkLine(player1, player2, board[3][0], board[2][1], board[1][2], board[0][3]) score += checkLine(player1, player2, board[4][0], board[3][1], board[2][2], board[1][3], board[0][4]); score += checkLine(player1, player2, board[5][0], board[4][1], board[3][2], board[2][3], board[1][4], board[0][5]); score += checkLine(player1, player2, board[5][1], board[4][2], board[3][3], board[2][4], board[1][5], board[0][6]); score += checkLine(player1, player2, board[5][2], board[4][3], board[3][4], board[2][5], board[1][6]); score += checkLine(player1, player2, board[5][3], board[4][4], board[3][5], board[2][6]); return score } function checkGameWon(currentPieceRow, currentPieceColumn) { currentPlayer = getPieceState(currentPieceColumn, currentPieceRow) var crossed = "crossed" + currentPlayer // Horizontal var sameColor = 0 for(var col = 0; col < items.columns; col++) { if(getPieceState(col, currentPieceRow) === currentPlayer) { if(++sameColor == 4) { setPieceState(col, currentPieceRow, crossed) setPieceState(col - 1, currentPieceRow, crossed) setPieceState(col - 2, currentPieceRow, crossed) setPieceState(col - 3, currentPieceRow, crossed) return true } } else { sameColor = 0 } } // Vertical sameColor = 0 for(var row = 0; row < items.rows; row++) { if(getPieceState(currentPieceColumn, row) === currentPlayer) { if(++sameColor == 4) { setPieceState(currentPieceColumn, row, crossed) setPieceState(currentPieceColumn, row - 1, crossed) setPieceState(currentPieceColumn, row - 2, crossed) setPieceState(currentPieceColumn, row - 3, crossed) return true } } else { sameColor = 0 } } // Diagonal top left / bottom right sameColor = 0 var row = 0 for(var col = currentPieceColumn - currentPieceRow; col < items.columns; col++) { row++ if(col < 0) continue if(row > items.rows) break if(getPieceState(col, row-1) === currentPlayer) { if(++sameColor == 4) { setPieceState(col, row - 1, crossed) setPieceState(col - 1, row - 2, crossed) setPieceState(col - 2, row - 3, crossed) setPieceState(col - 3, row - 4, crossed) return true } } else { sameColor = 0 } } // Diagonal top right / bottom left sameColor = 0 var row = 0 for(var col = currentPieceColumn + currentPieceRow; col >= 0; col--) { row++ if(col >= items.columns) continue if(row > items.rows) break if(getPieceState(col, row-1) === currentPlayer) { if(++sameColor == 4) { setPieceState(col, row - 1, crossed) setPieceState(col + 1, row - 2, crossed) setPieceState(col + 2, row - 3, crossed) setPieceState(col + 3, row - 4, crossed) return true } } else { sameColor = 0 } } } function continueGame() { items.pieces.set(currentPiece, {"stateTemp": items.counter++ % 2 ? "2": "1"}) /* Update score if game won */ if(twoPlayer) { if(checkGameWon(parseInt(currentPiece / items.columns), parseInt(currentPiece % items.columns))) { items.gameDone = true if(currentPlayer === "1") { items.player1score.win(); items.player2score.endTurn(); items.nextPlayerStart = 2; } else { items.player2score.win(); items.player1score.endTurn(); items.nextPlayerStart = 1; } items.bonus.good("flower") } else { if(currentPlayer === "2") { items.player1score.beginTurn(); items.player2score.endTurn(); } else { items.player2score.beginTurn(); items.player1score.endTurn(); } } } else { if(checkGameWon(parseInt(currentPiece / items.columns), parseInt(currentPiece % items.columns))) { items.gameDone = true if(currentPlayer === "1") { items.player1score.win() items.player2score.endTurn() items.bonus.good("flower") items.counter-- items.nextPlayerStart = 2; } else { items.player2score.win() items.player1score.endTurn() items.bonus.bad("flower") items.nextPlayerStart = 1; } } if(items.counter % 2) { items.player1score.endTurn() items.player2score.beginTurn() items.trigTuxMove.start() } } if(items.counter === 42) { items.player1score.endTurn() items.player2score.endTurn() items.bonus.bad("flower") items.nextPlayerStart = (items.nextPlayerStart == 1) ? 2 : 1; } } diff --git a/src/activities/babymatch/DropAnswerItem.qml b/src/activities/babymatch/DropAnswerItem.qml index 8cad28c46..1c824abe5 100644 --- a/src/activities/babymatch/DropAnswerItem.qml +++ b/src/activities/babymatch/DropAnswerItem.qml @@ -1,94 +1,94 @@ /* gcompris - DropAnswerItem.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Pulkit 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 "babymatch.js" as Activity Rectangle { id: dropCircle property string dropCircleColor: currentTileImageItem ? 'transparent' : 'pink' property string text property double posX property double posY property string imgName property double xCenter: x + width / 2 property double yCenter: y + height / 2 property Item currentTileImageItem width: parent.width >= parent.height ? parent.height/35 : parent.width/35 height: width radius: width/2 z: 200 border.width: 1 color: Activity.displayDropCircle ? dropCircleColor : "transparent" border.color: dropCircle.color == "#000000" ? "transparent" : "red" x: posX * parent.width - width/2 y: posY * parent.height - height/2 // Display a shadow of image, when the image is hovered over a target area Image { id: targetImage fillMode: Image.PreserveAspectFit anchors.centerIn: parent - z : -1 + z: -1 } function imageRemove() { if(currentTileImageItem) currentTileImageItem.imageRemove() currentTileImageItem = null } function imageAdd(tileImageItem) { currentTileImageItem = tileImageItem dropCircle.color = dropCircleColor } function show(tileImageItem) { if(Activity.displayDropCircle) dropCircle.color = "lightgreen" targetImage.source = tileImageItem.source targetImage.width = tileImageItem.fullWidth targetImage.height = tileImageItem.fullHeight if(currentTileImageItem) { currentTileImageItem.opacity = 0 } if (tileImageItem.parentIsTile) { targetImage.opacity = 1 tileImageItem.opacity = 0.5 dropCircle.z = 100 } else targetImage.opacity = 0.5 } function hide() { dropCircle.color = Activity.displayDropCircle ? dropCircleColor : "transparent" targetImage.opacity = 0 dropCircle.z = 200 if(currentTileImageItem) currentTileImageItem.opacity = 1 } } diff --git a/src/activities/babymatch/babymatch.js b/src/activities/babymatch/babymatch.js index e2d720b18..ad3af13a5 100644 --- a/src/activities/babymatch/babymatch.js +++ b/src/activities/babymatch/babymatch.js @@ -1,254 +1,254 @@ /* GCompris - babymatch.js * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Bruno Coudoin (GTK+ version) * Pulkit Gupta (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.6 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo var currentLevel = 1 var currentSubLevel = 0 var numberOfLevel var numberOfSubLevel var items var imagesUrl var soundsUrl var boardsUrl var glowEnabled var glowEnabledDefault var spots = [] var showText = [] var displayDropCircle function start(items_, imagesUrl_, soundsUrl_, boardsUrl_, levelCount_, answerGlow_, displayDropCircle_) { items = items_ imagesUrl = imagesUrl_ soundsUrl = soundsUrl_ boardsUrl = boardsUrl_ numberOfLevel = levelCount_ glowEnabledDefault = answerGlow_ displayDropCircle = displayDropCircle_ currentLevel = 1 currentSubLevel = 0 numberOfSubLevel = 0 spots = [] showText = [] initLevel() } function stop() { for(var i = 0 ; i < spots.length ; ++ i) spots[i].destroy() } function initLevel() { items.bar.level = currentLevel var filename = boardsUrl + "board" + "/" + "board" + currentLevel + "_" + currentSubLevel + ".qml" items.dataset.source = filename var levelData = items.dataset.item items.availablePieces.model.clear() for(var i = 0 ; i < spots.length ; ++ i) spots[i].destroy() spots = [] for(var i = 0 ; i < showText.length ; ++ i) showText[i].destroy() showText = [] items.backgroundPiecesModel.clear() items.backgroundImage.source = "" items.availablePieces.view.currentDisplayedGroup = 0 items.availablePieces.view.previousNavigation = 1 items.availablePieces.view.nextNavigation = 1 items.availablePieces.view.okShowed = false items.availablePieces.view.showGlow = false items.availablePieces.view.ok.height = 0 var dropItemComponent = Qt.createComponent("qrc:/gcompris/src/activities/babymatch/DropAnswerItem.qml") var textItemComponent = Qt.createComponent("qrc:/gcompris/src/activities/babymatch/TextItem.qml") //print(dropItemComponent.errorString()) if(currentSubLevel == 0 && levelData.numberOfSubLevel != undefined) numberOfSubLevel = levelData.numberOfSubLevel items.score.currentSubLevel = currentSubLevel + 1 items.score.numberOfSubLevels = numberOfSubLevel + 1 - if(levelData.glow == undefined) + if(levelData.glow === undefined) glowEnabled = glowEnabledDefault else glowEnabled = levelData.glow items.toolTip.show('') - if(levelData.instruction == undefined) { + if(levelData.instruction === undefined) { items.instruction.opacity = 0 items.instruction.text = "" } else if(!displayDropCircle) { items.instruction.opacity = 0 items.instruction.text = levelData.instruction } else { items.instruction.opacity = 1 items.instruction.text = levelData.instruction } // Fill available pieces var arr=[], levelDataLength = levelData.levels.length for(var i=0 ; i < levelDataLength ; i++) arr[i] = i var i = 0, j = 0, k = 0, n = 0 while(levelDataLength--) { //Randomize the order of pieces var rand = Math.floor(Math.random() * levelDataLength) i = arr[rand] arr.splice(rand,1) //Create answer pieces if(levelData.levels[i].type === undefined) { items.availablePieces.model.append( { "imgName": levelData.levels[i].pixmapfile, "imgSound": levelData.levels[i].soundFile ? soundsUrl + levelData.levels[i].soundFile : "qrc:/gcompris/src/core/resource/sounds/scroll.wav", - "imgHeight": levelData.levels[i].height == undefined ? 0 : levelData.levels[i].height, - "imgWidth": levelData.levels[i].width == undefined ? 0 : levelData.levels[i].width, + "imgHeight": levelData.levels[i].height === undefined ? 0 : levelData.levels[i].height, + "imgWidth": levelData.levels[i].width === undefined ? 0 : levelData.levels[i].width, "toolTipText": // We remove the text before the pipe symbol if any (translation disembiguation) - levelData.levels[i].toolTipText == undefined ? + levelData.levels[i].toolTipText === undefined ? "" : (levelData.levels[i].toolTipText.split('|').length > 1 ? levelData.levels[i].toolTipText.split('|')[1] : levelData.levels[i].toolTipText), }); spots[j++] = dropItemComponent.createObject( items.backgroundImage, { "posX": levelData.levels[i].x, "posY": levelData.levels[i].y, "imgName" : levelData.levels[i].pixmapfile, }); } //Create Text pieces for the level which has to display additional information - else if(levelData.levels[i].type == "DisplayText") { + else if(levelData.levels[i].type === "DisplayText") { showText[k++] = textItemComponent.createObject( items.backgroundImage, { "posX": levelData.levels[i].x, "posY": levelData.levels[i].y, "textWidth": levelData.levels[i].width, "showText" : levelData.levels[i].text }); } //Create static background pieces else { if(levelData.levels[i].type === "SHAPE_BACKGROUND_IMAGE") { items.backgroundImage.source = imagesUrl + levelData.levels[i].pixmapfile if(levelData.levels[i].width) items.backgroundImage.sourceSize.width = levelData.levels[i].width if(levelData.levels[i].height) items.backgroundImage.sourceSize.height = levelData.levels[i].height } else { items.backgroundPiecesModel.append( { "imgName": levelData.levels[i].pixmapfile, "posX": levelData.levels[i].x, "posY": levelData.levels[i].y, - "imgHeight": levelData.levels[i].height == undefined ? 0 : levelData.levels[i].height, - "imgWidth": levelData.levels[i].width == undefined ? 0 : levelData.levels[i].width, + "imgHeight": levelData.levels[i].height === undefined ? 0 : levelData.levels[i].height, + "imgWidth": levelData.levels[i].width === undefined ? 0 : levelData.levels[i].width, }); } } } //Initialize displayedGroup variable which is used for showing navigation bars for(var i=0;i * * Authors: * Holger Kaelberer * * 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 QtQuick.Window 2.2 import QtSensors 5.0 import QtGraphicalEffects 1.0 import GCompris 1.0 import Box2D 2.0 import QtQuick.Controls 1.5 import "../../core" import "editor/" import "balancebox.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity property string mode: "play" // "play" or "test" property string levelSet: "builtin" // "builtin" or "user" // When the user launches the activity in "user" mode by default(due to previously save config mode) for the first time after updating GCompris, default user file must be loaded as they must be having created levels in it. // From next time onwards, the saved file path is loaded. Refer to line 567. property string loadedFilePath: (levelSet == "builtin") ? Activity.builtinFile : Activity.userFile property var testLevel property bool inForeground: false // to avoid unneeded reconfigurations property bool alwaysStart: true // enforce start signal for editor-to-testing- and returning from config-transition property bool needRestart: true - onWidthChanged:if (inForeground && pageView.currentItem === activity) + onWidthChanged: if (inForeground && pageView.currentItem === activity) Activity.reconfigureScene(); onHeightChanged: if (inForeground && pageView.currentItem === activity) Activity.reconfigureScene(); onStart: { inForeground = true; focus = true; } onStop: inForeground = false; Keys.onPressed: Activity.processKeyPress(event.key) Keys.onReleased: Activity.processKeyRelease(event.key) pageComponent: Image { id: background source: Activity.baseUrl + "/maze_bg.svg" sourceSize.width: parent.width anchors.fill: parent signal start signal stop function startEditor() { editorLoader.active = true; if (activity.mode == "test") displayDialogs([dialogActivityConfig, editorLoader.item]); else displayDialog(editorLoader.item); } function handleBackEvent() { if (activity.mode == "test") { startEditor(); return true; } else return false; } Keys.onEscapePressed: event.accepted = handleBackEvent(); Keys.onReleased: { if (event.key === Qt.Key_Back) event.accepted = handleBackEvent(); } Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) items.dpi = Math.round(Screen.pixelDensity*25.4); } onStart: if (activity.needRestart) { Activity.start(items); activity.needRestart = false; } else Activity.initLevel(); onStop: { Activity.stop(); activity.needRestart = true; } QtObject { id: items property string mode: activity.mode property string levelSet: activity.levelSet property string filePath: activity.loadedFilePath property var testLevel: activity.testLevel property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias tilt: tilt property alias timer: timer property alias ball: ball property alias file: file property int ballSize: cellSize - 2*wallSize property alias mapWrapper: mapWrapper property int cellSize: mapWrapper.length / Math.min(mapWrapper.rows, mapWrapper.columns) property int wallSize: cellSize / 5 property var world: physicsWorld property alias keyboardTimer: keyboardTimer property var ballType: Fixture.Category1 property var wallType: Fixture.Category2 property var holeType: Fixture.Category3 property var goalType: Fixture.Category4 property var buttonType: Fixture.Category5 property alias parser: parser property double dpi property GCSfx audioEffects: activity.audioEffects property Loading loading: activity.loading } Loader { id: editorLoader active: false sourceComponent: BalanceboxEditor { id: editor visible: true testBox: activity onClose: activity.home() } } JsonParser { id: parser onError: console.error("Balancebox: Error parsing JSON: " + msg); } Rectangle { id: mapWrapper property double margin: 20 property int columns: 0 property int rows: 0 property double length: Math.min(background.height - 2*mapWrapper.margin, background.width - 2*mapWrapper.margin); color: "#E3DEDB" width: length height: length anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter transform: [ Rotation { origin.x: mapWrapper.width / 2 origin.y: mapWrapper.height / 2 axis { x: 1; y: 0; z: 0 } angle: ApplicationInfo.isMobile ? 0 : -items.tilt.xRotation }, Rotation { origin.x: mapWrapper.width / 2 origin.y: mapWrapper.height / 2 axis { x: 0; y: 1; z: 0 } angle: ApplicationInfo.isMobile ? 0 : items.tilt.yRotation } ] // right: Wall { id: rightWall width: items.wallSize height: parent.height + items.wallSize anchors.left: mapWrapper.right anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // bottom: Wall { id: bottomWall width: parent.width + items.wallSize height: items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.bottom anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // top: Wall { id: topWall width: parent.width + items.wallSize height: items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // left: Wall { id: leftWall width: items.wallSize height: parent.height + items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } BalanceItem { id: ball world: physicsWorld imageSource: Activity.baseUrl + "/ball.svg" visible: false scale: 1.0 width: items.ballSize height: items.ballSize z: 3 // above other BalanceItems categories: items.ballType collidesWith: items.wallType | items.holeType | items.goalType | items.buttonType density: 1 friction: Activity.friction linearDamping: Activity.friction restitution: Activity.restitution bodyType: Body.Dynamic shadow: true shadowHorizontalOffset: (items.tilt.yRotation > 0) ? Math.min(items.tilt.yRotation, items.wallSize) : Math.max(items.tilt.yRotation, -items.wallSize) shadowVerticalOffset: (items.tilt.xRotation > 0) ? Math.min(items.tilt.xRotation, items.wallSize) : Math.max(items.tilt.xRotation, -items.wallSize) Behavior on scale { NumberAnimation { id: fallAnimation duration: 1000 } } onBeginContact: { if (other.categories !== items.wallType) Activity.addBallContact(other); else { // sound-effect on each contact with a wall might be too annoying: //items.audioEffects.stop(); //items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav"); } } onEndContact: { if (other.categories !== items.wallType) Activity.removeBallContact(other); } } World { id: physicsWorld gravity: Qt.point(0, 0) // we calculate acceleration ourselves pixelsPerMeter: Activity.box2dPpm // default: 32 timeStep: Activity.step/1000 // default: 1/60 } DebugDraw { id: debugDraw world: physicsWorld visible: Activity.debugDraw z: 100 } } Timer { id: timer interval: Activity.step; running: false; repeat: true onTriggered: Activity.moveBall() } Item { id: tilt property double xRotation: 0 property double yRotation: 0 property bool swapAxes: false property bool invertX: false property bool invertY: false onXRotationChanged: { if (xRotation > 90) xRotation = 90; else if (xRotation < -90) xRotation = -90; } onYRotationChanged: { if (yRotation > 90) yRotation = 90; else if (yRotation < -90) yRotation = -90; } TiltSensor { id: tiltSensor active: ApplicationInfo.isMobile ? true : false onReadingChanged: { if (!tilt.swapAxes) { tilt.xRotation = tilt.invertX ? -reading.xRotation : reading.xRotation; tilt.yRotation = tilt.invertY ? -reading.yRotation : reading.yRotation; } else { tilt.xRotation = tilt.invertX ? -reading.yRotation : reading.yRotation; tilt.yRotation = tilt.invertY ? -reading.xRotation : reading.xRotation; } tiltText.text = "X/Y Rotation: " + tiltSensor.reading.xRotation + "/" + tiltSensor.reading.yRotation } } } Item { id: textWrapper anchors.left: parent.left anchors.top: parent.top width: parent.width height: parent.height / 3 visible: Activity.debugDraw Text { id: tiltText anchors.left: parent.left anchors.top: parent.top text: "X/Y Rotation: " + tilt.xRotation + "/" + tilt.yRotation font.pointSize: 12 } Text { id: posText anchors.left: parent.left anchors.top: tiltText.bottom text: "X/Y = " + ball.x + "/" + ball.y font.pointSize: 12 } } MultiPointTouchArea { anchors.fill: parent touchPoints: [ TouchPoint { id: point1 } ] property real startX property real startY property int offset: 30 function reset() { startX = point1.x startY = point1.y } onPressed: { reset() } onUpdated: { var moveX = point1.x - startX var moveY = point1.y - startY // Find the direction with the most move if(Math.abs(moveX) * ApplicationInfo.ratio > offset && Math.abs(moveX) > Math.abs(moveY)) { if(moveX > offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Right) reset() } else if(moveX < -offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Left) reset() } } else if(Math.abs(moveY) * ApplicationInfo.ratio > offset && Math.abs(moveX) < Math.abs(moveY)) { if(moveY > offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Down) reset() } else if(moveY < -offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Up) reset() } } } onReleased: { Activity.keyboardIsTilting = false } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: activity.mode == "play" ? (help | home | level | config ) : ( help | home ) } onHelpClicked: { // stop everything or the ball keeps moving while we're away: items.timer.stop(); displayDialog(dialogHelp); } onPreviousLevelClicked: if (!Activity.finishRunning) Activity.previousLevel() onNextLevelClicked: if (!Activity.finishRunning) Activity.nextLevel() onHomeClicked: { if (activity.mode == "test") background.startEditor(); else activity.home() } onConfigClicked: { items.timer.stop(); dialogActivityConfig.active = true // Set default values dialogActivityConfig.setDefaultValues(); displayDialog(dialogActivityConfig) } } Bonus { id: bonus looseSound: "qrc:/gcompris/src/core/resource/sounds/crash.wav" Component.onCompleted: { win.connect(Activity.nextLevel); loose.connect(Activity.initLevel); } } Timer { id: keyboardTimer interval: Activity.keyboardTimeStep; running: false repeat: false onTriggered: Activity.keyboardHandler() } GCCreationHandler { id: creationHandler readonly property bool isEditorActive: editorLoader.active && editorLoader.item.visible onFileLoaded: { if(!isEditorActive) activity.loadedFilePath = filePath else editorLoader.item.filename = filePath close() } parent: isEditorActive ? editorLoader.item : dialogActivityConfig } File { id: file } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias levelsBox: levelsBox property var availableLevels: [ { "text": qsTr("Built-in"), "value": "builtin" }, { "text": qsTr("User"), "value": "user" }, ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: levelsBox model: availableLevels background: dialogActivityConfig label: qsTr("Select your level set") } Column { spacing: 5 Button { id: editorButton style: GCButtonStyle {} height: levelsBox.height text: qsTr("Start Editor") visible: levelsBox.currentIndex == 1 onClicked: background.startEditor() } Button { id: loadButton style: GCButtonStyle {} height: levelsBox.height text: qsTr("Load saved levels") visible: levelsBox.currentIndex == 1 onClicked: creationHandler.loadWindow() } } } } } onClose: home(); onLoadData: { if(dataToSave && dataToSave["levels"]) { activity.levelSet = dataToSave["levels"]; if(dataToSave['filePath']) activity.loadedFilePath = dataToSave["filePath"] } } onSaveData: { var newLevels = dialogActivityConfig.configItem .availableLevels[dialogActivityConfig.configItem.levelsBox.currentIndex].value; var initialFilePath = dataToSave['filePath'] ? dataToSave['filePath'] : "" if(newLevels === "builtin") activity.loadedFilePath = Activity.builtinFile if (newLevels !== activity.levelSet || initialFilePath != activity.loadedFilePath) { activity.levelSet = newLevels; dataToSave = {"levels": activity.levelSet, "filePath": activity.loadedFilePath}; activity.needRestart = true; } } dataValidationFunc: function() { var newLevels = dialogActivityConfig.configItem .availableLevels[dialogActivityConfig.configItem.levelsBox.currentIndex].value if (newLevels === "user" && activity.loadedFilePath === Activity.builtinFile) { Core.showMessageDialog(dialogActivityConfig, qsTr("You selected the user-defined level set, but you have not yet defined any user levels!
" + "Either create your user levels by starting the level editor or choose the 'built-in' level set."), qsTr("Ok"), null, "", null, null); return false; } return true; } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableLevels.length; i ++) { if(dialogActivityConfig.configItem.availableLevels[i].value === activity.levelSet) { dialogActivityConfig.configItem.levelsBox.currentIndex = i; break; } } } } } } diff --git a/src/activities/balancebox/balancebox.js b/src/activities/balancebox/balancebox.js index 9172cbf22..af3c2eb1b 100644 --- a/src/activities/balancebox/balancebox.js +++ b/src/activities/balancebox/balancebox.js @@ -1,564 +1,564 @@ /* GCompris - balancebox.js * * Copyright (C) 2014-2016 Holger Kaelberer * * Authors: * Holger Kaelberer * * 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 . */ /* ToDo: - make sensitivity configurable - editor: add 'clear' button - editor: allow going back: level 1 -> last level - add new item: unordered contact, that has to be collected but in an arbitrary order */ .pragma library .import QtQuick 2.6 as Quick .import GCompris 1.0 as GCompris .import Box2D 2.0 as Box2D .import "qrc:/gcompris/src/core/core.js" as Core .import QtQml 2.2 as Qml Qt.include("balancebox_common.js") var dataset = null; // Parameters that control the ball's dynamics var m = 0.2; // without ppm-correction: 10 var g = 9.81; // without ppm-correction: 50.8 var box2dPpm = 32; // pixelsPerMeter used in Box2D's world var boardSizeM = 0.9; // board's real edge length, fixed to 90 cm var boardSizePix = 500; // board's current size in pix (acquired dynamically) var dpiBase=139; var boardSizeBase = 760; var curDpi = null; var pixelsPerMeter = null; var vFactor = pixelsPerMeter / box2dPpm; // FIXME: calculate! var step = 20; // time step (in ms) var friction = 0.15; var restitution = 0.3; // rebounce factor // stuff for keyboard based tilting var keyboardTiltStep = 0.5; // degrees var keyboardTimeStep = 20; // ms var lastKey; var keyboardIsTilting = false; // tilting or resetting to horizontal var debugDraw = false; var currentLevel = 0; var numberOfLevel = 0; var items; var level; var map; // current map var goal = null; var holes = new Array(); var walls = new Array(); var contacts = new Array(); var ballContacts = new Array(); var goalUnlocked; var lastContact; var wallComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/Wall.qml"); var contactComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/BalanceContact.qml"); var balanceItemComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/BalanceItem.qml"); var goalComponent = Qt.createComponent("qrc:/gcompris/src/activities/balancebox/Goal.qml"); var contactIndex = -1; var pendingObjects = 0; var pendingReconfigure = false; var finishRunning = false; function start(items_) { items = items_; currentLevel = 0; if (items.mode === "play") { if (GCompris.ApplicationInfo.isMobile) { // we don't have many touch events, therefore disable screensaver on android: GCompris.ApplicationInfo.setKeepScreenOn(true); // lock screen orientation to landscape: GCompris.ApplicationInfo.setRequestedOrientation(0); if (GCompris.ApplicationInfo.getNativeOrientation() === Qt.PortraitOrientation) { /* * Adjust tilting if native orientation != landscape. * * Note: As of Qt 5.4.1 QTiltSensor as well as QRotationSensor * report on Android * isFeatureSupported(AxesOrientation) == false. * Therefore we honour rotation manually. */ items.tilt.swapAxes = true; items.tilt.invertX = true; } } var levelsFile; if (items.levelSet === "user" && items.file.exists(items.filePath)) levelsFile = items.filePath; else { if(items.levelSet === "user") { Core.showMessageDialog(items.background, // The argument represents the file path name to be loaded. qsTr("The file '%1' is missing!
Falling back to builtin levels.").arg(items.filePath), "", null, "", null, null); } levelsFile = builtinFile; currentLevel = GCompris.ApplicationSettings.loadActivityProgress( "balancebox"); } dataset = items.parser.parseFromUrl(levelsFile, validateLevels); if (dataset == null) { console.error("Balancebox: Error loading levels from " + levelsFile + ", can't continue!"); return; } } else { // testmode: dataset = [items.testLevel]; } numberOfLevel = dataset.length; reconfigureScene(); } function reconfigureScene() { if (items === undefined || items.mapWrapper === undefined) return; if (pendingObjects > 0) { pendingReconfigure = true; return; } // set up dynamic variables for movement: pixelsPerMeter = (items.mapWrapper.length / boardSizeBase) * boardSizePix / boardSizeM; vFactor = pixelsPerMeter / box2dPpm; // console.log("Starting: mode=" + items.mode // + " pixelsPerM=" + items.world.pixelsPerMeter // + " timeStep=" + items.world.timeStep // + " posIterations=" + items.world.positionIterations // + " velIterations=" + items.world.velocityIterations // + " boardSizePix" + boardSizePix + " (real " + items.mapWrapper.length + ")" // + " pixelsPerMeter=" + pixelsPerMeter // + " vFactor=" + vFactor // + " dpi=" + items.dpi // + " nativeOrientation=" + GCompris.ApplicationInfo.getNativeOrientation()); initLevel(); } function sinDeg(num) { return Math.sin(num/180*Math.PI); } function moveBall() { var dt = step / 1000; var dvx = g*dt * sinDeg(items.tilt.yRotation); var dvy = g*dt * sinDeg(items.tilt.xRotation); // console.log("moving ball: dv: " + items.ball.body.linearVelocity.x // + "/" + items.ball.body.linearVelocity.y // + " -> " + (items.ball.body.linearVelocity.x+dvx) // + "/" + (items.ball.body.linearVelocity.y+dvy)); items.ball.body.linearVelocity.x += dvx * vFactor; items.ball.body.linearVelocity.y += dvy * vFactor; checkBallContacts(); } function checkBallContacts() { for (var k = 0; k < ballContacts.length; k++) { if (items.ball.x > ballContacts[k].x - items.ballSize/2 && items.ball.x < ballContacts[k].x + items.ballSize/2 && items.ball.y > ballContacts[k].y - items.ballSize/2 && items.ball.y < ballContacts[k].y + items.ballSize/2) { // collision - if (ballContacts[k].categories == items.holeType) + if (ballContacts[k].categories === items.holeType) finishBall(false, ballContacts[k].x, ballContacts[k].y); - else if (ballContacts[k].categories == items.goalType && goalUnlocked) + else if (ballContacts[k].categories === items.goalType && goalUnlocked) finishBall(true, ballContacts[k].x + (items.cellSize - items.wallSize - items.ballSize)/2, ballContacts[k].y + (items.cellSize - items.wallSize - items.ballSize)/2); - else if (ballContacts[k].categories == items.buttonType) { + else if (ballContacts[k].categories === items.buttonType) { if (!ballContacts[k].pressed - && ballContacts[k].orderNum == lastContact + 1) + && ballContacts[k].orderNum === lastContact + 1) { ballContacts[k].pressed = true; lastContact = ballContacts[k].orderNum; - if (lastContact == contacts.length) { + if (lastContact === contacts.length) { items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/win.wav"); goalUnlocked = true; goal.imageSource = baseUrl + "/door.svg"; } else items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/scroll.wav"); // bleep } } } } } function finishBall(won, x, y) { finishRunning = true; items.timer.stop(); items.keyboardTimer.stop(); items.ball.x = x; items.ball.y = y; items.ball.scale = 0.4; items.ball.body.linearVelocity = Qt.point(0, 0); if (won) { items.bonus.good("flower"); if (items.levelSet === "builtin" && items.mode === "play") { GCompris.ApplicationSettings.saveActivityProgress("balancebox", currentLevel+1 >= numberOfLevel ? 0 : currentLevel+1); } } else items.bonus.bad("flower"); } function stop() { // reset everything tearDown(); if(goal) { goal.destroy() goal = null } // unlock screen orientation if (GCompris.ApplicationInfo.isMobile) { GCompris.ApplicationInfo.setKeepScreenOn(false); GCompris.ApplicationInfo.setRequestedOrientation(-1); } // make sure loading overlay is really stopped items.loading.stop(); } function createObject(component, properties) { var p = properties; p.world = items.world; var object = component.createObject(items.mapWrapper, p); return object; } var incubators; // need to reference all returned incubators in global scope // or things don't work function incubateObject(targetArr, component, properties) { var p = properties; p.world = items.world; var incubator = component.incubateObject(items.mapWrapper, p); if (incubator === null) { console.error("Error during object incubation!"); items.loading.stop(); return; } incubators.push(incubator); if (incubator.status === Qml.Component.Ready) targetArr.push(incubator.object); else if (incubator.status === Qml.Component.Loading) { pendingObjects++; incubator.onStatusChanged = function(status) { if (status === Qml.Component.Ready) targetArr.push(incubator.object); else console.error("Error during object creation!"); if (--pendingObjects === 0) { // initMap completed if (pendingReconfigure) { pendingReconfigure = false; reconfigureScene(); } else { items.timer.start(); items.loading.stop(); } } } } else console.error("Error during object creation!"); } function initMap() { incubators = new Array(); goalUnlocked = true; finishRunning = false; items.mapWrapper.rows = map.length; items.mapWrapper.columns = map[0].length; pendingObjects = 0; for (var row = 0; row < map.length; row++) { for (var col = 0; col < map[row].length; col++) { var x = col * items.cellSize; var y = row * items.cellSize; var currentCase = map[row][col]; var orderNum = (currentCase & 0xFF00) >> 8; // debugging: if (debugDraw) { try { var rect = Qt.createQmlObject( "import QtQuick 2.6;Rectangle{" +"width:" + items.cellSize +";" +"height:" + items.cellSize+";" +"x:" + x + ";" +"y:" + y +";" +"color: \"transparent\";" +"border.color: \"blue\";" +"border.width: 1;" +"}", items.mapWrapper); } catch (e) { console.error("Error creating object: " + e); } } if (currentCase & NORTH) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y-items.wallSize/2, width: items.cellSize + items.wallSize, height: items.wallSize, shadow: false}); } if (currentCase & SOUTH) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y+items.cellSize-items.wallSize/2, width: items.cellSize+items.wallSize, height: items.wallSize, shadow: false}); } if (currentCase & EAST) { incubateObject(walls, wallComponent, { x: x+items.cellSize-items.wallSize/2, y: y-items.wallSize/2, width: items.wallSize, height: items.cellSize+items.wallSize, shadow: false}); } if (currentCase & WEST) { incubateObject(walls, wallComponent, { x: x-items.wallSize/2, y: y-items.wallSize/2, width: items.wallSize, height: items.cellSize+items.wallSize, shadow: false}); } if (currentCase & START) { items.ball.x = col * items.cellSize + items.wallSize; items.ball.y = row * items.cellSize + items.wallSize; items.ball.visible = true; } if (currentCase & GOAL) { var goalX = col * items.cellSize + items.wallSize/2; var goalY = row * items.cellSize + items.wallSize/2; if(goal === null) { goal = createObject(goalComponent, { x: goalX, y: goalY, width: items.cellSize - items.wallSize, height: items.cellSize - items.wallSize, imageSource: baseUrl + "/door_closed.svg", categories: items.goalType, sensor: true}); } else { goal.x = goalX; goal.y = goalY; goal.width = items.cellSize - items.wallSize; goal.height = goal.width; goal.imageSource = baseUrl + "/door_closed.svg"; } } if (currentCase & HOLE) { var holeX = col * items.cellSize + items.wallSize; var holeY = row * items.cellSize + items.wallSize; incubateObject(holes, balanceItemComponent, { x: holeX, y: holeY, width: items.ballSize, height: items.ballSize, imageSource: baseUrl + "/hole.svg", density: 0, friction: 0, restitution: 0, categories: items.holeType, sensor: true}); } if (orderNum > 0) { var contactX = col * items.cellSize + items.wallSize/2; var contactY = row * items.cellSize + items.wallSize/2; goalUnlocked = false; incubateObject(contacts, contactComponent, { x: contactX, y: contactY, width: items.cellSize - items.wallSize, height: items.cellSize - items.wallSize, pressed: false, density: 0, friction: 0, restitution: 0, categories: items.buttonType, sensor: true, orderNum: orderNum, text: level.targets[orderNum-1]}); } } } if (goalUnlocked && goal) // if we have no contacts at all goal.imageSource = baseUrl + "/door.svg"; if (pendingObjects === 0) { // don't have any pending objects (e.g. empty map!): stop overlay items.timer.start(); items.loading.stop(); } } function addBallContact(item) { if (ballContacts.indexOf(item) !== -1) return; ballContacts.push(item); } function removeBallContact(item) { var index = ballContacts.indexOf(item); if (index > -1) ballContacts.splice(index, 1); } function tearDown() { items.ball.body.linearVelocity = Qt.point(0, 0); items.ball.scale = 1; items.ball.visible = false; items.timer.stop(); items.keyboardTimer.stop(); if (holes.length > 0) { for (var i = 0; i< holes.length; i++) holes[i].destroy(); holes.length = 0; } if (walls.length > 0) { for (var i = 0; i< walls.length; i++) walls[i].destroy(); walls.length = 0; } if (contacts.length > 0) { for (var i = 0; i< contacts.length; i++) contacts[i].destroy(); contacts.length = 0; } lastContact = 0; items.tilt.xRotation = 0; items.tilt.yRotation = 0; ballContacts = new Array(); } function initLevel(testLevel) { items.loading.start(); items.bar.level = currentLevel + 1; // reset everything tearDown(); level = dataset[currentLevel]; map = level.map initMap(); } // keyboard tilting stuff: function keyboardHandler() { var MAX_TILT = 5 if (keyboardIsTilting) { if (lastKey == Qt.Key_Left && items.tilt.yRotation > -MAX_TILT) items.tilt.yRotation -= keyboardTiltStep; else if (lastKey == Qt.Key_Right && items.tilt.yRotation < MAX_TILT) items.tilt.yRotation += keyboardTiltStep; else if (lastKey == Qt.Key_Up && items.tilt.xRotation > -MAX_TILT) items.tilt.xRotation -= keyboardTiltStep; else if (lastKey == Qt.Key_Down && items.tilt.xRotation < MAX_TILT) items.tilt.xRotation += keyboardTiltStep; items.keyboardTimer.start(); } else {// is resetting // yRotation: if (items.tilt.yRotation < 0) items.tilt.yRotation = Math.min(items.tilt.yRotation + keyboardTiltStep, 0); else if (items.tilt.yRotation > 0) items.tilt.yRotation = Math.max(items.tilt.yRotation - keyboardTiltStep, 0); // xRotation: if (items.tilt.xRotation < 0) items.tilt.xRotation = Math.min(items.tilt.xRotation + keyboardTiltStep, 0); else if (items.tilt.xRotation > 0) items.tilt.xRotation = Math.max(items.tilt.xRotation - keyboardTiltStep, 0); // resetting done? if (items.tilt.yRotation != 0 || items.tilt.xRotation != 0) items.keyboardTimer.start(); } } function processKeyPress(key) { if (key == Qt.Key_Left || key == Qt.Key_Right || key == Qt.Key_Up || key == Qt.Key_Down) { lastKey = key; keyboardIsTilting = true; items.keyboardTimer.stop(); keyboardHandler(); } } function processKeyRelease(key) { if (key == Qt.Key_Left || key == Qt.Key_Right || key == Qt.Key_Up || key == Qt.Key_Down) { lastKey = key; keyboardIsTilting = false; items.keyboardTimer.stop(); keyboardHandler(); } } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } diff --git a/src/activities/ballcatch/Ballcatch.qml b/src/activities/ballcatch/Ballcatch.qml index 247eb88ec..3bb040380 100644 --- a/src/activities/ballcatch/Ballcatch.qml +++ b/src/activities/ballcatch/Ballcatch.qml @@ -1,267 +1,267 @@ /* gcompris - Ballcatch.qml Copyright (C) 2014 Johnny Jazeix Bruno Coudoin: initial Gtk+ version 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 "../ballcatch" import "ballcatch.js" as Activity ActivityBase { id: activity onStart: { focus = true; } onStop: {} Keys.onPressed: { Activity.processKey(event) } pageComponent: Image { id: background signal start signal stop focus: true fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/ballcatch/resource/beach1.svg" sourceSize.width: Math.max(parent.width, parent.height) property bool isVertical: background.width <= background.height // To check if in Vertical mode Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property alias background: background property alias bar: bar property alias ball: ball property alias rightHand: rightHand property alias leftHand: leftHand property alias deltaPressedTimer: deltaPressedTimer /* when the corresponding arrow key is pressed, the following boolean pass to true and is reset at the end of the level */ property bool leftPressed property bool rightPressed } onStart: { Activity.start(items) } onStop: { Activity.stop() } onWidthChanged: { leftHand.reinitPosition(); rightHand.reinitPosition(); ball.reinitBall(); } onHeightChanged: { leftHand.reinitPosition(); rightHand.reinitPosition(); ball.reinitBall(); } DialogHelp { id: dialogHelpLeftRight onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() } Bonus { id: bonus winSound: "qrc:/gcompris/src/activities/ballcatch/resource/tuxok.wav" looseSound: "qrc:/gcompris/src/activities/ballcatch/resource/youcannot.wav" Component.onCompleted: { win.connect(Activity.nextLevel) loose.connect(Activity.restartLevel) } onStart: tux.opacity = 0 onStop: tux.opacity = 1 } Image { id: tux x: background.width / 2 - width / 2 y: leftHand.y - height / 3 - height / 2 sourceSize.height: 200 * ApplicationInfo.ratio source: "qrc:/gcompris/src/activities/ballcatch/resource/tux.svg" Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad; duration: 250 } } } Image { id: leftHand y: background.height - 1.5 * height z: 5 sourceSize.height: 150 * ApplicationInfo.ratio source: "qrc:/gcompris/src/activities/ballcatch/resource/hand.svg" NumberAnimation { id: leftHandAnimation target: leftHand; property: "x"; to: background.width/2 - leftHand.width - 5; duration: 1000 easing.type: Easing.InOutQuad } function animate(newTime) { leftHandAnimation.duration = newTime leftHandAnimation.start(); } function reinitPosition() { leftHand.x = background.width / 2 - width * 2 leftHand.y = background.height - 1.5 * height } MultiPointTouchArea { id: mouseAreaLeftShift anchors.fill: parent onTouchUpdated: { // left if(!items.leftPressed && !Activity.gameFinished) { Activity.leftShiftPressed(); items.leftPressed = true } } } } Image { id: rightHand mirror: true y: background.height - 1.5 * height z: 5 sourceSize.height: 150 * ApplicationInfo.ratio source: "qrc:/gcompris/src/activities/ballcatch/resource/hand.svg" function animate(newTime) { rightHandAnimation.duration = newTime rightHandAnimation.start(); } function reinitPosition() { rightHand.x = background.width / 2 + width rightHand.y = background.height - 1.5 * height } NumberAnimation { id: rightHandAnimation target: rightHand; property: "x"; to: background.width / 2 + 5; duration: 1000; easing.type: Easing.InOutQuad } MultiPointTouchArea { id: mouseAreaRightShift anchors.fill: parent onTouchUpdated: { // right if(!items.rightPressed && !Activity.gameFinished) { Activity.rightShiftPressed(); items.rightPressed = true } } } } Image { id: leftShift x: background.width / 4 - width y: background.isVertical ? rightHand.y - height : rightHand.y - height / 2 source: "qrc:/gcompris/src/activities/ballcatch/resource/arrow_key.svg" scale: background.isVertical ? 0.75 : 1.0 smooth: true opacity: items.leftPressed ? 1 : 0.5 visible: !ApplicationInfo.isMobile } Image { id: rightShift mirror: true x: background.width - background.width / 4 y: background.isVertical ? rightHand.y - height : rightHand.y - height / 2 source: "qrc:/gcompris/src/activities/ballcatch/resource/arrow_key.svg" scale: background.isVertical ? 0.75 : 1.0 smooth: true opacity: items.rightPressed ? 1 : 0.5 visible: !ApplicationInfo.isMobile } // Instructions IntroMessage { id: instructions intro: ApplicationInfo.isMobile ? [qsTr("Tap both hands at the same time, " + "to make the ball go in a straight line.")] : [qsTr("Press left and right arrow keys at the same time, " + "to make the ball go in a straight line.")] anchors { top: parent.top topMargin: 10 } - z : 10 + z: 10 index: bar.level === 1 && !(items.leftPressed && items.rightPressed) ? 0 : -1 opacity: items.leftPressed ^ items.rightPressed ? 0 : 1 Behavior on opacity { NumberAnimation { duration: 120 } } } function playSound(identifier) { activity.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/"+ identifier + ".wav") } /* Timer starting when user first presses a first key. If still running when the user presses the other key, he wins ! */ Timer { id: deltaPressedTimer running: false; repeat: false onTriggered: { Activity.endTimer() ball.startAnimation() } } Ball { id: ball } } } diff --git a/src/activities/ballcatch/ballcatch.js b/src/activities/ballcatch/ballcatch.js index ec6d7f19a..55ad5e30e 100644 --- a/src/activities/ballcatch/ballcatch.js +++ b/src/activities/ballcatch/ballcatch.js @@ -1,176 +1,176 @@ /* gcompris - ballcatch.js Copyright (C) 2003, 2014: Bruno Coudoin: initial version 2014: Johnny Jazeix: Qt 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 GCompris 1.0 as GCompris //for ApplicationInfo var levelProperties = [ { "timerInc": 900, "backgroundImage": 1 }, { "timerInc": 350, "backgroundImage": 1 }, { "timerInc": 300, "backgroundImage": 2 }, { "timerInc": 200, "backgroundImage": 2 }, { "timerInc": 150, "backgroundImage": 3 }, { "timerInc": 100, "backgroundImage": 3 }, { "timerInc": 60, "backgroundImage": 4 }, { "timerInc": 30, "backgroundImage": 4 }, { "timerInc": 15, "backgroundImage": 4 }, ] var gameWon = false // When the timer is finished we set this variable to true to disabled key press var gameFinished = false // Store the time diff between left and right key var timerDiff = 0 // The child has to press both key between this laps of time var timerinc = 900 var currentLevel = 0 var items function start(items_) { items = items_ currentLevel = 0 initLevel() } function stop() { // Nothing to do } function leftShiftPressed() { if(!items.deltaPressedTimer.running && !items.rightPressed) { items.leftHand.animate(timerinc) items.deltaPressedTimer.start() } if(items.rightPressed) { items.leftHand.animate(timerinc) items.background.playSound("smudge") } } function rightShiftPressed() { if(!items.deltaPressedTimer.running && !items.leftPressed) { items.deltaPressedTimer.start() items.rightHand.animate(timerinc) } if(items.leftPressed) { items.rightHand.animate(timerinc) items.background.playSound("smudge") } } function endTimer() { gameFinished = true gameWon = items.rightPressed && items.leftPressed } function initLevel() { items.bar.level = currentLevel + 1 timerinc = levelProperties[currentLevel].timerInc timerDiff = 0 items.deltaPressedTimer.interval = timerinc items.background.source = "qrc:/gcompris/src/activities/ballcatch/resource/beach" + levelProperties[currentLevel].backgroundImage + ".svg" items.ball.reinitBall(); items.leftPressed = false items.rightPressed = false gameWon = false gameFinished = false items.leftHand.reinitPosition() items.rightHand.reinitPosition() } function nextLevel() { if(levelProperties.length <= ++ currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = levelProperties.length - 1 } initLevel(); } function restartLevel() { initLevel(); } function processKey(event) { if(!gameFinished) { - if(event.key == Qt.Key_Left) { + if(event.key === Qt.Key_Left) { // left if(!items.leftPressed) { leftShiftPressed(); items.leftPressed = true } } - else if(event.key == Qt.Key_Right) { + else if(event.key === Qt.Key_Right) { // right if(!items.rightPressed) { rightShiftPressed(); items.rightPressed = true } } } event.accepted = true; } diff --git a/src/activities/bargame/bargame.js b/src/activities/bargame/bargame.js index 1e62017bc..7ed957b0e 100644 --- a/src/activities/bargame/bargame.js +++ b/src/activities/bargame/bargame.js @@ -1,243 +1,243 @@ /* GCompris - bargame.js * * Copyright (C) 2016 UTKARSH TIWARI * * Authors: * Yves Combe (GTK+ version) * UTKARSH TIWARI (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 GCompris 1.0 as GCompris .import QtQuick 2.6 as Quick var levelsProperties = [ { "minNumberOfBalls": 1, "maxNumberOfBalls": 4, "elementSizeFactor": 0, "boardSize": 15 }, { "minNumberOfBalls": 2, "maxNumberOfBalls": 6, "elementSizeFactor": 4, "boardSize": 19 }, { "minNumberOfBalls": 3, "maxNumberOfBalls": 6, "elementSizeFactor": 7, "boardSize": 29 } ]; var moveCount = -1 var currentLevel = 1 var maxLevel = 4 var listWin = [] var items var gameMode var url= "qrc:/gcompris/src/activities/bargame/resource/"; function start(items_, gameMode_) { items = items_; gameMode = gameMode_; currentLevel = 1; initLevel(); } function stop() { } function initLevel() { if (items.isPlayer1Beginning === true) { initiatePlayer1(); items.isPlayer1Turn = true; } else { initiatePlayer2(); items.isPlayer1Turn = false; } items.okArea.enabled = true; calculateWinPlaces(); moveCount = -1; items.numberOfBalls = levelsProperties[items.mode - 1].minNumberOfBalls items.bar.level = currentLevel; // Hiding all visible balls for (var x = 0; x < items.answerBallsPlacement.columns; x++) { items.answerBallsPlacement.children[x].visible = false; } } function nextLevel() { currentLevel ++; if (currentLevel > maxLevel) { currentLevel = 1; } initLevel(); } function previousLevel() { currentLevel--; if (currentLevel < 1) { currentLevel = maxLevel; } initLevel(); } function restartLevel() { items.trigTuxMove.stop(); initLevel(); } function calculateWinPlaces() { /* Calculates all the possible winning moves in the available board size. It generates a list all winning moves for the computer */ var winners = []; var winnersList = []; var min = levelsProperties[items.mode - 1].minNumberOfBalls; var max = levelsProperties[items.mode - 1].maxNumberOfBalls; var boardSize = levelsProperties[items.mode - 1].boardSize; var period = (min + max); for (var x = 0; x < min; x++) { winnersList.push((boardSize - 1 - x) % period); } for (var x = period + 1; x < boardSize; x++) { if (winnersList.indexOf((x + 1) % period) >= 0) { winners.push(x); } } var levelWin = (currentLevel - 1) * min; if (levelWin == 0) { winners = []; } else { winners = winners.slice(-levelWin); if (currentLevel == maxLevel - 1) { winners = winners.slice(1); } } listWin = winners; } function machinePlay() { function accessible(x) { if (listWin.indexOf(x + moveCount) >= 0) { return true; } else { return false; } } function randomNumber(minimum, maximum) { return Math.round(Math.random() * (maximum - minimum) + minimum); } var playable = []; var min = levelsProperties[items.mode - 1].minNumberOfBalls; var max = levelsProperties[items.mode - 1].maxNumberOfBalls; for (var x = min; x <= max; x++) { if (accessible(x)) { playable.push(x); } } var value; if (playable.length != 0) { value = playable[Math.floor(Math.random()*playable.length)]; } else { value = randomNumber(min, max); } play(2, value); } function play(player, value) { for (var x = 0; x < value ; x++) { moveCount++; var boardSize = levelsProperties[items.mode - 1].boardSize; if (moveCount <= (boardSize - 1)) { items.answerBallsPlacement.children[moveCount].visible = true; - if (player == 1) { + if (player === 1) { items.answerBallsPlacement.children[moveCount].source = url + "ball_1.svg"; } else { items.answerBallsPlacement.children[moveCount].source = url + "ball_2.svg"; } } // one of the players has won if (moveCount == (boardSize - 1)) { items.okArea.enabled = false; - if (gameMode == 2) { + if (gameMode === 2) { items.isPlayer1Beginning = !items.isPlayer1Beginning; } - if (player == 2) { + if (player === 2) { items.player1score.win(); items.player2score.endTurn(); items.bonus.good("flower"); } else { items.player1score.endTurn(); items.player2score.win(); - if (gameMode == 1) { + if (gameMode === 1) { items.bonus.bad("flower"); } else { items.bonus.good("flower"); } } return; } } items.isPlayer1Turn = !items.isPlayer1Turn; - if (player == 1 && gameMode == 1) { + if (player === 1 && gameMode === 1) { items.player1score.endTurn(); items.player2score.beginTurn(); items.okArea.enabled = false; items.trigTuxMove.start(); - } else if (player == 2 && gameMode == 1) { + } else if (player === 2 && gameMode === 1) { items.player2score.endTurn(); items.player1score.beginTurn(); items.okArea.enabled = true; - } else if (gameMode == 2) { - if (player == 1) { + } else if (gameMode === 2) { + if (player === 1) { items.player1score.endTurn(); items.player2score.beginTurn(); } else { items.player2score.endTurn(); items.player1score.beginTurn(); } } } //Initial values at the start of game when its player 1 turn function initiatePlayer1() { items.player2score.endTurn(); items.player1score.beginTurn(); } //Initial values at the start of game when its player 2 turn function initiatePlayer2() { items.player1score.endTurn(); items.player2score.beginTurn(); } diff --git a/src/activities/binary_bulb/BinaryBulb.qml b/src/activities/binary_bulb/BinaryBulb.qml index 7fa1325e9..7000e547e 100644 --- a/src/activities/binary_bulb/BinaryBulb.qml +++ b/src/activities/binary_bulb/BinaryBulb.qml @@ -1,209 +1,209 @@ /* GCompris - BinaryBulb.qml * * Copyright (C) 2018 Rajat Asthana * * Authors: * RAJAT ASTHANA * * 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 "binary_bulb.js" as Activity import "numbers.js" as Dataset ActivityBase { id: activity onStart: focus = true onStop: {} property var dataset: Dataset pageComponent: Image { id: background anchors.fill: parent source: "../digital_electricity/resource/texture01.png" fillMode: Image.Tile signal start signal stop 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 alias bulbs: bulbs property int numberSoFar: 0 property int numberToConvert: 0 property int numberOfBulbs: 0 property int currentSelectedBulb: -1 property int currentLevel: 0 property alias score: score } onStart: { Activity.start(items, dataset) } onStop: { Activity.stop() } // Tutorial section starts Image { id: tutorialImage source: "../digital_electricity/resource/texture01.png" anchors.fill: parent fillMode: Image.Tile z: 5 visible: true Tutorial { id: tutorialSection tutorialDetails: Activity.tutorialInstructions useImage: false onSkipPressed: { Activity.initLevel() tutorialImage.visible = false } } } // Tutorial section ends Keys.onPressed: { if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { Activity.equalityCheck() } - else if(event.key == Qt.Key_Space) { + else if(event.key === Qt.Key_Space) { if(items.currentSelectedBulb != -1) { Activity.changeState(items.currentSelectedBulb) } } - else if(event.key == Qt.Key_Left) { + else if(event.key === Qt.Key_Left) { if(--items.currentSelectedBulb < 0) { items.currentSelectedBulb = items.numberOfBulbs-1 } } - else if(event.key == Qt.Key_Right) { + else if(event.key === Qt.Key_Right) { if(++items.currentSelectedBulb >= items.numberOfBulbs) { items.currentSelectedBulb = 0 } } } Rectangle { id: questionItemBackground opacity: 0 z: 10 anchors { horizontalCenter: parent.horizontalCenter bottomMargin: 10 } height: background.height / 6 width: parent.width - 20 * ApplicationInfo.ratio } GCText { id: questionItem anchors.fill: questionItemBackground anchors.bottom: questionItemBackground.bottom fontSizeMode: Text.Fit wrapMode: Text.Wrap z: 4 color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: qsTr("What is the binary representation of %1?").arg(items.numberToConvert) } Row { id: row anchors.top: questionItem.bottom anchors.topMargin: 30 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter spacing: 10 * ApplicationInfo.ratio Repeater { id: bulbs model: items.numberOfBulbs LightBulb { height: background.height / 5 width: (background.width >= background.height) ? (background.width / 20) : ((background.width - (16 * row.spacing)) / 8) valueVisible: Dataset.get()[items.currentLevel].bulbValueVisible } } } GCText { id: reachedSoFar anchors.horizontalCenter: row.horizontalCenter anchors.top: row.bottom anchors.topMargin: 30 * ApplicationInfo.ratio color: "white" fontSize: largeSize text: items.numberSoFar visible: Dataset.get()[items.currentLevel].enableHelp } BarButton { id: okButton anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: 10 * ApplicationInfo.ratio } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio onClicked: Activity.equalityCheck() enabled: !bonus.isPlaying && !score.isWinAnimationPlaying } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level} onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Score { id: score visible: !tutorialImage.visible anchors.bottom: bar.top anchors.right: bar.right anchors.left: parent.left anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.rightMargin: 0 } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/braille_alphabets/BrailleMap.qml b/src/activities/braille_alphabets/BrailleMap.qml index ee8ddddf4..895f32e63 100644 --- a/src/activities/braille_alphabets/BrailleMap.qml +++ b/src/activities/braille_alphabets/BrailleMap.qml @@ -1,186 +1,186 @@ /* GCompris - BrailleMap.qml * * Copyright (C) 2014 Arkit Vora * * Authors: * Srishti Sethi (GTK+ version) * Arkit Vora (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.6 import GCompris 1.0 import "../../core" import "braille_alphabets.js" as Activity Rectangle { id: brailleMap color: "#808080" border.color: "black" border.width: 1 z: 1000 property bool isDialog: true signal close signal start signal stop Flickable { id: flick anchors.fill: parent contentWidth: parent.width contentHeight: (grid1.height + grid2.height) * 1.1 flickableDirection: Flickable.VerticalFlick clip: true Flow { id: grid1 width: parent.width * 0.9 anchors { top: parent.top topMargin: 10 * ApplicationInfo.ratio horizontalCenter: parent.horizontalCenter } spacing: 5 * ApplicationInfo.ratio Repeater { id: cardRepeater model: [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ] Column { - width: (grid1.width - grid1.spacing * 10) / 10 + width: (grid1.width - grid1.spacing * 10) / 10 Rectangle { id: rect1 width: parent.width height: ins.height border.width: 3 border.color: "black" color: "white" BrailleChar { id: ins width: parent.width * 0.9 anchors.centerIn: parent clickable: false brailleChar: modelData } } GCText { id: text1 text: modelData font.weight: Font.DemiBold style: Text.Outline styleColor: "black" color: "white" fontSize: Math.max(Math.min(parent.width * 0.2, 24), 12) anchors { horizontalCenter: parent.horizontalCenter } } } } } Flow { id: grid2 width : parent.width * 0.9 anchors { horizontalCenter: parent.horizontalCenter top: grid1.bottom topMargin: 10 * ApplicationInfo.ratio } spacing: 6 Repeater { id: cardRepeater2 model: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "-", "*", "/", "#" ] Column { width: (grid1.width - grid1.spacing * 10) / 10 Rectangle { id: rect2 width: parent.width height: ins2.height border.width: 3 border.color: "black" color: "white" BrailleChar { id: ins2 width: parent.width * 0.9 anchors.centerIn: parent clickable: false brailleChar: modelData } } GCText { id: text2 text: modelData font.weight: Font.DemiBold style: Text.Outline styleColor: "black" color: "white" fontSize: Math.max(Math.min(parent.width * 0.2, 24), 12) anchors { horizontalCenter: parent.horizontalCenter } } } } } // The back button Image { id: cancel source: Activity.url + "back.svg" fillMode: Image.PreserveAspectFit anchors.right: parent.right anchors.bottom: parent.bottom smooth: true sourceSize.width: 60 * ApplicationInfo.ratio anchors.margins: 10 SequentialAnimation { id: anim running: true loops: Animation.Infinite NumberAnimation { target: cancel property: "rotation" from: -10; to: 10 duration: 500 easing.type: Easing.InOutQuad } NumberAnimation { target: cancel property: "rotation" from: 10; to: -10 duration: 500 easing.type: Easing.InOutQuad } } MouseArea { anchors.fill: parent onClicked: close() } } } } diff --git a/src/activities/braille_alphabets/FirstScreen.qml b/src/activities/braille_alphabets/FirstScreen.qml index 3094f4679..b0caf5875 100644 --- a/src/activities/braille_alphabets/FirstScreen.qml +++ b/src/activities/braille_alphabets/FirstScreen.qml @@ -1,142 +1,142 @@ /* GCompris - FirstScreen.qml * * Copyright (C) 2014 Arkit Vora * * Authors: * Srishti Sethi (GTK+ version) * Arkit Vora (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.6 import GCompris 1.0 import "../../core" import "braille_alphabets.js" as Activity Image { id: first_screen anchors.fill: parent fillMode: Image.PreserveAspectCrop source: Activity.url + "intro_bg.svg" sourceSize.width: Math.max(parent.width, parent.height) GCText { id: heading text: qsTr("Braille: Unlocking the Code") fontSize: largeSize fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter font.weight: Font.DemiBold anchors.centerIn: parent.Center color: "#2a2a2a" width: parent.width height: parent.height * 0.16 wrapMode: Text.WordWrap } Image { id: introChar source: Activity.url + "intro_braille_char.svg" sourceSize.height: parent.height * 0.25 fillMode: Image.PreserveAspectFit anchors { top: heading.bottom topMargin: 30 * ApplicationInfo.ratio left: parent.left leftMargin: 30 * ApplicationInfo.ratio } } GCText { id: body_text1 z: 1 text: qsTr('The Braille system is a method that is used by blind people to read and write.') + '\n \n ' + qsTr('Each Braille character, or cell, is made up of six dot positions, arranged in ' + 'a rectangle containing two columns of three dots each. As seen on the left, each ' + 'dot is referenced by a number from 1 to 6.') fontSize: regularSize fontSizeMode: Text.Fit font.weight: Font.DemiBold horizontalAlignment: Text.AlignJustify anchors { top: heading.bottom topMargin: 15 * ApplicationInfo.ratio right: parent.right rightMargin: 15 * ApplicationInfo.ratio left: introChar.right leftMargin: 15 * ApplicationInfo.ratio } color: "#2a2a2a" width: parent.width * 0.5 height: parent.height * 0.33 wrapMode: Text.WordWrap } GCText { id: bottom_text z: 2 text: qsTr("When you are ready, click on me and try reproducing Braille characters.") fontSize: regularSize fontSizeMode: Text.Fit font.weight: Font.Bold horizontalAlignment: Text.AlignRight color: "#2a2a2a" wrapMode: Text.WordWrap anchors { top: body_text1.bottom topMargin: 15 * ApplicationInfo.ratio left: parent.left leftMargin: 30 * ApplicationInfo.ratio } height: parent.height * 0.25 width: parent.width * 0.5 } Image { id: introTux - z:3 + z: 3 source: Activity.url + "tux_braille.svg" fillMode: Image.PreserveAspectFit sourceSize.width: parent.width * 0.2 anchors { top: body_text1.bottom topMargin: 5 * ApplicationInfo.ratio left: bottom_text.right leftMargin: 30 * ApplicationInfo.ratio } Behavior on scale { PropertyAnimation { duration: 100 } } MouseArea { id: tux_click anchors.fill: parent hoverEnabled: true onClicked: first_screen.visible = false onEntered: introTux.scale = 1.1 onExited: introTux.scale = 1 } } Rectangle { id: bgTux - z:0 + z: 0 color: "#94c1d2" width: introTux.width * 1.5 height: introTux.height * 1.1 radius: bgTux.width * 0.5 anchors { horizontalCenter: introTux.horizontalCenter verticalCenter: introTux.verticalCenter } } } diff --git a/src/activities/categorization/CategoryReview.qml b/src/activities/categorization/CategoryReview.qml index 96b256ecd..474433269 100644 --- a/src/activities/categorization/CategoryReview.qml +++ b/src/activities/categorization/CategoryReview.qml @@ -1,238 +1,238 @@ /* GCompris - CategoryReview.qml * * Copyright (C) 2016 Divyam Madaan * * Authors: * Divyam Madaan * * 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 "categorization.js" as Activity Item { id: rootItem property alias score: score property alias categoryDataset: categoryDataset property alias instructionBox: instructionBox property alias categoryImage: categoryImage property bool isDropped: true property bool leftAreaContainsDrag: false property bool rightAreaContainsDrag: false property bool started: rootItem.opacity == 1 property bool horizontalLayout: categoryBackground.width >= categoryBackground.height property alias leftZone: leftZone.model property alias rightZone: rightZone.model property alias middleZone: middleZone.model property alias leftScreen: leftScreen property alias middleScreen: middleScreen property alias rightScreen: rightScreen anchors.fill: parent Loader { id: categoryDataset asynchronous: false } Image { id: categoryBackground source: "qrc:/gcompris/src/activities/categorization/resource/background.svg" anchors.fill: parent sourceSize.width:parent.width Zone { - id:leftZone + id: leftZone x: 0.012 * middleScreen.width z: 2 y: 0.05 * parent.height spacing: x } Rectangle { id: leftScreen width: parent.width/3 height: parent.height x: 0 color: leftAreaContainsDrag ? "#F9F8B4" : "#F9B4B4" border.width: 5 border.color: "#EC1313" opacity: 0.5 } Zone { id: rightZone spacing: leftZone.x x: leftScreen.width + middleScreen.width + spacing z: 2 anchors.top: categoryBackground.top anchors.topMargin: items.mode != "expert" ? rootItem.categoryImage.height + 0.027 * rightScreen.height : 0.05 * categoryBackground.height } Rectangle { id: rightScreen width: parent.width/3 height: parent.height x: leftScreen.width + middleScreen.width color: rightAreaContainsDrag ? "#F9F8B4" : "#B4F9C5" border.width: 5 border.color: "#13EC52" opacity: 0.5 } Rectangle { id: middleScreen width: parent.width/3 height: parent.height x: leftScreen.width color: "#00FFFFFF" } Rectangle { id: instructionBox anchors.left: categoryBackground.left anchors.right: categoryImage.left anchors.leftMargin: 0.32 * parent.width anchors.rightMargin: 0.03 * parent.width color: "black" opacity: items.instructionsVisible ? 0.85 : 0 z: 3 radius: 10 border.width: 2 width: horizontalLayout ? parent.width/5 : parent.width/3 height: horizontalLayout ? parent.height/6 : parent.height * 0.09 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } } Zone { id: middleZone spacing: 0.012 * middleScreen.width anchors { left: leftScreen.right right: rightScreen.left top: parent.top topMargin: 0.05 * parent.height bottom: categoryBackground.bottom leftMargin: 0.015 * middleScreen.width } } GCText { id: instructions text: items.mode !== "expert" && items.details && items.details[bar.level-1] && items.details[bar.level - 1].instructions ? items.details[bar.level - 1].instructions : qsTr("Place the majority category images to the right and other images to the left") visible: items.instructionsVisible anchors.fill: instructionBox anchors.bottom: instructionBox.bottom fontSizeMode: Text.Fit wrapMode: Text.Wrap z: 3 color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } Image { id: categoryImage fillMode: Image.PreserveAspectFit source: items.details && items.details[bar.level-1] && items.details[bar.level-1].image ? items.details[bar.level-1].image : "" sourceSize.width: horizontalLayout ? rightZone.width * 0.35 : rightZone.width * 0.35 width: sourceSize.width height: sourceSize.width y: 0.015*parent.height visible: items.categoryImageChecked anchors { left: middleScreen.right leftMargin: 0.15 * rightZone.width } } BarButton { id: validate source: "qrc:/gcompris/src/core/resource/bar_ok.svg" width: horizontalLayout ? rightZone.width * 0.20 : rightZone.width * 0.35 height: width sourceSize.width: width sourceSize.height: height y: parent.height*0.8 z: 2 anchors { rightMargin: 14 * ApplicationInfo.ratio right: parent.right } MouseArea { anchors.fill: parent onClicked: { Activity.allPlaced(); } } } DropArea { id: rightArea anchors.fill: rightZone } DropArea { id: leftArea anchors.fill: leftZone } DialogHelp { id: dialogHelp onClose: home() } Score { id: score visible: items.scoreChecked width: rightZone.width * 0.4 height: width * 0.6 margins: 10 * ApplicationInfo.ratio anchors { top: parent.top right: parent.right left: undefined bottom: undefined } } } Keys.onEscapePressed: { Activity.launchMenuScreen(); } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true Activity.launchMenuScreen() } } function stop() { if(items.mode == "expert") items.menuScreen.iAmReady.visible = true focus = false rootItem.visible = false } function start() { focus = true rootItem.visible = true } } diff --git a/src/activities/categorization/MenuScreen.qml b/src/activities/categorization/MenuScreen.qml index 638211a0c..c5bc004bd 100644 --- a/src/activities/categorization/MenuScreen.qml +++ b/src/activities/categorization/MenuScreen.qml @@ -1,248 +1,248 @@ /* GCompris - MenuScreen.qml * * Copyright (C) Divyam Madaan (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * 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 QtGraphicalEffects 1.0 import QtQuick.Controls 1.5 import "../../core" import "categorization.js" as Activity Image { id: menuScreen anchors.fill: parent fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" sourceSize.width: Math.max(parent.width, parent.height) opacity: 0 property alias menuModel: menuModel property alias iAmReady: iAmReady property bool keyboardMode: false property bool started: opacity == 1 Behavior on opacity { PropertyAnimation { duration: 200 } } visible: opacity != 0 function start() { focus = true forceActiveFocus() menuGrid.currentIndex = 0 opacity = 1 } function stop() { focus = false opacity = 0 } Keys.onEscapePressed: { home() } - Keys.enabled : (items.mode == "expert") ? false : true + Keys.enabled: (items.mode == "expert") ? false : true Keys.onPressed: { if(event.key === Qt.Key_Space) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Enter) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Return) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Left) { menuGrid.moveCurrentIndexLeft() event.accepted = true } if(event.key === Qt.Key_Right) { menuGrid.moveCurrentIndexRight() event.accepted = true } if(event.key === Qt.Key_Up) { menuGrid.moveCurrentIndexUp() event.accepted = true } if(event.key === Qt.Key_Down) { menuGrid.moveCurrentIndexDown() event.accepted = true } } Keys.onReleased: { keyboardMode = true event.accepted = false } // sections property int iconWidth: 180 * ApplicationInfo.ratio property int iconHeight: 180 * ApplicationInfo.ratio property int levelCellWidth: background.width / Math.floor(background.width / iconWidth ) property int levelCellHeight: iconHeight * 1.2 ListModel { id: menuModel } GridView { id: menuGrid anchors { fill: parent bottomMargin: bar.height } cellWidth: levelCellWidth cellHeight: levelCellHeight clip: true model: menuModel keyNavigationWraps: true property int spacing: 10 ReadyButton { id: iAmReady focus: true visible: items.iAmReadyChecked onClicked: { Activity.startCategory() } } delegate: Item { id: delegateItem width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing property string sectionName: name opacity: (items.mode == "expert") ? 0.25 : 1 Rectangle { id: activityBackground width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { id: containerImage source: image anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter height: activityBackground.height*0.8 - 6 width: height anchors.margins: 5 sourceSize.height: height fillMode: Image.PreserveAspectCrop clip: true } GCText { id: categoryName anchors.top: containerImage.bottom horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width: activityBackground.width height: activityBackground.height*0.2 - 6 fontSizeMode: Text.Fit elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: name opacity: (items.mode == "expert") ? 0 : 1 } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground enabled: menuScreen.started && items.mode !== "expert" onClicked: selectCurrentItem() } function selectCurrentItem() { particles.burst(50) Activity.storeCategoriesLevels(index) } Image { source: "qrc:/gcompris/src/activities/menu/resource/" + ( favorite ? "all.svg" : "all_disabled.svg" ); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: { for(var i = 0; i < items.menuModel.count; i++) { var category = items.menuModel.get(i) var categoryIndex = category.index if(index == categoryIndex) menuModel.get(i)['favorite'] = !menuModel.get(i)['favorite'] } } } } } //delegate close highlight: Rectangle { width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing color: "#AA41AAC4" border.width: 3 border.color: "black" visible: (items.mode == "expert") ? false : true Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle { id: menusMask visible: false anchors.fill: menuGrid gradient: Gradient { GradientStop { position: 0.0; color: "#FFFFFFFF" } GradientStop { position: 0.92; color: "#FFFFFFFF" } GradientStop { position: 0.96; color: "#00FFFFFF" } } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: menuGrid maskSource: menusMask anchors.fill: menuGrid } } // grid view close } diff --git a/src/activities/categorization/categorization.js b/src/activities/categorization/categorization.js index 0876601c2..1d733e538 100644 --- a/src/activities/categorization/categorization.js +++ b/src/activities/categorization/categorization.js @@ -1,305 +1,305 @@ /* GCompris - categorization.js * * Copyright (C) 2016 Divyam Madaan * * Authors: * Divyam Madaan * * 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.6 as Quick .import GCompris 1.0 as GCompris .import "qrc:/gcompris/src/core/core.js" as Core var categoryModelIndex = 0; var currentSubLevel = 0; var items var lessons var categories var images var items var currentLevel = 1 var numberOfLevel var index var imagesData = [] var categoriesData = [] var expertCategories = [] var boardsUrl var answerTable = {} var totalImages var fileName = ":/gcompris/data/words/animals/camel.jpg" function init(items_,boardsUrl_) { boardsUrl = boardsUrl_ items = items_ items.menuModel.clear() currentSubLevel = 0 } function start() { categoriesData = [] items.categoryReview.stop() var isEmbeddedMode = items.file.exists(fileName) ? true : false items.categoriesFallback = !isEmbeddedMode var categoriesFilename; var categoryDataset = items.categoryReview.categoryDataset var categoryLists = items.categories for(var i = 0; i < categoryLists.length; i++) { categoriesFilename = "qrc" + boardsUrl + categoryLists[i] categoryDataset.source = categoriesFilename if(isEmbeddedMode || categoryDataset.item.isEmbedded) { categoriesData.push(categoryDataset.item) } } lessons = getAllLessons(categoriesData) categories = getCategoryModel(categoriesData) addPropertiesToCategories(categories) items.menuModel.append(categories) savedPropertiesToCategories(items.dialogActivityConfig.dataToSave) sortByFavorites() items.menuScreen.start() } // Inserts specific properties to the categories function addPropertiesToCategories(categories) { for (var i = 0; i < categories.length; i++) { categories[i]['name'] = categories[i].name categories[i]['image'] = categories[i].image categories[i]['favorite'] = false categories[i]['categoryIndex'] = i } } // Return all the properties we have to save function categoriesToSavedProperties() { var props = {} for(var i = 0; i < items.menuModel.count; i++) { var category = items.menuModel.get(i) props[category.name] = { 'favorite': category['favorite'] } } return props } // Update the categories based on a previous saving function savedPropertiesToCategories(dataToSave) { var props = dataToSave["data"] for(var i = 0; i < items.menuModel.count; i++) { var category = items.menuModel.get(i) var categoryname = category.name if(props && props[category.name]) { category['favorite'] = props[category.name].favorite } else { category['favorite'] = false } } } function sortByFavorites() { for(var i = 0; i < items.menuModel.count; i++) { if(items.menuModel.get(i)['favorite']) { items.menuModel.move(i, 0, 1); } } } function launchMenuScreen() { items.categoryReview.stop() items.menuScreen.start() } function startCategory() { items.categoryReview.start() items.menuScreen.stop() currentLevel = 0 items.bar.level = 0 initLevel() } function storeCategoriesLevels(index_) { index = index_ currentLevel = 0 numberOfLevel = 0 initLevel() } function initLevel() { items.bar.level = currentLevel + 1 items.categoryReview.score.currentSubLevel = 0 items.instructionsVisible = true getCategoryLevels(index); numberOfLevel = items.details.length; items.categoryReview.leftZone.clear(); items.categoryReview.rightZone.clear(); items.categoryReview.start(); items.menuScreen.stop() } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } items.categoryReview.score.currentSubLevel = 0 initLevel(index); getCategoryLevels(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(index); getCategoryLevels(); } // Checks if all the items are dragged and dropped in the correct or incorrect area. function allPlaced() { items.categoryReview.score.currentSubLevel = 0; for(var i = 0 ; i < items.categoryReview.leftZone.count; ++i) { for(var answer in answerTable) { var droppedZoneItem = items.categoryReview.leftZone.get(i) if(answer === droppedZoneItem.name && ((droppedZoneItem.droppedZone === "right" && answerTable[answer]) || (droppedZoneItem.droppedZone === "left" && !answerTable[answer]))) items.categoryReview.score.currentSubLevel ++ } } for(var i = 0 ; i < items.categoryReview.rightZone.count; ++i) { for(var answer in answerTable) { var droppedZoneItem = items.categoryReview.rightZone.get(i) if(answer === droppedZoneItem.name && ((droppedZoneItem.droppedZone === "right" && answerTable[answer]) || (droppedZoneItem.droppedZone === "left" && !answerTable[answer]))) items.categoryReview.score.currentSubLevel ++ } } - if(items.categoryReview.score.currentSubLevel == totalImages) + if(items.categoryReview.score.currentSubLevel === totalImages) items.bonus.good("flower") else items.bonus.bad("flower") } // Save properties to lessons function getCategoryLevels() { var randomGood = 0; var randomBad = 0; items.categoryReview.middleZone.clear() /* If easy or medium mode is selected, store the details of levels of category of that respective index in items.details. */ if(items.mode !== "expert") { items.details = lessons[index].map(function(ele) { return { "instructions": ele.instructions, "image": ele.image, "numberOfGood": ele.maxNumberOfGood, "numberofBad": ele.maxNumberOfBad, "categoryImages": ele.levelImages ,"good": ele.good, "bad": ele.bad ,"prefix": ele.prefix } }); } // If expert mode is selected, select a random level (selectedLevel) from a random category (selectedCategory) else if(items.mode === "expert") { var selectedCategory = Math.floor(Math.random() * expertCategories.length) var selectedLevel = [] selectedLevel[0] = expertCategories[selectedCategory][Math.floor(Math.random() * expertCategories[selectedCategory].length)] items.details = selectedLevel.map(function(ele) { return { "instructions": ele.instructions, "image": ele.image, "numberOfGood": ele.maxNumberOfGood, "numberofBad": ele.maxNumberOfBad, "categoryImages": ele.levelImages ,"good": ele.good, "bad": ele.bad, "prefix": ele.prefix } }); } var imagesPrefix = items.details[items.bar.level - 1].prefix // Good set of images var goodImages = items.details[items.bar.level - 1].good var numberOfGood = Math.min(goodImages.length,items.details[items.bar.level-1].numberOfGood); var goodZoneImages = goodImages.map(function(obj) { obj = imagesPrefix + obj return { "name": obj, "isRight": true } }); goodZoneImages = goodZoneImages.splice(0, numberOfGood); // Bad set of images var badImages = items.details[items.bar.level - 1].bad var badZoneImages = badImages.map(function(obj) { obj = imagesPrefix + obj return { "name": obj, "isRight": false } }); var numberOfBad = Math.min(badImages.length,items.details[items.bar.level-1].numberofBad); badZoneImages = badZoneImages.splice(0, numberOfBad); // Concat both set of images(good and bad) in allImages and store in middleZone model var allImages = goodZoneImages.concat(badZoneImages); Core.shuffle(allImages); for(var i = 0; i < allImages.length; i++) { answerTable[allImages[i].name] = allImages[i].isRight items.categoryReview.middleZone.append({"isRight": allImages[i].isRight,"name": allImages[i].name}) } totalImages = allImages.length items.categoryReview.score.numberOfSubLevels = totalImages } // get categories details from the complete dataset function getCategoryModel(dataset) { var categories = [] for (var c = 0; c < dataset.length; c++) { categories.push({ 'name': dataset[c].levels[0].name, 'image': dataset[c].levels[0].image, 'index': c }) } return categories } // get all the content (levels) from the category in dataset function getAllLessons(dataset) { var lessons = [] for(var c = 0; c < dataset.length; c++) { lessons.push(dataset[c].levels[0].content) if(dataset[c].allowExpertMode) { expertCategories.push(dataset[c].levels[0].content) } } return lessons } function setValues() { items.categoryReview.leftAreaContainsDrag = false items.categoryReview.rightAreaContainsDrag = false } function isDragInLeftArea(leftAreaRightBorderPos, elementRightPos) { if(elementRightPos <= leftAreaRightBorderPos) return true; else return false; } function isDragInRightArea(rightAreaLeftBorderPos, elementLeftPos) { if((rightAreaLeftBorderPos <= elementLeftPos)) return true; else return false; } function dropControl(sourcePosition, destinationPosition, image, index) { - var destinationZone = destinationPosition == "left" ? items.categoryReview.leftZone : destinationPosition == "right" ? items.categoryReview.rightZone : items.categoryReview.middleZone - var sourceZone = sourcePosition == "left" ? items.categoryReview.leftZone : sourcePosition == "right" ? items.categoryReview.rightZone : items.categoryReview.middleZone + var destinationZone = destinationPosition === "left" ? items.categoryReview.leftZone : destinationPosition === "right" ? items.categoryReview.rightZone : items.categoryReview.middleZone + var sourceZone = sourcePosition === "left" ? items.categoryReview.leftZone : sourcePosition === "right" ? items.categoryReview.rightZone : items.categoryReview.middleZone destinationZone.append({"name": image, "droppedZone": destinationPosition}) sourceZone.remove(index) } diff --git a/src/activities/checkers/Checkers.qml b/src/activities/checkers/Checkers.qml index 9fcb15ebd..574d796fd 100644 --- a/src/activities/checkers/Checkers.qml +++ b/src/activities/checkers/Checkers.qml @@ -1,490 +1,490 @@ /* GCompris - checkers.qml * * Copyright (C) 2017 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 import "../../core" import "." import "checkers.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop 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 GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min(background.width / (items.numberOfCases + 2), (background.height - controls.height) / (items.numberOfCases + barHeightAddon)) : Math.min(background.width / (items.numberOfCases + 2), background.height / (items.numberOfCases + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property var movesToDo: [] property string message property alias trigComputerMove: trigComputerMove property int numberOfCases: 10 Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize / 2 leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true) ? 1 : 3 rows: (items.isPortrait==true) ? 2 : 1 width: (items.isPortrait==true) ? undefined : background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 60 * ApplicationInfo.ratio columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Button { id: undo height: 30 * ApplicationInfo.ratio width: height text: ""; style: GCButtonStyle { theme: "light" } onClicked: Activity.undo() enabled: (items.history && items.history.length > 0) ? true : false opacity: enabled ? 1 : 0 Image { source: 'qrc:/gcompris/src/activities/chess/resource/undo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { id: redo height: undo.height width: undo.height text: ""; style: GCButtonStyle { theme: "light" } onClicked: { Activity.redo() } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Image { source: 'qrc:/gcompris/src/activities/chess/resource/redo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { height: undo.height width: undo.height text: ""; style: GCButtonStyle { theme: "light" } enabled: items.twoPlayer opacity: enabled Image { source: 'qrc:/gcompris/src/activities/chess/resource/turn.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } onClicked: chessboard.swap() } } } Rectangle { - id:boardBg + id: boardBg width: items.cellSize * (items.numberOfCases + 0.2) height: items.cellSize * (items.numberOfCases + 0.2) z: 09 color: "#2E1B0C" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * items.numberOfCases height: items.cellSize * items.numberOfCases interactive: false keyNavigationWraps: true model: items.numberOfCases*items.numberOfCases layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / items.numberOfCases) % 2) == 1 ? Activity.url + 'checkers-white.svg' : Activity.url + 'checkers-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (9 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 width: items.cellSize - spacing height: items.cellSize - spacing z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove: false property bool jumpable: false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove || parent.jumpable ? 5 : 0 border.color: parent.acceptMove ? '#FF808080' : '#C0808080' radius: parent.acceptMove ? width*0.5 : 0 z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return undefined } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: items.cellSize - spacing source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (items.numberOfCases - 1 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 if(pieces.getPieceAt(pos)) pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to, moves) { items.movesToDo.push({"from": from, "to": to, "move": moves}); if(items.movesToDo.length == 1) { moveInternal(); } } function moveInternal() { var moveToDo = items.movesToDo[0] var from = moveToDo.from; var to = moveToDo.to; var moves = moveToDo.move; var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) if(moves.jumps.length != 0) items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) movingPiece = fromPiece - if(moves.jumps.length != 0) { + if(moves.jumps.length !== 0) { listJumps = moves.jumps } else { // create the move if needed listJumps = [Activity.viewPosToEngine(from), Activity.viewPosToEngine(to)] } } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { - if(pieces.itemAt(i).newPos == pos) + if(pieces.itemAt(i).newPos === pos) return pieces.itemAt(i) } return undefined } } property var movingPiece: undefined property var listJumps property bool restartNeeded: false onListJumpsChanged: { if(listJumps.length >= 2) { if(!animationTimer.isRunning) animationTimer.restart(); else restartNeeded = true; } } SequentialAnimation { id: animationTimer onRunningChanged: { if(!running && restartNeeded) start() } ScriptAction { script: { restartNeeded = false var to = Activity.engineToViewPos(listJumps[1]) movingPiece.move(to) } } PauseAnimation { duration: 200 } ScriptAction { script: { listJumps.shift() // only shifting does not trigger the onChanged var tmp = listJumps listJumps = tmp // only change player once all the jumps have been done - if(listJumps.length == 1) { + if(listJumps.length === 1) { items.movesToDo.shift() if(items.movesToDo.length > 0) { pieces.moveInternal() } else { Activity.refresh() movingPiece = undefined } } } } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/checkers/checkers.js b/src/activities/checkers/checkers.js index 33f8faefa..205f46350 100644 --- a/src/activities/checkers/checkers.js +++ b/src/activities/checkers/checkers.js @@ -1,364 +1,364 @@ /* GCompris - checkers.js * * Copyright (C) 2017 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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.6 as Quick .import "qrc:/gcompris/src/core/core.js" as Core .import "engine.js" as Engine var url = "qrc:/gcompris/src/activities/checkers/resource/" var currentLevel var numberOfLevel = 5 var items var state function start(items_) { items = items_ currentLevel = 0 initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 state = new Engine.Draughts('W:W31-50:B1-20') state.resetGame() items.from = -1 items.gameOver = false items.redo_stack = [] refresh() items.positions = 0 // Force a model reload items.positions = simplifiedState(state.position()) clearAcceptMove() } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function simplifiedState(position) { var newState = new Array() for(var i = 0; i < position.length; ++i) { var img = position[i] !== '0' && position[i] !== '?' ? position[i] : '' newState.push( { 'pos': engineToViewPos(i), 'img': img }) } return newState } function updateMessage() { items.gameOver = false items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") if(state.gameOver()) { items.message = items.blackTurn ? qsTr("White wins") : qsTr("Black wins") items.gameOver = true if(!items.twoPlayer) if(items.blackTurn) items.bonus.good('gnu') else items.bonus.good('tux') else items.bonus.good('flower') } } function refresh() { items.blackTurn = (state.getTurn() == 'b') items.history = state.history() updateMessage() items.positions = 0 // Force a model reload items.positions = simplifiedState(state.position()) } // Convert view position (QML) to the chess engine coordinate // // engine view: // | 01 02 03 04 05| // |06 07 08 09 10 | // | 11 12 13 14 15| // |16 17 18 19 20 | // | 21 22 23 24 25| // |26 27 28 29 30 | // | 31 32 33 34 35| // |36 37 38 39 40 | // | 41 42 43 44 45| // |46 47 48 49 50 | // // QML view: // | 91 93 95 97 99| // |80 82 84 86 88 | // | 71 73 75 77 79| // |60 62 64 66 68 | // | 51 53 55 57 59| // |40 42 44 46 48 | // | 31 33 35 37 39| // |20 22 24 26 28 | // | 11 13 15 17 19| // |00 02 04 06 08 | // function viewPosToEngine(pos) { var casesNumber = items.numberOfCases*items.numberOfCases var a = 10 * Math.floor((casesNumber - pos) / 10 + 1); var b = 20; if(pos % 10 !== 0) { b = (casesNumber - pos) % 10; } var newPos = (a - b + 1) newPos = Math.floor(newPos / 2 + 0.5) return newPos } // Convert chess engine coordinate to view position (QML) function engineToViewPos(pos) { var newPos = 90-10*Math.floor((2*pos-1)/10)+2*((pos-1)%5)+1+((-1+Math.pow(-1, Math.floor((2*pos-1)/10)))/2) return newPos } // move is the result from the engine move function visibleMove(move, from, to) { var piece = items.pieces.getPieceAt(from); items.pieces.moveTo(from, to, move) // promotion if (move.to <= 5 && piece.img === 'w') piece.promotion() else if (move.to >= 46 && piece.img === 'b') piece.promotion() } function findBestMove(currentState, depth, sign) { if(depth <= 0) { return getScore(currentState); } var moves = currentState.moves() - if(moves.length == 0) { + if(moves.length === 0) { return [100, 0]; } var bestScore = -1000; var bestScores; for(var move in moves) { currentState.move(moves[move]); var newScore = findBestMove(currentState, depth-1, -1.0*sign) currentState.undo() var score = sign*(newScore[0] - newScore[1]) if(score > bestScore) { bestScore = score; bestScores = newScore; } } return bestScores } function computerMove() { var moves = state.moves() var bestScore = -1000 var bestMoves = [] var newState = new Engine.Draughts(state.fen()) // 0 is b, 1 is b -> w, 2 is b -> w -> b guesses - var depth = currentLevel == 5 ? 2 : 1; + var depth = currentLevel === 5 ? 2 : 1; for(var move in moves) { newState.move(moves[move]); var newScore = findBestMove(newState, depth, 1) newState.undo() var score = newScore[1] - newScore[0] if(bestMoves.length == 0 || bestScore < score) { bestScore = score bestMoves = [] bestMoves.push(moves[move]) } else if(bestScore == score) { bestMoves.push(moves[move]) } } bestMoves = Core.shuffle(bestMoves) var computer = bestMoves[0] var move = state.move({"from": computer.from, "to": computer.to}) if(move) { visibleMove(move, engineToViewPos(computer.from), engineToViewPos(computer.to)) } return move } function moveTo(from, to) { var move = state.move({"from": viewPosToEngine(from), "to": viewPosToEngine(to)}) if(move) { visibleMove(move, from, to) clearAcceptMove() items.redo_stack = [] if(!items.twoPlayer && !state.gameOver()) items.trigComputerMove.start() items.from = -1; } else { // Probably a check makes the move is invalid updateMessage(move) } return move } function undo() { var move = state.undo(); var redo_stack = items.redo_stack redo_stack.push(move) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play - if(!items.twoPlayer && state.getTurn() == 'b') { + if(!items.twoPlayer && state.getTurn() === 'b') { move = state.undo(); redo_stack.push(move) } items.redo_stack = redo_stack refresh() } function moveByEngine(engineMove) { if(!engineMove) return var move = state.move(engineMove) visibleMove(move, engineToViewPos(engineMove.from), engineToViewPos(engineMove.to)) } function redo() { var redo_stack = items.redo_stack var move = redo_stack.pop() moveByEngine(move) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play - if(!items.twoPlayer && state.getTurn() == 'b') { + if(!items.twoPlayer && state.getTurn() === 'b') { move = redo_stack.pop() moveByEngine(move) } // Force refresh items.redo_stack = [] items.redo_stack = redo_stack clearAcceptMove() } // Random move depending on the level function randomMove() { if(!items.difficultyByLevel) { computerMove() return } // Disable random move if the situation is too bad for the user // This avoid having the computer playing bad against a user // with too few pieces making the game last too long var score = getScore(state) if(score[0] / score[1] < 0.7) { computerMove() return } // At level 2 we let the computer play 20% of the time // and 80% of the time we make a random move. if(Math.random() < currentLevel / (numberOfLevel - 1)) { computerMove() return } // Get all possible moves var moves = state.moves() moves = Core.shuffle(moves) var move = state.move(moves[0]) if(move) { visibleMove(move, engineToViewPos(moves[0].from), engineToViewPos(moves[0].to)) } else { // Bad move, should not happen computerMove() } } // Clear all accept move marker from the chessboard function clearAcceptMove() { for(var i=0; i < 50; ++i) { var square = items.squares.getSquareAt(engineToViewPos(i)) if(square) { square['acceptMove'] = false square['jumpable'] = false } } } // Highlight the possible moves for the piece at position 'from' function showPossibleMoves(from) { var fromEngine = viewPosToEngine(from) var result = state.moves(); clearAcceptMove() for(var i=0; i < result.length; ++i) { if(fromEngine === result[i].from) { var pos = engineToViewPos(result[i].to) items.squares.getSquareAt(pos)['acceptMove'] = true for(var v = 1; v < result[i].jumps.length; ++ v) { items.squares.getSquareAt(engineToViewPos(result[i].jumps[v]))['jumpable'] = true } } } } // Calculate the score for black and white // Count the number of pieces with each piece having a given weight // Piece pawn queen // Value 1 4 // @return [white, black] function getScore(board) { var white = 0 var black = 0 var queenScore = 4 var position = board.position() for(var i = 0; i < position.length; ++i) { var img = position[i] !== '0' && position[i] !== '?' ? position[i] : '' - if(img == '') + if(img === '') continue; - else if(img == 'w') { + else if(img === 'w') { white ++; } - else if(img == 'W') { + else if(img === 'W') { white += queenScore; } - if(img == 'b') { + if(img === 'b') { black ++; } - else if(img == 'B') { + else if(img === 'B') { black += queenScore; } } return [white, black] } diff --git a/src/activities/chess/Chess.qml b/src/activities/chess/Chess.qml index 9a601b44f..1840d25fc 100644 --- a/src/activities/chess/Chess.qml +++ b/src/activities/chess/Chess.qml @@ -1,486 +1,486 @@ /* GCompris - chess.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 import "../../core" import "." import "chess.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false property int coordsOpacity: 1 // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true property var fen: [ ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"] ] onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop 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 GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - controls.height - letters.childrenRect.height) / (8 + barHeightAddon)) : Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - letters.childrenRect.height) / (8.5 + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property string message property bool isWarningMessage property alias trigComputerMove: trigComputerMove Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true)?1:3 rows: (items.isPortrait==true)?2:1 width: (items.isPortrait==true)?undefined:background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } z: 20 width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: items.isWarningMessage ? "red" : "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 60 * ApplicationInfo.ratio columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Button { id: undo height: 30 * ApplicationInfo.ratio width: height text: ""; style: GCButtonStyle { theme: "light" } onClicked: Activity.undo() enabled: items.history.length > 0 ? 1 : 0 opacity: enabled Image { source: Activity.url + 'undo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { id: redo height: undo.height width: undo.height text: ""; style: GCButtonStyle { theme: "light" } onClicked: { if (!twoPlayers) { acceptClick = false; Activity.redo() } else { Activity.redo() } } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Image { source: Activity.url + 'redo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { height: undo.height width: undo.height text: ""; style: GCButtonStyle { theme: "light" } enabled: items.twoPlayer opacity: enabled Image { source: Activity.url + 'turn.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } onClicked: chessboard.swap() } } } Rectangle { - id:boardBg + id: boardBg width: items.cellSize * 8.2 height: boardBg.width z: 08 color: "#452501" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * 8 height: chessboard.width interactive: false keyNavigationWraps: true model: 64 layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / 8) % 2) == 1 ? Activity.url + 'chess-white.svg' : Activity.url + 'chess-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') coordsOpacity = 0 timerSwap.start() if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } Timer { id: timerSwap interval: 1500 running: false repeat: false onTriggered: coordsOpacity = 1 } Grid { id: letters anchors.left: chessboard.left anchors.top: chessboard.bottom opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { id: lettersA model: chessboard.rotation == 0 ? ["F", "G", "F", "E", "D", "C", "B", "A"] : ["A", "B", "C", "D", "E", "F", "G", "H"] GCText { x: items.cellSize * (index % 8) + (items.cellSize/2-width/2) y: items.cellSize * Math.floor(index / 8) text: modelData color: "#CBAE7B" } } } Grid { id: numbers anchors.left: chessboard.right anchors.top: chessboard.top opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { model: chessboard.rotation == 0 ? ["1", "2", "3", "4", "5", "6", "7", "8"] : ["8", "7", "6", "5", "4", "3", "2", "1"] GCText { x: items.cellSize * Math.floor(index / 8) + width y: items.cellSize * (index % 8) + (items.cellSize/2-height/2) text: modelData color: "#CBAE7B" } } } Rectangle { id: boardBorder width: items.cellSize * 10 height: boardBorder.width anchors.centerIn: boardBg z: -1 color: "#542D0F" border.color: "#3A1F0A" border.width: items.cellSize * 0.1 } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 width: items.cellSize - spacing height: square.width z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove : false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove ? 5 : 0 border.color: '#FF808080' z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return(undefined) } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: piece.width source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to) { var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) - if(toPiece.img != '') + if(toPiece.img !== '') items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) fromPiece.move(to) } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { - if(pieces.itemAt(i).newPos == pos) + if(pieces.itemAt(i).newPos === pos) return pieces.itemAt(i) } return(undefined) } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/chess/Piece.qml b/src/activities/chess/Piece.qml index 1bf0722e2..86a68914d 100644 --- a/src/activities/chess/Piece.qml +++ b/src/activities/chess/Piece.qml @@ -1,107 +1,107 @@ /* GCompris - chess.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 Image { id: piece property int pos Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } Behavior on x { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } Behavior on y { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } z: 10 property string img - property bool acceptMove : false + property bool acceptMove: false property int newPos // color = -1 if no piece, 0 is black and 1 is white property int isWhite: img.length != 2 ? -1 : img[0] == 'w' ? 1 : 0 SequentialAnimation { id: hideAnim NumberAnimation { target: piece property: "scale" duration: 200 to: 0 } PropertyAction { target: piece property: 'img' value: "" } PropertyAction { target: piece property: 'pos' value: piece.newPos } PropertyAction { target: piece property: 'scale' value: 1 } PropertyAction { target: piece property: 'z' value: 2 } } SequentialAnimation { id: promotionAnim PauseAnimation { duration: 200 } NumberAnimation { target: piece property: 'scale' to: 0 } PropertyAction { target: piece property: 'img' value: isWhite ? 'wq' : 'bq' } NumberAnimation { target: piece property: 'scale' to: 1 easing.type: Easing.OutElastic } } function hide(newPos) { piece.newPos = newPos hideAnim.start() } function promotion() { promotionAnim.start() } function move(to) { piece.newPos = to piece.pos = to piece.z = 2 } } diff --git a/src/activities/chess/chess.js b/src/activities/chess/chess.js index ee76e8039..c88c3b073 100644 --- a/src/activities/chess/chess.js +++ b/src/activities/chess/chess.js @@ -1,346 +1,346 @@ /* GCompris - chess.js * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 as Quick .import "qrc:/gcompris/src/core/core.js" as Core .import "engine.js" as Engine var url = "qrc:/gcompris/src/activities/chess/resource/" var currentLevel var numberOfLevel var items var state function start(items_) { items = items_ currentLevel = 0 numberOfLevel = items.fen.length initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 state = Engine.p4_fen2state(items.fen[currentLevel][1]) items.from = -1 items.gameOver = false items.redo_stack = [] refresh() Engine.p4_prepare(state) items.positions = 0 // Force a model reload items.positions = simplifiedState(state['board']) clearAcceptMove() } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function simplifiedState(state) { var newState = new Array() for(var i = state.length - 1; i >= 0; --i) { - if(state[i] != 16) { + if(state[i] !== 16) { var img = "" switch(state[i]) { case 2: img = "wp" break case 3: img = "bp" break case 4: img = "wr" break case 5: img = "br" break case 6: img = "wn" break case 7: img = "bn" break case 8: img = "wb" break case 9: img = "bb" break case 10: img = "wk" break case 11: img = "bk" break case 12: img = "wq" break case 13: img = "bq" break default: break } newState.push( { 'pos': engineToViewPos(i), 'img': img }) } } return newState } function updateMessage(move) { items.isWarningMessage = false items.gameOver = false items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") if(!move) return if((move.flags & (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) == (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) { items.message = items.blackTurn ? qsTr("White mates", "white wins") : qsTr("Black mates", "black wins") items.gameOver = true if(!items.twoPlayer) - if(state.to_play != 0) + if(state.to_play !== 0) items.bonus.good('gnu') else items.bonus.good('tux') else items.bonus.good('flower') } else if((move.flags & Engine.P4_MOVE_FLAG_MATE) == Engine.P4_MOVE_FLAG_MATE) { items.message = qsTr("Drawn game") items.gameOver = true items.bonus.good('flower') } else if((move.flags & Engine.P4_MOVE_FLAG_CHECK) == Engine.P4_MOVE_FLAG_CHECK) { items.message = items.blackTurn ? qsTr("White checks", "black king is under attack") : qsTr("Black checks", "white king is under attack") - } else if(move.flags == Engine.P4_MOVE_ILLEGAL) { + } else if(move.flags === Engine.P4_MOVE_ILLEGAL) { items.isWarningMessage = true items.message = qsTr("Invalid, your king may be in check") } } function refresh(move) { items.blackTurn = state.to_play // 0=w 1=b items.history = state.history updateMessage(move) } // Convert view position (QML) to the chess engine coordinate // // The engine manages coordinate into a 120 element array, which is conceptually // a 10x12 board, with the 8x8 board placed at the centre, thus: // + 0123456789 // 0 ########## // 10 ########## // 20 #RNBQKBNR# // 30 #PPPPPPPP# // 40 #........# // 50 #........# // 60 #........# // 70 #........# // 80 #pppppppp# // 90 #rnbqkbnr# //100 ########## //110 ########## // // In QML each cell is in the regular range [0-63] // function viewPosToEngine(pos) { return (Math.floor(pos / 8) + 2) * 10 + pos % 8 + 1 } // Convert chess engine coordinate to view position (QML) function engineToViewPos(pos) { var newpos = pos - 21 - (Math.floor((pos - 20) / 10) * 2) return newpos } // move is the result from the engine move function visibleMove(move, from, to) { items.pieces.moveTo(from, to) // Castle move if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_KING) items.pieces.moveTo(from + 3, to - 1) else if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_QUEEN) items.pieces.moveTo(from - 4, to + 1) - else if(items.pieces.getPieceAt(to).img == 'wp' && to > 55) + else if(items.pieces.getPieceAt(to).img === 'wp' && to > 55) items.pieces.promotion(to) - else if(items.pieces.getPieceAt(to).img == 'bp' && to < 8) + else if(items.pieces.getPieceAt(to).img === 'bp' && to < 8) items.pieces.promotion(to) } function computerMove() { var computer = state.findmove(3) var move = state.move(computer[0], computer[1]) if(move.ok) { visibleMove(move, engineToViewPos(computer[0]), engineToViewPos(computer[1])) refresh(move) } return move } function moveTo(from, to) { var move = state.move(viewPosToEngine(from), viewPosToEngine(to)) if(move.ok) { visibleMove(move, from, to) refresh(move) clearAcceptMove() items.redo_stack = [] if(!items.twoPlayer) items.trigComputerMove.start() items.from = -1; } else { // Probably a check makes the move is invalid updateMessage(move) } return move.ok } function undo() { var redo_stack = items.redo_stack redo_stack.push(state.history[state.moveno - 1]) state.jump_to_moveno(state.moveno - 1) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play - if(!items.twoPlayer && state.to_play != 0) { + if(!items.twoPlayer && state.to_play !== 0) { redo_stack.push(state.history[state.moveno - 1]) state.jump_to_moveno(state.moveno - 1) } // without it, you can't move again the same piece Engine.p4_prepare(state) items.redo_stack = redo_stack refresh() items.positions = [] // Force a model reload items.positions = simplifiedState(state['board']) } function moveByEngine(engineMove) { if(!engineMove) return var move = state.move(engineMove[0], engineMove[1]) visibleMove(move, engineToViewPos(engineMove[0]), engineToViewPos(engineMove[1])) refresh(move) } function redo() { var redo_stack = items.redo_stack moveByEngine(items.redo_stack.pop()) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play - if(!items.twoPlayer && state.to_play != 0) { + if(!items.twoPlayer && state.to_play !== 0) { items.redoTimer.moveByEngine(items.redo_stack.pop()) } // Force refresh items.redo_stack = [] items.redo_stack = redo_stack } // Random move depending on the level function randomMove() { if(!items.difficultyByLevel) { computerMove() return } // Disable random move if the situation is too bad for the user // This avoid having the computer playing bad against a user // with too few pieces making the game last too long var score = getScore() if(score[0] / score[1] < 0.7) { computerMove() return } // At level 2 we let the computer play 20% of the time // and 80% of the time we make a random move. if(Math.random() < currentLevel / (numberOfLevel - 1)) { computerMove() return } // Get all possible moves var moves = Engine.p4_parse(state, state.to_play, 0, 0) moves = Core.shuffle(moves) var move = state.move(moves[0][1], moves[0][2]) if(move.ok) { visibleMove(move, engineToViewPos(moves[0][1]), engineToViewPos(moves[0][2])) refresh(move) } else { // Bad move, should not happens computerMove() } } // Clear all accept move marker from the chessboard function clearAcceptMove() { for(var i=0; i < items.positions.length; ++i) items.squares.getSquareAt(i)['acceptMove'] = false } // Highlight the possible moves for the piece at position 'from' function showPossibleMoves(from) { var result = Engine.p4_parse(state, state.to_play, 0, 0) clearAcceptMove() var fromEngine = viewPosToEngine(from) for(var i=0; i < result.length; ++i) { - if(fromEngine == result[i][1]) { + if(fromEngine === result[i][1]) { var pos = engineToViewPos(result[i][2]) items.squares.getSquareAt(pos)['acceptMove'] = true } } } // Calculate the score for black and white // Count the number of pieces with each piece having a given weight // Piece pawn knight bishop rook queen // Value 1 3 3 5 9 // @return [white, black] function getScore() { var lut = {2: 1, 4: 5, 6: 3, 8: 3, 12: 9} var white = 0 var black = 0 for(var i=0; i < state['board'].length; ++i) { var score = lut[state['board'][i] & 0xFE] if(score) if(state['board'][i] & 0x01) black += score else white += score } return [white, black] } diff --git a/src/activities/chess/engine.js b/src/activities/chess/engine.js index 2543f5283..4ae321bd8 100644 --- a/src/activities/chess/engine.js +++ b/src/activities/chess/engine.js @@ -1,1603 +1,1603 @@ /* p4wn, AKA 5k chess - by Douglas Bagnall * * This code is in the public domain, or as close to it as various * laws allow. No warranty; no restrictions. * * lives at http://p4wn.sf.net/ */ /*Compatibility tricks: * backwards for old MSIEs (to 5.5) * sideways for seed command-line javascript.*/ var p4_log; if (this.imports !== undefined && this.printerr !== undefined){//seed or gjs p4_log = function(){ var args = Array.prototype.slice.call(arguments); printerr(args.join(', ')); }; } else if (this.console === undefined){//MSIE p4_log = function(){}; } else { // disable the console p4_log = function(){}; //p4_log = function(){console.log.apply(console, arguments);}; } /*MSIE Date.now backport */ if (Date.now === undefined) Date.now = function(){return (new Date).getTime();}; /* The pieces are stored as numbers between 2 and 13, inclusive. * Empty squares are stored as 0, and off-board squares as 16. * There is some bitwise logic to it: * piece & 1 -> colour (white: 0, black: 1) * piece & 2 -> single move piece (including pawn) * if (piece & 2) == 0: * piece & 4 -> row and column moves * piece & 8 -> diagonal moves */ var P4_PAWN = 2, P4_ROOK = 4, P4_KNIGHT = 6, P4_BISHOP = 8, P4_QUEEN = 12, P4_KING = 10; var P4_EDGE = 16; /* in order, even indices: , pawn, rook, knight, bishop, king, queen. Only the * even indices are used.*/ var P4_MOVES = [[], [], [], [], [1,10,-1,-10], [], [21,19,12,8,-21,-19,-12,-8], [], [11,9,-11,-9], [], [1,10,11,9,-1,-10,-11,-9], [], [1,10,11,9,-1,-10,-11,-9], [] ]; /*P4_VALUES defines the relative value of various pieces. * * It follows the 1,3,3,5,9 pattern you learn as a kid, multiplied by * 20 to give sub-pawn resolution to other factors, with bishops given * a wee boost over knights. */ var P4_VALUES=[0, 0, //Piece values 20, 20, //pawns 100, 100, //rooks 60, 60, //knights 61, 61, //bishops 8000, 8000,//kings 180, 180, //queens 0]; /* A score greater than P4_WIN indicates a king has been taken. It is * less than the value of a king, in case someone finds a way to, say, * sacrifice two queens in order to checkmate. */ var P4_KING_VALUE = P4_VALUES[10]; var P4_WIN = P4_KING_VALUE >> 1; /* every move, a winning score decreases by this much */ var P4_WIN_DECAY = 300; var P4_WIN_NOW = P4_KING_VALUE - 250; /* P4_{MAX,MIN}_SCORE should be beyond any possible evaluated score */ var P4_MAX_SCORE = 9999; // extremes of evaluation range var P4_MIN_SCORE = -P4_MAX_SCORE; /*initialised in p4_initialise_state */ var P4_CENTRALISING_WEIGHTS; var P4_BASE_PAWN_WEIGHTS; var P4_KNIGHT_WEIGHTS; /*P4_DEBUG turns on debugging features */ var P4_DEBUG = 0; var P4_INITIAL_BOARD = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"; /*use javascript typed arrays rather than plain arrays * (faster in some browsers, unsupported in others, possibly slower elsewhere) */ var P4_USE_TYPED_ARRAYS = this.Int32Array !== undefined; var P4_PIECE_LUT = { /*for FEN, PGN interpretation */ P: 2, p: 3, R: 4, r: 5, N: 6, n: 7, B: 8, b: 9, K: 10, k: 11, Q: 12, q: 13 }; var P4_ENCODE_LUT = ' PPRRNNBBKKQQ'; function p4_alphabeta_treeclimber(state, count, colour, score, s, e, alpha, beta){ var move = p4_make_move(state, s, e, P4_QUEEN); var i; var ncolour = 1 - colour; var movelist = p4_parse(state, colour, move.ep, -score); var movecount = movelist.length; if(count){ //branch nodes var t; for(i = 0; i < movecount; i++){ var mv = movelist[i]; var mscore = mv[0]; var ms = mv[1]; var me = mv[2]; if (mscore > P4_WIN){ //we won! Don't look further. alpha = P4_KING_VALUE; break; } t = -p4_alphabeta_treeclimber(state, count - 1, ncolour, mscore, ms, me, -beta, -alpha); if (t > alpha){ alpha = t; } if (alpha >= beta){ break; } } if (alpha < -P4_WIN_NOW && ! p4_check_check(state, colour)){ /* Whatever we do, we lose the king. * But if it is not check then this is stalemate, and the * score doesn't apply. */ alpha = state.stalemate_scores[colour]; } if (alpha < -P4_WIN){ /*make distant checkmate seem less bad */ alpha += P4_WIN_DECAY; } } else{ //leaf nodes while(beta > alpha && --movecount != -1){ if(movelist[movecount][0] > alpha){ alpha = movelist[movecount][0]; } } } p4_unmake_move(state, move); return alpha; } /* p4_prepare() works out weightings for assessing various moves, * favouring centralising moves early, for example. * * It is called before each tree search, not for each parse(), so it * is OK for it to be a little bit slow. But that also means it drifts * out of sync with the real board state, especially on deep searches. */ function p4_prepare(state){ var i, j, x, y, a; var pieces = state.pieces = [[], []]; /*convert state.moveno half move count to move cycle count */ var moveno = state.moveno >> 1; var board = state.board; /* high earliness_weight indicates a low move number. The formula * should work above moveno == 50, but this is javascript. */ var earliness_weight = (moveno > 50) ? 0 : parseInt(6 * Math.exp(moveno * -0.07)); var king_should_hide = moveno < 12; var early = moveno < 5; /* find the pieces, kings, and weigh material*/ var kings = [0, 0]; var material = [0, 0]; var best_pieces = [0, 0]; for(i = 20; i < 100; i++){ a = board[i]; var piece = a & 14; var colour = a & 1; if(piece){ pieces[colour].push([a, i]); if (piece == P4_KING){ kings[colour] = i; } else{ material[colour] += P4_VALUES[piece]; best_pieces[colour] = Math.max(best_pieces[colour], P4_VALUES[piece]); } } } /*does a draw seem likely soon?*/ var draw_likely = (state.draw_timeout > 90 || state.current_repetitions >= 2); if (draw_likely) p4_log("draw likely", state.current_repetitions, state.draw_timeout); state.values = [[], []]; var qvalue = P4_VALUES[P4_QUEEN]; /*used as ballast in various ratios*/ var material_sum = material[0] + material[1] + 2 * qvalue; var wmul = 2 * (material[1] + qvalue) / material_sum; var bmul = 2 * (material[0] + qvalue) / material_sum; var multipliers = [wmul, bmul]; var emptiness = 4 * P4_QUEEN / material_sum; state.stalemate_scores = [parseInt(0.5 + (wmul - 1) * 2 * qvalue), parseInt(0.5 + (bmul - 1) * 2 * qvalue)]; //p4_log("value multipliers (W, B):", wmul, bmul, // "stalemate scores", state.stalemate_scores); for (i = 0; i < P4_VALUES.length; i++){ var v = P4_VALUES[i]; if (v < P4_WIN){//i.e., not king state.values[0][i] = parseInt(v * wmul + 0.5); state.values[1][i] = parseInt(v * bmul + 0.5); } else { state.values[0][i] = v; state.values[1][i] = v; } } /*used for pruning quiescence search */ state.best_pieces = [parseInt(best_pieces[0] * wmul + 0.5), parseInt(best_pieces[1] * bmul + 0.5)]; var kx = [kings[0] % 10, kings[1] % 10]; var ky = [parseInt(kings[0] / 10), parseInt(kings[1] / 10)]; /* find the frontmost pawns in each file */ var pawn_cols = [[], []]; for (y = 3; y < 9; y++){ for (x = 1; x < 9; x++){ i = y * 10 + x; a = board[i]; if ((a & 14) != P4_PAWN) continue; if ((a & 1) == 0){ pawn_cols[0][x] = y; } else if (pawn_cols[1][x] === undefined){ pawn_cols[1][x] = y; } } } var target_king = (moveno >= 20 || material_sum < 5 * qvalue); var weights = state.weights; for (y = 2; y < 10; y++){ for (x = 1; x < 9; x++){ i = y * 10 + x; var early_centre = P4_CENTRALISING_WEIGHTS[i] * earliness_weight; var plateau = P4_KNIGHT_WEIGHTS[i]; for (var c = 0; c < 2; c++){ var dx = Math.abs(kx[1 - c] - x); var dy = Math.abs(ky[1 - c] - y); var our_dx = Math.abs(kx[c] - x); var our_dy = Math.abs(ky[c] - y); var d = Math.max(Math.sqrt(dx * dx + dy * dy), 1) + 1; var mul = multipliers[c]; /*(mul < 1) <==> we're winning*/ var mul3 = mul * mul * mul; var at_home = y == 2 + c * 7; var pawn_home = y == 3 + c * 5; var row4 = y == 5 + c; var promotion_row = y == 9 - c * 7; var get_out = (early && at_home) * -5; var knight = parseInt(early_centre * 0.3) + 2 * plateau + get_out; var rook = parseInt(early_centre * 0.3); var bishop = parseInt(early_centre * 0.6) + plateau + get_out; if (at_home){ rook += (x == 4 || x == 5) * (earliness_weight + ! target_king); rook += (x == 1 || x == 8) * (moveno > 10 && moveno < 20) * -3; rook += (x == 2 || x == 7) * (moveno > 10 && moveno < 20) * -1; } /*Queen wants to stay home early, then jump right in*/ /*keep kings back on home row for a while*/ var queen = parseInt(plateau * 0.5 + early_centre * (0.5 - early)); var king = (king_should_hide && at_home) * 2 * earliness_weight; /*empty board means pawn advancement is more urgent*/ var get_on_with_it = Math.max(emptiness * 2, 1); var pawn = get_on_with_it * P4_BASE_PAWN_WEIGHTS[c ? 119 - i : i]; if (early){ /* Early pawn weights are slightly randomised, so each game is different. */ if (y >= 4 && y <= 7){ var boost = 1 + 3 * (y == 5 || y == 6); pawn += parseInt((boost + p4_random_int(state, 4)) * 0.1 * early_centre); } if (x == 4 || x == 5){ //discourage middle pawns from waiting at home pawn -= 3 * pawn_home; pawn += 3 * row4; } } /*pawn promotion row is weighted as a queen minus a pawn.*/ if (promotion_row) pawn += state.values[c][P4_QUEEN] - state.values[c][P4_PAWN]; /*pawns in front of a castled king should stay there*/ pawn += 4 * (y == 3 && ky[c] == 2 && Math.abs(our_dx) < 2 && kx[c] != 5 && x != 4 && x != 5); /*passed pawns (having no opposing pawn in front) are encouraged. */ var cols = pawn_cols[1 - c]; if (cols[x] == undefined || (c == 0 && cols[x] < y) || (c == 1 && cols[x] > y)) pawn += 2; /* After a while, start going for opposite king. Just * attract pieces into the area so they can mill about in * the area, waiting for an opportunity. * * As prepare is only called at the beginning of each tree * search, the king could wander out of the targeted area * in deep searches. But that's OK. Heuristics are * heuristics. */ if (target_king){ knight += 2 * parseInt(8 * mul / d); rook += 2 * ((dx < 2) + (dy < 2)); bishop += 3 * (Math.abs((dx - dy)) < 2); queen += 2 * parseInt(8 / d) + (dx * dy == 0) + (dx - dy == 0); /* The losing king wants to stay in the middle, while the winning king goes in for the kill.*/ var king_centre_wt = 8 * emptiness * P4_CENTRALISING_WEIGHTS[i]; king += parseInt(150 * emptiness / (mul3 * d) + king_centre_wt * mul3); } weights[P4_PAWN + c][i] = pawn; weights[P4_KNIGHT + c][i] = knight; weights[P4_ROOK + c][i] = rook; weights[P4_BISHOP + c][i] = bishop; weights[P4_QUEEN + c][i] = queen; weights[P4_KING + c][i] = king; if (draw_likely && mul < 1){ /*The winning side wants to avoid draw, so adds jitter to its weights.*/ var range = 3 / mul3; for (j = 2 + c; j < 14; j += 2){ weights[j][i] += p4_random_int(state, range); } } } } } state.prepared = true; } function p4_maybe_prepare(state){ if (! state.prepared) p4_prepare(state); } function p4_parse(state, colour, ep, score) { var board = state.board; var s, e; //start and end position var E, a; //E=piece at end place, a= piece moving var i, j; var other_colour = 1 - colour; var dir = (10 - 20 * colour); //dir= 10 for white, -10 for black var movelist = []; var captures = []; var weight; var pieces = state.pieces[colour]; var castle_flags = (state.castles >> (colour * 2)) & 3; var values = state.values[other_colour]; var all_weights = state.weights; for (j = pieces.length - 1; j >= 0; j--){ s = pieces[j][1]; // board position a = board[s]; //piece number var weight_lut = all_weights[a]; weight = score - weight_lut[s]; a &= 14; if(a > 2){ //non-pawns var moves = P4_MOVES[a]; if(a & 2){ for(i = 0; i < 8; i++){ e = s + moves[i]; E = board[e]; if(!E){ movelist.push([weight + values[E] + weight_lut[e], s, e]); } else if((E&17)==other_colour){ captures.push([weight + values[E] + weight_lut[e] + all_weights[E][e], s, e]); } } - if(a == P4_KING && castle_flags){ + if(a === P4_KING && castle_flags){ if((castle_flags & 1) && (board[s-1] + board[s-2] + board[s-3] == 0) && p4_check_castling(board, s - 2,other_colour,dir,-1)){//Q side movelist.push([weight + 12, s, s - 2]); //no analysis, just encouragement } if((castle_flags & 2) && (board[s+1]+board[s+2] == 0)&& p4_check_castling(board, s, other_colour, dir, 1)){//K side movelist.push([weight + 13, s, s + 2]); } } } else{//rook, bishop, queen var mlen = moves.length; for(i=0;i= 0; i--){ var mv = movelist[i]; var score = mv[0]; s = mv[1]; e = mv[2]; if(! board[e]){ var x = scores[s]; x.score = Math.max(x.score, score); } } /* moving out of a threat is worth considering, especially * if it is a pawn and you are not.*/ for(i = threats.length - 1; i >= 0; i--){ var mv = threats[i]; var x = scores[mv[2]]; if (x !== undefined){ var S = board[mv[1]]; var r = (1 + x.piece > 3 + S < 4) * 0.01; if (x.threatened < r) x.threatened = r; } } var pieces2 = []; for (i = 20; i < 100; i++){ p = scores[i]; if (p !== undefined){ p.score += p.threatened * our_values[p.piece]; pieces2.push(p); } } pieces2.sort(function(a, b){return a.score - b.score;}); for (i = 0; i < pieces2.length; i++){ p = pieces2[i]; pieces[i] = [p.piece, p.pos]; } } } function p4_findmove(state, level, colour, ep){ p4_prepare(state); p4_optimise_piece_list(state); var board = state.board; if (arguments.length == 2){ colour = state.to_play; ep = state.enpassant; } var movelist = p4_parse(state, colour, ep, 0); var alpha = P4_MIN_SCORE; var mv, t, i; var bs = 0; var be = 0; if (level <= 0){ for (i = 0; i < movelist.length; i++){ mv = movelist[i]; if(movelist[i][0] > alpha){ alpha = mv[0]; bs = mv[1]; be = mv[2]; } } return [bs, be, alpha]; } for(i = 0; i < movelist.length; i++){ mv = movelist[i]; var mscore = mv[0]; var ms = mv[1]; var me = mv[2]; if (mscore > P4_WIN){ p4_log("XXX taking king! it should never come to this"); alpha = P4_KING_VALUE; bs = ms; be = me; break; } t = -state.treeclimber(state, level - 1, 1 - colour, mscore, ms, me, P4_MIN_SCORE, -alpha); if (t > alpha){ alpha = t; bs = ms; be = me; } } if (alpha < -P4_WIN_NOW && ! p4_check_check(state, colour)){ alpha = state.stalemate_scores[colour]; } return [bs, be, alpha]; } /*p4_make_move changes the state and returns an object containing * everything necessary to undo the change. * * p4_unmake_move uses the p4_make_move return value to restore the * previous state. */ function p4_make_move(state, s, e, promotion){ var board = state.board; var S = board[s]; var E = board[e]; board[e] = S; board[s] = 0; var piece = S & 14; var moved_colour = S & 1; var end_piece = S; /* can differ from S in queening*/ //now some stuff to handle queening, castling var rs = 0, re, rook; var ep_taken = 0, ep_position; var ep = 0; if(piece == P4_PAWN){ if((60 - e) * (60 - e) > 900){ /*got to end; replace the pawn on board and in pieces cache.*/ promotion |= moved_colour; board[e] = promotion; end_piece = promotion; } - else if (((s ^ e) & 1) && E == 0){ + else if (((s ^ e) & 1) && E === 0){ /*this is a diagonal move, but the end spot is empty, so we surmise enpassant */ ep_position = e - 10 + 20 * moved_colour; ep_taken = board[ep_position]; board[ep_position] = 0; } else if ((s - e) * (s - e) == 400){ /*delta is 20 --> two row jump at start*/ ep = (s + e) >> 1; } } else if (piece == P4_KING && ((s - e) * (s - e) == 4)){ //castling - move rook too rs = s - 4 + (s < e) * 7; re = (s + e) >> 1; //avg of s,e=rook's spot rook = moved_colour + P4_ROOK; board[rs] = 0; board[re] = rook; //piece_locations.push([rook, re]); } var old_castle_state = state.castles; if (old_castle_state){ var mask = 0; var shift = moved_colour * 2; var side = moved_colour * 70; var s2 = s - side; var e2 = e + side; //wipe both our sides if king moves if (s2 == 25) mask |= 3 << shift; //wipe one side on any move from rook points else if (s2 == 21) mask |= 1 << shift; else if (s2 == 28) mask |= 2 << shift; //or on any move *to* opposition corners if (e2 == 91) mask |= 4 >> shift; else if (e2 == 98) mask |= 8 >> shift; state.castles &= ~mask; } var old_pieces = state.pieces.concat(); var our_pieces = old_pieces[moved_colour]; var dest = state.pieces[moved_colour] = []; for (var i = 0; i < our_pieces.length; i++){ var x = our_pieces[i]; var pp = x[0]; var ps = x[1]; if (ps != s && ps != rs){ dest.push(x); } } dest.push([end_piece, e]); if (rook) dest.push([rook, re]); if (E || ep_taken){ var their_pieces = old_pieces[1 - moved_colour]; dest = state.pieces[1 - moved_colour] = []; var gone = ep_taken ? ep_position : e; for (i = 0; i < their_pieces.length; i++){ var x = their_pieces[i]; if (x[1] != gone){ dest.push(x); } } } return { /*some of these (e.g. rook) could be recalculated during * unmake, possibly more cheaply. */ s: s, e: e, S: S, E: E, ep: ep, castles: old_castle_state, rs: rs, re: re, rook: rook, ep_position: ep_position, ep_taken: ep_taken, pieces: old_pieces }; } function p4_unmake_move(state, move){ var board = state.board; if (move.ep_position){ board[move.ep_position] = move.ep_taken; } board[move.s] = move.S; board[move.e] = move.E; //move.piece_locations.length--; if(move.rs){ board[move.rs] = move.rook; board[move.re] = 0; //move.piece_locations.length--; } state.pieces = move.pieces; state.castles = move.castles; } function p4_insufficient_material(state){ var knights = false; var bishops = undefined; var i; var board = state.board; for(i = 20; i < 100; i++){ var piece = board[i] & 14; if(piece == 0 || piece == P4_KING){ continue; } if (piece == P4_KNIGHT){ /* only allow one knight of either colour, never with a bishop */ if (knights || bishops !== undefined){ return false; } knights = true; } else if (piece == P4_BISHOP){ /*any number of bishops, but on only one colour square */ var x = i & 1; var y = parseInt(i / 10) & 1; var parity = x ^ y; if (knights){ return false; } else if (bishops === undefined){ bishops = parity; } else if (bishops != parity){ return false; } } else { return false; } } return true; } /* p4_move(state, s, e, promotion) * s, e are start and end positions * * promotion is the desired pawn promotion if the move gets a pawn to the other * end. * * return value contains bitwise flags */ var P4_MOVE_FLAG_OK = 1; var P4_MOVE_FLAG_CHECK = 2; var P4_MOVE_FLAG_MATE = 4; var P4_MOVE_FLAG_CAPTURE = 8; var P4_MOVE_FLAG_CASTLE_KING = 16; var P4_MOVE_FLAG_CASTLE_QUEEN = 32; var P4_MOVE_FLAG_DRAW = 64; var P4_MOVE_ILLEGAL = 0; var P4_MOVE_MISSED_MATE = P4_MOVE_FLAG_CHECK | P4_MOVE_FLAG_MATE; var P4_MOVE_CHECKMATE = P4_MOVE_FLAG_OK | P4_MOVE_FLAG_CHECK | P4_MOVE_FLAG_MATE; var P4_MOVE_STALEMATE = P4_MOVE_FLAG_OK | P4_MOVE_FLAG_MATE; function p4_move(state, s, e, promotion){ var board = state.board; var colour = state.to_play; var other_colour = 1 - colour; if (s != parseInt(s)){ if (e === undefined){ var mv = p4_interpret_movestring(state, s); s = mv[0]; e = mv[1]; if (s == 0) return {flags: P4_MOVE_ILLEGAL, ok: false}; promotion = mv[2]; } else {/*assume two point strings: 'e2', 'e4'*/ s = p4_destringify_point(s); e = p4_destringify_point(e); } } if (promotion === undefined) promotion = P4_QUEEN; var E=board[e]; var S=board[s]; /*See if this move is even slightly legal, disregarding check. */ var i; var legal = false; p4_maybe_prepare(state); var moves = p4_parse(state, colour, state.enpassant, 0); for (i = 0; i < moves.length; i++){ - if (e == moves[i][2] && s == moves[i][1]){ + if (e === moves[i][2] && s === moves[i][1]){ legal = true; break; } } if (! legal) { return {flags: P4_MOVE_ILLEGAL, ok: false}; } /*Try the move, and see what the response is.*/ var changes = p4_make_move(state, s, e, promotion); /*is it check? */ if (p4_check_check(state, colour)){ p4_unmake_move(state, changes); p4_log('in check', changes); return {flags: P4_MOVE_ILLEGAL, ok: false, string: "in check!"}; } /*The move is known to be legal. We won't be undoing it.*/ var flags = P4_MOVE_FLAG_OK; state.enpassant = changes.ep; state.history.push([s, e, promotion]); /*draw timeout: 50 moves without pawn move or capture is a draw */ if (changes.E || changes.ep_position){ state.draw_timeout = 0; flags |= P4_MOVE_FLAG_CAPTURE; } else if ((S & 14) == P4_PAWN){ state.draw_timeout = 0; } else{ state.draw_timeout++; } if (changes.rs){ flags |= (s > e) ? P4_MOVE_FLAG_CASTLE_QUEEN : P4_MOVE_FLAG_CASTLE_KING; } var shortfen = p4_state2fen(state, true); var repetitions = (state.position_counts[shortfen] || 0) + 1; state.position_counts[shortfen] = repetitions; state.current_repetitions = repetitions; if (state.draw_timeout > 100 || repetitions >= 3 || p4_insufficient_material(state)){ flags |= P4_MOVE_FLAG_DRAW; } state.moveno++; state.to_play = other_colour; if (p4_check_check(state, other_colour)){ flags |= P4_MOVE_FLAG_CHECK; } /* check for (stale|check)mate, by seeing if there is a move for * the other side that doesn't result in check. (In other words, * reduce the pseudo-legal-move list down to a legal-move list, * and check it isn't empty). * * We don't need to p4_prepare because other colour pieces can't * have moved (just disappeared) since previous call. Also, * setting the promotion piece is unnecessary, because all * promotions block check equally well. */ var is_mate = true; var replies = p4_parse(state, other_colour, changes.ep, 0); for (i = 0; i < replies.length; i++){ var m = replies[i]; var change2 = p4_make_move(state, m[1], m[2], P4_QUEEN); var check = p4_check_check(state, other_colour); p4_unmake_move(state, change2); if (!check){ is_mate = false; break; } } if (is_mate) flags |= P4_MOVE_FLAG_MATE; var movestring = p4_move2string(state, s, e, S, promotion, flags, moves); p4_log("successful move", s, e, movestring, flags); state.prepared = false; return { flags: flags, string: movestring, ok: true }; } function p4_move2string(state, s, e, S, promotion, flags, moves){ var piece = S & 14; var src, dest; var mv, i; var capture = flags & P4_MOVE_FLAG_CAPTURE; src = p4_stringify_point(s); dest = p4_stringify_point(e); if (piece == P4_PAWN){ if (capture){ mv = src.charAt(0) + 'x' + dest; } else mv = dest; if (e > 90 || e < 30){ //end row, queening if (promotion === undefined) promotion = P4_QUEEN; mv += '=' + P4_ENCODE_LUT.charAt(promotion); } } else if (piece == P4_KING && (s-e) * (s-e) == 4) { if (e < s) mv = 'O-O-O'; else mv = 'O-O'; } else { var row_qualifier = ''; var col_qualifier = ''; var pstr = P4_ENCODE_LUT.charAt(S); var sx = s % 10; var sy = parseInt(s / 10); /* find any other pseudo-legal moves that would put the same * piece in the same place, for which we'd need * disambiguation. */ var co_landers = []; for (i = 0; i < moves.length; i++){ var m = moves[i]; if (e == m[2] && s != m[1] && state.board[m[1]] == S){ co_landers.push(m[1]); } } if (co_landers.length){ for (i = 0; i < co_landers.length; i++){ var c = co_landers[i]; var cx = c % 10; var cy = parseInt(c / 10); if (cx == sx)/*same column, so qualify by row*/ row_qualifier = src.charAt(1); if (cy == sy) col_qualifier = src.charAt(0); } if (row_qualifier == '' && col_qualifier == ''){ /*no co-landers on the same rank or file, so one or the other will do. * By convention, use the column (a-h) */ col_qualifier = src.charAt(0); } } mv = pstr + col_qualifier + row_qualifier + (capture ? 'x' : '') + dest; } if (flags & P4_MOVE_FLAG_CHECK){ if (flags & P4_MOVE_FLAG_MATE) mv += '#'; else mv += '+'; } else if (flags & P4_MOVE_FLAG_MATE) mv += ' stalemate'; return mv; } function p4_jump_to_moveno(state, moveno){ p4_log('jumping to move', moveno); if (moveno === undefined || moveno > state.moveno) moveno = state.moveno; else if (moveno < 0){ moveno = state.moveno + moveno; } var state2 = p4_fen2state(state.beginning); var i = 0; while (state2.moveno < moveno){ var m = state.history[i++]; p4_move(state2, m[0], m[1], m[2]); } /* copy the replayed state across, not all that deeply, but * enough to cover, eg, held references to board. */ var attr, dest; for (attr in state2){ var src = state2[attr]; if (attr instanceof Array){ dest = state[attr]; dest.length = 0; for (i = 0; i < src.length; i++){ dest[i] = src[i]; } } else { state[attr] = src; } } state.prepared = false; } /* write a standard FEN notation * http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation * */ function p4_state2fen(state, reduced){ var piece_lut = ' PpRrNnBbKkQq'; var board = state.board; var fen = ''; //fen does Y axis backwards, X axis forwards */ for (var y = 9; y > 1; y--){ var count = 0; for (var x = 1; x < 9; x++){ var piece = board[y * 10 + x]; - if (piece == 0) + if (piece === 0) count++; else{ if (count) fen += count.toString(); fen += piece_lut.charAt(piece); count = 0; } } if (count) fen += count; if (y > 2) fen += '/'; } /*white or black */ fen += ' ' + 'wb'.charAt(state.to_play) + ' '; /*castling */ if (state.castles){ var lut = [2, 'K', 1, 'Q', 8, 'k', 4, 'q']; for (var i = 0; i < 8; i += 2){ if (state.castles & lut[i]){ fen += lut[i + 1]; } } } else fen += '-'; /*enpassant */ if (state.enpassant !== 0){ fen += ' ' + p4_stringify_point(state.enpassant); } else fen += ' -'; if (reduced){ /*if the 'reduced' flag is set, the move number and draw *timeout are not added. This form is used to detect draws by *3-fold repetition.*/ return fen; } fen += ' ' + state.draw_timeout + ' '; fen += (state.moveno >> 1) + 1; return fen; } function p4_stringify_point(p){ var letters = " abcdefgh"; var x = p % 10; var y = (p - x) / 10 - 1; return letters.charAt(x) + y; } function p4_destringify_point(p){ var x = parseInt(p.charAt(0), 19) - 9; //a-h <-> 10-18, base 19 var y = parseInt(p.charAt(1)) + 1; if (y >= 2 && y < 10 && x >= 1 && x < 9) return y * 10 + x; return undefined; } /* read a standard FEN notation * http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation * */ function p4_fen2state(fen, state){ if (state === undefined) state = p4_initialise_state(); var board = state.board; var fenbits = fen.split(' '); var fen_board = fenbits[0]; var fen_toplay = fenbits[1]; var fen_castles = fenbits[2]; var fen_enpassant = fenbits[3]; var fen_timeout = fenbits[4]; var fen_moveno = fenbits[5]; if (fen_timeout === undefined) fen_timeout = 0; //fen does Y axis backwards, X axis forwards */ var y = 90; var x = 1; var i; for (var j = 0; j < fen_board.length; j++){ var c = fen_board.charAt(j); - if (c == '/'){ + if (c === '/'){ x = 1; y -= 10; if (y < 20) break; continue; } var piece = P4_PIECE_LUT[c]; if (piece && x < 9){ board[y + x] = piece; x++; } else { var end = Math.min(x + parseInt(c), 9); for (; x < end; x++){ board[y + x] = 0; } } } state.to_play = (fen_toplay.toLowerCase() == 'b') ? 1 : 0; state.castles = 0; for (i = 0; i < fen_castles.length; i++){ var bit = {k: 8, q: 4, K: 2, Q: 1}[fen_castles.charAt(i)]; state.castles |= (bit || 0); } state.enpassant = (fen_enpassant != '-') ? p4_destringify_point(fen_enpassant) : 0; state.draw_timeout = parseInt(fen_timeout); if (fen_moveno === undefined){ /*have a guess based on entropy and pieces remaining*/ var pieces = 0; var mix = 0; var p, q, r; for (y = 20; y < 100; y+=10){ for (x = 1; x < 9; x++){ p = board[y + x] & 15; pieces += (!!p); if (x < 8){ q = board[y + x + 1]; mix += (!q) != (!p); } if (y < 90){ q = board[y + x + 10]; mix += (!q) != (!p); } } } fen_moveno = Math.max(1, parseInt((32 - pieces) * 1.3 + (4 - fen_castles.length) * 1.5 + ((mix - 16) / 5))); //p4_log("pieces", pieces, "mix", mix, "estimate", fen_moveno); } state.moveno = 2 * (parseInt(fen_moveno) - 1) + state.to_play; state.history = []; state.beginning = fen; state.prepared = false; state.position_counts = {}; /* Wrap external functions as methods. */ state.move = function(s, e, promotion){ return p4_move(this, s, e, promotion); }; state.findmove = function(level){ return p4_findmove(this, level); }; state.jump_to_moveno = function(moveno){ return p4_jump_to_moveno(this, moveno); }; return state; } /* Weights would all fit within an Int8Array *except* for the last row for pawns, which is close to the queen value (180, max is 127). Int8Array seems slightly quicker in Chromium 18, no different in Firefox 12. Int16Array is no faster, perhaps slower than Int32Array. Int32Array is marginally slower than plain arrays with Firefox 12, but significantly quicker with Chromium. */ var P4_ZEROS = []; function p4_zero_array(){ if (P4_USE_TYPED_ARRAYS) return new Int32Array(120); if (P4_ZEROS.length == 0){ for(var i = 0; i < 120; i++){ P4_ZEROS[i] = 0; } } return P4_ZEROS.slice(); } /* p4_initialise_state() creates the board and initialises weight * arrays etc. Some of this is really only needs to be done once. */ function p4_initialise_state(){ var board = p4_zero_array(); P4_CENTRALISING_WEIGHTS = p4_zero_array(); P4_BASE_PAWN_WEIGHTS = p4_zero_array(); P4_KNIGHT_WEIGHTS = p4_zero_array(); for(var i = 0; i < 120; i++){ var y = parseInt(i / 10); var x = i % 10; var dx = Math.abs(x - 4.5); var dy = Math.abs(y - 5.5); P4_CENTRALISING_WEIGHTS[i] = parseInt(6 - Math.pow((dx * dx + dy * dy) * 1.5, 0.6)); //knights have a flat topped centre (bishops too, but less so). P4_KNIGHT_WEIGHTS[i] = parseInt(((dx < 2) + (dy < 2) * 1.5) + (dx < 3) + (dy < 3)) - 2; P4_BASE_PAWN_WEIGHTS[i] = parseInt('000012347000'.charAt(y)); if (y > 9 || y < 2 || x < 1 || x > 8) board[i] = 16; } var weights = []; for (i = 0; i < 14; i++){ weights[i] = p4_zero_array(); } var state = { board: board, weights: weights, history: [], treeclimber: p4_alphabeta_treeclimber }; p4_random_seed(state, P4_DEBUG ? 1 : Date.now()); return state; } function p4_new_game(){ return p4_fen2state(P4_INITIAL_BOARD); } /*convert an arbitrary movestring into a pair of integers offsets into * the board. The string might be in any of these forms: * * "d2-d4" "d2d4" "d4" -- moving a pawn * * "b1-c3" "b1c3" "Nc3" "N1c3" "Nbc3" "Nb1c3" -- moving a knight * * "b1xc3" "b1xc3" "Nxc3" -- moving a knight, also happens to capture. * * "O-O" "O-O-O" -- special cases for castling ("e1-c1", etc, also work) * * Note that for the "Nc3" (pgn) format, some knowledge of the board * is necessary, so the state parameter is required. If it is * undefined, the other forms will still work. */ function p4_interpret_movestring(state, str){ /* Ignore any irrelevant characters, then tokenise. * */ var FAIL = [0, 0]; var algebraic_re = /^\s*([RNBQK]?[a-h]?[1-8]?)[ :x-]*([a-h][1-8]?)(=[RNBQ])?[!?+#e.p]*\s*$/; var castle_re = /^\s*([O0o]-[O0o](-[O0o])?)\s*$/; var position_re = /^[a-h][1-8]$/; var m = algebraic_re.exec(str); if (m == null){ /*check for castling notation (O-O, O-O-O) */ m = castle_re.exec(str); if (m){ s = 25 + state.to_play * 70; if (m[2])/*queenside*/ e = s - 2; else e = s + 2; } else return FAIL; } var src = m[1]; var dest = m[2]; var queen = m[3]; var s, e, q; var moves, i; if (src == '' || src == undefined){ /* a single coordinate pawn move */ e = p4_destringify_point(dest); s = p4_find_source_point(state, e, 'P' + dest.charAt(0)); } else if (/^[RNBQK]/.test(src)){ /*pgn format*/ e = p4_destringify_point(dest); s = p4_find_source_point(state, e, src); } else if (position_re.test(src) && position_re.test(dest)){ s = p4_destringify_point(src); e = p4_destringify_point(dest); } else if (/^[a-h]$/.test(src)){ e = p4_destringify_point(dest); s = p4_find_source_point(state, e, 'P' + src); } if (s == 0) return FAIL; if (queen){ /* the chosen queen piece */ q = P4_PIECE_LUT[queen.charAt(1)]; } return [s, e, q]; } function p4_find_source_point(state, e, str){ var colour = state.to_play; var piece = P4_PIECE_LUT[str.charAt(0)]; piece |= colour; var s, i; var row, column; /* can be specified as Na, Na3, N3, and who knows, N3a? */ for (i = 1; i < str.length; i++){ var c = str.charAt(i); if (/[a-h]/.test(c)){ column = str.charCodeAt(i) - 96; } else if (/[1-8]/.test(c)){ /*row goes 2 - 9 */ row = 1 + parseInt(c); } } var possibilities = []; p4_prepare(state); var moves = p4_parse(state, colour, state.enpassant, 0); for (i = 0; i < moves.length; i++){ var mv = moves[i]; - if (e == mv[2]){ + if (e === mv[2]){ s = mv[1]; - if (state.board[s] == piece && - (column === undefined || column == s % 10) && - (row === undefined || row == parseInt(s / 10)) + if (state.board[s] === piece && + (column === undefined || column === s % 10) && + (row === undefined || row === parseInt(s / 10)) ){ var change = p4_make_move(state, s, e, P4_QUEEN); if (! p4_check_check(state, colour)) possibilities.push(s); p4_unmake_move(state, change); } } } p4_log("finding", str, "that goes to", e, "got", possibilities); if (possibilities.length == 0){ return 0; } else if (possibilities.length > 1){ p4_log("p4_find_source_point seems to have failed", state, e, str, possibilities); } return possibilities[0]; } /*random number generator based on * http://burtleburtle.net/bob/rand/smallprng.html */ function p4_random_seed(state, seed){ seed &= 0xffffffff; state.rng = (P4_USE_TYPED_ARRAYS) ? new Uint32Array(4) : []; state.rng[0] = 0xf1ea5eed; state.rng[1] = seed; state.rng[2] = seed; state.rng[3] = seed; for (var i = 0; i < 20; i++) p4_random31(state); } function p4_random31(state){ var rng = state.rng; var b = rng[1]; var c = rng[2]; /* These shifts amount to rotates. * Note the three-fold right shift '>>>', meaning an unsigned shift. * The 0xffffffff masks are needed to keep javascript to 32bit. (supposing * untyped arrays). */ var e = rng[0] - ((b << 27) | (b >>> 5)); rng[0] = b ^ ((c << 17) | (c >>> 15)); rng[1] = (c + rng[3]) & 0xffffffff; rng[2] = (rng[3] + e) & 0xffffffff; rng[3] = (e + rng[0]) & 0xffffffff; return rng[3] & 0x7fffffff; } function p4_random_int(state, top){ /* uniform integer in range [0 < n < top), supposing top < 2 ** 31 * * This method is slightly (probably pointlessly) more accurate * than converting to 0-1 float, multiplying and truncating, and * considerably more accurate than a simple modulus. * Obviously it is a bit slower. */ /* mask becomes one less than the next highest power of 2 */ var mask = top; mask--; mask |= mask >>> 1; mask |= mask >>> 2; mask |= mask >>> 4; mask |= mask >>> 8; mask |= mask >>> 16; // can't do-while loop: https://bugreports.qt.io/browse/QTBUG-59012 var r = top + 1; while(r >= top) r = p4_random31(state) & mask; return r; } diff --git a/src/activities/click_on_letter/Carriage.qml b/src/activities/click_on_letter/Carriage.qml index e12789829..49da2f093 100644 --- a/src/activities/click_on_letter/Carriage.qml +++ b/src/activities/click_on_letter/Carriage.qml @@ -1,175 +1,175 @@ /* GCompris - Carriage.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Pascal Georges (GTK+ version) * Bruno Coudoin (GTK+ Mostly full rewrite) * Holger Kaelberer (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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "click_on_letter.js" as Activity Item { id: carriageItem property int nbCarriage property bool isCarriage: index <= nbCarriage Image { id: carriageImage sourceSize.width: carriageItem.width fillMode: Image.PreserveAspectFit source: isCarriage ? Activity.url + "carriage.svg": Activity.url + "cloud.svg" z: (state == 'scaled') ? 1 : -1 Rectangle { id: carriageBg visible: isCarriage width: parent.width - 8 height: parent.height / 1.8 anchors.bottom: parent.top anchors.bottomMargin: - parent.height / 1.5 radius: height / 10 color: "#f0d578" border.color: "#b98a1c" border.width: 3 } GCText { id: text anchors.horizontalCenter: isCarriage ? carriageBg.horizontalCenter : parent.horizontalCenter anchors.verticalCenter: isCarriage ? carriageBg.verticalCenter : parent.verticalCenter z: 11 - text: letter font.pointSize: NaN // need to clear font.pointSize explicitly font.pixelSize: parent.width * 0.65 font.bold: true style: Text.Outline styleColor: "#2a2a2a" color: "white" } DropShadow { anchors.fill: text cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3 samples: 16 color: "#422a2a2a" source: text } Image { - id:softFailure + id: softFailure z: 12 source: "qrc:/gcompris/src/activities/tic_tac_toe/resource/cross.svg" width: parent.width height: width anchors.centerIn: text - opacity:0 + opacity: 0 visible: ApplicationInfo.useOpenGL ? false : true } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: ApplicationInfo.isMobile ? false : true onClicked: { if (Activity.checkAnswer(index)) { successAnimation.restart(); particle.burst(30) } else { failureAnimation.restart() } } } ParticleSystemStarLoader { id: particle clip: false } states: State { name: "scaled"; when: mouseArea.containsMouse PropertyChanges { target: carriageItem scale: /*carriageImage.scale * */ 1.2 - z: 2} + z: 2 + } } transitions: Transition { NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } } SequentialAnimation { id: successAnimation NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation" to: 20; duration: 100 } NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation"; to: -20 duration: 100 } NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation" - to: 0 - duration: 50 } + to: 0; duration: 50 + } } SequentialAnimation { id: failureAnimation NumberAnimation { target: ApplicationInfo.useOpenGL ? color : softFailure property: "opacity" to: 1; duration: 400 } NumberAnimation { target: ApplicationInfo.useOpenGL ? color : softFailure property: "opacity" to: 0; duration: 200 } } } Colorize { id: color z: 5 anchors.fill: carriageImage source: carriageImage hue: 0.0 saturation: 1 opacity: 0 } } diff --git a/src/activities/click_on_letter/ClickOnLetter.qml b/src/activities/click_on_letter/ClickOnLetter.qml index 705e5b287..8d7794a71 100644 --- a/src/activities/click_on_letter/ClickOnLetter.qml +++ b/src/activities/click_on_letter/ClickOnLetter.qml @@ -1,326 +1,324 @@ /* GCompris - ClickOnLetter.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Pascal Georges (GTK+ version) * Bruno Coudoin (GTK+ Mostly full rewrite) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "click_on_letter.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity focus: true /* mode of the activity, either "lowercase" (click_on_letter) * or "uppercase" (click_on_letter_up): */ property string mode: "lowercase" onStart: focus = true pageComponent: Image { id: background source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop focus: true // system locale by default property string locale: "system" signal start signal stop signal voiceError Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property alias bar: bar property alias trainModel: trainModel property GCAudio audioVoices: activity.audioVoices property alias parser: parser property alias questionItem: questionItem property alias repeatItem: repeatItem property alias score: score property alias bonus: bonus property alias locale: background.locale } onVoiceError: { questionItem.visible = true repeatItem.visible = false } onStart: { activity.audioVoices.error.connect(voiceError) Activity.start(items, mode); } onStop: Activity.stop() DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig label: qsTr("Select your locale") } } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["locale"]) { background.locale = dataToSave["locale"]; } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; // Remove .UTF-8 if(newLocale.indexOf('.') != -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } dataToSave = {"locale": newLocale } background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale) { background.stop(); background.start(); } } function setDefaultValues() { var localeUtf8 = background.locale; if(background.locale != "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { dialogActivityConfig.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelpLeftRight onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Score { id: score anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.left: parent.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined anchors.right: undefined } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 80 * ApplicationInfo.ratio anchors { top: parent.top right: parent.right margins: 10 } onClicked: Activity.playLetter(Activity.currentLetter); } Image { id: railway source: Activity.url + "railway.svg" fillMode: Image.PreserveAspectCrop anchors.bottom: bar.top anchors.left: parent.left anchors.right: parent.right height: 15 * ApplicationInfo.ratio sourceSize.width: Math.max(parent.width, parent.height) anchors.bottomMargin: 13 * ApplicationInfo.ratio } Item { id: questionItem anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.topMargin: parent.height * 0.25 z: 10 width: questionText.width * 2 height: questionText.height * 1.3 visible: false property alias text: questionText.text Rectangle { id: questionRect anchors.fill: parent border.color: "#FFFFFFFF" border.width: 2 color: "#000065" opacity: 0.31 radius: 10 } GCText { id: questionText anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter opacity: 1.0 z:11 text: "" fontSize: 44 font.bold: true style: Text.Outline styleColor: "#2a2a2a" color: "white" } DropShadow { anchors.fill: questionText cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3 samples: 16 color: "#422a2a2a" source: questionText } } ListModel { id: trainModel } property int itemWidth: Math.min(parent.width / 7.5, parent.height / 5) property int itemHeight: itemWidth * 1.11 Image { id: engine source: Activity.url + "engine.svg" - anchors.bottom: railway.bottom anchors.left: railway.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottomMargin: 5 * ApplicationInfo.ratio sourceSize.width: itemWidth fillMode: Image.PreserveAspectFit } Image { id: smoke source: Activity.url + "smoke.svg" - anchors.bottom: engine.top anchors.left: railway.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottomMargin: 5 * ApplicationInfo.ratio sourceSize.width: itemWidth fillMode: Image.PreserveAspectFit } GridView { id: train anchors.bottom: railway.bottom anchors.left: engine.right anchors.right: parent.right anchors.top: parent.top anchors.bottomMargin: 5 * ApplicationInfo.ratio cellWidth: itemWidth cellHeight: itemHeight clip: false interactive: false verticalLayoutDirection: GridView.BottomToTop layoutDirection: Qt.LeftToRight model: trainModel delegate: Carriage { width: background.itemWidth nbCarriage: (parent.width - engine.width) / background.itemWidth } } JsonParser { id: parser onError: console.error("Click_on_letter: Error parsing JSON: " + msg); } } } diff --git a/src/activities/click_on_letter/click_on_letter.js b/src/activities/click_on_letter/click_on_letter.js index 4a24e6dfd..3ff67221e 100644 --- a/src/activities/click_on_letter/click_on_letter.js +++ b/src/activities/click_on_letter/click_on_letter.js @@ -1,210 +1,210 @@ /* GCompris - click_on_letter.js * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Pascal Georges (GTK+ version) * Bruno Coudoin (GTK+ Mostly full rewrite) * Holger Kaelberer (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.6 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo .import "qrc:/gcompris/src/core/core.js" as Core var url = "qrc:/gcompris/src/activities/click_on_letter/resource/" var defaultLevelsFile = ":/gcompris/src/activities/click_on_letter/resource/levels-en.json"; var maxLettersPerLine = 6; var levels; var currentLevel; var maxLevel; var currentSubLevel; var currentLetter; var maxSubLevel; var level; var questions; var answers; var items; var mode; function start(_items, _mode) { Core.checkForVoices(_items.main); items = _items; mode = _mode; // register the voices for the locale var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(locale)) loadLevels(); currentLevel = 0; currentSubLevel = 0; maxLevel = levels.length; initLevel(); } function validateLevels(levels) { var i; for (i = 0; i < levels.length; i++) { if (undefined === levels[i].questions || typeof levels[i].questions != "string" || levels[i].questions.length < 1 || typeof levels[i].answers != "string" || levels[i].answers.length < 1) return false; } if (i < 1) return false; return true; } function loadLevels() { var ret; var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) var filename = GCompris.ApplicationInfo.getLocaleFilePath(url + "levels-" + locale + ".json"); levels = items.parser.parseFromUrl(filename); if (levels == null) { console.warn("Click_on_letter: Invalid levels file " + filename); // fallback to default Latin (levels-en.json) file: levels = items.parser.parseFromUrl(defaultLevelsFile); if (levels == null) { console.error("Click_on_letter: Invalid default levels file " + defaultLevelsFile + ". Can't continue!"); // any way to error-exit here? return; } } // at this point we have valid levels for (var i = 0; i < levels.length; i++) { if (mode === "lowercase") { levels[i].questions = levels[i].questions.toLocaleLowerCase(); levels[i].answers = levels[i].answers.toLocaleLowerCase(); } else { levels[i].questions = levels[i].questions.toLocaleUpperCase(); levels[i].answers = levels[i].answers.toLocaleUpperCase(); } } } function stop() { } function shuffleString(s) { var a = s.split(""); var n = a.length; for(var i = n-1; i>0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = a[i]; a[i] = a[j]; a[j] = tmp; } return a.join(""); } function initLevel() { items.bar.level = currentLevel + 1; if (currentSubLevel == 0) { level = levels[currentLevel]; maxSubLevel = level.questions.length; items.score.numberOfSubLevels = maxSubLevel; items.score.currentSubLevel = 1; questions = shuffleString(level.questions); answers = shuffleString(level.answers); var answerArr = answers.split(""); items.trainModel.clear(); for (var i = 0; i < answerArr.length; i++) { items.trainModel.append({ "letter": answerArr[i], }); } } else { items.score.currentSubLevel = currentSubLevel + 1; } var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale); currentLetter = questions.split("")[currentSubLevel]; if (GCompris.ApplicationSettings.isAudioVoicesEnabled && GCompris.DownloadManager.haveLocalResource( GCompris.DownloadManager.getVoicesResourceForLocale(locale))) { items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/"+locale+"/misc/click_on_letter.$CA")) items.audioVoices.silence(100) playLetter(currentLetter) items.questionItem.visible = false items.repeatItem.visible = true } else { // no sound -> show question items.questionItem.visible = true; items.repeatItem.visible = false } // Maybe we will display it if sound fails items.questionItem.text = currentLetter; } function playLetter(letter) { var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/"+locale+"/alphabet/" + Core.getSoundFilenamForChar(letter))) } function nextLevel() { items.audioVoices.clearQueue() if(maxLevel <= ++currentLevel) { currentLevel = 0 } currentSubLevel = 0; initLevel(); } function previousLevel() { items.audioVoices.clearQueue() if(--currentLevel < 0) { currentLevel = maxLevel - 1 } currentSubLevel = 0; initLevel(); } function nextSubLevel() { items.audioVoices.clearQueue() if(++currentSubLevel >= maxSubLevel) { currentSubLevel = 0 nextLevel() } initLevel(); } function checkAnswer(index) { var modelEntry = items.trainModel.get(index); - if (modelEntry.letter == currentLetter) { + if (modelEntry.letter === currentLetter) { playLetter(modelEntry.letter); items.bonus.good("flower"); return true } else { return false } } diff --git a/src/activities/clickgame/Clickgame.qml b/src/activities/clickgame/Clickgame.qml index 1ffeaacaf..0fb5bc348 100644 --- a/src/activities/clickgame/Clickgame.qml +++ b/src/activities/clickgame/Clickgame.qml @@ -1,98 +1,100 @@ /* GCompris - Clickgame.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 import GCompris 1.0 import "../../core" import "clickgame.js" as Activity ActivityBase { id: activity focus: true onStart: {} onStop: {} pageComponent: Image { id: background signal start signal stop focus: true fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/clickgame/resource/sea1.jpg" sourceSize.width: Math.max(parent.width, parent.height) Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } onStart: { Activity.start(activity, background, bar, bonus, items) } onStop: { Activity.stop() timer.stop() } QtObject { id: items property alias score: score property alias killedFishes: score.currentSubLevel } Timer { id: timer - interval: 5000; running: true; repeat: true + interval: 5000 + running: true + repeat: true onTriggered: activity.audioEffects.play("qrc:/gcompris/src/activities/clickgame/resource/bubble.wav") } DialogHelp { id: dialogHelpLeftRight onClose: home() } Score { id: score anchors { top: parent.top bottom: undefined right: parent.right margins: 10 * ApplicationInfo.ratio } } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/colors/findit.js b/src/activities/colors/findit.js index 690f2c9a8..3a04b650a 100644 --- a/src/activities/colors/findit.js +++ b/src/activities/colors/findit.js @@ -1,109 +1,109 @@ /* GCompris - findit.js * * Copyright (C) 2015 Bruno Coudoin * * 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.6 as Quick .import "qrc:/gcompris/src/core/core.js" as Core var url = "qrc:/gcompris/src/activities/colors/resource/" var currentLevel var numberOfLevel var items var dataset var currentQuestion var hasWon function start(items_, dataset_, mode_) { - if (mode_ == "Colors") + if (mode_ === "Colors") Core.checkForVoices(items_.background); items = items_ dataset = dataset_.get() currentLevel = 0 numberOfLevel = dataset.length items.firstQuestion = true items.audioOk = false items.score.currentSubLevel = 1 initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 items.containerModel.clear() currentQuestion = 0 dataset[currentLevel] = Core.shuffle(dataset[currentLevel]) for(var i = 0; i < dataset[currentLevel].length; ++i) { items.containerModel.append(dataset[currentLevel][i]) } items.score.numberOfSubLevels = dataset[currentLevel].length // Shuffle again not to ask the question in the model order dataset[currentLevel] = Core.shuffle(dataset[currentLevel]) hasWon = false initQuestion() } function initQuestion() { // We just set the opacity to 0, the questionItem will then grab // the new question by itself // Need to set opacity to 0.1 before in order to be sure it's changed and trigger the questionItem onOpacityChanged items.questionItem.opacity = 0.1 items.questionItem.opacity = 0 } function nextQuestion() { if(dataset[currentLevel].length <= currentQuestion + 1) { items.bonus.good("flower") hasWon = true } else { currentQuestion++ items.score.currentSubLevel++ initQuestion() } } function nextLevel() { items.score.currentSubLevel = 1 if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { items.score.currentSubLevel = 1 if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function getCurrentTextQuestion() { return dataset[currentLevel][currentQuestion].text } function getCurrentAudioQuestion() { return dataset[currentLevel][currentQuestion].audio } diff --git a/src/activities/digital_electricity/DigitalElectricity.qml b/src/activities/digital_electricity/DigitalElectricity.qml index 535c8ebfc..07e04730d 100644 --- a/src/activities/digital_electricity/DigitalElectricity.qml +++ b/src/activities/digital_electricity/DigitalElectricity.qml @@ -1,529 +1,529 @@ /* GCompris - DigitalElectricity.qml * * Copyright (C) 2016 Pulkit Gupta * * Authors: * Bruno Coudoin (GTK+ version) * Pulkit Gupta (Qt Quick port) * Rudra Nil Basu (Qt Quick port) * Timothée Giet (mouse drag refactoring) * * 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 "digital_electricity.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property string mode: "tutorial" property bool isTutorialMode: mode == "tutorial" ? true : false pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "texture02.png" fillMode: Image.Tile signal start signal stop property bool hori: background.width >= background.height Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { - if ((event.key == Qt.Key_Return || event.key == Qt.Key_Enter) && okButton.enabled) { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && okButton.enabled) { Activity.checkAnswer() } - if (event.key == Qt.Key_Plus) { + if (event.key === Qt.Key_Plus) { Activity.zoomIn() } - if (event.key == Qt.Key_Minus) { + if (event.key === Qt.Key_Minus) { Activity.zoomOut() } - if (event.key == Qt.Key_Right) { + if (event.key === Qt.Key_Right) { playArea.x -= 200; } - if (event.key == Qt.Key_Left) { + if (event.key === Qt.Key_Left) { playArea.x += 200 } - if (event.key == Qt.Key_Up) { + if (event.key === Qt.Key_Up) { playArea.y += 200 } - if (event.key == Qt.Key_Down) { + if (event.key === Qt.Key_Down) { playArea.y -= 200 } if (playArea.x >= mousePan.drag.maximumX) { playArea.x = mousePan.drag.maximumX } if (playArea.y >= mousePan.drag.maximumY) { playArea.y = mousePan.drag.maximumY } if (playArea.x <= mousePan.drag.minimumX) { playArea.x = mousePan.drag.minimumX } if (playArea.y <= mousePan.drag.minimumY) { playArea.y = mousePan.drag.minimumY } } onHoriChanged: { if (hori == true) { playArea.x += items.toolsMargin playArea.y -= items.toolsMargin } else { playArea.x -= items.toolsMargin playArea.y += items.toolsMargin } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias playArea: playArea property alias mousePan: mousePan property alias bar: bar property alias bonus: bonus property alias availablePieces: availablePieces property alias toolTip: toolTip property alias infoTxt: infoTxt property alias truthTablesModel: truthTablesModel property alias displayTruthTable: inputOutputTxt.displayTruthTable property alias dataset: dataset property alias tutorialDataset: tutorialDataset property alias infoImage: infoImage property bool isTutorialMode: activity.isTutorialMode property alias tutorialInstruction: tutorialInstruction property real toolsMargin: 90 * ApplicationInfo.ratio property real zoomLvl: 0.25 } Loader { id: dataset asynchronous: false } Dataset { id: tutorialDataset } IntroMessage { id: tutorialInstruction intro: [] textContainerWidth: background.hori ? parent.width - inputComponentsContainer.width - items.toolsMargin : 0.9 * background.width textContainerHeight: background.hori ? 0.5 * parent.height : parent.height - inputComponentsContainer.height - (bar.height * 2) - items.toolsMargin anchors { fill: undefined top: background.hori ? parent.top : inputComponentsContainer.bottom topMargin: 10 right: parent.right rightMargin: 5 left: background.hori ? inputComponentsContainer.right : parent.left leftMargin: 5 } z: 5 } onStart: { Activity.start(items) } onStop: { Activity.stop() } Rectangle { id: visibleArea color: "#00000000" width: background.hori ? background.width - items.toolsMargin - 10 : background.width - 10 height: background.hori ? background.height - bar.height - items.toolsMargin - 10 : background.height - bar.height - 10 anchors { fill: undefined top: background.hori ? parent.top : inputComponentsContainer.bottom topMargin: 5 right: parent.right rightMargin: 5 left: background.hori ? inputComponentsContainer.right : parent.left leftMargin: 5 bottom: bar.top bottomMargin: 20 } z: 6 GCText { id: infoTxt anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 2 } fontSizeMode: Text.Fit minimumPixelSize: 10 font.pixelSize: 150 color: "white" horizontalAlignment: Text.AlignHLeft width: Math.min(implicitWidth, 0.90 * parent.width) height: inputOutputTxt.visible == false ? Math.min(implicitHeight, 0.7 * parent.height) : Math.min(implicitHeight, (inputOutputTxt.inputs > 2 ? 0.3 : 0.4) * parent.height) wrapMode: TextEdit.WordWrap visible: false z: 4 } Rectangle { id: infoTxtContainer anchors.fill: parent opacity: 1 radius: 10 color: "#373737" border.width: 2 border.color: "#F2F2F2" visible: infoTxt.visible MouseArea { anchors.fill: parent onClicked: infoTxt.visible = false } z: 3 } Image { id: infoImage property bool imgVisible: false height: source == "" ? 0 : parent.height * 0.3 - 10 width: source == "" ? 0 : parent.width - 10 fillMode: Image.PreserveAspectFit visible: infoTxt.visible && imgVisible anchors { top: infoTxt.bottom horizontalCenter: infoTxtContainer.horizontalCenter } z: 5 } ListModel { id: truthTablesModel property int rows property int columns property int inputs property int outputs } Row { id: inputOutputTxt z: 5 property bool displayTruthTable visible: infoTxt.visible && displayTruthTable property int inputs: truthTablesModel.inputs property int outputs: truthTablesModel.outputs property int cellSize: (inputs > 2 ? 0.65 : 0.5) * parent.height / (truthTablesModel.rows + 1) property int maxWidth: Math.min(cellSize, parent.width * 0.95 / truthTablesModel.columns) property int minSize: 2.5 * cellSize height: cellSize anchors { top: infoTxt.bottom horizontalCenter: parent.horizontalCenter } Rectangle { color: "#A7D9F9" width: inputOutputTxt.inputs > 1 ? inputOutputTxt.maxWidth * inputOutputTxt.inputs : inputOutputTxt.minSize height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 GCText { anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter height: parent.height width: parent.width text: qsTr("Input") } } Rectangle { color: "#A7F9DD" width: inputOutputTxt.outputs > 1 ? inputOutputTxt.maxWidth * inputOutputTxt.outputs : inputOutputTxt.minSize height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 GCText { anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter height: parent.height width: parent.width text: qsTr("Output") } } } Grid { id: truthTable rows: truthTablesModel.rows columns: truthTablesModel.columns height: rows * inputOutputTxt.cellSize z: 5 visible: inputOutputTxt.visible anchors { top: inputOutputTxt.bottom horizontalCenter: parent.horizontalCenter } Repeater { id: repeater model: truthTablesModel delegate: blueSquare Component { id: blueSquare Rectangle { width: ((index % truthTable.columns) / (truthTablesModel.inputs - 1)) <= 1 ? (inputOutputTxt.inputs > 1 ? inputOutputTxt.maxWidth : inputOutputTxt.minSize) : (inputOutputTxt.outputs > 1 ? inputOutputTxt.maxWidth : inputOutputTxt.minSize) height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 color: { if(truthTablesModel.inputs == 1) { return index%2 == 0 ? "#A7D9F9" : "#A7F9DD" } else { return ((index % truthTable.columns) / (truthTablesModel.inputs - 1)) <= 1 ? "#A7D9F9" : "#A7F9DD" } } GCText { id: truthTableValue anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter height: parent.height width: parent.width text: value } } } } } } Rectangle { id: playArea color: "#10000000" x: background.hori ? items.toolsMargin : 0 y: background.hori ? 0 : items.toolsMargin width: background.hori ? background.width * 4 - items.toolsMargin : background.width * 4 height: background.hori ? background.height * 4 - (bar.height * 1.1) : background.height * 4 - (bar.height * 1.1) - items.toolsMargin PinchArea { id: pinchZoom anchors.fill: parent onPinchFinished: { if (pinch.scale < 1) { Activity.zoomOut() } if (pinch.scale > 1) { Activity.zoomIn() } } MouseArea { id: mousePan anchors.fill: parent scrollGestureEnabled: false //needed for pinchZoom drag.target: playArea drag.axis: Drag.XandYAxis drag.minimumX: - playArea.width * items.zoomLvl drag.maximumX: background.hori ? items.toolsMargin : 0 drag.minimumY: - playArea.height * items.zoomLvl drag.maximumY: background.hori ? 0 : items.toolsMargin onClicked: { Activity.deselect() availablePieces.hideToolbar() } } } } Rectangle { id: inputComponentsContainer width: background.hori ? items.toolsMargin : background.width height: background.hori ? background.height : items.toolsMargin color: "#4A3823" anchors.left: parent.left Image { anchors.fill: parent anchors.rightMargin: background.hori ? 3 * ApplicationInfo.ratio : 0 anchors.bottomMargin: background.hori ? 0 : 3 * ApplicationInfo.ratio source: Activity.url + "texture01.png" fillMode: Image.Tile ListWidget { id: availablePieces hori: background.hori } } z: 10 } Rectangle { id: toolTip anchors { bottom: bar.top bottomMargin: 10 left: inputComponentsContainer.left leftMargin: 5 } width: toolTipTxt.width + 10 height: toolTipTxt.height + 5 color: "#373737" opacity: 1 radius: 10 z: 100 border.width: 2 border.color: "#F2F2F2" property alias text: toolTipTxt.text Behavior on opacity { NumberAnimation { duration: 120 } } function show(newText) { if(newText) { text = newText opacity = 1 } else { opacity = 0 } } GCText { id: toolTipTxt anchors.centerIn: parent fontSize: regularSize color: "white" horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias modesComboBox: modesComboBox property var availableModes: [ { "text": qsTr("Tutorial Mode"), "value": "tutorial" }, { "text": qsTr("Free Mode"), "value": "free" }, ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: modesComboBox model: availableModes background: dialogActivityConfig label: qsTr("Select your Mode") } } } } onClose: home(); onLoadData: { if(dataToSave && dataToSave["modes"]) { activity.mode = dataToSave["modes"]; } } onSaveData: { var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modesComboBox.currentIndex].value; if (newMode !== activity.mode) { activity.mode = newMode; dataToSave = {"modes": activity.mode}; Activity.reset() } } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length; i ++) { if(dialogActivityConfig.configItem.availableModes[i].value === activity.mode) { dialogActivityConfig.configItem.modesComboBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } BarButton { id: okButton visible: activity.isTutorialMode anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: height * 0.5 } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio enabled: !tutorialInstruction.visible onClicked: Activity.checkAnswer() } Bar { id: bar content: BarEnumContent { value: help | home | ( activity.isTutorialMode ? level : 0) | reload | config} onHelpClicked: {displayDialog(dialogHelp)} onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reset() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/explore_farm_animals/AnimalLevels.qml b/src/activities/explore_farm_animals/AnimalLevels.qml index 01b053eec..aa8554168 100644 --- a/src/activities/explore_farm_animals/AnimalLevels.qml +++ b/src/activities/explore_farm_animals/AnimalLevels.qml @@ -1,119 +1,117 @@ /* GCompris - AnimalLevels.qml * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * Johnny Jazeix (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.6 import GCompris 1.0 import "../../core" import "explore-level.js" as Activity Image { id: animalImg width: animalWidth height: animalHeight sourceSize.width: width sourceSize.height: height fillMode: Image.PreserveAspectFit property string name: name property alias starVisible: star.visible property int questionId property string title property string description property string imageSource property string question property string audio signal displayDescription(var animal) SequentialAnimation { id: anim running: true loops: 1 NumberAnimation { target: animalImg property: "rotation" from: 0; to: 360 duration: 400 + Math.floor(Math.random() * 400) easing.type: Easing.InOutQuad } } Image { id: star - x: animalImg.width / 2.5 y: animalImg.height * 0.8 visible: false - source:"qrc:/gcompris/src/core/resource/star.png" } MultiPointTouchArea { id: touchArea anchors.centerIn: parent // Make the item big enough to be clicked easily width: Math.max(parent.width, 55 * ApplicationInfo.ratio) height: Math.max(parent.height, 55 * ApplicationInfo.ratio) touchPoints: [ TouchPoint { id: point1 } ] mouseEnabled: progressbar.value != progressbar.maximumValue && !items.bonus.isPlaying onPressed: { if(items.progressbar.value >= progressbar.maximumValue) { return } var questionTargetId = items.questionOrder[Activity.items.progressbar.value] Activity.items.instruction.visible = false - if (Activity.items.score.currentSubLevel == 1) { + if (Activity.items.score.currentSubLevel === 1) { if(animalImg.audio) { audioVoices.play(animalImg.audio); } displayDescription(animalImg) star.visible = true; } else { if (questionId === questionTargetId) { animWin.start(); items.progressbar.value ++; items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav"); Activity.nextSubSubLevel(); } else { items.bonus.bad("smiley") } } } } SequentialAnimation { id: animWin running: false loops: 1 NumberAnimation { target: animalImg property: "rotation" from: 0; to: 360 duration: 600 easing.type: Easing.InOutQuad } } } diff --git a/src/activities/football/Football.qml b/src/activities/football/Football.qml index 84249c0bb..e5aaf1a4a 100644 --- a/src/activities/football/Football.qml +++ b/src/activities/football/Football.qml @@ -1,176 +1,176 @@ /* GCompris - football.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (Qt Quick port) * Bharath M S (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.6 import "../../core" import "football.js" as Activity import GCompris 1.0 ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent signal start signal stop 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 field: field property alias border: border property alias ball: ball property alias line: line property alias tux: tux property alias moveTux: moveTux property alias moveUp: moveUp property alias moveDown: moveDown property alias bar: bar property alias bonus: bonus property alias timer: timer property GCSfx audioEffects: activity.audioEffects } onStart: { Activity.start(items) } onStop: { Activity.stop() } /* To modify when screen size changes */ onHeightChanged: { moveUp.to = 0 moveDown.to = background.height * 0.75 - tux.height moveTux.restart() } Image { id: field source: Activity.url + "background.svg" anchors.fill: parent Rectangle { id: border height: parent.height * 0.75 width: parent.width * 0.856 x: parent.width * 0.075 y: parent.height * 0.125 color: "transparent" Rectangle { id: line opacity: 0.0 color: "#ee4b4b" transformOrigin: Item.TopLeft } Image { id: ball source: Activity.url + "ball.svg" sourceSize.height: 50 * ApplicationInfo.ratio property real change z: 10 MultiPointTouchArea { anchors.fill: parent touchPoints: [ TouchPoint { id: point1 }] onReleased: { line.opacity = 0 Activity.startMotion(point1.x - ball.width / 2, point1.y - ball.height / 2) activity.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav") } onPressed: line.opacity = 1 onTouchUpdated: { var point = ball.mapToItem(border, point1.x, point1.y) Activity.drawLine(point.x, point.y, ball.x + ball.width/2, ball.y + ball.height/2) } } } Image { id: tux source: Activity.url+"tux_top.svg" sourceSize.height: 80 * ApplicationInfo.ratio x: border.width - tux.width y: border.height / 2 SequentialAnimation on y { id: moveTux loops: Animation.Infinite running: true PropertyAnimation { id: moveUp duration: 800 easing.type: Easing.InOutQuad } PropertyAnimation { id: moveDown duration: 800 easing.type: Easing.InOutQuad } } } } Rectangle { width: parent.width * 0.1 height: parent.height * 0.75 color: "blue" anchors.top: border.top anchors.left: border.right - z:10 + z: 10 opacity: 0.3 } } Timer { id: timer interval: 16; running: false; repeat: true onTriggered: Activity.ballMotion() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/gletters/FallingDomino.qml b/src/activities/gletters/FallingDomino.qml index 9f59baf81..312a72683 100644 --- a/src/activities/gletters/FallingDomino.qml +++ b/src/activities/gletters/FallingDomino.qml @@ -1,121 +1,121 @@ /* GCompris - FallingDomino.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: domino.width height: domino.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property string text property var dominoValues property bool wonState: false signal won onWon: { wonState = true particle.burst(30) fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible if (x + width >= parent.width) x = parent.width - width; } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return var chars = text.split(""); - if (chars[unmatchedIndex] == c) { + if (chars[unmatchedIndex] === c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } Domino { id: domino width: 120 * ApplicationInfo.ratio height: width / 2 visible: dominoValues.length != 0 value1: dominoValues[0] value2: dominoValues[1] isClickable: false ParticleSystemStarLoader { id: particle clip: false } } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gletters/FallingImage.qml b/src/activities/gletters/FallingImage.qml index cd14aa466..e8aeec7c2 100644 --- a/src/activities/gletters/FallingImage.qml +++ b/src/activities/gletters/FallingImage.qml @@ -1,130 +1,129 @@ /* GCompris - Word.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: image.width height: image.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property string text property alias image: image.source; property bool wonState: false signal won onWon: { wonState = true particle.burst(30) dropShadow.opacity = 0 fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible if (x + width >= parent.width) x = parent.width - width; } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 - onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return var chars = text.split(""); - if (chars[unmatchedIndex] == c) { + if (chars[unmatchedIndex] === c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } Image { id: image // FIXME, the size should be passed from the caller sourceSize.height: 106 * ApplicationInfo.ratio ParticleSystemStarLoader { id: particle clip: false } } DropShadow { id: dropShadow anchors.fill: image cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3.0 samples: 16 color: "#422a2a2a" source: image } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gletters/FallingWord.qml b/src/activities/gletters/FallingWord.qml index e51013b8f..5d85a3082 100644 --- a/src/activities/gletters/FallingWord.qml +++ b/src/activities/gletters/FallingWord.qml @@ -1,169 +1,165 @@ /* GCompris - Word.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: wordText.width height: wordText.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property alias text: wordText.text; property bool wonState: false property string mode signal won onWon: { wonState = true particle.burst(30) dropShadow.opacity = 0 fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible if (x + width >= parent.width) x = parent.width - width; } onUnmatchedIndexChanged: { if (unmatchedIndex <= 0) highlightedWordText.text = ""; else if (wordText.text.length > 0 && wordText.text.length >= unmatchedIndex) { highlightedWordText.text = wordText.text.substring(0, unmatchedIndex); /* Need to add the ZERO WIDTH JOINER to force joined char in Arabic and * Hangul: http://en.wikipedia.org/wiki/Zero-width_joiner * * FIXME: this works only on desktop systems, on android this * shifts the typed word a few pixels down. */ if (!ApplicationInfo.isMobile) highlightedWordText.text += "\u200C"; } } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 - onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return if(word.mode === 'letter') { // Only highlight letter if it is the good one unmatchedIndex = text === c ? c.length : 0 return (text === c) } else { var chars = text.split(""); - if (chars[unmatchedIndex] == c) { + if (chars[unmatchedIndex] === c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } GCText { id: wordText - text: "" fontSize: 35 font.bold: true color: "navy" style: Text.Outline styleColor: "white" ParticleSystemStarLoader { id: particle clip: false } GCText { id: highlightedWordText - anchors.fill: parent - text: "" fontSize: parent.fontSize font.bold: parent.font.bold color: "red" style: Text.Outline styleColor: "white" } } DropShadow { id: dropShadow anchors.fill: wordText cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3.0 samples: 16 color: "#422a2a2a" source: wordText } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gletters/Gletters.qml b/src/activities/gletters/Gletters.qml index a08e209fe..cf9ba9392 100644 --- a/src/activities/gletters/Gletters.qml +++ b/src/activities/gletters/Gletters.qml @@ -1,274 +1,270 @@ /* GCompris - gletters.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import GCompris 1.0 import "../../core" import "gletters.js" as Activity ActivityBase { id: activity // Overload this in your activity to change it // Put you default-.json files in it property string dataSetUrl: "qrc:/gcompris/src/activities/gletters/resource/" /* no need to display the configuration button for smallnumbers */ property bool configurationButtonVisible: true property bool uppercaseOnly: false /* mode of the activity, "letter" (gletters) or "word" (wordsgame):*/ property string mode: "letter" // Override if you want to replace texts by your image function getImage(key) { return "" } // Override if you want to replace texts by the domino function getDominoValues(key) { return [] } onStart: focus = true onStop: {} // When going on configuration, it steals the focus and re set it to the activity. // We need to set it back to the textinput item in order to have key events. onFocusChanged: { if(focus) { Activity.focusTextInput() } } pageComponent: Image { id: background source: activity.dataSetUrl + "background.svg" fillMode: Image.PreserveAspectCrop sourceSize.height: parent.height signal start signal stop // system locale by default property string locale: "system" Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property Item ourActivity: activity property GCAudio audioVoices: activity.audioVoices property alias background: background property alias bar: bar property alias bonus: bonus property alias wordlist: wordlist property alias score: score property alias keyboard: keyboard property alias wordDropTimer: wordDropTimer property GCSfx audioEffects: activity.audioEffects property alias locale: background.locale property alias textinput: textinput } onStart: { Activity.start(items, uppercaseOnly, mode); Activity.focusTextInput() } onStop: { Activity.stop() } TextInput { // Helper element to capture composed key events like french ô which // are not available via Keys.onPressed() on linux. Must be // disabled on mobile! id: textinput anchors.centerIn: background enabled: !ApplicationInfo.isMobile focus: true visible: false onTextChanged: { if (text != "") { Activity.processKeyPress(text); text = ""; } } } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox property alias uppercaseBox: uppercaseBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig label: qsTr("Select your locale") } } GCDialogCheckBox { id: uppercaseBox width: dialogActivityConfig.width text: qsTr("Uppercase only mode") checked: activity.uppercaseOnly } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["locale"]) { background.locale = dataToSave["locale"]; activity.uppercaseOnly = dataToSave["uppercaseMode"] === "true" ? true : false; } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; // Remove .UTF-8 if(newLocale.indexOf('.') != -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } var oldUppercaseMode = activity.uppercaseOnly activity.uppercaseOnly = dialogActivityConfig.configItem.uppercaseBox.checked dataToSave = {"locale": newLocale, "uppercaseMode": ""+activity.uppercaseOnly} background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale || oldUppercaseMode !== activity.uppercaseOnly) { background.stop(); background.start(); } } function setDefaultValues() { var localeUtf8 = background.locale; if(background.locale != "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { dialogActivityConfig.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: configurationButtonVisible ? (help | home | level | config) : (help | home | level)} onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus interval: 2000 Component.onCompleted: win.connect(Activity.nextLevel) } Score { id: score - anchors.top: undefined anchors.topMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.bottom: keyboard.top } VirtualKeyboard { id: keyboard - anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter width: parent.width - onKeypress: Activity.processKeyPress(text) - onError: console.log("VirtualKeyboard error: " + msg); } Wordlist { id: wordlist defaultFilename: activity.dataSetUrl + "default-en.json" // To switch between locales: xx_XX stored in configuration and // possibly correct xx if available (ie fr_FR for french but dataset is fr.) useDefault: false filename: "" onError: console.log("Gletters: Wordlist error: " + msg); } Timer { id: wordDropTimer repeat: false onTriggered: Activity.dropWord(); } } } diff --git a/src/activities/gnumch-equality/Monster.qml b/src/activities/gnumch-equality/Monster.qml index 6a0a132d0..37ef80d4d 100644 --- a/src/activities/gnumch-equality/Monster.qml +++ b/src/activities/gnumch-equality/Monster.qml @@ -1,87 +1,87 @@ /* GCompris - Monster.qml * * Copyright (C) 2014 Manuel Tondeur * * Authors: * Joe Neeman (spuzzzzzzz@gmail.com) (GTK+ version) * Manuel Tondeur (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.6 import "gnumch-equality.js" as Activity Creature { id: monster property int direction property var player function checkCell() { - if (index == player.index) { + if (index === player.index) { player.getCaught(-1) eating = true } if (monsters.checkOtherMonster(index)) { eating = true } } opacity: 0 onMovingOnChanged: { if (movingOn == false) { checkCell() } } onOpacityChanged: { if (opacity == 1) { checkCell() } } Timer { id: timerMove interval: 2000 running: true repeat: true onTriggered: { if (!moveTo(direction)) { var vertical = Math.floor(direction/2) var sign = Math.pow(-1,(direction)) y = y + sign * grid.cellHeight * vertical x = x - sign * grid.cellWidth * (vertical - 1) opacity = 0 } } } Behavior on opacity { NumberAnimation { id: animationEnd duration: 500 onRunningChanged: { if (!animationEnd.running && monster.opacity == 0) { monster.destroy() } } } } } diff --git a/src/activities/gnumch-equality/WarnMonster.qml b/src/activities/gnumch-equality/WarnMonster.qml index 70542d116..0ef367e45 100644 --- a/src/activities/gnumch-equality/WarnMonster.qml +++ b/src/activities/gnumch-equality/WarnMonster.qml @@ -1,63 +1,63 @@ /* GCompris - WarnMonster.qml * * Copyright (C) 2014 Manuel Tondeur * * Authors: * Joe Neeman (spuzzzzzzz@gmail.com) (GTK+ version) * Manuel Tondeur (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.6 import GCompris 1.0 import "../../core" Rectangle { - property var text : warningText + property alias text: warningText width: warningText.contentWidth * 1.1 height: warningText.height * 1.1 opacity: 0 border.width: 2 radius: 5 anchors.horizontalCenter: topPanel.horizontalCenter anchors.verticalCenter: topPanel.verticalCenter GCText { id: warningText text: qsTr("Be careful, a troggle!") fontSize: largeSize wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter color: "red" } onOpacityChanged: timerWarn.start() Timer { id: timerWarn interval: 2500 onTriggered: parent.opacity = 0 } Behavior on opacity { NumberAnimation { duration: 500 } } } diff --git a/src/activities/gnumch-equality/Warning.qml b/src/activities/gnumch-equality/Warning.qml index a80ad9317..2b2bfb8a4 100644 --- a/src/activities/gnumch-equality/Warning.qml +++ b/src/activities/gnumch-equality/Warning.qml @@ -1,152 +1,152 @@ /* GCompris - Warning.qml * * Copyright (C) 2014 Manuel Tondeur * * Authors: * Joe Neeman (spuzzzzzzz@gmail.com) (GTK+ version) * Manuel Tondeur (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.6 import GCompris 1.0 import "../../core" import "gnumch-equality.js" as Activity Rectangle { function hideWarning() { if (opacity > 0) { opacity = 0 if (Activity._currentLevel % 6 != 0) { spawningMonsters.start() timerActivateWarn.start() } muncher.movable = true monsters.setMovable(true) } } property string warningText: warning.text property string fault property var mArea: area function setFault(index) { - if (index == -1) { + if (index === -1) { fault = qsTr("You were eaten by a Troggle.") + "
" return } fault = qsTr("You ate a wrong number.") +"
" var num1 = modelCells.get(index).number1 var num2 = modelCells.get(index).number2 if (activity.type == "equality" || activity.type == "inequality") { if (activity.operator == " + ") { fault += num1 + " + " + num2 + " = " + (num1 + num2) } else { fault += num1 + " - " + num2 + " = " + (num1 - num2) } } else if (activity.type == "primes") { - if (num1 == 1) { + if (num1 === 1) { fault += qsTr("1 is not a prime number.") return } var divisors = [] for (var div = 2; div < num1; ++div) { if ((num1 / div) % 1 == 0) divisors.push(div) } fault += qsTr("%1 is divisible by %2").arg(num1).arg(divisors[0]) if (divisors.length > 2) { for (var div = 1; div < divisors.length - 1; ++div) { fault += ", " + divisors[div] } } fault += " " + qsTr("and") + " " + divisors[divisors.length - 1] + "." } else if (activity.type == "factors") { // First we find the multiples of the wrong number. var multiples = "" + num1*2 + ", " + num1*3 + ", " + num1*4 fault += qsTr("Multiples of %1 include %2, ").arg(num1).arg(multiples) fault += qsTr("but %1 is not a multiple of %2.").arg(Activity.getGoal()).arg(num1) } else if (activity.type == "multiples") { // First we find divisors of the wrong number. var divisors = [] for (var div = 1; div < Activity.getGoal() * 6; ++div) { if ((num1 / div) % 1 == 0) divisors.push(div) } fault += divisors[0] if (divisors.length > 2) { for (var div = 1; div < divisors.length - 1; ++div) { fault += ", " + divisors[div] } } fault += " " + qsTr("and %1 are the divisors of %2.").arg(divisors[divisors.length - 1]).arg(num1) } } width: 400 * ApplicationInfo.ratio height: 150 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter z: 3 border.width: 2 radius: 5 opacity: 0 color: "#00d635" onOpacityChanged: { if (opacity == 0) { muncher.opacity = 1 area.enabled = false } else { area.enabled = true } } GCText { id: warning anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: fault + "
" + qsTr("Press \"Return\" or click on me to continue.") fontSizeMode: Text.Fit minimumPointSize: 10 fontSize: 28 wrapMode: Text.WordWrap } Behavior on opacity { NumberAnimation { duration: 200 } } MouseArea { id: area anchors.fill: parent enabled: false } } diff --git a/src/activities/lang/ImageReview.qml b/src/activities/lang/ImageReview.qml index eb992d564..923711658 100644 --- a/src/activities/lang/ImageReview.qml +++ b/src/activities/lang/ImageReview.qml @@ -1,421 +1,421 @@ /* GCompris - lang.qml * * Copyright (C) Siddhesh suthar (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * 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 QtGraphicalEffects 1.0 import "../../core" import "lang.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core Item { id: imageReview anchors.fill: parent property alias category: categoryText.text property int wordListIndex // This is the current sub list of words property var word: rootItem.opacity == 1 ? items.wordList[wordListIndex][score.currentSubLevel - 1] : undefined // miniGames is list of miniGames // first element is Activity name, // second element is mode of miniGame // third element is the qml to load property var miniGames: [ ["QuizActivity", 1, "Quiz.qml"], ["QuizActivity", 2, "Quiz.qml"], ["QuizActivity", 3, "Quiz.qml"], ["SpellActivity", 1, "SpellIt.qml"] ] property var currentMiniGame property var loadedItems property bool started: rootItem.opacity == 1 // Start at last wordListIndex function start() { initLevel(wordListIndex) } // Start the image review at wordList sublesson function initLevel(wordListIndex_) { wordListIndex = wordListIndex_ score.currentSubLevel = 1 score.numberOfSubLevels = items.wordList[wordListIndex].length focus = true forceActiveFocus() miniGameLoader.source = "" currentMiniGame = -1 rootItem.opacity = 1 } function stop() { focus = false rootItem.opacity = 0 wordImage.changeSource('') wordText.changeText('') repeatItem.visible = false } onWordChanged: { if(word) { if (Activity.playWord(word.voice)) { word['hasVoice'] = true repeatItem.visible = true } else { word['hasVoice'] = false repeatItem.visible = false } wordImage.changeSource(word.image) wordText.changeText(word.translatedTxt) } } // Cheat codes to access mini games directly Keys.onPressed: { if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_1)) { initLevel(wordListIndex) event.accepted = true } if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_2)) { startMiniGame(0) event.accepted = true } if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_3)) { startMiniGame(1) event.accepted = true } if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_4)) { startMiniGame(2) event.accepted = true } if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_5)) { startMiniGame(3) event.accepted = true } } Keys.onEscapePressed: { Activity.launchMenuScreen() } Keys.onLeftPressed: { if( score.currentSubLevel > 1 ) { imageReview.prevWord() event.accepted = true } } Keys.onRightPressed: { imageReview.nextWord() event.accepted = true } Keys.onSpacePressed: { imageReview.nextWord() event.accepted = true } Keys.onEnterPressed: { imageReview.nextWord() event.accepted = true } Keys.onReturnPressed: { imageReview.nextWord() event.accepted = true } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true Activity.launchMenuScreen() } } Item { id: rootItem anchors.fill: parent opacity: 0 Behavior on opacity { PropertyAnimation { duration: 200 } } Rectangle { id: categoryTextbg parent: rootItem x: categoryText.x - 4 y: categoryText.y - 4 width: imageFrame.width + 8 height: categoryText.height + 8 color: "#5090ff" border.color: "#000000" border.width: 2 radius: 16 anchors { horizontalCenter: parent.horizontalCenter top: rootItem.top topMargin: 4 * ApplicationInfo.ratio } GCText { id: categoryText fontSize: mediumSize font.weight: Font.DemiBold width: parent.width - 8 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "white" wrapMode: Text.WordWrap } } Image { id: imageFrame parent: rootItem source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" sourceSize.width: Math.min((parent.width - previousWordButton.width * 2) * 0.8, (parent.height - categoryTextbg.height - wordTextbg.height - bar.height) * 1.1) anchors { horizontalCenter: parent.horizontalCenter top: categoryTextbg.bottom margins: 10 * ApplicationInfo.ratio } z: 11 Image { id: wordImage // Images are not svg width: Math.min(parent.width, parent.height) * 0.9 height: width anchors.centerIn: parent property string nextSource function changeSource(nextSource_) { nextSource = nextSource_ animImage.start() } SequentialAnimation { id: animImage PropertyAnimation { target: wordImage property: "opacity" to: 0 duration: 100 } PropertyAction { target: wordImage property: "source" value: wordImage.nextSource } PropertyAnimation { target: wordImage property: "opacity" to: 1 duration: 100 } } MouseArea { anchors.fill: parent enabled: rootItem.opacity == 1 onClicked: Activity.playWord(word.voice) } } Image { id: previousWordButton source: "qrc:/gcompris/src/core/resource/bar_previous.svg"; sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio visible: score.currentSubLevel > 1 ? true : false anchors { right: parent.left rightMargin: 30 top: parent.top topMargin: parent.height/2 - previousWordButton.height/2 } MouseArea { id: previousWordButtonArea anchors.fill: parent onClicked: imageReview.prevWord() } } Image { id: nextWordButton source: "qrc:/gcompris/src/core/resource/bar_next.svg"; sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio anchors { left: parent.right leftMargin: 30 top: parent.top topMargin: parent.height/2 - previousWordButton.height/2 } MouseArea { id: nextWordButtonArea anchors.fill: parent onClicked: imageReview.nextWord(); } } } Rectangle { id: wordTextbg parent: rootItem x: wordText.x - 4 y: wordText.y - 4 width: imageFrame.width height: wordText.height + 4 color: "#5090ff" border.color: "#000000" border.width: 2 radius: 16 anchors { top: imageFrame.bottom left: imageFrame.left margins: 10 * ApplicationInfo.ratio } GCText { id: wordText text: "" fontSize: largeSize font.weight: Font.DemiBold width: parent.width horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "white" wrapMode: Text.WordWrap property string nextWord function changeText(nextWord_) { nextWord = nextWord_ animWord.start() } SequentialAnimation { id: animWord PropertyAnimation { target: wordText property: "opacity" to: 0 duration: 100 } PropertyAction { target: wordText property: "text" value: wordText.nextWord } PropertyAnimation { target: wordText property: "opacity" to: 1 duration: 100 } } } } BarButton { id: repeatItem parent: rootItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 80 * ApplicationInfo.ratio z: 12 anchors { top: parent.top left: parent.left margins: 10 * ApplicationInfo.ratio } onClicked: Activity.playWord(imageReview.word.voice) Behavior on opacity { PropertyAnimation { duration: 200 } } } Score { id: score parent: rootItem anchors.bottom: undefined anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top } } Loader { id: miniGameLoader width: parent.width height: parent.height anchors.fill: parent asynchronous: false } function nextPressed() { - if(currentMiniGame == 0) { + if(currentMiniGame === 0) { nextSubLevel() } } function nextWord() { ++score.currentSubLevel; if(score.currentSubLevel == score.numberOfSubLevels + 1) { nextMiniGame() } } function prevWord() { --score.currentSubLevel } function startMiniGame(miniGameIndex) { currentMiniGame = miniGameIndex var mode = miniGames[miniGameIndex][1]; var itemToLoad = miniGames[miniGameIndex][2]; // Starting a minigame we don't wan't pending voices to play Activity.clearVoiceQueue() // preparing the wordList var wordList = Core.shuffle(items.wordList[wordListIndex]).slice() miniGameLoader.source = itemToLoad; var loadedItems = miniGameLoader.item rootItem.opacity = 0 focus = false // Initiate the loaded item mini game // Some Mini Games may not start because they miss voices // In this case we try the next one if(!loadedItems.init(loadedItems, wordList, mode)) nextMiniGame() } //called by a miniGame when it is won function nextMiniGame() { if(currentMiniGame < miniGames.length - 1) { startMiniGame(++currentMiniGame) } else { Activity.markProgress() if(wordListIndex < items.wordList.length - 1) { initLevel(wordListIndex + 1) } else { Activity.launchMenuScreen() } } } } diff --git a/src/activities/louis-braille/LouisBraille.qml b/src/activities/louis-braille/LouisBraille.qml index e91bf804c..29d70dfba 100644 --- a/src/activities/louis-braille/LouisBraille.qml +++ b/src/activities/louis-braille/LouisBraille.qml @@ -1,291 +1,291 @@ /* GCompris - louis-braille.qml * * Copyright (C) 2014 Arkit Vora * * Authors: * (GTK+ version) * Arkit Vora (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.6 import GCompris 1.0 import "../../core" import "../braille_alphabets" import "louis-braille.js" as Activity import "louis_braille_data.js" as Dataset import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop 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 int count: 0 property var dataset: Dataset.dataset } onStart: { Activity.start(items) } onStop: { Activity.stop() } Image { id: charList y: 20 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter source: Activity.url + "top_back.svg" sourceSize.width: parent.width * 0.90 Row { id: row spacing: 10 * ApplicationInfo.ratio anchors.centerIn: charList anchors.horizontalCenterOffset: 5 width: parent.width Repeater { id: cardRepeater model: ["L","O","U","I","S"," ","B","R","A","I","L","L","E"] // workaround for https://bugreports.qt.io/browse/QTBUG-72643 (qml binding with global variable in Repeater do not work) property alias rowSpacing: row.spacing Item { id: inner height: charList.height * 0.95 width: (charList.width - 13 * cardRepeater.rowSpacing)/ 13 Rectangle { id: rect1 width: charList.width / 13 height: ins.height border.width: 3 opacity: index == 5 ? 0 :1 border.color: "black" color: "white" BrailleChar { id: ins width: parent.width * 0.9 anchors.centerIn: parent clickable: false brailleChar: modelData } } GCText { text: modelData font.weight: Font.DemiBold style: Text.Outline styleColor: "white" color: "black" fontSize: regularSize anchors { top: rect1.bottom topMargin: 4 * ApplicationInfo.ratio horizontalCenter: rect1.horizontalCenter } } } } } } Keys.onRightPressed: background.next() Keys.onLeftPressed: background.previous() function previous() { if(items.count == 0) items.count = items.dataset.length - 1 else items.count-- } function next() { if(items.count == items.dataset.length - 1) { list.shuffle() items.count = 0 list.visible = true } else { items.count++ } } Image { id: previous anchors.right: img.left anchors.rightMargin: 20 * ApplicationInfo.ratio anchors.verticalCenter: img.verticalCenter source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.height: 80 * ApplicationInfo.ratio Behavior on scale { PropertyAnimation { duration: 100} } MouseArea { anchors.fill: parent hoverEnabled: true onEntered: previous.scale = 1.1 onExited: previous.scale = 1 onClicked: background.previous() } } // The image description Rectangle { id: info_rect border.color: "black" border.width: 1 * ApplicationInfo.ratio color: "white" width: parent.width * 0.9 height: background.height/5 anchors.top: charList.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 5 * ApplicationInfo.ratio GCText { - id:info + id: info color: "black" anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter width: parent.width * 0.94 height: info_rect.height wrapMode: Text.WordWrap fontSize: regularSize text: items.dataset[items.count].text fontSizeMode: Text.Fit } } // Image and date Image { id: img anchors.top: info_rect.bottom anchors.topMargin: 10 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter sourceSize.height: parent.height - (charList.height + info_rect.height + bar.height) height: (parent.height - (charList.height + info_rect.height + bar.height)) * 0.8 width: parent.width * 0.7 source: items.dataset[items.count].img fillMode: Image.PreserveAspectFit Rectangle { id: year_rect border.color: "black" border.width: 1 color: "white" width: year.width * 1.1 height: year.height * 1.1 anchors { bottom: img.bottom horizontalCenter: img.horizontalCenter bottomMargin: 5 * ApplicationInfo.ratio } GCText { id: year color: "black" fontSize: regularSize anchors.centerIn: year_rect text: items.dataset[items.count].year } } MouseArea { anchors.fill: parent onClicked: background.next() } } Image { id: next anchors.left: img.right anchors.leftMargin: 20 * ApplicationInfo.ratio anchors.verticalCenter: img.verticalCenter source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.height: 80 * ApplicationInfo.ratio Behavior on scale { PropertyAnimation { duration: 100} } MouseArea { anchors.fill: parent hoverEnabled: true onEntered: next.scale = 1.1 onExited: next.scale = 1 onClicked: background.next() } } Keys.onUpPressed: list.up() Keys.onDownPressed: list.down() Keys.onEnterPressed: list.space() Keys.onReturnPressed: list.space() Keys.onSpacePressed: list.space() ReorderList { id: list visible: false bonus: bonus signal shuffle onShuffle: { containerModel.clear() var dataitems = Object.create(items.dataset) dataitems = Core.shuffle(dataitems) for(var i = 0 ; i < dataitems.length ; i++) { containerModel.append(dataitems[i]); } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { items.count = 0 list.visible = false } } Bonus { id: bonus Component.onCompleted: win.connect(list.shuffle) } } } diff --git a/src/activities/magic-hat-plus/MagicHatPlus.qml b/src/activities/magic-hat-plus/MagicHatPlus.qml index 64a90e6cd..c8c640741 100644 --- a/src/activities/magic-hat-plus/MagicHatPlus.qml +++ b/src/activities/magic-hat-plus/MagicHatPlus.qml @@ -1,29 +1,29 @@ /* GCompris - MagicHat.qml * * Copyright (C) 2014 Thibaut ROMAIN * * Authors: * (GTK+ version) * Thibaut ROMAIN (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.6 import "../../core" import "../magic-hat-minus/" -MagicHat{ +MagicHat { mode: "plus" } diff --git a/src/activities/mazerelative/Mazerelative.qml b/src/activities/mazerelative/Mazerelative.qml index ddc0f909a..c9422650b 100644 --- a/src/activities/mazerelative/Mazerelative.qml +++ b/src/activities/mazerelative/Mazerelative.qml @@ -1,26 +1,27 @@ /* GCompris - mazerelative.qml * * Copyright (C) 2014 Stephane Mankowski * * Authors: * Bastiaan Verhoef (GTK+ version) * Stephane Mankowski (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.6 import "../maze" + Maze { relativeMode: true } diff --git a/src/activities/memory/CardItem.qml b/src/activities/memory/CardItem.qml index fa5e8531e..408cdbe5c 100644 --- a/src/activities/memory/CardItem.qml +++ b/src/activities/memory/CardItem.qml @@ -1,152 +1,152 @@ /* gcompris - CardItem.qml * * Copyright (C) 2014 JB BUTET * * Authors: * Bruno Coudoin (GTK+ version) * JB BUTET (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.6 import GCompris 1.0 import "../../core" import "memory.js" as Activity Flipable { id: card property var pairData property bool isBack: true property bool isShown: false property bool isFound: false property bool tuxTurn property GCAudio audioVoices property GCSfx audioEffects onIsFoundChanged: { opacity = 0 timer.start() } Timer { id: timer interval: 100 running: false repeat: false onTriggered: particles.burst(50) } ParticleSystemStarLoader { id: particles clip: false } Timer { id: animationTimer interval: 1500 running: false repeat: false onTriggered: selectionReady() } back: Image { source: card.pairData.emptyCard sourceSize.width: parent.width fillMode: Image.PreserveAspectFit anchors.centerIn: parent anchors.fill: parent Image { id: contentImage source: card.pairData.image width: parent.paintedWidth * 0.9 height: parent.paintedHeight * 0.9 sourceSize.width: contentImage.width sourceSize.height: contentImage.height anchors.centerIn: parent fillMode: Image.PreserveAspectFit } GCText { anchors.centerIn: parent fontSize: largeSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" font.bold: true style: Text.Outline styleColor: "white" text: card.pairData.text } } // Warning front and back property are reversed. Could not find // a way to display back at start time without this trick front: Image { fillMode: Image.PreserveAspectFit source: card.pairData.back sourceSize.width: parent.width anchors.centerIn: parent anchors.fill: parent } transform: Rotation { id: rotation origin.x: card.width / 2 origin.y: card.height / 2 axis.x: 0; axis.y: 1; axis.z: 0 angle: 0 } transitions: Transition { NumberAnimation { target: rotation; property: "angle"; duration: 750 } } MouseArea { anchors.fill: parent enabled: card.isBack && !card.isFound && !card.tuxTurn && items.selectionCount < 2 onClicked: selected() } function selected() { card.isBack = false card.isShown = true items.selectionCount++ animationTimer.start() audioEffects.play(Activity.url + "card_flip.wav") } function selectionReady() { var pairs = Activity.addPlayQueue(card) var win = Activity.reverseCardsIfNeeded() if(tuxTurn && win || tuxTurn && !pairs) Activity.tuxPlay() if (card.pairData.sound) { audioVoices.play(card.pairData.sound) } } Behavior on opacity { NumberAnimation { duration: 1000 } } - states : [ + states: [ State { name: "front" PropertyChanges { target: rotation; angle: 180 } when: !card.isBack } ] } diff --git a/src/activities/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index cef63ac20..9926f4a90 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,425 +1,425 @@ /* GCompris - NoteNames.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 QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyNoteBindings = {} keyNoteBindings[Qt.Key_1] = 'C' keyNoteBindings[Qt.Key_2] = 'D' keyNoteBindings[Qt.Key_3] = 'E' keyNoteBindings[Qt.Key_4] = 'F' keyNoteBindings[Qt.Key_5] = 'G' keyNoteBindings[Qt.Key_6] = 'A' keyNoteBindings[Qt.Key_7] = 'B' if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { if(keyNoteBindings[event.key]) { // If the key pressed matches the note, pass the correct answer as parameter. isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false function getTranslatedNoteName(noteName) { for(var i = 0; i < doubleOctave.keyNames.length; i++) { if(doubleOctave.keyNames[i][0] == noteName) return doubleOctave.keyNames[i][1] } return "" } onVisibleChanged: { - if(Activity.targetNotes[0] == undefined) + if(Activity.targetNotes[0] === undefined) text = "" else if(items.isTutorialMode) text = qsTr("New note: %1").arg(getTranslatedNoteName(Activity.targetNotes[0])) else text = getTranslatedNoteName(Activity.newNotesSequence[Activity.currentNoteIndex]) } property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } ProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage maximumValue: 100 visible: !items.isTutorialMode anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" softColorOpacity: 0 isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] property var keyNames: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } Component.onCompleted: doubleOctave.keyNames = whiteKeyNoteLabelsArray } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow iconsWidth: 0 visible: false } 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: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml index c708f84d1..cefa05337 100644 --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -1,563 +1,563 @@ /* GCompris - Piano_composition.qml * * Copyright (C) 2016 Johnny Jazeix * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * Timothée Giet (refactoring) * * 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 QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "qrc:/gcompris/src/core/core.js" as Core import "piano_composition.js" as Activity import "melodies.js" as Dataset ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true property bool horizontalLayout: background.width >= background.height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(event.key >= Qt.Key_1 && event.key <= Qt.Key_7 && keyboardBindings[event.key] < piano.whiteKeyNoteLabels.length) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { piano.currentOctaveNb-- } if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { piano.currentOctaveNb++ } if(event.key === Qt.Key_Delete) { optionsRow.clearButtonClicked() } if(event.key === Qt.Key_Backspace) { optionsRow.undoButtonClicked() } if(event.key === Qt.Key_Space) { multipleStaff.play() } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { - if(piano.keyRepeater.itemAt(i) == undefined) + if(piano.keyRepeater.itemAt(i) === undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- - if(keyNumber == 0) + if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias bonus: bonus property alias multipleStaff: multipleStaff property string staffLength: "short" property alias melodyList: melodyList property alias file: file property alias piano: piano property alias optionsRow: optionsRow property alias lyricsArea: lyricsArea } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string currentType: "Quarter" property string restType: "Whole" property string clefType: bar.level == 2 ? "Bass" : "Treble" property bool isLyricsMode: (optionsRow.lyricsOrPianoModeIndex === 1) && optionsRow.lyricsOrPianoModeOptionVisible property int layoutMargins: 5 * ApplicationInfo.ratio File { id: file onError: console.error("File error: " + msg) } Item { id: clickedOptionMessage signal show(string message) onShow: { messageText.text = message messageAnimation.stop() messageAnimation.start() } height: width * 0.4 visible: false anchors.top: optionsRow.bottom anchors.horizontalCenter: optionsRow.horizontalCenter z: 5 Rectangle { id: messageRectangle width: messageText.contentWidth + 5 height: messageText.height + 5 anchors.centerIn: messageText color: "black" opacity: 0.5 border.width: 3 border.color: "black" radius: 15 } GCText { id: messageText anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit color: "white" } SequentialAnimation { id: messageAnimation onStarted: clickedOptionMessage.visible = true PauseAnimation { duration: 1000 } NumberAnimation { targets: [messageRectangle, messageText] property: "opacity" to: 0 duration: 200 } onStopped: { clickedOptionMessage.visible = false messageRectangle.opacity = 0.5 messageText.opacity = 1 } } } MelodyList { id: melodyList onClose: { visible = false piano.enabled = true focus = false activity.focus = true } } Rectangle { id: instructionBox radius: 10 width: background.width * 0.75 height: background.height * 0.15 anchors.right: parent.right opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { id: instructionText color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: Activity.instructions[bar.level - 1].text } } MultipleStaff { id: multipleStaff nbStaves: 2 clef: clefType coloredNotes: ['C','D', 'E', 'F', 'G', 'A', 'B'] noteHoverEnabled: true anchors.margins: layoutMargins onNoteClicked: { if(selectedIndex === noteIndex) selectedIndex = -1 else { selectedIndex = noteIndex background.clefType = musicElementModel.get(selectedIndex).soundPitch_ playNoteAudio(musicElementModel.get(selectedIndex).noteName_, musicElementModel.get(selectedIndex).noteType_, background.clefType, musicElementRepeater.itemAt(selectedIndex).duration) } } } GCButtonScroll { id: multipleStaffFlickButton anchors.left: multipleStaff.right anchors.verticalCenter: multipleStaff.verticalCenter width: bar.height * 0.3 height: width * 2 onUp: multipleStaff.flickableStaves.flick(0, multipleStaff.height * 1.3) onDown: multipleStaff.flickableStaves.flick(0, -multipleStaff.height * 1.3) upVisible: multipleStaff.flickableStaves.visibleArea.yPosition > 0 downVisible: (multipleStaff.flickableStaves.visibleArea.yPosition + multipleStaff.flickableStaves.visibleArea.heightRatio) < 1 } Item { id: pianoLayout width: multipleStaff.width + multipleStaffFlickButton.width height: multipleStaff.height anchors.margins: layoutMargins PianoOctaveKeyboard { id: piano height: parent.height width: parent.width * 0.8 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter blackLabelsVisible: [3, 4, 5, 6, 7, 8].indexOf(items.bar.level) == -1 ? false : true useSharpNotation: bar.level != 4 blackKeysEnabled: bar.level > 2 visible: !background.isLyricsMode currentOctaveNb: (background.clefType === "Bass") ? 0 : 1 onNoteClicked: { parent.addMusicElementAndPushToStack(note, currentType) } } function addMusicElementAndPushToStack(noteName, noteType, elementType) { if(noteType === "Rest") elementType = "rest" - else if(elementType == undefined) + else if(elementType === undefined) elementType = "note" var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement(elementType, noteName, noteType, false, true, background.clefType) } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: parent.width * 0.1 width: sourceSize.width height: parent.height fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb > 0) && piano.visible anchors.right: piano.left anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb-- } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: parent.width * 0.1 width: sourceSize.width height: parent.height fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb < piano.maxNbOctaves - 1) && piano.visible anchors.left: piano.right anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb++ } } LyricsArea { id: lyricsArea width: parent.width height: parent.height anchors.fill: pianoLayout } } GCCreationHandler { id: creationHandler onFileLoaded: { // We need to draw the notes twice since we first need to count the number of staffs needed for the melody (we get that from // the 1st redraw call), then we redraw the 2nd time to actually display the notes perfectly. This is done because for some reason, the // staves model is updated slower than the addition of notes, so the notes aggregates in their default position instead of // their required position, due to unavailability of the updated staff at that instant. So calculating the number of required staffs first seems the only solution for now. multipleStaff.redraw(data) multipleStaff.redraw(data) lyricsArea.resetLyricsArea() } onClose: { optionsRow.lyricsOrPianoModeIndex = 0 } } OptionsRow { id: optionsRow anchors.margins: layoutMargins anchors.left: background.left iconsWidth: 0 noteOptionsVisible: bar.level > 4 playButtonVisible: true keyOption.clefButtonVisible: bar.level > 2 clearButtonVisible: true undoButtonVisible: true openButtonVisible: bar.level > 6 saveButtonVisible: bar.level > 6 changeAccidentalStyleButtonVisible: bar.level >= 4 lyricsOrPianoModeOptionVisible: bar.level > 6 restOptionsVisible: bar.level > 5 bpmVisible: true onUndoButtonClicked: { Activity.undoChange() } onClearButtonClicked: { if((multipleStaff.musicElementModel.count > 1) && multipleStaff.selectedIndex === -1) { Core.showMessageDialog(main, qsTr("You have not selected any note. Do you want to erase all the notes?"), qsTr("Yes"), function() { Activity.undoStack = [] lyricsArea.resetLyricsArea() multipleStaff.eraseAllNotes() multipleStaff.nbStaves = 2 }, qsTr("No"), null, null ) } else if((multipleStaff.musicElementModel.count > 1) && !multipleStaff.musicElementModel.get(multipleStaff.selectedIndex).isDefaultClef_) { var noteIndex = multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.eraseNote(noteIndex) } } onOpenButtonClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onSaveButtonClicked: { var notesToSave = multipleStaff.createNotesBackup() // add bpm at start notesToSave.unshift({"bpm": multipleStaff.bpmValue}); creationHandler.saveWindow(notesToSave) } keyOption.onClefAdded: { var insertingIndex = multipleStaff.selectedIndex === -1 ? multipleStaff.musicElementModel.count : multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement("clef", "", "", false, false, background.clefType) if(background.clefType === "Bass") piano.currentOctaveNb = 0 else piano.currentOctaveNb = 1 } onBpmDecreased: { if(multipleStaff.bpmValue - 1 >= 1) multipleStaff.bpmValue-- } onBpmIncreased: { multipleStaff.bpmValue++ } onEmitOptionMessage: clickedOptionMessage.show(message) } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 10 width: dialogActivityConfig.width height: dialogActivityConfig.height GCText { text: qsTr("Select the type of melody to load.") fontSizeMode: mediumSize } Button { text: qsTr("Pre-defined melodies") onClicked: { melodyList.melodiesModel.clear() var dataset = Dataset.get() for(var i = 0; i < dataset.length; i++) { melodyList.melodiesModel.append(dataset[i]) } melodyList.visible = true piano.enabled = false melodyList.forceActiveFocus() dialogActivityConfig.close() } width: 150 * ApplicationInfo.ratio height: 60 * ApplicationInfo.ratio style: GCButtonStyle { theme: "dark" } } Button { text: qsTr("Your saved melodies") onClicked: { creationHandler.loadWindow() dialogActivityConfig.close() } width: 150 * ApplicationInfo.ratio height: 60 * ApplicationInfo.ratio style: GCButtonStyle { theme: "dark" } } } } onClose: home() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } states: [ State { name: "hScreen" when: horizontalLayout PropertyChanges { target: clickedOptionMessage width: background.width / 12 } AnchorChanges { target: optionsRow anchors.top: instructionBox.bottom } PropertyChanges { target: optionsRow columns: 11 iconsWidth: background.width / 15 } AnchorChanges { target: multipleStaff anchors.left: background.horizontalCenter anchors.top: optionsRow.bottom } PropertyChanges { target: multipleStaff width: background.width * 0.5 - multipleStaffFlickButton.width - layoutMargins * 3 height: background.height - instructionBox.height - optionsRow.height - bar.height - layoutMargins * 4 } AnchorChanges { target: pianoLayout anchors.left: background.left anchors.top: optionsRow.bottom } }, State { name: "vScreen" when: !horizontalLayout PropertyChanges { target: clickedOptionMessage width: background.width / 6 } AnchorChanges{ target: optionsRow anchors.top: background.top } PropertyChanges { target: optionsRow columns: 1 iconsWidth: (background.height - bar.height) / 12 } AnchorChanges { target: multipleStaff anchors.left: optionsRow.right anchors.top: instructionBox.bottom } PropertyChanges { target: multipleStaff width: background.width - multipleStaffFlickButton.width - optionsRow.width - layoutMargins * 3 height: (background.height - instructionBox.height - bar.height - layoutMargins * 4) * 0.5 } AnchorChanges { target: pianoLayout anchors.left: optionsRow.right anchors.top: multipleStaff.bottom } } ] } } diff --git a/src/activities/play_piano/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml index 0eae358a2..ffb41c7e8 100644 --- a/src/activities/play_piano/PlayPiano.qml +++ b/src/activities/play_piano/PlayPiano.qml @@ -1,331 +1,331 @@ /* GCompris - PlayPiano.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (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.6 import QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "../piano_composition" import "play_piano.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true property bool horizontalLayout: width >= height * 1.2 pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_8] = 7 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(piano.whiteKeysEnabled && !iAmReady.visible) { if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } else if(event.key === Qt.Key_Space) { multipleStaff.play() } else if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { Activity.undoPreviousAnswer() } } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { - if(piano.keyRepeater.itemAt(i) == undefined) + if(piano.keyRepeater.itemAt(i) === undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias multipleStaff: multipleStaff property alias piano: piano property alias bar: bar property alias bonus: bonus property alias score: score property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias parser: parser property string mode: "coloredNotes" } onStart: { dialogActivityConfig.getInitialConfiguration() Activity.start(items) } onStop: { Activity.stop() } property string clefType: (items.bar.level <= 5) ? "Treble" : "Bass" Timer { id: introductoryAudioTimer interval: 4000 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 5 width: horizontalLayout ? parent.width / 10 : (parent.width - instruction.x - instruction.width - 1.5 * anchors.rightMargin) } Rectangle { id: instruction radius: 10 width: background.width * 0.6 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: qsTr("Click on the piano keys that match the given notes.") } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.8 height: horizontalLayout ? parent.height * 0.85 : parent.height * 0.58 nbStaves: 1 clef: clefType coloredNotes: (items.mode === "coloredNotes") ? ['C', 'D', 'E', 'F', 'G', 'A', 'B'] : [] isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: instruction.bottom anchors.topMargin: horizontalLayout ? parent.height * 0.02 : parent.height * 0.15 onNoteClicked: { playNoteAudio(musicElementModel.get(noteIndex).noteName_, musicElementModel.get(noteIndex).noteType_, musicElementModel.get(noteIndex).soundPitch_) } centerNotesPosition: true } PianoOctaveKeyboard { id: piano width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.7 height: parent.height * 0.3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 20 blackLabelsVisible: ([4, 5, 9, 10].indexOf(items.bar.level) != -1) blackKeysEnabled: blackLabelsVisible && !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeysEnabled: !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeyNoteLabelsTreble: [ whiteKeyNoteLabelsArray.slice(18, 26) ] whiteKeyNoteLabelsBass: [ whiteKeyNoteLabelsArray.slice(11, 19)] onNoteClicked: { multipleStaff.playNoteAudio(note, "Quarter", clefType, 500) Activity.checkAnswer(note) } useSharpNotation: true } Rectangle { id: optionDeck width: optionsRow.changeAccidentalStyleButtonVisible ? optionsRow.iconsWidth * 3.3 : optionsRow.iconsWidth * 2.2 height: optionsRow.iconsWidth * 1.1 color: "white" opacity: 0.5 radius: 10 y: horizontalLayout ? piano.y : multipleStaff.y / 2 + instruction.height - height / 2 x: horizontalLayout ? multipleStaff.x + multipleStaff.width + 25 : background.width / 2 - width / 2 } OptionsRow { id: optionsRow anchors.centerIn: optionDeck playButtonVisible: true undoButtonVisible: true onUndoButtonClicked: Activity.undoPreviousAnswer() } ExclusiveGroup { id: configOptions } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias coloredNotesModeBox: coloredNotesModeBox property alias colorlessNotesModeBox: colorlessNotesModeBox GCDialogCheckBox { id: coloredNotesModeBox width: column.width - 50 text: qsTr("Display colored notes.") checked: items.mode === "coloredNotes" exclusiveGroup: configOptions onCheckedChanged: { if(coloredNotesModeBox.checked) { items.mode = "coloredNotes" } } } GCDialogCheckBox { id: colorlessNotesModeBox width: coloredNotesModeBox.width text: qsTr("Display colorless notes.") checked: items.mode === "colorlessNotes" exclusiveGroup: configOptions onCheckedChanged: { if(colorlessNotesModeBox.checked) { items.mode = "colorlessNotes" } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] } onSaveData: dataToSave["mode"] = items.mode onClose: { home() } onVisibleChanged: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config | reload } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/readingh/Readingh.qml b/src/activities/readingh/Readingh.qml index c0012a041..7eb3640cd 100644 --- a/src/activities/readingh/Readingh.qml +++ b/src/activities/readingh/Readingh.qml @@ -1,302 +1,302 @@ /* GCompris - readingh.qml * * Copyright (C) 2015 Johnny Jazeix * * Authors: * Bruno Coudoin (GTK+ version) * Johnny Jazeix (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.6 import GCompris 1.0 import "../../core" import "readingh.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity onStart: focus = true onStop: {} /* mode of the activity, "readingh" (horizontal) or "readingv" (vertical):*/ property string mode: "readingh" pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "reading-bg.svg" signal start signal stop sourceSize.width: parent.width fillMode: Image.Stretch Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } // system locale by default property string locale: "system" // 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 alias wordlist: wordlist property alias wordDropTimer: wordDropTimer property alias locale: background.locale property alias iAmReady: iAmReady property alias answerButtonFound: answerButtonFound property alias answerButtonNotFound: answerButtonNotFound property alias answerButtonsFlow: answerButtonsFlow property alias wordDisplayRepeater: wordDisplayRepeater property string textToFind property int currentIndex property bool buttonsBlocked: false } onStart: { Activity.start(items, mode) } onStop: { Activity.stop() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig width: dialogActivityConfig.width label: qsTr("Select your locale") } } } } } onClose: home() onLoadData: { if(dataToSave) { if(dataToSave["locale"]) { background.locale = dataToSave["locale"]; } } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; // Remove .UTF-8 - if(newLocale.indexOf('.') != -1) { + if(newLocale.indexOf('.') !== -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } dataToSave = { "locale": newLocale, } background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale) { background.stop(); wordDisplayList.layoutDirection = Core.isLeftToRightLocale(background.locale) ? Qt.LeftToRight : Qt.RightToLeft; background.start(); } } function setDefaultValues() { var localeUtf8 = background.locale; if(background.locale != "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { dialogActivityConfig.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus // Do not pass automatically at next level, allowing the child to do more than one try, or add sublevels? Component.onCompleted: { win.connect(resetClickInProgress) loose.connect(resetClickInProgress) } } function resetClickInProgress() { items.buttonsBlocked = false Activity.initLevel() } Flow { id: wordDisplayList spacing: 20 x: 70/800*parent.width y: 100/600*parent.height - 40 * ApplicationInfo.ratio width: 350/800*parent.width-x height: 520/600*parent.height-y - 40 * ApplicationInfo.ratio flow: mode == "readingh" ? Flow.LeftToRight : Flow.TopToBottom layoutDirection: Core.isLeftToRightLocale(locale) ? Qt.LeftToRight : Qt.RightToLeft Repeater { id: wordDisplayRepeater model: Activity.words property int idToHideBecauseOverflow: 0 delegate: GCText { text: modelData color: "#373737" opacity: iAmReady.visible ? false : (index == items.currentIndex ? 1 : 0) onOpacityChanged: { /* Handle case where we go over the image On these cases, we hide all above items to restart to 0 As we don't replay the same level and always replace the model, we do not care about restoring visible to true */ if((x+width > wordDisplayList.width) || (y+height > wordDisplayList.height)) { var i = wordDisplayRepeater.idToHideBecauseOverflow; for(; i < index; ++i) { wordDisplayRepeater.itemAt(i).visible=false } wordDisplayRepeater.idToHideBecauseOverflow = i } } } } } GCText { id: wordToFindBox x: 430/800*parent.width y: 90/600*parent.height text: qsTr("Check if the word
%1
is displayed").arg(items.textToFind) color: "#373737" horizontalAlignment: Text.AlignHCenter width: background.width/3 height: background.height/5 fontSizeMode: Text.Fit } ReadyButton { id: iAmReady onClicked: Activity.run() x: background.width / 2 y: background.height / 2.2 anchors.verticalCenter: undefined anchors.horizontalCenter: undefined theme: "light" } Flow { id: answerButtonsFlow x: iAmReady.x y: iAmReady.y width: wordToFindBox.width AnswerButton { id : answerButtonFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 60 * ApplicationInfo.ratio textLabel: qsTr("Yes, I saw it!") isCorrectAnswer: Activity.words ? Activity.words.indexOf(items.textToFind) != -1 : false onCorrectlyPressed: bonus.good("flower") onIncorrectlyPressed: bonus.bad("flower") blockAllButtonClicks: items.buttonsBlocked onPressed: { items.buttonsBlocked = true } } AnswerButton { id : answerButtonNotFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 60 * ApplicationInfo.ratio textLabel: qsTr("No, it was not there!") isCorrectAnswer: !answerButtonFound.isCorrectAnswer onCorrectlyPressed: bonus.good("flower") onIncorrectlyPressed: bonus.bad("flower") blockAllButtonClicks: items.buttonsBlocked onPressed: { items.buttonsBlocked = true } } } Wordlist { id: wordlist defaultFilename: Activity.dataSetUrl + "default-en.json" // To switch between locales: xx_XX stored in configuration and // possibly correct xx if available (ie fr_FR for french but dataset is fr.) useDefault: false filename: "" onError: console.log("Reading: Wordlist error: " + msg); } Timer { id: wordDropTimer repeat: true interval: 1000 onTriggered: Activity.dropWord(); } } } diff --git a/src/activities/redraw/Redraw.qml b/src/activities/redraw/Redraw.qml index 6011df873..5c411c637 100644 --- a/src/activities/redraw/Redraw.qml +++ b/src/activities/redraw/Redraw.qml @@ -1,440 +1,440 @@ /* GCompris - redraw.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "redraw.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property bool symmetry: false pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) property bool landscape: width >= height signal start signal stop 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 int colorSelector: 0 property alias userModel: userModel property int numberOfColumn property int numberOfColor property int numberOfLine: targetModelData.length / numberOfColumn property alias targetModel: targetModel property var targetModelData } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onPressed: { if(event.key >= Qt.Key_0 && event.key < Qt.Key_0 + items.numberOfColor) items.colorSelector = event.key - Qt.Key_0 - if(event.key == Qt.Key_Backspace) + if(event.key === Qt.Key_Backspace) userModel.clearCurrentItem() } Keys.onEnterPressed: userModel.paintCurrentItem() Keys.onReturnPressed: userModel.paintCurrentItem() Keys.onSpacePressed: userModel.paintCurrentItem() Keys.onDeletePressed: userModel.clearCurrentItem() Keys.onRightPressed: userModel.moveCurrentIndexRight() Keys.onLeftPressed: userModel.moveCurrentIndexLeft() Keys.onDownPressed: userModel.moveCurrentIndexDown() Keys.onUpPressed: userModel.moveCurrentIndexUp() // For creating new content, dump the drawing on the console Keys.onTabPressed: Activity.dump() Row { anchors { top: parent.top right: parent.right left: parent.left bottom: bar.top } anchors.margins: 10 spacing: 20 // The color selector Flickable { id: flickable interactive: true width: 70 * ApplicationInfo.ratio height: background.height boundsBehavior: Flickable.StopAtBounds contentHeight: items.numberOfColor * width bottomMargin: bar.height Column { id: colorSelector Repeater { model: items.numberOfColor Item { width: flickable.width height: width Image { id: img source: Activity.url + Activity.colorShortcut[modelData] + ".svg" sourceSize.width: parent.width z: iAmSelected ? 10 : 1 property bool iAmSelected: modelData == items.colorSelector states: [ State { name: "notclicked" when: !img.iAmSelected && !mouseArea.containsMouse PropertyChanges { target: img scale: 0.8 } }, State { name: "clicked" when: mouseArea.pressed PropertyChanges { target: img scale: 0.7 } }, State { name: "hover" when: mouseArea.containsMouse PropertyChanges { target: img scale: 1.1 } }, State { name: "selected" when: img.iAmSelected PropertyChanges { target: img scale: 1 } } ] SequentialAnimation { id: anim running: img.iAmSelected loops: Animation.Infinite alwaysRunToEnd: true NumberAnimation { target: img property: "rotation" from: 0; to: 10 duration: 200 easing.type: Easing.OutQuad } NumberAnimation { target: img property: "rotation" from: 10; to: -10 duration: 400 easing.type: Easing.InOutQuad } NumberAnimation { target: img property: "rotation" from: -10; to: 0 duration: 200 easing.type: Easing.InQuad } } Behavior on scale { NumberAnimation { duration: 70 } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: { activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') items.colorSelector = modelData } } } GCText { id: text1 anchors.fill: parent text: modelData fontSize: regularSize z: modelData == items.colorSelector ? 12 : 2 font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text1 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text1 } } } } } Grid { id: drawAndExampleArea columns: background.landscape ? 2 : 1 width: gridWidth height: parent.height spacing: 10 property int gridWidth: parent.width - colorSelector.width // The drawing area Grid { id: drawingArea width: background.landscape ? parent.gridWidth / 2 - parent.spacing * 2 : parent.gridWidth height: background.landscape ? parent.height : parent.height / 2 columns: items.numberOfColumn Repeater { id: userModel model: items.targetModelData.length property int currentItem: 0 property bool keyNavigation: false function reset() { for(var i=0; i < items.userModel.count; ++i) userModel.itemAt(i).paint(items.colorSelector) currentItem = 0 keyNavigation = false } function clearCurrentItem() { userModel.itemAt(currentItem).paint(0) } function paintCurrentItem() { userModel.itemAt(currentItem).playEffect(items.colorSelector) userModel.itemAt(currentItem).paint(items.colorSelector) } function moveCurrentIndexRight() { keyNavigation = true if(currentItem++ >= items.targetModelData.length - 1) currentItem = 0 } function moveCurrentIndexLeft() { keyNavigation = true if(currentItem-- <= 0) currentItem = items.targetModelData.length - 1 } function moveCurrentIndexUp() { keyNavigation = true currentItem -= items.numberOfColumn if(currentItem < 0) currentItem += items.targetModelData.length } function moveCurrentIndexDown() { keyNavigation = true currentItem += items.numberOfColumn if(currentItem > items.targetModelData.length - 1) currentItem -= items.targetModelData.length } Item { id: userItem width: Math.min(drawingArea.width / items.numberOfColumn, drawingArea.height / items.numberOfLine) height: width property color color: Activity.colors[colorIndex] property int colorIndex function paint(color) { colorIndex = color } function playEffect(color) { if(color === 0) activity.audioEffects.play(Activity.url + 'eraser.wav') else activity.audioEffects.play(Activity.url + 'brush.wav') } Rectangle { id: userRect anchors.fill: parent border.width: userModel.keyNavigation && userModel.currentItem == modelData ? 3 : 1 border.color: 'black' color: parent.color Behavior on color { ColorAnimation { duration: 200 onRunningChanged: { if(!running && Activity.checkModel()) bonus.good("flower") } } } } GCText { id: text2 anchors.fill: parent anchors.margins: 4 text: parent.colorIndex == 0 ? "" : parent.colorIndex fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text2 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text2 } } } } // The painting to reproduce Grid { id: imageArea width: drawingArea.width height: drawingArea.height columns: items.numberOfColumn LayoutMirroring.enabled: activity.symmetry LayoutMirroring.childrenInherit: true Repeater { id: targetModel model: items.targetModelData Item { width: Math.min(imageArea.width / items.numberOfColumn, imageArea.height / items.numberOfLine) height: width property alias color: targetRect.color Rectangle { id: targetRect anchors.fill: parent color: Activity.colors[modelData] border.width: 1 border.color: 'black' } GCText { id: text3 anchors.fill: parent anchors.margins: 4 text: modelData == 0 ? "" : modelData fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text3 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text3 } } } } } } MultiPointTouchArea { x: drawAndExampleArea.x y: drawAndExampleArea.y width: drawAndExampleArea.width height: drawAndExampleArea.height onPressed: checkTouchPoint(touchPoints) onTouchUpdated: checkTouchPoint(touchPoints) function checkTouchPoint(touchPoints) { for(var i in touchPoints) { var touch = touchPoints[i] var block = drawingArea.childAt(touch.x, touch.y) if(block) { block.playEffect(items.colorSelector) block.paint(items.colorSelector) } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/roman_numerals/RomanNumerals.qml b/src/activities/roman_numerals/RomanNumerals.qml index 3b55020c8..0470096b7 100644 --- a/src/activities/roman_numerals/RomanNumerals.qml +++ b/src/activities/roman_numerals/RomanNumerals.qml @@ -1,530 +1,530 @@ /* GCompris - roman_numerals.qml * * Copyright (C) 2016 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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" ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Image { id: background source: items.toArabic ? "qrc:/gcompris/src/activities/roman_numerals/resource/arcs.svg" : "qrc:/gcompris/src/activities/roman_numerals/resource/torrazzo-crema.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) signal start signal stop 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 alias score: score property alias textInput: textInput property bool toArabic: dataset[currentLevel].toArabic property string questionText: dataset[currentLevel].question property string instruction: dataset[currentLevel].instruction property string questionValue property int currentLevel: 0 property int numberOfLevel: dataset.length property var dataset: [ { values: ['I', 'V', 'X', 'L', 'C', 'D', 'M'], instruction: qsTr("The roman numbers are all built out of these 7 numbers:\nI and V (units, 1 and 5)\nX and L (tens, 10 and 50)\nC and D (hundreds, 100 and 500)\n and M (1000).\n An interesting observation here is that the roman numeric system lacks the number 0."), question: qsTr("Convert the roman number %1 in arabic."), toArabic: true }, { values: [1, 5, 10, 50, 100, 500, 1000], instruction: qsTr("The roman numbers are all built out of these 7 numbers:\nI and V (units, 1 and 5)\nX and L (tens, 10 and 50)\nC and D (hundreds, 100 and 500)\n and M (1000).\n An interesting observation here is that the roman numeric system lacks the number 0."), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'], instruction: qsTr("All the units except 4 and 9 are built using sums of I and V:\nI, II, III, V, VI, VII, VIII.\n The 4 and the 9 units are built using differences:\nIV (5 – 1) and IX (10 – 1)"), question: qsTr("Convert the roman number %1 in arabic."), toArabic: true }, { values: [2, 3, 4, 5, 6, 7, 8, 9], instruction: qsTr("All the units except 4 and 9 are built using sums of I and V:\nI, II, III, V, VI, VII, VIII.\n The 4 and the 9 units are built using differences:\nIV (5 – 1) and IX (10 – 1)"), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['XX', 'XXX', 'XL', 'LX', 'LXX', 'LXXX', 'XC'], instruction: qsTr("All the tens except 40 and 90 are built using sums of X and L:\nX, XX, XXX, L, LX, LXX, LXXX.\nThe 40 and the 90 tens are built using differences:\nXL (10 taken from 50) and XC (10 taken from 100)\n "), question: qsTr("Convert the roman number %1 in arabic."), toArabic: true }, { values: [20, 30, 40, 60, 70, 80, 90], instruction: qsTr("All the tens except 40 and 90 are built using sums of X and L:\nX, XX, XXX, L, LX, LXX, LXXX.\nThe 40 and the 90 tens are built using differences:\nXL (10 taken from 50) and XC (10 taken from 100)\n "), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['CC', 'CCC', 'CD', 'DC', 'DCC', 'DCCC', 'CM', ], instruction: qsTr("All the hundreds except 400 and 900 are built using sums of C and D:\nC, CC, CCC, D, DC, DCC, DCCC.\nThe 400 and the 900 hundreds are built using differences:\nCD (100 taken from 500) and CM (100 taken from 1000)"), question: qsTr("Convert the roman number %1 in arabic."), toArabic: true }, { values: [200, 300, 400, 600, 700, 800, 900], instruction: qsTr("All the hundreds except 400 and 900 are built using sums of C and D:\nC, CC, CCC, D, DC, DCC, DCCC.\nThe 400 and the 900 hundreds are built using differences:\nCD (100 taken from 500) and CM (100 taken from 1000)"), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['MM', 'MMM'], instruction: qsTr("Sums of M are used for building thousands: M, MM, MMM.\nNotice that you cannot join more than three identical symbols. The first implication of this rule is that you cannot use just sums for building all possible units, tens or hundreds, you must use differences too. On the other hand, it limits the maximum roman number to 3999 (MMMCMXCIX).\n"), question: qsTr("Convert the roman number %1 in arabic."), toArabic: true }, { values: [2000, 3000], instruction: qsTr("Sums of M are used for building thousands: M, MM, MMM.\nNotice that you cannot join more than three identical symbols. The first implication of this rule is that you cannot use just sums for building all possible units, tens or hundreds, you must use differences too. On the other hand, it limits the maximum roman number to 3999 (MMMCMXCIX).\n"), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['_random_', 50 /* up to this number */ , 10 /* sublevels */], instruction: qsTr("Now you know the rules, you can read and write numbers in roman numerals."), question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['_random_', 100, 10], instruction: '', question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['_random_', 500, 10], instruction: '', question: qsTr("Convert the arabic number %1 in roman."), toArabic: false }, { values: ['_random_', 1000, 10], instruction: '', question: qsTr("Convert the arabic number %1 in roman."), toArabic: false } ] onQuestionValueChanged: { textInput.text = '' romanConverter.arabic = 0 if(toArabic) keyboard.populateArabic() else keyboard.populateRoman() } function start() { if (!ApplicationInfo.isMobile) textInput.forceActiveFocus(); items.currentLevel = 0 initLevel() } function initLevel() { score.currentSubLevel = 1 initSubLevel() } function initSubLevel() { - if(dataset[currentLevel].values[0] == '_random_') { + if(dataset[currentLevel].values[0] === '_random_') { questionValue = Math.round(Math.random() * dataset[currentLevel].values[1] + 1) score.numberOfSubLevels = dataset[currentLevel].values[2] } else { questionValue = dataset[currentLevel].values[score.currentSubLevel - 1] score.numberOfSubLevels = dataset[currentLevel].values.length } } function nextLevel() { if(numberOfLevel - 1 == currentLevel ) { currentLevel = 0 } else { currentLevel++ } initLevel(); } function previousLevel() { if(currentLevel == 0) { currentLevel = numberOfLevel - 1 } else { currentLevel-- } initLevel(); } function nextSubLevel() { if(++score.currentSubLevel > score.numberOfSubLevels) { nextLevel() } else { initSubLevel(); } } property bool isChecking: false function check() { if(isChecking) { return } isChecking = true if(feedback.value == items.questionValue) { bonus.good('tux') } else { bonus.bad('tux') } } } onStart: { items.start() } onStop: { } Keys.onPressed: { if ((event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)) { items.check() } } QtObject { id: romanConverter property int arabic property string roman // conversion code copied from: // http://blog.stevenlevithan.com/archives/javascript-roman-numeral-converter function arabic2Roman(num) { if (!+num) return ''; var digits = String(+num).split(""), key = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM", "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC", "","I","II","III","IV","V","VI","VII","VIII","IX"], roman = "", i = 3; while (i--) roman = (key[+digits.pop() + (i * 10)] || "") + roman; return new Array(+digits.join("") + 1).join("M") + roman; } function roman2Arabic(str) { var str = str.toUpperCase(), validator = /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/, token = /[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, key = {M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}, num = 0, m; if (!(str && validator.test(str))) return false; while (m = token.exec(str)) num += key[m[0]]; return num; } onArabicChanged: roman = arabic2Roman(arabic) onRomanChanged: arabic = roman2Arabic(roman) } Column { id: column anchors.fill: parent anchors.topMargin: 10 GCText { id: questionLabel anchors.horizontalCenter: parent.horizontalCenter wrapMode: TextEdit.WordWrap text: items.questionValue ? items.questionText.arg(items.questionValue) : '' color: 'white' width: parent.width * 0.9 horizontalAlignment: Text.AlignHCenter Rectangle { z: -1 border.color: 'black' border.width: 1 anchors.centerIn: parent width: parent.width * 1.1 height: parent.height opacity: 0.8 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } radius: 10 } } Item { // Just a margin width: 1 height: 5 * ApplicationInfo.ratio } TextInput { id: textInput x: parent.width / 2 width: 60 * ApplicationInfo.ratio color: 'white' text: '' maximumLength: items.toArabic ? ('' + romanConverter.roman2Arabic(items.questionValue)).length + 1 : romanConverter.arabic2Roman(items.questionValue).length + 1 horizontalAlignment: Text.AlignHCenter verticalAlignment: TextInput.AlignVCenter anchors.horizontalCenter: parent.horizontalCenter font.pointSize: questionLabel.pointSize font.weight: Font.DemiBold font.family: GCSingletonFontLoader.fontLoader.name font.capitalization: ApplicationSettings.fontCapitalization font.letterSpacing: ApplicationSettings.fontLetterSpacing cursorVisible: true validator: RegExpValidator{regExp: items.toArabic ? /[0-9]+/ : /[ivxlcdmIVXLCDM]*/} onTextChanged: if(text) { text = text.toUpperCase(); if(items.toArabic) romanConverter.arabic = parseInt(text) else romanConverter.roman = text } Rectangle { z: -1 opacity: 0.8 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } radius: 10 border.color: 'black' border.width: 1 anchors.horizontalCenter: parent.horizontalCenter width: column.width * 0.7 height: parent.height } function appendText(car) { if(car === keyboard.backspace) { if(text && cursorPosition > 0) { var oldPos = cursorPosition text = text.substring(0, cursorPosition - 1) + text.substring(cursorPosition) cursorPosition = oldPos - 1 } return } var oldPos = cursorPosition text = text.substring(0, cursorPosition) + car + text.substring(cursorPosition) cursorPosition = oldPos + 1 } } Item { // Just a margin width: 1 height: 5 * ApplicationInfo.ratio } GCText { id: feedback anchors.horizontalCenter: parent.horizontalCenter text: items.toArabic ? qsTr("Roman value: %1").arg(value) : qsTr('Arabic value: %1').arg(value) color: 'white' Rectangle { z: -1 opacity: 0.8 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } radius: 10 border.color: 'black' border.width: 1 anchors.centerIn: parent width: parent.width * 1.1 height: parent.height } property string value: items.toArabic ? romanConverter.roman : romanConverter.arabic ? romanConverter.arabic : '' } Item { // Just a margin width: 1 height: 5 * ApplicationInfo.ratio } Rectangle { color: "transparent" width: parent.width height: (background.height - (y + bar.height + okButton.height + keyboard.height) * 1.1 ) Flickable { width: parent.width height: parent.height contentWidth: parent.width contentHeight: instructionContainer.height anchors.fill: parent flickableDirection: Flickable.VerticalFlick clip: true GCText { id: instruction visible: items.instruction != '' wrapMode: TextEdit.WordWrap fontSize: tinySize anchors.horizontalCenter: parent.horizontalCenter width: parent.width * 0.95 text: items.instruction horizontalAlignment: Text.AlignHCenter color: 'white' Rectangle { id: instructionContainer z: -1 opacity: 0.8 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } radius: 10 border.color: 'black' border.width: 1 anchors.centerIn: parent width: parent.width height: parent.contentHeight } } } } } Score { id: score anchors.bottom: bar.top currentSubLevel: 0 numberOfSubLevels: 1 } DialogHelp { id: dialogHelp onClose: home() } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter function populateArabic() { layout = [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" }, { label: keyboard.backspace } ] ] } function populateRoman() { layout = [ [ { label: "I" }, { label: "V" }, { label: "X" }, { label: "L" }, { label: "C" }, { label: "D" }, { label: "M" }, { label: keyboard.backspace } ] ] } onKeypress: textInput.appendText(text) onError: console.log("VirtualKeyboard error: " + msg); } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | level | hint } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: items.previousLevel() onNextLevelClicked: items.nextLevel() onHomeClicked: activity.home() level: items.currentLevel + 1 onHintClicked: feedback.visible = !feedback.visible } BarButton { id: okButton source: "qrc:/gcompris/src/core/resource/bar_ok.svg"; sourceSize.width: 60 * ApplicationInfo.ratio visible: true anchors { verticalCenter: score.verticalCenter right: score.left rightMargin: 10 * ApplicationInfo.ratio bottomMargin: 10 * ApplicationInfo.ratio } onClicked: items.check() } Bonus { id: bonus Component.onCompleted: win.connect(items.nextSubLevel) onWin: items.isChecking = false onLoose: items.isChecking = false } } } diff --git a/src/activities/submarine/Submarine.qml b/src/activities/submarine/Submarine.qml index 21f98016a..53cc64d1f 100644 --- a/src/activities/submarine/Submarine.qml +++ b/src/activities/submarine/Submarine.qml @@ -1,980 +1,980 @@ /* GCompris - submarine.qml * * Copyright (C) 2017 RUDRA NIL BASU * * Authors: * Pascal Georges (GTK+ version) * Rudra Nil Basu (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.6 import QtQuick.Particles 2.0 import Box2D 2.0 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "submarine.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property string url: "qrc:/gcompris/src/activities/submarine/resource/" pageComponent: Image { id: background source: url + "background.svg" anchors.fill: parent sourceSize.height: parent.height sourceSize.width: parent.width onWidthChanged: updateOnWidthReset.start() onHeightChanged: Activity.resetUpperGate() property bool hori: background.width >= background.height signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } /* Testing purposes, A / Left Key => Reduces velocity, D / Right Key => Increases velocity */ Keys.onPressed: { - if ((event.key == Qt.Key_D || event.key == Qt.Key_Right) && !tutorial.visible) { + if ((event.key === Qt.Key_D || event.key === Qt.Key_Right) && !tutorial.visible) { submarine.increaseHorizontalVelocity(1) } - if ((event.key == Qt.Key_A || event.key == Qt.Key_Left) && !tutorial.visible) { + if ((event.key === Qt.Key_A || event.key === Qt.Key_Left) && !tutorial.visible) { submarine.decreaseHorizontalVelocity(1) } - if ((event.key == Qt.Key_W || event.key == Qt.Key_Up) && !tutorial.visible) { + if ((event.key === Qt.Key_W || event.key === Qt.Key_Up) && !tutorial.visible) { centralBallastTank.fillBallastTanks() controls.updateVannes(centralBallastTank.waterFilling, controls.rotateCentralFill) } - if ((event.key == Qt.Key_S || event.key == Qt.Key_Down) && !tutorial.visible) { + if ((event.key === Qt.Key_S || event.key === Qt.Key_Down) && !tutorial.visible) { centralBallastTank.flushBallastTanks() controls.updateVannes(centralBallastTank.waterFlushing, controls.rotateCentralFlush) } - if ((event.key == Qt.Key_Plus) && !tutorial.visible) { + if ((event.key === Qt.Key_Plus) && !tutorial.visible) { submarine.increaseWingsAngle(1) } - if ((event.key == Qt.Key_Minus) && !tutorial.visible) { + if ((event.key === Qt.Key_Minus) && !tutorial.visible) { submarine.decreaseWingsAngle(1) } - if ((event.key == Qt.Key_R) && !tutorial.visible) { + if ((event.key === Qt.Key_R) && !tutorial.visible) { leftBallastTank.fillBallastTanks() controls.updateVannes(leftBallastTank.waterFilling, controls.rotateLeftFill) } - if ((event.key == Qt.Key_F) && !tutorial.visible) { + if ((event.key === Qt.Key_F) && !tutorial.visible) { leftBallastTank.flushBallastTanks() controls.updateVannes(leftBallastTank.waterFlushing, controls.rotateLeftFlush) } - if ((event.key == Qt.Key_T) && !tutorial.visible) { + if ((event.key === Qt.Key_T) && !tutorial.visible) { rightBallastTank.fillBallastTanks() controls.updateVannes(rightBallastTank.waterFilling, controls.rotateRightFill) } - if ((event.key == Qt.Key_G) && !tutorial.visible) { + if ((event.key === Qt.Key_G) && !tutorial.visible) { rightBallastTank.flushBallastTanks() controls.updateVannes(rightBallastTank.waterFlushing, controls.rotateRightFlush) } } // 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 alias crown: crown property alias whale: whale property var submarineCategory: Fixture.Category1 property var crownCategory: Fixture.Category2 property var whaleCategory: Fixture.Category3 property var upperGatefixerCategory: Fixture.Category4 property var lowerGatefixerCategory: Fixture.Category5 property var shipCategory: Fixture.Category6 property var rockCategory: Fixture.Category7 property var maxDepthCategory: Fixture.Category8 property alias submarine: submarine property alias tutorial: tutorial property alias upperGate: upperGate property alias ship: ship property alias controls: controls property alias physicalWorld: physicalWorld property bool processingAnswer: false } IntroMessage { id: tutorial textContainerHeight: 0.5 * parent.height z: 100 onIntroDone: { tutorial.visible = false } } onStart: { Activity.start(items) } onStop: { Activity.stop() } World { id: physicalWorld running: !tutorial.visible && !items.processingAnswer gravity: Qt.point(0,0) autoClearForces: false } Item { id: waterLevel x: 0 y: background.height / 15 } Rectangle { id: maximumWaterDepth width: background.width height: 10 color: "transparent" y: background.height * 0.65 Body { id: maxDepthBody target: maximumWaterDepth bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: Box { categories: items.maxDepthCategory collidesWith: items.submarineCategory width: maximumWaterDepth.width height: maximumWaterDepth.height density: 1 friction: 0 restitution: 0 } } } Item { id: submarine z: 1 property point initialPosition: Qt.point(0,waterLevel.y - submarineImage.height/2) property bool isHit: false property int terminalVelocityIndex: 75 property int maxAbsoluteRotationAngle: 15 /* Maximum depth the submarine can dive when ballast tank is full */ property real maximumDepthOnFullTanks: maximumWaterDepth.y * 0.45 property real ballastTankDiveSpeed: 10 /* Engine properties */ property point velocity property int maximumXVelocity: 5 property int currentFinalVelocity: 0 /* Wings property */ property int wingsAngle property int initialWingsAngle: 0 property int maxWingsAngle: 2 property int minWingsAngle: -2 function destroySubmarine() { isHit = true } function resetSubmarine() { isHit = false submarineImage.reset() leftBallastTank.resetBallastTanks() rightBallastTank.resetBallastTanks() centralBallastTank.resetBallastTanks() currentFinalVelocity = 0 velocity = Qt.point(0,0) smoothHorizontalVelocity.stop() wingsAngle = initialWingsAngle } /* While increasing or decreasing, we can't use submarine.velocity.x since it is interpolating */ function increaseHorizontalVelocity(amount) { if (submarine.currentFinalVelocity + amount <= submarine.maximumXVelocity) { submarine.currentFinalVelocity += amount smoothHorizontalVelocity.stop() smoothHorizontalVelocity.setFinalVelocity(submarine.currentFinalVelocity) smoothHorizontalVelocity.setIncreaseVelocity(true) smoothHorizontalVelocity.start() } } function decreaseHorizontalVelocity(amount) { if (submarine.currentFinalVelocity - amount >= 0) { submarine.currentFinalVelocity -= amount smoothHorizontalVelocity.stop() smoothHorizontalVelocity.setFinalVelocity(submarine.currentFinalVelocity) smoothHorizontalVelocity.setIncreaseVelocity(false) smoothHorizontalVelocity.start() } } function increaseWingsAngle(amount) { if (wingsAngle + amount <= maxWingsAngle) { wingsAngle += amount } else { wingsAngle = maxWingsAngle } } function decreaseWingsAngle(amount) { if (wingsAngle - amount >= minWingsAngle) { wingsAngle -= amount } else { wingsAngle = minWingsAngle } } function changeVerticalVelocity() { /* Check if we are currently using diving planes or ballast tanks */ var isDivingPlanesActive if (submarineImage.y > 0 && submarine.velocity.x > 0 && wingsAngle != 0) { /* * Movement due to planes * Movement is affected only when the submarine is moving forward * When the submarine is on the surface, the planes cannot be used */ isDivingPlanesActive = true } else { isDivingPlanesActive = false } var yPosition if (isDivingPlanesActive) { /* Currently using diving planes */ var multiplier if (wingsAngle == 1) { multiplier = 0.6 } else if (wingsAngle == 2) { multiplier = 0.8 } else if (wingsAngle == -1) { multiplier = 0.2 } else if (wingsAngle == -2) { multiplier = 0.1 } yPosition = multiplier * maximumWaterDepth.y } else { /* Currently under the influence of Ballast Tanks */ yPosition = submarineImage.currentWaterLevel / submarineImage.totalWaterLevel * submarine.maximumDepthOnFullTanks if (bar.level >= 7) { var finalAngle = ((rightBallastTank.waterLevel - leftBallastTank.waterLevel) / leftBallastTank.maxWaterLevel) * submarine.maxAbsoluteRotationAngle submarineRotation.angle = finalAngle } } var depthToMove if (submarineImage.y <= submarine.initialPosition.y && yPosition == 0){ depthToMove = 0 }else { depthToMove = yPosition - submarineImage.y } submarine.velocity.y = ballastTankDiveSpeed * (depthToMove / background.width) } Timer { id: smoothHorizontalVelocity running: false repeat: true interval: 100 property real finalVelocity property real smoothRate: 0.1 property bool increaseVelocity function increaseVelocitySmoothly() { if (submarine.velocity.x + smoothRate > finalVelocity) { submarine.velocity.x = finalVelocity smoothHorizontalVelocity.stop() } else { submarine.velocity.x += smoothRate } } function decreaseVelocitySmoothly() { if (submarine.velocity.x - smoothRate <= finalVelocity) { submarine.velocity.x = finalVelocity smoothHorizontalVelocity.stop() } else { submarine.velocity.x -= smoothRate } } function setFinalVelocity(_finalVelocity) { finalVelocity = _finalVelocity } function setIncreaseVelocity(value) { increaseVelocity = value } onTriggered: { if (increaseVelocity) { increaseVelocitySmoothly() } else { decreaseVelocitySmoothly() } } } BallastTank { id: leftBallastTank } BallastTank { id: rightBallastTank } BallastTank { id: centralBallastTank } Image { id: submarineImage source: submarine.isHit ? url + "submarine-broken.svg" : url + "submarine.svg" property int currentWaterLevel: bar.level < 7 ? centralBallastTank.waterLevel : leftBallastTank.waterLevel + rightBallastTank.waterLevel property int totalWaterLevel: bar.level < 7 ? centralBallastTank.maxWaterLevel : leftBallastTank.maxWaterLevel + rightBallastTank.maxWaterLevel width: background.width / 9 sourceSize.width: submarineImage.width fillMode: Image.PreserveAspectFit function reset() { x = submarine.initialPosition.x y = submarine.initialPosition.y } onXChanged: { if (submarineImage.x >= background.width) { Activity.finishLevel(true) } } transform: Rotation { id: submarineRotation origin.x: submarineImage.width / 2; origin.y: 0; angle: 0; Behavior on angle { NumberAnimation { duration: 1000 } } } Loader { anchors.fill: parent active: ApplicationInfo.hasShader && submarine.velocity.x > 0 && submarineImage.y > 0 && !submarine.isHit sourceComponent: ParticleSystem { anchors.fill: parent Emitter { x: parent.x y: parent.y + parent.height / 1.75 width: 1 height: 1 emitRate: 0.8 lifeSpan: 800 lifeSpanVariation: 2500 acceleration: PointDirection { x: -20 xVariation: 5 y: 0 yVariation: 0 } velocity: PointDirection { x: -20 xVariation: 10 y: 0 yVariation: 0 } size: 12 sizeVariation: 8 } ImageParticle { source: "qrc:/gcompris/src/activities/clickgame/resource/bubble.png" } } } } Body { id: submarineBody target: submarineImage bodyType: Body.Dynamic fixedRotation: true linearDamping: 0 linearVelocity: submarine.isHit ? Qt.point(0,0) : submarine.velocity fixtures: [ Box { id: submarineFixer y: submarineImage.height * 0.50 width: submarineImage.width height: submarineImage.height * 0.50 categories: items.submarineCategory collidesWith: Fixture.All density: 1 friction: 0 restitution: 0 onBeginContact: { var collidedObject = other.getBody().target if (collidedObject == whale) { whale.hit() } if (collidedObject == crown) { crown.captureCrown() } else { Activity.finishLevel(false) } } }, Box { id: submarinePeriscopeFixer x: submarineImage.width * 0.5 width: submarineImage.width * 0.25 height: submarineImage.height categories: items.submarineCategory collidesWith: Fixture.All density: 1 friction: 0 restitution: 0 onBeginContact: { var collidedObject = other.getBody().target - if (collidedObject == whale) { + if (collidedObject === whale) { whale.hit() } - if (collidedObject == crown) { + if (collidedObject === crown) { crown.captureCrown() } else { Activity.finishLevel(false) } } } ] } Timer { id: updateVerticalVelocity interval: 50 running: true repeat: true onTriggered: submarine.changeVerticalVelocity() } } Image { id: sparkle source: "qrc:/gcompris/src/activities/mining/resource/sparkle.svg" x: crown.x y: crown.y z: 1 width: crown.width height: width * 0.7 property bool isCaptured: false scale: isCaptured ? 1 : 0 function createSparkle() { isCaptured = true removeSparkleTimer.start() } function removeSparkle() { isCaptured = false } Behavior on scale { NumberAnimation { duration: 100 } } Timer { id: removeSparkleTimer interval: 3000 repeat: false running: false onTriggered: sparkle.removeSparkle() } } Rectangle { id: upperGate visible: (bar.level > 1) ? true : false width: background.width / 18 height: isGateOpen ? background.height * (5 / 36) : background.height * (5 / 12) + 4 y: -2 z: 2 color: "#9E948A" border.color: "#766C62" border.width: 2 anchors.right: background.right anchors.rightMargin: -2 property bool isGateOpen: false Body { id: upperGateBody target: upperGate bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: upperGatefixer width: upperGate.width height: upperGate.height categories: items.upperGatefixerCategory collidesWith: upperGate.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 } } Behavior on height { NumberAnimation { duration: 1000 } } } Rectangle { id: lowerGate z: 1 visible: upperGate.visible width: background.width / 18 height: background.height * (5 / 12) - subSchemaImage.height / 1.4 y: background.height * (5 / 12) color: "#9E948A" border.color: "#766C62" border.width: 2 anchors.right:background.right anchors.rightMargin: -2 Body { id: lowerGateBody target: lowerGate bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: lowerGatefixer width: lowerGate.width height: lowerGate.height categories: items.lowerGatefixerCategory collidesWith: lowerGate.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 } } } Rectangle { id: subSchemaImage width: background.width/1.3 height: background.height/4 x: background.width/9 y: background.height/1.5 visible: false } Image { id: crown width: submarineImage.width * 0.85 height: crown.width * 0.5 sourceSize.width: crown.width sourceSize.height: crown.height visible: ((bar.level > 2) && !isCaptured) ? true : false source: url + "crown.svg" property bool isCaptured: false function captureCrown() { upperGate.isGateOpen = true isCaptured = true sparkle.createSparkle() } function reset() { isCaptured = false upperGate.isGateOpen = false } x: background.width / 2 y: background.height - (subSchemaImage.height * 2) z: 1 Body { id: crownbody target: crown bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: crownfixer width: crown.width height: crown.height sensor: true categories: items.crownCategory collidesWith: crown.visible ? items.submarineCategory : Fixture.None density: 0.1 friction: 0 restitution: 0 } } } Whale { id: whale visible: (bar.level > 5) ? true : false y: rock2.y - (rock2.height * 1.15) z: 1 leftLimit: 0 rightLimit: background.width - whale.width - (upperGate.visible ? upperGate.width : 0) } Image { id: ship width: background.width / 9 sourceSize.width: ship.width fillMode: Image.PreserveAspectFit visible: (bar.level > 3) ? true : false source: collided ? url + "boat-hit.svg" : url + "boat.svg" x: initialXPosition z: 1 anchors.bottom: waterLevel.top property bool movingLeft: true property bool collided: false property real initialXPosition: background.width - ship.width - (upperGate.visible ? upperGate.width : 0) property real horizontalSpeed: 1 function reset() { ship.collided = false ship.x = initialXPosition } function collide() { /* Add few visual effects */ collided = true } transform: Rotation { id: rotateShip origin.x: ship.width / 2; origin.y: 0; axis { x: 0; y: 1; z: 0 } angle: 0 } SequentialAnimation { id: rotateShipLeft loops: 1 PropertyAnimation { target: rotateShip properties: "angle" from: 0 to: 180 duration: 500 } } SequentialAnimation { id: rotateShipRight loops: 1 PropertyAnimation { target: rotateShip properties: "angle" from: 180 to: 0 duration: 500 } } onXChanged: { if (x <= 0) { rotateShipLeft.start() movingLeft = false } else if (x >= background.width - ship.width - (upperGate.visible ? upperGate.width : 0)) { rotateShipRight.start() movingLeft = true } } Body { id: shipbody target: ship bodyType: Body.Dynamic sleepingAllowed: true fixedRotation: true linearDamping: 0 linearVelocity: Qt.point( (ship.collided ? 0 : ((ship.movingLeft ? -1 : 1) * ship.horizontalSpeed)), 0) fixtures: Box { id: shipfixer width: ship.width height: ship.height categories: items.shipCategory collidesWith: ship.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 onBeginContact: ship.collide() } } } Image { id: rock2 width: background.width / 6 height: rock2.width * 0.48 z: 5 visible: (bar.level > 4) ? true : false anchors.bottom: crown.bottom anchors.left: crown.right source: "qrc:/gcompris/src/activities/mining/resource/stone2.svg" transform: Rotation { origin.x: rock2.width / 2; origin.y: rock2.height / 2 axis { x: 0; y: 0; z: 1 } angle: 180 } Body { id: rock2Body target: rock2 bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: Box { id: rock2Fixer categories: items.rockCategory collidesWith: rock2.visible ? items.submarineCategory : Fixture.None x: rock2.width / 8 y: rock2.height / 12 width: rock2.width / 1.2 height: rock2.height / 1.5 density: 1 friction: 0 restitution: 0 } } } /* Just a space */ Rectangle { id: space width: bar.level < 8 ? rock1.width : rock1.width * (1 - (Math.random() * 0.5)) height: rock1.height color: "transparent" anchors { right: crown.left bottom: crown.bottom } } Image { id: rock1 width: rock2.width height: rock2.width * 0.46 z: 5 visible: (bar.level > 6) ? true : false anchors.bottom: crown.bottom anchors.right: space.left source: "qrc:/gcompris/src/activities/mining/resource/stone1.svg" Body { id: rock1Body target: rock1 bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: [ Circle { id: rock1FixerLeft categories: items.rockCategory collidesWith: rock1.visible ? items.submarineCategory : Fixture.None x: rock1.width / 10 radius: rock1.width / 4 density: 1 friction: 0 restitution: 0 },Circle { id: rock1FixerRight categories: items.rockCategory collidesWith: rock1.visible ? items.submarineCategory : Fixture.None x: rock1.width / 1.6 y: rock1.height / 4 radius: rock1.width / 6 density: 1 friction: 0 restitution: 0 } ] } } Image { id: rock3 width: background.width height: background.height * 0.25 sourceSize.width: rock3.width sourceSize.height: rock3.height visible: (bar.level > 2) ? true : false anchors.top: crown.top anchors.horizontalCenter: crown.left // anchors.topMargin: height * 0.5 source: url + "rocks.svg" } Timer { /* * A delay is used since on setting fullscreen on/off * first the onWidthChanged is executed, followed by * the width change */ id: updateOnWidthReset repeat: false interval: 100 running: false onTriggered: { whale.reset() ship.reset() } } Controls { id: controls z: 10 enginePosition.x: background.width * 0.1 enginePosition.y: buttonPlusY + buttonSize * 0.2 engineWidth: background.width / 8 engineHeight: hori ? buttonSize * 1.8 : buttonSize * 2.5 submarineHorizontalSpeed: submarine.currentFinalVelocity * 1000 leftTankVisible: bar.level >= 7 ? true : false leftBallastTankPosition.x: background.width * 0.35 leftBallastTankPosition.y: enginePosition.y leftBallastTankWidth: background.width / 8 leftBallastTankHeight: engineHeight centralTankVisible: bar.level < 7 ? true : false centralBallastTankPosition.x: background.width * 0.45 centralBallastTankPosition.y: enginePosition.y centralBallastTankWidth: background.width / 8 centralBallastTankHeight: engineHeight rightTankVisible: bar.level >= 7 ? true : false rightBallastTankPosition.x: background.width * 0.6 rightBallastTankPosition.y: enginePosition.y rightBallastTankWidth: background.width / 8 rightBallastTankHeight: engineHeight divingPlaneVisible: true divingPlanePosition.x: background.width * 0.8 divingPlanePosition.y: enginePosition.y + (engineHeight * 0.5) - (divingPlaneHeight * 0.5) divingPlaneWidth: hori ? background.width * 0.08 : background.width * 0.12 divingPlaneHeight: divingPlaneWidth * 0.33 buttonSize: hori ? subSchemaImage.height * 0.3 : subSchemaImage.height * 0.2 buttonPlusY: hori ? background.height * 0.61 : background.height * 0.63 buttonMinusY: enginePosition.y + engineHeight - buttonSize * 0.8 } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus onLoose: Activity.initLevel() Component.onCompleted: win.connect(Activity.nextLevel) } /* DebugDraw { id: debugDraw world: physicalWorld anchors.fill: parent opacity: 0.75 visible: false } MouseArea { id: debugMouseArea anchors.fill: parent onPressed: debugDraw.visible = !debugDraw.visible } */ } } diff --git a/src/activities/sudoku/Sudoku.qml b/src/activities/sudoku/Sudoku.qml index d2cdb578e..f41b67c92 100644 --- a/src/activities/sudoku/Sudoku.qml +++ b/src/activities/sudoku/Sudoku.qml @@ -1,210 +1,210 @@ /* gcompris - Sudoku.qml Copyright (C) 2014 Johnny Jazeix 2003, 2014: Bruno Coudoin: initial version 2014: Johnny Jazeix: Qt 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.6 import GCompris 1.0 import "../../core" import "sudoku.js" as Activity import "." ActivityBase { id: activity focus: true onStart: {focus=true} onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) focus = true } property int nbRegions QtObject { id: items property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property GCSfx audioEffects: activity.audioEffects property alias availablePiecesModel: availablePieces property alias columns: sudoColumn.columns property alias rows: sudoColumn.rows property alias sudokuModel: sudokuModel } onStart: Activity.start(items) onStop: { Activity.stop() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onReloadClicked: Activity.initLevel() onHomeClicked: activity.home() } Bonus { id: bonus z: 1002 Component.onCompleted: win.connect(Activity.incrementLevel) } Score { id: score z: 1003 anchors.bottom: background.bottom anchors.right: background.right } Keys.onPressed: { Activity.onKeyPressed(event); } SudokuListWidget { id: availablePieces audioEffects: activity.audioEffects } ListModel { id: sudokuModel } Grid { z: 100 anchors { left: availablePieces.right top: parent.top topMargin: 2 * ApplicationInfo.ratio } id: sudoColumn width: Math.min(background.width - availablePieces.width - availablePieces.anchors.leftMargin, background.height - 2 * bar.height) height: width Repeater { id: repeater model: sudokuModel delegate: blueSquare Component { id: blueSquare SudokuCase { x: (index%sudoColumn.columns)*width y: Math.floor(index/sudoColumn.columns)*height width: parent != null ? parent.width / sudoColumn.columns : 1 height: parent != null ? parent.height/ sudoColumn.columns : 1 gridIndex: index isInitial: initial text: textValue state: mState } } } } MouseArea { id: dynamic anchors.fill: sudoColumn hoverEnabled: true property int previousHoveredCase: -1 onPositionChanged: { var x = Math.floor(sudoColumn.rows * mouseX / (sudoColumn.width+1)); var y = Math.floor(sudoColumn.columns * mouseY / (sudoColumn.height+1)); var id = x + y * sudoColumn.rows; // Only color if we can modify the case - if(sudokuModel.get(id).mState == "default") + if(sudokuModel.get(id).mState === "default") sudokuModel.get(id).mState = "hovered"; // Restore previous case if different from the new one if(previousHoveredCase != id) { - if(previousHoveredCase != -1 && sudokuModel.get(previousHoveredCase).mState == "hovered") + if(previousHoveredCase != -1 && sudokuModel.get(previousHoveredCase).mState === "hovered") sudokuModel.get(previousHoveredCase).mState = "default" previousHoveredCase = id } } onExited: { - if(previousHoveredCase != -1 && sudokuModel.get(previousHoveredCase).mState == "hovered") + if(previousHoveredCase != -1 && sudokuModel.get(previousHoveredCase).mState === "hovered") sudokuModel.get(previousHoveredCase).mState = "default" previousHoveredCase = -1 } onClicked: { var x = Math.floor(sudoColumn.rows * mouseX / sudoColumn.width); var y = Math.floor(sudoColumn.columns * mouseY / sudoColumn.height); Activity.clickOn(x, y) } } Grid { z: 1001 id: regionGrid columns: rows rows: nbRegions visible: nbRegions > 1 anchors.fill: sudoColumn Repeater { id: regionRepeater model: nbRegions*nbRegions Rectangle { z: 1001 color: "transparent" border.color: "orange" border.width: 4 x: index / nbRegions * sudoColumn.width y: (index % nbRegions) * sudoColumn.width width: nbRegions * sudoColumn.width / sudoColumn.columns height: nbRegions * sudoColumn.height/ sudoColumn.columns } } } } } diff --git a/src/activities/target/Target.qml b/src/activities/target/Target.qml index c3c2cfa2c..9fe5bd1d9 100644 --- a/src/activities/target/Target.qml +++ b/src/activities/target/Target.qml @@ -1,242 +1,242 @@ /* GCompris - target.qml * * Copyright (C) 2014 Bruno coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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.6 import GCompris 1.0 import "../../core" import "target.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Item { id: background anchors.fill: parent signal start signal stop signal targetReached Keys.onPressed: { if(items.currentArrow != items.nbArrow) return if(event.key === Qt.Key_Backspace) { backspace() } appendText(event.text) } 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 alias targetModel: targetItem.model property alias targetItem: targetItem property alias userEntry: userEntry property int currentArrow property int nbArrow property int currentSubLevel property int numberOfSubLevel property bool arrowFlying onNbArrowChanged: { arrowRepeater.init(nbArrow) } } onStart: { keyboard.populate(); Activity.start(items) } onStop: { Activity.stop() } TargetItem { id: targetItem } onTargetReached: { items.arrowFlying = false if(items.currentArrow == items.nbArrow) { targetItem.stop() targetItem.scoreText += " = " userEntry.text = "?" } } Arrow { id: arrowRepeater } Image { id: cross anchors.centerIn: parent source: Activity.url + "cross.svg" opacity: items.currentArrow != items.nbArrow ? 1 : 0 sourceSize.width: 50 * ApplicationInfo.ratio } MouseArea { id: mouseArea anchors.fill: parent enabled: items.currentArrow != items.nbArrow && !items.arrowFlying onClicked: { activity.audioEffects.play(Activity.url + 'arrow.wav') items.arrowFlying = true if(items.currentArrow != items.nbArrow) { arrowRepeater.itemAt(items.currentArrow).opacity = 1 arrowRepeater.itemAt(items.currentArrow++).scale = 0.5 } } } GCText { id: scoreItem anchors.horizontalCenter: parent.horizontalCenter width: parent.width text: targetItem.scoreText fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } function backspace() { userEntry.text = userEntry.text.slice(0, -1) if(userEntry.text.length === 0) { userEntry.text = "?" } else { - if(targetItem.scoreTotal == userEntry.text) + if(targetItem.scoreTotal === userEntry.text) bonus.good("flower") } } function appendText(text) { if(text === keyboard.backspace) { backspace() return } var number = parseInt(text) if(isNaN(number)) return if(userEntry.text === "?") { userEntry.text = "" } if(userEntry.text.length > ('' + targetItem.scoreTotal).length) { return } userEntry.text += text - if(targetItem.scoreTotal == userEntry.text) + if(targetItem.scoreTotal === userEntry.text) bonus.good("flower") } GCText { id: userEntry anchors.top: scoreItem.bottom width: parent.width fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter hide: items.currentArrow == items.nbArrow ? false : true function populate() { layout = [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" }, { label: keyboard.backspace } ] ] } onKeypress: background.appendText(text) onError: console.log("VirtualKeyboard error: " + msg); } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Score { id: score anchors.right: parent.right anchors.top: parent.top anchors.bottom: undefined currentSubLevel: items.currentSubLevel + 1 numberOfSubLevels: items.numberOfSubLevel } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/traffic/Traffic.qml b/src/activities/traffic/Traffic.qml index 8a7a312c7..000cc7d87 100644 --- a/src/activities/traffic/Traffic.qml +++ b/src/activities/traffic/Traffic.qml @@ -1,192 +1,191 @@ /* GCompris - Traffic.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import GCompris 1.0 import "../../core" import "traffic.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/traffic/resource/traffic_bg.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop signal start signal stop property string mode: "IMAGE" // allow to choose between "COLOR" and "IMAGE" // mode, candidate for a config dialog Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias jamBox: jamBox property alias jamGrid: jamGrid } onStart: { Activity.start(items, mode) } onStop: { Activity.stop() } Image { id: jamBox source: "qrc:/gcompris/src/activities/traffic/resource/traffic_box.svg" anchors.centerIn: parent sourceSize.width: Math.min(background.width * 0.85, background.height * 0.85) fillMode: Image.PreserveAspectFit property double scaleFactor: background.width / background.sourceSize.width Grid { id: jamGrid anchors.centerIn: parent width: parent.width - 86 * jamBox.scaleFactor * ApplicationInfo.ratio height: parent.height - 86 * jamBox.scaleFactor * ApplicationInfo.ratio columns: 6 rows: 6 spacing: 0 // Add an alias to mode so it can be used on Car items property alias mode: background.mode Repeater { id: gridRepeater model: jamGrid.columns * jamGrid.rows delegate: Rectangle { id: gridDelegate height: jamGrid.height/ jamGrid.rows width: height border.width: 1 border.color: "white" color: "#444444" } } } } DialogHelp { id: dialogHelp onClose: home() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias modeBox: modeBox property var availableModes: [ { "text": qsTr("Colors"), "value": "COLOR" }, { "text": qsTr("Images"), "value": "IMAGE" } ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: modeBox model: availableModes background: dialogActivityConfig label: qsTr("Select your mode") } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["mode"]) { mode = dataToSave["mode"]; Activity.mode = dataToSave["mode"]; } } onSaveData: { mode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modeBox.currentIndex].value; dataToSave = {"mode": mode} Activity.mode = mode; } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length ; i ++) { if(dialogActivityConfig.configItem.availableModes[i].value === mode) { dialogActivityConfig.configItem.modeBox.currentIndex = i; break; } } } } Bar { id: bar content: BarEnumContent { value: help | home | level | reload | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.initLevel() onConfigClicked: { dialogActivityConfig.active = true // Set default values dialogActivityConfig.setDefaultValues(); displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } Score { id: score - anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined } } }