Warning, /education/gcompris/src/activities/chess/Chess.qml is written in an unsupported language. File is not indexed.
0001 /* GCompris - chess.qml 0002 * 0003 * SPDX-FileCopyrightText: 2015 Bruno Coudoin <bruno.coudoin@gcompris.net> 0004 * 0005 * Authors: 0006 * Bruno Coudoin <bruno.coudoin@gcompris.net> (GTK+ version) 0007 * Bruno Coudoin <bruno.coudoin@gcompris.net> (Qt Quick port) 0008 * Timothée Giet <animtim@gmail.com> (big layout refactoring) 0009 * 0010 * SPDX-License-Identifier: GPL-3.0-or-later 0011 */ 0012 import QtQuick 2.12 0013 import GCompris 1.0 0014 0015 import "../../core" 0016 import "." 0017 import "chess.js" as Activity 0018 0019 ActivityBase { 0020 id: activity 0021 0022 property bool acceptClick: true 0023 property bool buttonsBlocked: false 0024 onButtonsBlockedChanged: if(buttonsBlocked) unlockButtonBlock.restart() 0025 Timer { 0026 id: unlockButtonBlock 0027 interval: 400 0028 running: false 0029 repeat: false 0030 onTriggered: buttonsBlocked = false 0031 } 0032 0033 property bool twoPlayers: false 0034 property bool displayTakenPiecesButton: true 0035 property int coordsOpacity: 1 0036 property int movesCount: 0 0037 // difficultyByLevel means that at level 1 computer is bad better at last level 0038 property bool difficultyByLevel: true 0039 property var fen: [ 0040 ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], 0041 ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], 0042 ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], 0043 ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], 0044 ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"] 0045 ] 0046 0047 onStart: focus = true 0048 onStop: unlockButtonBlock.stop() 0049 0050 pageComponent: Image { 0051 id: background 0052 anchors.fill: parent 0053 source: Activity.url + 'background-wood.svg' 0054 signal start 0055 signal stop 0056 0057 Component.onCompleted: { 0058 activity.start.connect(start) 0059 activity.stop.connect(stop) 0060 } 0061 0062 // Add here the QML items you need to access in javascript 0063 QtObject { 0064 id: items 0065 property Item main: activity.main 0066 property GCSfx audioEffects: activity.audioEffects 0067 property alias background: background 0068 property int currentLevel: activity.currentLevel 0069 property alias bonus: bonus 0070 property var barHeightAddon: ApplicationSettings.isBarHidden ? textMessage.height : bar.height 0071 property bool isPortrait: (background.height >= background.width) 0072 property int cellSize: boardBg.width * 0.1 0073 property var fen: activity.fen 0074 property bool twoPlayer: activity.twoPlayers 0075 property bool difficultyByLevel: activity.difficultyByLevel 0076 property var positions 0077 property var pieces: pieces 0078 property var squares: squares 0079 property var history 0080 property var redo_stack 0081 property alias redoTimer: redoTimer 0082 property int from 0083 property bool blackTurn 0084 property bool gameOver 0085 property string message 0086 property bool isWarningMessage 0087 property alias trigComputerMove: trigComputerMove 0088 property alias timerSwap: timerSwap 0089 property alias whiteTakenPieceModel: whiteTakenPieces.takenPiecesModel 0090 property alias blackTakenPieceModel: blackTakenPieces.takenPiecesModel 0091 property bool displayUndoAllDialog: false 0092 // 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 0093 property bool noPieceAnimation: false 0094 } 0095 0096 onStart: { Activity.start(items) } 0097 onStop: { Activity.stop() } 0098 0099 function performUndo() { 0100 if(items.history.length <= 0) 0101 return 0102 Activity.undo() 0103 if(whiteTakenPieces.pushedLast[whiteTakenPieces.pushedLast.length-1] == movesCount) { 0104 whiteTakenPieces.pushedLast.pop() 0105 whiteTakenPieces.takenPiecesModel.remove(whiteTakenPieces.takenPiecesModel.count-1) 0106 } 0107 if(!items.twoPlayer) { 0108 movesCount-- 0109 } 0110 if(blackTakenPieces.pushedLast[blackTakenPieces.pushedLast.length-1] == movesCount) { 0111 blackTakenPieces.pushedLast.pop() 0112 blackTakenPieces.takenPiecesModel.remove(blackTakenPieces.takenPiecesModel.count-1) 0113 } 0114 movesCount-- 0115 } 0116 0117 Loader { 0118 id: undoAllDialogLoader 0119 sourceComponent: GCDialog { 0120 parent: activity 0121 isDestructible: false 0122 //: Translators: undo all the moves in chess activity 0123 message: qsTr("Do you really want to undo all the moves?") 0124 button1Text: qsTr("Yes") 0125 button2Text: qsTr("No") 0126 onClose: items.displayUndoAllDialog = false 0127 onButton1Hit: { 0128 stop() 0129 while(items.history.length > 0) 0130 performUndo() 0131 } 0132 onButton2Hit: {} 0133 } 0134 anchors.fill: parent 0135 focus: true 0136 active: items.displayUndoAllDialog 0137 onStatusChanged: if (status == Loader.Ready) item.start() 0138 } 0139 0140 GCText { 0141 id: textMessage 0142 z: 20 0143 color: items.isWarningMessage ? "red" : "white" 0144 anchors.horizontalCenter: parent.horizontalCenter 0145 anchors.top: parent.top 0146 width: layoutArea.width 0147 fontSize: smallSize 0148 text: items.message 0149 horizontalAlignment: Text.AlignHCenter 0150 wrapMode: TextEdit.WordWrap 0151 } 0152 0153 Grid { 0154 id: controls 0155 z: 20 0156 spacing: (boardBg.width - items.cellSize * 4) * 0.25 0157 columns: items.isPortrait ? 4 : 1 0158 horizontalItemAlignment: Grid.AlignHCenter 0159 verticalItemAlignment: Grid.AlignVCenter 0160 0161 GCTimerButton { 0162 id: undo 0163 height: items.cellSize 0164 width: items.cellSize 0165 enabled: !buttonsBlocked && opacity == 1 0166 opacity: items.history.length > 0 ? 1 : 0 0167 Image { 0168 source: Activity.url + 'undo.svg' 0169 height: items.cellSize 0170 width: items.cellSize 0171 sourceSize.height: items.cellSize 0172 fillMode: Image.PreserveAspectFit 0173 } 0174 0175 onClicked: { 0176 if(!items.displayUndoAllDialog) 0177 performUndo() 0178 } 0179 onPressAndHold: { 0180 items.displayUndoAllDialog = true 0181 } 0182 Behavior on opacity { 0183 PropertyAnimation { 0184 easing.type: Easing.InQuad 0185 duration: 200 0186 } 0187 } 0188 } 0189 0190 GCButton { 0191 id: redo 0192 height: items.cellSize 0193 width: items.cellSize 0194 text: ""; 0195 theme: "noStyle" 0196 onClicked: { 0197 buttonsBlocked = true 0198 if (!twoPlayers) { 0199 acceptClick = false; 0200 Activity.redo() 0201 } else { 0202 Activity.redo() 0203 } 0204 } 0205 enabled: !buttonsBlocked && opacity == 1 0206 opacity: items.redo_stack.length > 0 && acceptClick ? 1 : 0 0207 Image { 0208 source: Activity.url + 'redo.svg' 0209 height: items.cellSize 0210 width: items.cellSize 0211 sourceSize.height: items.cellSize 0212 fillMode: Image.PreserveAspectFit 0213 } 0214 Behavior on opacity { 0215 PropertyAnimation { 0216 easing.type: Easing.InQuad 0217 duration: 200 0218 } 0219 } 0220 } 0221 0222 GCButton { 0223 id: drawerButton 0224 height: items.cellSize 0225 width: items.cellSize 0226 text: ""; 0227 theme: "noStyle" 0228 0229 onClicked: { 0230 whiteTakenPieces.open = !whiteTakenPieces.open 0231 blackTakenPieces.open = !blackTakenPieces.open 0232 } 0233 0234 enabled: !buttonsBlocked && displayTakenPiecesButton 0235 opacity: displayTakenPiecesButton ? 1 : 0 0236 Image { 0237 source: Activity.url + 'captured.svg' 0238 height: items.cellSize 0239 width: items.cellSize 0240 sourceSize.height: items.cellSize 0241 fillMode: Image.PreserveAspectFit 0242 } 0243 Behavior on opacity { 0244 PropertyAnimation { 0245 easing.type: Easing.InQuad 0246 duration: 200 0247 } 0248 } 0249 } 0250 0251 GCButton { 0252 height: items.cellSize 0253 width: items.cellSize 0254 text: ""; 0255 theme: "noStyle" 0256 enabled: !timerSwap.running && items.twoPlayer 0257 opacity: items.twoPlayer 0258 Image { 0259 source: Activity.url + 'turn.svg' 0260 height: items.cellSize 0261 width: items.cellSize 0262 sourceSize.height: items.cellSize 0263 fillMode: Image.PreserveAspectFit 0264 } 0265 onClicked: chessboard.swap() 0266 } 0267 } 0268 0269 Rectangle { 0270 id: layoutArea 0271 width: background.width 0272 height: background.height - textMessage.height - items.barHeightAddon * 1.1 0273 opacity: 0 0274 anchors.horizontalCenter: background.horizontalCenter 0275 } 0276 0277 Rectangle { 0278 id: controlsArea 0279 anchors.left: background.left 0280 anchors.right: boardBg.left 0281 anchors.top: boardBg.top 0282 anchors.bottom: boardBg.bottom 0283 opacity: 0 0284 } 0285 0286 states: [ 0287 State { 0288 name: "portraitLayout"; when: items.isPortrait 0289 PropertyChanges { 0290 target: layoutArea 0291 width: background.width * 0.86 0292 height: background.height - textMessage.height - bar.height * 1.1 0293 } 0294 PropertyChanges { 0295 target: controls 0296 width:layoutArea.width 0297 height: items.cellSize * 1.2 0298 anchors.leftMargin: controls.spacing * 0.5 0299 anchors.topMargin: 0 0300 anchors.horizontalCenterOffset: 0 0301 } 0302 PropertyChanges { 0303 target: boardBg 0304 anchors.verticalCenterOffset: items.cellSize * -0.6 0305 } 0306 AnchorChanges { 0307 target: layoutArea 0308 anchors.top: controls.bottom 0309 } 0310 AnchorChanges { 0311 target: controls 0312 anchors.top: textMessage.bottom 0313 anchors.horizontalCenter: undefined 0314 anchors.left: boardBg.left 0315 } 0316 }, 0317 State { 0318 name: "horizontalLayout"; when: !items.isPortrait 0319 PropertyChanges { 0320 target: layoutArea 0321 width: background.width 0322 height: background.height - textMessage.height - items.barHeightAddon * 1.1 0323 } 0324 PropertyChanges { 0325 target: controls 0326 width: items.cellSize * 1.2 0327 height: layoutArea.height 0328 anchors.leftMargin: 0 0329 anchors.topMargin: controls.spacing * 0.5 0330 anchors.horizontalCenterOffset: items.cellSize * 0.8 0331 } 0332 PropertyChanges { 0333 target: boardBg 0334 anchors.verticalCenterOffset: 0 0335 } 0336 AnchorChanges { 0337 target: layoutArea 0338 anchors.top: textMessage.bottom 0339 } 0340 AnchorChanges { 0341 target: controls 0342 anchors.top: controlsArea.top 0343 anchors.horizontalCenter: controlsArea.horizontalCenter 0344 anchors.left: undefined 0345 } 0346 } 0347 ] 0348 0349 Rectangle { 0350 id: boardBg 0351 width: Math.min(layoutArea.width, layoutArea.height) 0352 height: boardBg.width 0353 anchors.centerIn: layoutArea 0354 z: 08 0355 color: "#452501" 0356 onWidthChanged: items.noPieceAnimation = true 0357 0358 // The chessboard 0359 GridView { 0360 id: chessboard 0361 cellWidth: items.cellSize 0362 cellHeight: items.cellSize 0363 width: items.cellSize * 8 0364 height: chessboard.width 0365 interactive: false 0366 keyNavigationWraps: true 0367 model: 64 0368 layoutDirection: Qt.RightToLeft 0369 delegate: square 0370 rotation: 180 0371 z: 10 0372 anchors.centerIn: boardBg 0373 0374 Component { 0375 id: square 0376 Image { 0377 source: index % 2 + (Math.floor(index / 8) % 2) == 1 ? 0378 Activity.url + 'chess-white.svg' : Activity.url + 'chess-black.svg'; 0379 width: items.cellSize 0380 height: items.cellSize 0381 } 0382 } 0383 0384 Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } 0385 0386 function swap() { 0387 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') 0388 coordsOpacity = 0 0389 timerSwap.start() 0390 if(chessboard.rotation == 180) 0391 chessboard.rotation = 0 0392 else 0393 chessboard.rotation = 180 0394 } 0395 } 0396 0397 Timer { 0398 id: timerSwap 0399 interval: 1500 0400 running: false 0401 repeat: false 0402 onTriggered: coordsOpacity = 1 0403 } 0404 0405 Item { 0406 id: letters 0407 anchors.left: chessboard.left 0408 anchors.top: chessboard.bottom 0409 opacity: coordsOpacity 0410 Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } 0411 Repeater { 0412 id: lettersA 0413 model: chessboard.rotation == 0 ? ["H", "G", "F", "E", "D", "C", "B", "A"] : ["A", "B", "C", "D", "E", "F", "G", "H"] 0414 GCText { 0415 x: items.cellSize * (index % 8) + (items.cellSize/2-width/2) 0416 y: items.cellSize * Math.floor(index / 8) 0417 text: modelData 0418 color: "#CBAE7B" 0419 font.pointSize: NaN 0420 font.pixelSize: items.cellSize * 0.5 0421 } 0422 } 0423 } 0424 Item { 0425 id: numbers 0426 anchors.left: chessboard.right 0427 anchors.top: chessboard.top 0428 opacity: coordsOpacity 0429 Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } 0430 Repeater { 0431 model: chessboard.rotation == 0 ? ["1", "2", "3", "4", "5", "6", "7", "8"] : ["8", "7", "6", "5", "4", "3", "2", "1"] 0432 GCText { 0433 x: items.cellSize * Math.floor(index / 8) + width 0434 y: items.cellSize * (index % 8) + (items.cellSize/2-height/2) 0435 text: modelData 0436 color: "#CBAE7B" 0437 font.pointSize: NaN 0438 font.pixelSize: items.cellSize * 0.5 0439 } 0440 } 0441 } 0442 0443 Rectangle { 0444 id: boardBorder 0445 width: items.cellSize * 10 0446 height: boardBorder.width 0447 anchors.centerIn: boardBg 0448 z: -1 0449 color: "#542D0F" 0450 border.color: "#3A1F0A" 0451 border.width: items.cellSize * 0.1 0452 } 0453 } 0454 0455 Repeater { 0456 id: squares 0457 model: items.positions 0458 delegate: square 0459 parent: chessboard 0460 0461 DropArea { 0462 id: square 0463 x: items.cellSize * (7 - pos % 8) + spacing / 2 0464 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 0465 width: items.cellSize - spacing 0466 height: square.width 0467 z: 1 0468 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] 0469 property bool acceptMove : false 0470 property int pos: modelData.pos 0471 property int spacing: 6 * ApplicationInfo.ratio 0472 Rectangle { 0473 id: possibleMove 0474 anchors.fill: parent 0475 color: parent.containsDrag ? '#803ACAFF' : 'transparent' 0476 border.width: parent.acceptMove ? 5 : 0 0477 border.color: '#FF4EB271' 0478 z: 1 0479 } 0480 } 0481 0482 function getSquareAt(pos) { 0483 for(var i=0; i < squares.count; i++) { 0484 if(squares.itemAt(i).pos === pos) 0485 return squares.itemAt(i) 0486 } 0487 return(undefined) 0488 } 0489 } 0490 0491 Repeater { 0492 id: pieces 0493 model: items.positions 0494 delegate: piece 0495 parent: chessboard 0496 0497 Piece { 0498 id: piece 0499 sourceSize.width: items.cellSize 0500 width: items.cellSize - spacing 0501 height: piece.width 0502 source: img ? Activity.url + img + '.svg' : '' 0503 img: modelData.img 0504 x: items.cellSize * (7 - pos % 8) + spacing / 2 0505 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 0506 z: 1 0507 pos: modelData.pos 0508 newPos: modelData.pos 0509 rotation: -chessboard.rotation 0510 0511 property int spacing: 6 * ApplicationInfo.ratio 0512 0513 Drag.active: dragArea.drag.active 0514 Drag.hotSpot.x: width / 2 0515 Drag.hotSpot.y: height / 2 0516 0517 MouseArea { 0518 id: dragArea 0519 anchors.fill: parent 0520 enabled: !items.gameOver && !items.trigComputerMove.running 0521 drag.target: ((items.blackTurn && !parent.isWhite) || (!items.blackTurn && parent.isWhite)) ? 0522 parent : null 0523 onPressed: { 0524 piece.Drag.keys = ['acceptMe'] 0525 parent.z = 100 0526 if(parent.isWhite == 1 && !items.blackTurn || 0527 parent.isWhite == 0 && items.blackTurn) { 0528 items.from = parent.newPos 0529 Activity.showPossibleMoves(items.from) 0530 } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { 0531 Activity.moveTo(items.from, parent.newPos) 0532 } 0533 } 0534 onReleased: { 0535 // If no target or move not possible, we reset the position 0536 if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { 0537 var pos = parent.pos 0538 // Force recalc of the old x,y position 0539 parent.pos = -1 0540 pieces.getPieceAt(pos).move(pos) 0541 } 0542 } 0543 } 0544 } 0545 0546 function moveTo(from, to) { 0547 movesCount ++ 0548 var fromPiece = getPieceAt(from) 0549 var toPiece = getPieceAt(to) 0550 0551 // Specific case for en passant move. It is a case where 0552 // we capture a pawn without having it at the "to" position. 0553 // To know if we captured, we browse the whole board to look 0554 // for missing pawn 0555 var state = Activity.simplifiedState(Activity.state.board) 0556 for(var i=0; i < state.length; ++i) { 0557 var pos = state[i].pos 0558 var pawnPiece = getPieceAt(pos) 0559 if(pos != from && state[i].img === "" && 0560 pawnPiece && pawnPiece.img[1] === 'p') { 0561 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') 0562 if(pawnPiece.isWhite) { 0563 whiteTakenPieces.takenPiecesModel.append(pawnPiece) 0564 whiteTakenPieces.pushedLast.push(movesCount) 0565 } else { 0566 blackTakenPieces.takenPiecesModel.append(pawnPiece) 0567 blackTakenPieces.pushedLast.push(movesCount) 0568 } 0569 pawnPiece.hide(pawnPiece.pos) 0570 break 0571 } 0572 } 0573 0574 if(toPiece.img !== '') { 0575 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') 0576 if(toPiece.isWhite) { 0577 whiteTakenPieces.takenPiecesModel.append(toPiece) 0578 whiteTakenPieces.pushedLast.push(movesCount) 0579 } else { 0580 blackTakenPieces.takenPiecesModel.append(toPiece) 0581 blackTakenPieces.pushedLast.push(movesCount) 0582 } 0583 } 0584 else 0585 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') 0586 toPiece.hide(from) 0587 fromPiece.move(to) 0588 } 0589 0590 function promotion(to) { 0591 var toPiece = getPieceAt(to) 0592 toPiece.promotion() 0593 } 0594 0595 function getPieceAt(pos) { 0596 for(var i=0; i < pieces.count; i++) { 0597 if(pieces.itemAt(i).newPos === pos) 0598 return pieces.itemAt(i) 0599 } 0600 return(undefined) 0601 } 0602 } 0603 0604 Timer { 0605 id: trigComputerMove 0606 repeat: false 0607 interval: 400 0608 onTriggered: Activity.randomMove() 0609 } 0610 0611 // Use to redo the computer move after the user move 0612 Timer { 0613 id: redoTimer 0614 repeat: false 0615 interval: 400 0616 onTriggered: { 0617 acceptClick = true; 0618 Activity.moveByEngine(move) 0619 } 0620 property var move 0621 0622 function moveByEngine(engineMove) { 0623 move = engineMove 0624 redoTimer.start() 0625 } 0626 } 0627 0628 TakenPiecesList { 0629 id: whiteTakenPieces 0630 width: items.cellSize * 0.8 0631 edge: false 0632 } 0633 TakenPiecesList { 0634 id: blackTakenPieces 0635 width: items.cellSize * 0.8 0636 edge: true 0637 } 0638 0639 DialogHelp { 0640 id: dialogHelp 0641 onClose: home() 0642 } 0643 0644 Bar { 0645 id: bar 0646 level: items.currentLevel + 1 0647 content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | 0648 (items.twoPlayer && !items.gameOver ? 0 : reload) } 0649 0650 onHelpClicked: { 0651 displayDialog(dialogHelp) 0652 } 0653 onPreviousLevelClicked: Activity.previousLevel() 0654 onNextLevelClicked: Activity.nextLevel() 0655 onHomeClicked: activity.home() 0656 onReloadClicked: { 0657 trigComputerMove.stop() 0658 Activity.initLevel() 0659 } 0660 } 0661 0662 Bonus { 0663 id: bonus 0664 } 0665 } 0666 0667 }