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

0001 /* GCompris - checkers.qml
0002  *
0003  * SPDX-FileCopyrightText: 2017 Johnny Jazeix <jazeix@gmail.com>
0004  *
0005  * Authors:
0006  *   Johnny Jazeix <jazeix@gmail.com>
0007  *   Timothée Giet <animtim@gmail.com> (big layout refactoring)
0008  *
0009  *   SPDX-License-Identifier: GPL-3.0-or-later
0010  */
0011 import QtQuick 2.12
0012 import GCompris 1.0
0013 
0014 import "../../core"
0015 import "."
0016 import "checkers.js" as Activity
0017 
0018 ActivityBase {
0019     id: activity
0020 
0021     property bool acceptClick: true
0022     property bool twoPlayers: false
0023     // difficultyByLevel means that at level 1 computer is bad better at last level
0024     property bool difficultyByLevel: true
0025 
0026     onStart: focus = true
0027     onStop: {}
0028 
0029     pageComponent: Image {
0030         id: background
0031         anchors.fill: parent
0032         source: "qrc:/gcompris/src/activities/chess/resource/background-wood.svg"
0033         signal start
0034         signal stop
0035 
0036         Component.onCompleted: {
0037             activity.start.connect(start)
0038             activity.stop.connect(stop)
0039         }
0040 
0041         // Add here the QML items you need to access in javascript
0042         QtObject {
0043             id: items
0044             property Item main: activity.main
0045             property GCSfx audioEffects: activity.audioEffects
0046             property alias background: background
0047             property int currentLevel: activity.currentLevel
0048             property alias bonus: bonus
0049             property var barHeightAddon: ApplicationSettings.isBarHidden ? textMessage.height : bar.height
0050             property bool isPortrait: (background.height >= background.width)
0051             property int cellSize: boardBg.width * 0.098
0052             property var fen: activity.fen
0053             property bool twoPlayer: activity.twoPlayers
0054             property bool difficultyByLevel: activity.difficultyByLevel
0055             property var positions
0056             property var pieces: pieces
0057             property var squares: squares
0058             property var history
0059             property var redo_stack
0060             property alias redoTimer: redoTimer
0061             property int from
0062             property bool blackTurn
0063             property bool gameOver
0064             property var movesToDo: []
0065             property string message
0066             property alias trigComputerMove: trigComputerMove
0067              // Used to stop piece animation on board resize; set to true on board resize, and to false on any action that triggers a piece move
0068             property bool noPieceAnimation: false
0069 
0070             property int numberOfCases: 10
0071         }
0072 
0073         onStart: { Activity.start(items) }
0074         onStop: { Activity.stop() }
0075 
0076         GCText {
0077             id: textMessage
0078             z: 20
0079             color: "white"
0080             anchors.horizontalCenter: parent.horizontalCenter
0081             width: parent.width
0082             fontSize: smallSize
0083             text: items.message
0084             horizontalAlignment: Text.AlignHCenter
0085             wrapMode: TextEdit.WordWrap
0086         }
0087 
0088         Grid {
0089             id: controls
0090             z: 20
0091             spacing: (boardBg.width - items.cellSize * 3) / 3
0092             columns: items.isPortrait ? 3 : 1
0093             horizontalItemAlignment: Grid.AlignHCenter
0094             verticalItemAlignment: Grid.AlignVCenter
0095 
0096             GCButton {
0097                 id: undo
0098                 height: items.cellSize
0099                 width: items.cellSize
0100                 text: "";
0101                 theme: "noStyle"
0102                 onClicked: Activity.undo()
0103                 enabled: (items.history && items.history.length > 0) ? true : false
0104                 opacity: enabled ? 1 : 0
0105                 Image {
0106                     source: 'qrc:/gcompris/src/activities/chess/resource/undo.svg'
0107                     height: items.cellSize
0108                     width: items.cellSize
0109                     sourceSize.height: items.cellSize
0110                     fillMode: Image.PreserveAspectFit
0111                 }
0112                 Behavior on opacity {
0113                     PropertyAnimation {
0114                         easing.type: Easing.InQuad
0115                         duration: 200
0116                     }
0117                 }
0118             }
0119 
0120             GCButton {
0121                 id: redo
0122                 height: items.cellSize
0123                 width: items.cellSize
0124                 text: "";
0125                 theme: "noStyle"
0126                 onClicked: {
0127                     Activity.redo()
0128                 }
0129                 enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0
0130                 opacity: enabled
0131                 Image {
0132                     source: 'qrc:/gcompris/src/activities/chess/resource/redo.svg'
0133                     height: items.cellSize
0134                     width: items.cellSize
0135                     sourceSize.height: items.cellSize
0136                     fillMode: Image.PreserveAspectFit
0137                 }
0138                 Behavior on opacity {
0139                     PropertyAnimation {
0140                         easing.type: Easing.InQuad
0141                         duration: 200
0142                     }
0143                 }
0144             }
0145 
0146             GCButton {
0147                 height: items.cellSize
0148                 width: items.cellSize
0149                 text: "";
0150                 theme: "noStyle"
0151                 enabled: items.twoPlayer
0152                 opacity: enabled
0153                 Image {
0154                     source: 'qrc:/gcompris/src/activities/chess/resource/turn.svg'
0155                     height: items.cellSize
0156                     width: items.cellSize
0157                     sourceSize.height: items.cellSize
0158                     fillMode: Image.PreserveAspectFit
0159                 }
0160                 onClicked: chessboard.swap()
0161             }
0162         }
0163 
0164         Rectangle {
0165             id: layoutArea
0166             width: background.width
0167             height: background.height - textMessage.height - items.barHeightAddon * 1.1
0168             opacity: 0
0169             anchors.horizontalCenter: background.horizontalCenter
0170         }
0171 
0172         Rectangle {
0173             id: controlsArea
0174             anchors.left: background.left
0175             anchors.right: boardBg.left
0176             anchors.top: boardBg.top
0177             anchors.bottom: boardBg.bottom
0178             opacity: 0
0179         }
0180 
0181         states: [
0182             State {
0183                 name: "portraitLayout"; when: items.isPortrait
0184                 PropertyChanges {
0185                     target: layoutArea
0186                     width: background.width * 0.86
0187                     height: background.height - textMessage.height - bar.height * 1.1
0188                 }
0189                 PropertyChanges {
0190                     target: controls
0191                     width:layoutArea.width
0192                     height: items.cellSize * 1.2
0193                     anchors.leftMargin: controls.spacing * 0.5
0194                     anchors.topMargin: 0
0195                 }
0196                 PropertyChanges {
0197                     target: boardBg
0198                     anchors.verticalCenterOffset: items.cellSize * -0.6
0199                 }
0200                 AnchorChanges {
0201                     target: layoutArea
0202                     anchors.top: controls.bottom
0203                 }
0204                 AnchorChanges {
0205                     target: controls
0206                     anchors.top: textMessage.bottom
0207                     anchors.horizontalCenter: undefined
0208                     anchors.left: boardBg.left
0209                 }
0210             },
0211             State {
0212                 name: "horizontalLayout"; when: !items.isPortrait
0213                 PropertyChanges {
0214                     target: layoutArea
0215                     width: background.width
0216                     height: background.height - textMessage.height - items.barHeightAddon * 1.1
0217                 }
0218                 PropertyChanges {
0219                     target: controls
0220                     width: items.cellSize * 1.2
0221                     height: layoutArea.height
0222                     anchors.leftMargin: 0
0223                     anchors.topMargin: controls.spacing * 0.5
0224                 }
0225                 PropertyChanges {
0226                     target: boardBg
0227                     anchors.verticalCenterOffset: 0
0228                 }
0229                 AnchorChanges {
0230                     target: layoutArea
0231                     anchors.top: textMessage.bottom
0232                 }
0233                 AnchorChanges {
0234                     target: controls
0235                     anchors.top: controlsArea.top
0236                     anchors.horizontalCenter: controlsArea.horizontalCenter
0237                     anchors.left: undefined
0238                 }
0239             }
0240         ]
0241 
0242         Rectangle {
0243             id: boardBg
0244             width: Math.min(layoutArea.width, layoutArea.height)
0245             height: boardBg.width
0246             anchors.centerIn: layoutArea
0247             z: 09
0248             color: "#2E1B0C"
0249             onWidthChanged: items.noPieceAnimation = true
0250 
0251             // The chessboard
0252             GridView {
0253                 id: chessboard
0254                 cellWidth: items.cellSize
0255                 cellHeight: items.cellSize
0256                 width: items.cellSize * items.numberOfCases
0257                 height: items.cellSize * items.numberOfCases
0258                 interactive: false
0259                 keyNavigationWraps: true
0260                 model: items.numberOfCases*items.numberOfCases
0261                 layoutDirection: Qt.RightToLeft
0262                 delegate: square
0263                 rotation: 180
0264                 z: 10
0265                 anchors.centerIn: boardBg
0266                 Component {
0267                     id: square
0268                     Image {
0269                         source: index % 2 + (Math.floor(index / items.numberOfCases) % 2) == 1 ?
0270                         Activity.url + 'checkers-white.svg' : Activity.url + 'checkers-black.svg';
0271                         width: items.cellSize
0272                         height: items.cellSize
0273                     }
0274                 }
0275 
0276                 Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } }
0277 
0278                 function swap() {
0279                     items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav')
0280                     if(chessboard.rotation == 180)
0281                         chessboard.rotation = 0
0282                         else
0283                             chessboard.rotation = 180
0284                 }
0285             }
0286         }
0287 
0288 
0289         Repeater {
0290             id: squares
0291             model: items.positions
0292             delegate: square
0293             parent: chessboard
0294 
0295             DropArea {
0296                 id: square
0297                 x: items.cellSize * (9 - pos % items.numberOfCases) + spacing / 2
0298                 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2
0299                 width: items.cellSize - spacing
0300                 height: items.cellSize - spacing
0301                 z: 1
0302                 keys: acceptMove ? ['acceptMe'] : ['sorryNo']
0303                 property bool acceptMove: false
0304                 property bool jumpable: false
0305                 property int pos: modelData.pos
0306                 property int spacing: 6 * ApplicationInfo.ratio
0307                 Rectangle {
0308                     id: possibleMove
0309                     anchors.fill: parent
0310                     color: parent.containsDrag ?  '#803ACAFF' : 'transparent'
0311                     border.width: parent.acceptMove || parent.jumpable ? 5 : 0
0312                     border.color: parent.acceptMove ? '#FF808080' : '#C0808080'
0313                     radius: parent.acceptMove ? width*0.5 : 0
0314                     z: 1
0315                 }
0316             }
0317 
0318             function getSquareAt(pos) {
0319                 for(var i=0; i < squares.count; i++) {
0320                     if(squares.itemAt(i).pos === pos)
0321                         return squares.itemAt(i)
0322                 }
0323                 return undefined
0324             }
0325         }
0326 
0327         Repeater {
0328             id: pieces
0329             model: items.positions
0330             delegate: piece
0331             parent: chessboard
0332 
0333             Piece {
0334                 id: piece
0335                 sourceSize.width: items.cellSize
0336                 width: items.cellSize - spacing
0337                 height: items.cellSize - spacing
0338                 source: img ? Activity.url + img + '.svg' : ''
0339                 img: modelData.img
0340                 x: items.cellSize * (items.numberOfCases - 1 - pos % items.numberOfCases) + spacing / 2
0341                 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2
0342                 z: 1
0343                 pos: modelData.pos
0344                 newPos: modelData.pos
0345                 rotation: - chessboard.rotation
0346 
0347                 property int spacing: 6 * ApplicationInfo.ratio
0348 
0349                 Drag.active: dragArea.drag.active
0350                 Drag.hotSpot.x: width / 2
0351                 Drag.hotSpot.y: height / 2
0352 
0353                 MouseArea {
0354                     id: dragArea
0355                     anchors.fill: parent
0356                     enabled: !items.gameOver && !items.trigComputerMove.running
0357                     drag.target: ((items.blackTurn && !parent.isWhite) || (!items.blackTurn && parent.isWhite)) ?
0358                                 parent : null
0359                     onPressed: {
0360                         piece.Drag.keys = ['acceptMe']
0361                         parent.z = 100
0362 
0363                         if(parent.isWhite == 1 && !items.blackTurn ||
0364                                 parent.isWhite == 0 && items.blackTurn) {
0365                             items.from = parent.newPos
0366                             Activity.showPossibleMoves(items.from)
0367                         } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) {
0368                             Activity.moveTo(items.from, parent.newPos)
0369                         }
0370                     }
0371                     onReleased: {
0372                         // If no target or move not possible, we reset the position
0373                         if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) {
0374                             var pos = parent.pos
0375                             // Force recalc of the old x,y position
0376                             parent.pos = -1
0377                             if(pieces.getPieceAt(pos))
0378                                 pieces.getPieceAt(pos).move(pos)
0379                         }
0380                     }
0381                 }
0382             }
0383 
0384             function moveTo(from, to, moves) {
0385                 items.movesToDo.push({"from": from, "to": to, "move": moves});
0386                 if(items.movesToDo.length == 1) {
0387                     moveInternal();
0388                 }
0389             }
0390 
0391             function moveInternal() {
0392                 var moveToDo = items.movesToDo[0]
0393                 var from = moveToDo.from;
0394                 var to = moveToDo.to;
0395                 var moves = moveToDo.move;
0396 
0397                 var fromPiece = getPieceAt(from)
0398                 var toPiece = getPieceAt(to)
0399 
0400                 if(moves.jumps.length != 0)
0401                     items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav')
0402                 else
0403                     items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav')
0404 
0405                 toPiece.hide(from)
0406                 movingPiece = fromPiece
0407 
0408                 if(moves.jumps.length !== 0) {
0409                     listJumps = moves.jumps
0410                 }
0411                 else {
0412                     // create the move if needed
0413                     listJumps = [Activity.viewPosToEngine(from), Activity.viewPosToEngine(to)]
0414                 }
0415             }
0416 
0417             function promotion(to) {
0418                 var toPiece = getPieceAt(to)
0419                 toPiece.promotion()
0420             }
0421 
0422             function getPieceAt(pos) {
0423                 for(var i=0; i < pieces.count; i++) {
0424                     if(pieces.itemAt(i).newPos === pos)
0425                         return pieces.itemAt(i)
0426                 }
0427                 return undefined
0428             }
0429         }
0430 
0431         property var movingPiece: undefined
0432         property var listJumps
0433         property bool restartNeeded: false
0434         onListJumpsChanged: {
0435             if(listJumps.length >= 2) {
0436                 if(!animationTimer.isRunning)
0437                     animationTimer.restart();
0438                 else
0439                     restartNeeded = true;
0440             }
0441         }
0442 
0443         SequentialAnimation {
0444             id: animationTimer
0445             onRunningChanged: {
0446                 if(!running && restartNeeded)
0447                     start()
0448             }
0449             ScriptAction {
0450                 script: {
0451                     restartNeeded = false
0452                     var to = Activity.engineToViewPos(listJumps[1])
0453                     movingPiece.move(to)
0454                 }
0455             }
0456             PauseAnimation { duration: 200 }
0457             ScriptAction {
0458                 script: {
0459                     listJumps.shift()
0460                     // only shifting does not trigger the onChanged
0461                     var tmp = listJumps
0462                     listJumps = tmp
0463                     // only change player once all the jumps have been done
0464                     if(listJumps.length === 1) {
0465 
0466                         items.movesToDo.shift()
0467                         if(items.movesToDo.length > 0) {
0468                            pieces.moveInternal()
0469                         }
0470                         else {
0471                             Activity.refresh()
0472                             movingPiece = undefined
0473                         }
0474                     }
0475                 }
0476             }
0477         }
0478 
0479         Timer {
0480             id: trigComputerMove
0481             repeat: false
0482             interval: 400
0483             onTriggered: Activity.randomMove()
0484         }
0485 
0486         // Use to redo the computer move after the user move
0487         Timer {
0488             id: redoTimer
0489             repeat: false
0490             interval: 400
0491             onTriggered: {
0492                 acceptClick = true;
0493                 Activity.moveByEngine(move)
0494             }
0495             property var move
0496 
0497             function moveByEngine(engineMove) {
0498                 move = engineMove
0499                 redoTimer.start()
0500             }
0501         }
0502 
0503         DialogHelp {
0504             id: dialogHelp
0505             onClose: home()
0506         }
0507 
0508         Bar {
0509             id: bar
0510             level: items.currentLevel + 1
0511             content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) |
0512                                              (items.twoPlayer && !items.gameOver ? 0 : reload) }
0513 
0514             onHelpClicked: {
0515                 displayDialog(dialogHelp)
0516             }
0517             onPreviousLevelClicked: Activity.previousLevel()
0518             onNextLevelClicked: Activity.nextLevel()
0519             onHomeClicked: activity.home()
0520             onReloadClicked: {
0521                 trigComputerMove.stop()
0522                 Activity.initLevel()
0523             }
0524         }
0525 
0526         Bonus {
0527             id: bonus
0528         }
0529     }
0530 }