Warning, /education/gcompris/src/activities/guess24/Guess24.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - guess24.qml
0002  *
0003  * SPDX-FileCopyrightText: 2023 Bruno ANSELME <be.root@free.fr>
0004  *
0005  * Authors:
0006  *   Bruno ANSELME <be.root@free.fr> (Qt Quick native)
0007  *   Timothée Giet <animtim@gmail.com> (Graphics and layout refactoring)
0008  *
0009  * SPDX-License-Identifier: GPL-3.0-or-later
0010  *
0011  * References :
0012  *  https://www.4nums.com/game/difficulties/
0013  *  https://www.4nums.com/solutions/allsolvables/   (2023-07-08 datas)
0014  *  https://fr.y8.com/games/make_24
0015  */
0016 import QtQuick 2.12
0017 import GCompris 1.0
0018 
0019 import "../../core"
0020 import "qrc:/gcompris/src/core/core.js" as Core
0021 import "guess24.js" as Activity
0022 
0023 ActivityBase {
0024     id: activity
0025 
0026     onStart: focus = true
0027     onStop: {}
0028 
0029     pageComponent: Image {
0030         id: background
0031         source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg"
0032         sourceSize.width: width
0033         sourceSize.height: height
0034         fillMode: Image.PreserveAspectCrop
0035         anchors.fill: parent
0036         signal start
0037         signal stop
0038 
0039         property int baseMargins: 5 * ApplicationInfo.ratio
0040         property int baseRadius: 2 * ApplicationInfo.ratio
0041         property string baseColor: "#A1CBD9"
0042         property string selectionColor: "#4B9BB5"
0043         property int selectionWidth: 4 * ApplicationInfo.ratio
0044 
0045         Component.onCompleted: {
0046             dialogActivityConfig.initialize()
0047             activity.start.connect(start)
0048             activity.stop.connect(stop)
0049         }
0050 
0051         // Add here the QML items you need to access in javascript
0052         QtObject {
0053             id: items
0054             property Item main: activity.main
0055             property alias background: background
0056             property int currentLevel: activity.currentLevel
0057             property alias bonus: bonus
0058             property alias score: score
0059             property alias errorRectangle: errorRectangle
0060             property var levels: activity.datasetLoader.data
0061             property GCSfx audioEffects: activity.audioEffects
0062             property alias jsonParser: jsonParser
0063             property int currentValue: 0
0064             property int currentOperator: -1
0065             property int operatorsCount: 4
0066             property int subLevelCount: 0
0067             property bool keysOnValues: true    // True when focus is on values false if focus is on operators
0068             property alias cardsModel: cardsModel
0069             property alias cardsBoard: cardsBoard
0070             property alias operators: operators
0071             property alias cancelButton: cancelButton
0072             property alias hintButton: hintButton
0073             property alias animationCard: animationCard
0074             property alias steps: steps
0075             property alias solution: solution
0076             property alias solutionRect: solutionRect
0077             property alias animSol: animSol
0078             property bool keyboardNavigation: false
0079             property bool buttonsBlocked: false
0080         }
0081 
0082         onStart: { Activity.start(items) }
0083         onStop: { Activity.stop() }
0084 
0085         JsonParser { id: jsonParser }
0086 
0087         ListModel {
0088             id: cardsModel
0089             function randPosition() { return (Math.floor(Math.random() * count)) }                                      // choose a random position
0090             function shuffleModel() { for (var i = 0 ; i < count; i++) { move(randPosition(), randPosition(), 1) } }    // shuffle elements
0091         }
0092 
0093         Rectangle {
0094             id: captionBg
0095             width: caption.paintedWidth + background.baseMargins * 2
0096             height: caption.paintedHeight + background.baseMargins
0097             color: "#80FFFFFF"
0098             radius: background.baseRadius
0099             anchors.horizontalCenter: parent.horizontalCenter
0100             anchors.top: parent.top
0101             anchors.topMargin: background.baseMargins
0102             GCText {
0103                 id: caption
0104                 width: background.width - background.baseMargins * 4
0105                 anchors.centerIn: captionBg
0106                 text: qsTr("Use the four numbers with given operators to find 24.")
0107                 verticalAlignment: Text.AlignVCenter
0108                 horizontalAlignment: Text.AlignHCenter
0109                 wrapMode: Text.WordWrap
0110             }
0111             MouseArea {
0112                 anchors.fill: parent
0113                 onClicked: captionBg.opacity = 0.0
0114             }
0115         }
0116 
0117         Item {
0118             id: layoutArea
0119             anchors.top: captionBg.bottom
0120             anchors.bottom: score.top
0121             anchors.horizontalCenter: background.horizontalCenter
0122             anchors.margins: background.baseMargins
0123             width: Math.min(background.width - 2 * background.baseMargins,
0124                                 400 * ApplicationInfo.ratio)
0125         }
0126         // Main section
0127         Rectangle {     // Values
0128             id: valuesArea
0129             anchors.top: layoutArea.top
0130             anchors.left: layoutArea.left
0131             width: layoutArea.width * 0.66
0132             height: Math.min(250 * ApplicationInfo.ratio, layoutArea.height * 0.75)
0133             color: "#80FFFFFF"
0134             radius: background.baseRadius
0135             GridView {
0136                 id: cardsBoard
0137                 anchors.fill: parent
0138                 cellWidth: (parent.width - background.baseMargins) * 0.5
0139                 cellHeight: (parent.height - background.baseMargins) * 0.5
0140                 highlightFollowsCurrentItem: false
0141                 boundsBehavior: Flickable.StopAtBounds
0142                 model: cardsModel
0143                 delegate: Item {   // Display a card with a number inside
0144                     id: cardNumber
0145                     property int value: value_
0146                     width: cardsBoard.cellWidth
0147                     height: cardsBoard.cellHeight
0148                     Rectangle {
0149                         id: cardRect
0150                         width: parent.width - background.baseMargins
0151                         height: parent.height - background.baseMargins
0152                         anchors.top: parent.top
0153                         anchors.left: parent.left
0154                         anchors.margins: background.baseMargins
0155                         visible: true
0156                         color: (items.currentValue === index) ? background.baseColor :  "#F0F0F0"
0157                         border.width: (items.keyboardNavigation && items.keysOnValues && (cardsBoard.currentIndex === index)) ? background.selectionWidth : ApplicationInfo.ratio
0158                         border.color: (items.keyboardNavigation && items.keysOnValues && (cardsBoard.currentIndex === index)) ? background.selectionColor : background.baseColor
0159                         radius: background.baseMargins
0160                         GCText {
0161                             anchors.fill: parent
0162                             horizontalAlignment: Text.AlignHCenter
0163                             verticalAlignment: Text.AlignVCenter
0164                             fontSize: hugeSize
0165                             fontSizeMode: Text.Fit
0166                             text: value
0167                         }
0168                         MouseArea {
0169                             id: boardArea
0170                             anchors.fill: parent
0171                             enabled: animationCard.state === "" && !items.buttonsBlocked
0172                             onClicked: Activity.valueClicked(index)
0173                         }
0174                     }
0175                 }
0176             }
0177 
0178             ErrorRectangle {
0179                 id: errorRectangle
0180                 anchors.fill: valuesArea
0181                 radius: valuesArea.radius
0182                 imageSize: Math.min(width, height) * 0.5
0183                 function releaseControls() { items.buttonsBlocked = false; }
0184             }
0185 
0186             Item {     // Animation card visible during animations
0187                 id: animationCard
0188                 property string value: ""
0189                 property string action: Activity.animActions[0]
0190                 width: cardsBoard.cellWidth
0191                 height: cardsBoard.cellHeight
0192                 visible: false
0193                 Rectangle {
0194                     id: animationCardBg
0195                     width: parent.width - background.baseMargins
0196                     height: parent.height - background.baseMargins
0197                     color: background.baseColor
0198                     radius: background.baseMargins
0199                     border.color: background.baseColor
0200                     border.width: background.selectionWidth
0201                     anchors.top: parent.top
0202                     anchors.left: parent.left
0203                     anchors.margins: background.baseMargins
0204                 }
0205                 GCText {
0206                     anchors.fill: parent
0207                     horizontalAlignment: Text.AlignHCenter
0208                     verticalAlignment: Text.AlignVCenter
0209                     fontSize: hugeSize
0210                     fontSizeMode: Text.Fit
0211                     text: animationCard.value
0212                 }
0213                 states: [
0214                     State {
0215                         name: "moveto"
0216                         PropertyChanges {
0217                             target: animationCard
0218                             visible: true
0219                             x: items.cardsBoard.currentItem.x
0220                             y: items.cardsBoard.currentItem.y
0221                         }
0222                     },
0223                     State {
0224                         name: "wait"
0225                         PropertyChanges {
0226                             target: animationCard
0227                             visible: true
0228                         }
0229                         PropertyChanges {
0230                             target: animationCardBg
0231                             color: "tomato"
0232                             border.color: "tomato"
0233                         }
0234                     }
0235                 ]
0236                 transitions: [
0237                     Transition {
0238                         to: "moveto"
0239                         SequentialAnimation {
0240                             alwaysRunToEnd: true
0241                             NumberAnimation { properties: "x,y"; duration: 300 }
0242                             ScriptAction {
0243                                 script: {
0244                                     animationCard.state = ""
0245                                     if (animationCard.action === "forward")
0246                                         Activity.checkResult()
0247                                     else if (animationCard.action === "backward")
0248                                         Activity.endPopOperation()
0249                                 }
0250                             }
0251                         }
0252                     },
0253                     Transition {
0254                         to: "wait"
0255                         SequentialAnimation {
0256                             alwaysRunToEnd: true
0257                             PauseAnimation { duration: 800 }
0258                             ScriptAction { script: { audioEffects.play('qrc:/gcompris/src/core/resource/sounds/crash.wav') } }
0259                             PauseAnimation { duration: 800 }
0260                             ScriptAction { script: { Activity.popOperation() }}
0261                         }
0262                     }
0263                 ]
0264             }
0265         }
0266 
0267         Rectangle {     // Operators
0268             id: operatorsArea
0269             anchors.top: valuesArea.bottom
0270             anchors.topMargin: background.baseMargins
0271             anchors.left: valuesArea.left
0272             anchors.right: valuesArea.right
0273             height: Math.min(layoutArea.height - valuesArea.height - background.baseMargins,
0274                              80 * ApplicationInfo.ratio)
0275             radius: background.baseRadius
0276             color: "#80FFFFFF"
0277             enabled: ((items.currentValue !== -1) && (animationCard.state === ""))
0278             ListView {
0279                 id: operators
0280                 anchors.fill: parent
0281                 orientation: ListView.Horizontal
0282                 boundsBehavior: Flickable.StopAtBounds
0283                 model: [ "+", "−", "×", "÷" ]
0284                 delegate: Item {
0285                     width: (operatorsArea.width - background.baseMargins) * 0.25
0286                     height: operatorsArea.height
0287                     Rectangle {     // Display an operator button
0288                         id: opRect
0289                         width: parent.width - background.baseMargins
0290                         height: parent.height - background.baseMargins * 2
0291                         anchors.top: parent.top
0292                         anchors.left: parent.left
0293                         anchors.margins: background.baseMargins
0294                         visible: index < items.operatorsCount
0295                         color: (items.currentOperator === index) ? background.baseColor : "#F0F0F0"
0296                         opacity: (items.currentValue !== -1) ? 1.0 : 0.5
0297                         border.width: (items.keyboardNavigation && !items.keysOnValues && (operators.currentIndex === index) && (items.currentValue !== -1)) ? background.selectionWidth : ApplicationInfo.ratio
0298                         border.color: (items.keyboardNavigation && !items.keysOnValues && (operators.currentIndex === index) && (items.currentValue !== -1)) ? background.selectionColor : background.baseColor
0299                         radius: background.baseMargins
0300                         GCText {
0301                             anchors.fill: parent
0302                             horizontalAlignment: Text.AlignHCenter
0303                             verticalAlignment: Text.AlignVCenter
0304                             text: operators.model[index]
0305                             fontSize: hugeSize
0306                             fontSizeMode: Text.Fit
0307                         }
0308                         MouseArea {
0309                             id: opArea
0310                             anchors.fill: parent
0311                             enabled: !items.buttonsBlocked
0312                             onClicked: Activity.operatorClicked(index)
0313                         }
0314                     }
0315                 }
0316             }
0317         }
0318 
0319         Rectangle {
0320             id: stepsRect
0321             width: layoutArea.width - valuesArea.width - background.baseMargins
0322             height: Math.min(80 * ApplicationInfo.ratio,
0323                              (layoutArea.height - background.baseMargins) * 0.5)
0324             anchors.top: layoutArea.top
0325             anchors.right: layoutArea.right
0326             color: "#F0F0F0"
0327             radius: background.baseRadius
0328             GCText {
0329                 id: steps
0330                 anchors.fill: parent
0331                 anchors.leftMargin: background.baseMargins
0332                 fontSize: tinySize
0333                 text: ""
0334             }
0335         }
0336 
0337         Rectangle {
0338             id: solutionRect
0339             width: stepsRect.width
0340             height: stepsRect.height
0341             color: "#F0F0F0"
0342             radius: background.baseRadius
0343             anchors.top: stepsRect.bottom
0344             anchors.topMargin: background.baseMargins
0345             anchors.right: layoutArea.right
0346             opacity: 0.0
0347             GCText {
0348                 id: solution
0349                 anchors.fill: parent
0350                 anchors.leftMargin: background.baseMargins
0351                 fontSize: tinySize
0352                 opacity: 0.5
0353                 text: ""
0354             }
0355             NumberAnimation on opacity {
0356                 id: animSol
0357                 property bool firstTime: true
0358                 alwaysRunToEnd: true
0359                 to: 0.0
0360                 duration: 1000
0361                 onStarted: cancelButton.visible = false
0362                 onStopped: {
0363                     if (!firstTime) {   // animation is triggered on activity start
0364                         Activity.unstack = true
0365                         Activity.popOperation()
0366                     }
0367                     firstTime = false
0368                     cardsBoard.enabled = true
0369                     operators.enabled = true
0370                 }
0371             }
0372         }
0373 
0374         Image {
0375             id: cancelButton
0376             source: "qrc:/gcompris/src/activities/chess/resource/undo.svg"
0377             height: score.height
0378             width: height
0379             sourceSize.width: width
0380             sourceSize.height: height
0381             anchors.top: operatorsArea.bottom
0382             anchors.topMargin: background.baseMargins
0383             anchors.left: operatorsArea.left
0384             enabled: animationCard.state === ""
0385             MouseArea {
0386                 anchors.fill: parent
0387                 enabled: !items.buttonsBlocked
0388                 onClicked: {
0389                     if (solutionRect.opacity !== 0.0) {
0390                         animSol.start()
0391                     } else {
0392                         animSol.stop()
0393                         Activity.unstack = false
0394                         Activity.popOperation()
0395                     }
0396                 }
0397             }
0398         }
0399 
0400         Image {
0401             id: hintButton
0402             source: "qrc:/gcompris/src/core/resource/bar_hint.svg"
0403             height: score.height
0404             width: height
0405             sourceSize.width: width
0406             sourceSize.height: height
0407             anchors.right: operatorsArea.right
0408             anchors.top: operatorsArea.bottom
0409             anchors.topMargin: background.baseMargins
0410             enabled: animationCard.state === ""
0411             MouseArea {
0412                 anchors.fill: parent
0413                 enabled: !items.buttonsBlocked
0414                 onClicked: {
0415                     if (Activity.helpCount < 4)
0416                         Activity.helpCount++
0417                         solution.text = Activity.splittedSolution.slice(0, Activity.helpCount).join("\n")
0418                         solutionRect.opacity = 1.0
0419                         cardsBoard.enabled = false
0420                         operators.enabled = false
0421                         hintButton.visible = false
0422                 }
0423             }
0424         }
0425 
0426         DialogChooseLevel {
0427             id: dialogActivityConfig
0428             currentActivity: activity.activityInfo
0429 
0430             onSaveData: {
0431                 levelFolder = dialogActivityConfig.chosenLevels
0432                 currentActivity.currentLevels = dialogActivityConfig.chosenLevels
0433                 ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels)
0434             }
0435             onClose: {
0436                 home()
0437             }
0438             onStartActivity: {
0439                 background.stop()
0440                 background.start()
0441             }
0442         }
0443 
0444         DialogHelp {
0445             id: dialogHelp
0446             onClose: home()
0447         }
0448 
0449         Score {
0450             id: score
0451             numberOfSubLevels: items.subLevelCount
0452             currentSubLevel: 0
0453             anchors.top: undefined
0454             anchors.right: background.right
0455             anchors.rightMargin: background.baseMargins
0456             anchors.left: undefined
0457             anchors.bottom: background.bottom
0458             anchors.bottomMargin: bar.height * 1.5
0459             onStop: Activity.nextSubLevel()
0460         }
0461 
0462         Bar {
0463             id: bar
0464             level: items.currentLevel + 1
0465             content: BarEnumContent { value: help | home | level | activityConfig }
0466             onHelpClicked: {
0467                 displayDialog(dialogHelp)
0468             }
0469             onActivityConfigClicked: {
0470                 displayDialog(dialogActivityConfig)
0471             }
0472             onPreviousLevelClicked: Activity.previousLevel()
0473             onNextLevelClicked: Activity.nextLevel()
0474             onHomeClicked: activity.home()
0475         }
0476 
0477         Bonus {
0478             id: bonus
0479             Component.onCompleted: win.connect(Activity.nextLevel)
0480         }
0481 
0482         Keys.onPressed: Activity.handleKeys(event)
0483     }
0484 }