diff --git a/src/activities/oware/Oware.qml b/src/activities/oware/Oware.qml index cb8261cfc..a4fd9e90b 100644 --- a/src/activities/oware/Oware.qml +++ b/src/activities/oware/Oware.qml @@ -1,468 +1,469 @@ /* GCompris - Oware.qml * * Copyright (C) 2017 Divyam Madaan * * Authors: * Frederic Mazzarol (GTK+ version) * Divyam Madaan (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 "oware.js" as Activity import "." ActivityBase { id: activity property bool twoPlayer: false property bool horizontalLayout: (background.width > background.height) ? true : false onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.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 alias background: background property alias bar: bar property alias bonus: bonus property alias cellGridRepeater: cellGridRepeater property bool playerOneTurn: true property int playerOneScore: 0 property int playerTwoScore: 0 property alias playerOneLevelScore: playerOneLevelScore property alias playerTwoLevelScore: playerTwoLevelScore property alias boardModel: boardModel property bool computerTurn: false property var currentMove property var player property int indexValue + property bool gameEnded: false } onStart: { Activity.start(items,twoPlayer) } onStop: { Activity.stop() } // Timer to trigger computer move Timer { id: trigComputerMove repeat: false interval: 600 onTriggered: Activity.computerMove() } Item { id: boardModel width: parent.width * 0.7 height: width * 0.4 z: 2 anchors.centerIn: parent rotation: horizontalLayout ? 0 : 90 Image { id: board source: Activity.url + "/owareBoard.png" anchors.fill: parent } Rectangle { id: playerOneBorder height: 5 width: parent.width/4 color: "orange" anchors.top: board.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 6 } Rectangle { id: playerTwoBorder height: 5 width: parent.width/4 color: "blue" anchors.bottom: board.top anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 5 } // Grid of houses with 6 houses for each player Grid { id: boardGrid columns: 6 rows: 2 anchors.horizontalCenter: board.horizontalCenter anchors.top: board.top Repeater { id: cellGridRepeater model: 12 Rectangle { id: house color: "transparent" height: board.height/2 width: board.width * (1/6.25) property real circleRadius: width property int value property var nextMove GCText { text: value color: "white" anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter z: 2 rotation: (background.width > background.height) ? 0 : 270 fontSize: smallSize } MouseArea { id: buttonClick anchors.fill: parent onPressed: { for(var i = 0; i < grainRepeater.count; i++) grainRepeater.itemAt(i).source = Activity.url + "grain2.png" cellGridRepeater.itemAt(items.indexValue).z = 0 items.indexValue = index items.currentMove = items.playerOneTurn ? (index - 6) : (11 - index) items.player = items.playerOneTurn ? 0 : 1 if ((!items.computerTurn && items.playerOneTurn && (items.currentMove >= 0 && items.currentMove <= 5) && Activity.isValidMove(items.currentMove,1,Activity.house)) || (!items.playerOneTurn && (items.currentMove >= 6 && items.currentMove <= 11) && Activity.isValidMove(items.currentMove,0,Activity.house)) && Activity.house[items.currentMove] != 0) { cellGridRepeater.itemAt(items.indexValue).z = 20 firstMove() items.playerOneTurn = !items.playerOneTurn // Activity.seedsExhausted(Activity.house,0,Activity.scoreHouse) } } } function firstMove() { items.boardModel.enabled = false /* If the indexValue on which player has clicked is between 6 and 11 then the first move will be towards right. */ if(items.indexValue >= 6 && items.indexValue < 11) nextMove = "right" /* Else if the indexValue on which player has clicked is 11 then first move will be up */ else if(items.indexValue == 11) nextMove = "up" /* Similarly if the indexValue on which player has clicked is between 0 and 5 then first move will be left and if equal to 0 then it will be down. */ else if(items.indexValue > 0 && items.indexValue <= 5) nextMove = "left" else if(items.indexValue == 0) nextMove = "down" for(var i = 0; i < grainRepeater.count; i++) { grainRepeater.itemAt(i).startAnimation() } } Repeater { id: grainRepeater model: value Image { id: grain source: Activity.url + "grain2.png" height: circleRadius * 0.2 width: circleRadius * 0.2 x: circleRadius/2 + Activity.getX(circleRadius/6, index,value) y: circleRadius/2 + Activity.getY(circleRadius/5, index,value) property int currentIndex: index property int currentSeeds: grainRepeater.count property int totalSeeds: grainRepeater.count // moveCount is the current index of the moving seed wrt board. property int moveCount: items.indexValue signal checkAnimation Timer { id: moveSeedsTimer repeat: false interval: 500 onTriggered: { Activity.setValues(Activity.house) } } onCheckAnimation: { - if(!currentSeeds) { + if(!currentSeeds && !items.gameEnded) { if((twoPlayer) || (!twoPlayer && !items.playerOneTurn)) { Activity.sowSeeds(items.currentMove,Activity.house,Activity.scoreHouse,items.player) } moveSeedsTimer.start() if(!twoPlayer && !items.playerOneTurn) { items.computerTurn = true trigComputerMove.start() items.playerOneTurn = !items.playerOneTurn } } } function startAnimation() { grainRepeater.itemAt(index).source = Activity.url + "grain.png" if(currentIndex >= 0 && currentSeeds > 0) { if(nextMove == "right" && currentIndex >= 0) xRightAnimation.start() else if(nextMove == "up" && currentIndex >= 0) yUpAnimation.start() else if(nextMove == "left") xLeftAnimation.start() else if(nextMove == "down" && currentIndex >= 0) yDownAnimation.start() } checkAnimation() } property var xLeftAnimation: NumberAnimation { target: grain properties: "x" from: x ;to: x - (0.15 * board.width) duration: 450 onStopped: { if(currentIndex >= 0 && currentSeeds > 0) { currentSeeds-- currentIndex--; moveCount-- if(moveCount > 0 && moveCount < 6) nextMove = "left" else if(moveCount == 0) nextMove = "down" startAnimation() } } } property var xRightAnimation: NumberAnimation { target: grain properties: "x" from: x ;to: x + (0.15 * board.width) duration: 450 onStopped: { if(currentIndex >= 0 && currentSeeds > 0) { currentSeeds-- currentIndex-- moveCount++ if(moveCount >= 6 && moveCount < 11) nextMove = "right" else if(moveCount == 11) nextMove = "up" startAnimation() } } } property var yUpAnimation: NumberAnimation { target: grain properties: "y" from: y; to: y - 0.5 * board.height duration: 350 onStopped: { if(currentIndex >= 0 && currentSeeds > 0) { currentSeeds-- currentIndex-- moveCount = 5 nextMove = "left" startAnimation() } } } property var yDownAnimation: NumberAnimation { target: grain properties: "y" loops: 1 from: y; to: y + 0.5 * board.height duration: 350 onStopped: { if(currentIndex >= 0 && currentSeeds > 0) { currentSeeds-- currentIndex-- moveCount = 6 nextMove = "right" startAnimation() } } } } } } } } Image { id: playerOneScoreBox height: board.height * 0.5 width: height source:Activity.url+"/score.png" anchors.verticalCenter: parent.verticalCenter anchors.right: boardModel.left Flow { width: board.width * (1/7.25) height: parent.height anchors.centerIn: parent Repeater { id: playerOneScoreRepeater model: items.playerOneScore Image { id: playerOneSeedsImage source: Activity.url + "grain2.png" height: board.width * (1 / 7.25) * 0.2 width: board.width * (1 / 7.25) * 0.2 x: parent.width/2 + Activity.getX(parent.width/6, index,items.playerOneScore) y: parent.width/2 + Activity.getY(parent.width/5, index,items.playerOneScore) } } } GCText { id: playerOneScoreText color: "white" anchors.bottom: parent.top anchors.horizontalCenter: parent.horizontalCenter fontSize: smallSize text: items.playerOneScore horizontalAlignment: Text.AlignHCenter rotation: (background.width > background.height) ? 0 : 270 wrapMode: TextEdit.WordWrap } } Image { id: playerTwoScore height: board.height * 0.5 width: height source:Activity.url+"/score.png" anchors.verticalCenter: parent.verticalCenter anchors.left: boardModel.right Flow { width: board.width * (1/7.25) height: parent.height anchors.centerIn: parent Repeater { id: playerTwoScoreRepeater model: items.playerTwoScore Image { id: playerTwoSeedsImage source: Activity.url + "grain2.png" height: board.width * (1 / 7.25) * 0.2 width: board.width * (1 / 7.25) * 0.2 x: parent.width/2 + Activity.getX(parent.width/6, index,items.playerTwoScore) y: parent.width/2 + Activity.getY(parent.width/5, index,items.playerTwoScore) } } } GCText { id: playerTwoScoreText color: "white" fontSize: smallSize text: items.playerTwoScore anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.top rotation: (background.width > background.height) ? 0 : 270 wrapMode: TextEdit.WordWrap } } } Image { id: tutorialImage source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" anchors.fill: parent z: 5 visible: twoPlayer ? false : true Tutorial { id:tutorialSection tutorialDetails: Activity.tutorialInstructions onSkipPressed: { Activity.initLevel() tutorialImage.z = 0 playerOneLevelScore.beginTurn() } } } ScoreItem { id: playerOneLevelScore player: 1 height: Math.min(background.height/7, Math.min(background.width/7, bar.height * 1.05)) width: height * 11/8 playerScore: 0 anchors { top: background.top topMargin: 5 left: background.left leftMargin: 5 } playerImageSource: "qrc:/gcompris/src/activities/align4-2players/resource/player_1.svg" backgroundImageSource: "qrc:/gcompris/src/activities/align4-2players/resource/score_1.svg" } ScoreItem { id: playerTwoLevelScore player: 2 height: Math.min(background.height/7, Math.min(background.width/7, bar.height * 1.05)) width: height * 11/8 playerScore: 0 anchors { top: background.top topMargin: 5 right: background.right rightMargin: 5 } playerImageSource: "qrc:/gcompris/src/activities/align4-2players/resource/player_2.svg" backgroundImageSource: "qrc:/gcompris/src/activities/align4-2players/resource/score_2.svg" playerScaleOriginX: playerTwoLevelScore.width } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: twoPlayer ? (help | home | reload) : (tutorialSection.visible ? (help | home) : (help | home | level | reload)) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reset() } Bonus { id: bonus } } } diff --git a/src/activities/oware/oware.js b/src/activities/oware/oware.js index 34973f5b9..d8b3bd830 100644 --- a/src/activities/oware/oware.js +++ b/src/activities/oware/oware.js @@ -1,334 +1,335 @@ /* GCompris - oware.js * * Copyright (C) 2017 Divyam Madaan * * Authors: * Frederic Mazzarol (GTK+ version) * Divyam Madaan (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 = 0 var numberOfLevel = 5 var items var url = "qrc:/gcompris/src/activities/oware/resource/" // house variable is used for storing the count of all the seeds as we play. var house = [] var scoreHouse = [0, 0] var nextPlayer = 1 var playerSideEmpty = false; var maxDiff = [20, 15, 10, 5, 0] var depth var heuristicValue var lastMove var finalMove var twoPlayer var tutorialInstructions = [{ - "instruction": qsTr("At the beginning of the game four seeds are placed in each house. Each player has 6 houses. The first 6 houses starting from the bottom left belong to one player and the upper 6 houses belong to the other player."), + "instruction": qsTr("At the beginning of the game four seeds are placed in each house. Each player has 6 houses. The first 6 houses starting from the bottom left belong to one player and the upper 6 houses belong to the other player."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial1.png" }, { "instruction": qsTr("In each turn, a player chooses one of their 6 houses. All seeds from that house are picked and dropped one in each house counter-clockwise from the house they chose, in a process called sowing. As in the image below the blue seeds from the second house are picked and sown in next houses (yellow seeds represent the sown seeds)."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial2.png" }, { "instruction": qsTr("However if the number of seeds in the house chosen is equal or more than 12, then seed is not dropped in the house from which the player picked up the seeds. As in the image below second house has more than 12 seeds so while sowing the seed is not dropped in that house."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial3.png" }, { "instruction": qsTr("After a turn, if the last seed was placed into the opponent's house and brought the total number of seeds in that house to two or three, all the seeds in that house are captured and added to player's scoring house. If the previous-to-last seed dropped also brought the total seeds in an opponent's house to two or three, these are captured as well, and so on."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial4.png" }, { "instruction": qsTr("If all the houses of one player are empty, the other player has to take such a move that it gives one or more seeds to the other player to continue the game."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial5.png" }, { "instruction": qsTr("However, if the current player is unable to give any seed to the opponent, then the current player keeps all the seeds in the houses of his side and the game ends."), "instructionImage": "qrc:/gcompris/src/activities/oware/resource/tutorial6.png" } ] function start(items_, twoPlayer_) { items = items_ twoPlayer = twoPlayer_ currentLevel = 0 reset() } function stop() {} // Function to reload the activity. function reset() { items.boardModel.enabled = true items.playerOneLevelScore.endTurn() items.playerTwoLevelScore.endTurn() items.playerOneLevelScore.beginTurn() items.playerOneTurn = true initLevel() } function initLevel() { items.bar.level = currentLevel + 1 var singleHouseSeeds = 4 for (var i = 11; i >= 0; i--) house[i] = singleHouseSeeds items.playerOneScore = 0 items.playerTwoScore = 0 scoreHouse = [0, 0] depth = currentLevel setValues(house) } function nextLevel() { if (numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel(); } function previousLevel() { if (--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } // Function to get the x position of seeds. function getX(radius, index, value) { var step = (2 * Math.PI) * index / value; return radius * Math.cos(step); } // Function to get the y position of seeds. function getY(radius, index, value) { var step = (2 * Math.PI) * index / value; return radius * Math.sin(step); } function computerMove() { if (items.playerOneScore - items.playerTwoScore >= maxDiff[currentLevel]) { var houseClone = house.slice() var scoreClone = scoreHouse.slice() var index = alphaBeta(4, -200, 200, houseClone, scoreClone, 0, lastMove) finalMove = index[0] } if (items.playerOneScore - items.playerTwoScore < maxDiff[currentLevel]) randomMove() sowSeeds(finalMove, house, scoreHouse, 1) items.cellGridRepeater.itemAt(items.indexValue).z = 0 items.indexValue = 11 - finalMove - items.cellGridRepeater.itemAt(items.indexValue).z = 20 - items.cellGridRepeater.itemAt(11 - finalMove).firstMove() - items.computerTurn = false - items.playerTwoScore = scoreHouse[1] - items.playerOneScore = scoreHouse[0] + if(!items.gameEnded) { + items.cellGridRepeater.itemAt(items.indexValue).z = 20 + items.cellGridRepeater.itemAt(11 - finalMove).firstMove() + items.computerTurn = false + } } // Random moves are made when the difference between scores is less than maxDiff[levelNumber] function randomMove() { var move = Math.floor(Math.random() * (12 - 6) + 6) if (house[move] != 0 && isValidMove(move, 0, house)) { finalMove = move } else randomMove() } function gameOver(board, score) { if (score[0] > 24 || score[1] > 24) return true return false } function seedsExhausted(board,next,score) { var canGive = false if(!next) { for(var i = 6; i < 12; i++) { if(board[i] % 12 > 12 - i) canGive = true } } else if(next) { for(var i = 0; i < 6; i++) { if( board[move] % 12 > 6 - move) canGive = true } } if(canGive) return true else { for(var i = next * 6; i < next * 6 + 6; i++) scoreHouse[next] += house[i] setValues(board) } } function alphaBeta(depth, alpha, beta, board, score, nextPlayer, lastMove) { var heuristicValue var childHeuristics var bestMove if (depth == 0 || gameOver(board, score)) { heuristicValue = heuristicEvaluation(score) return [-1, heuristicValue] } for (var move = 0; move < 12; move++) { if (!isValidMove(move, nextPlayer, board)) continue board = house.slice() score = scoreHouse.slice() var lastMoveAI = sowSeeds(move, board, score, nextPlayer) var out = alphaBeta(depth - 1, alpha, beta, lastMoveAI.board, lastMoveAI.scoreHouse, lastMoveAI.nextPlayer, lastMoveAI.lastMove) childHeuristics = out[1] if (nextPlayer) { if (beta > childHeuristics) { beta = childHeuristics bestMove = lastMoveAI.lastMove } if (alpha >= childHeuristics) break; } else { if (alpha < childHeuristics) { alpha = childHeuristics bestMove = lastMoveAI.lastMove } if (beta <= childHeuristics) break; } } heuristicValue = nextPlayer ? beta : alpha return [bestMove, heuristicValue] } function heuristicEvaluation(score) { var playerScores = []; for (var i = 0; i < 2; i++) { playerScores[i] = score[i] if (playerScores[i] > 24) playerScores[i] += 100 } return playerScores[1] - playerScores[0] } function isValidMove(move, next, board) { if ((next && move > 6) || (!next && move < 6)) return false if (!board[move]) return false var sum = 0; for (var j = next * 6; j < (next * 6 + 6); j++) sum += board[j]; if (sum == 0 && ((!next && board[move] % 12 < 12 - move) || (next && board[move] % 12 < 6 - move))) return false else return true } function setValues(board) { for (var i = 6, j = 0; i < 12, j < 6; j++, i++) items.cellGridRepeater.itemAt(i).value = board[j] for (var i = 0, j = 11; i < 6, j > 5; j--, i++) items.cellGridRepeater.itemAt(i).value = board[j] - var gameEnded = false + items.gameEnded = false items.playerTwoScore = scoreHouse[1] items.playerOneScore = scoreHouse[0] if (items.playerTwoScore >= 25) { if(!twoPlayer) items.bonus.bad("flower") else items.bonus.good("flower") items.playerOneLevelScore.endTurn() items.playerTwoLevelScore.endTurn() items.playerTwoLevelScore.win() items.boardModel.enabled = false - gameEnded = true + items.gameEnded = true } else if (items.playerOneScore >= 25) { print("won") items.playerOneLevelScore.win() items.playerTwoLevelScore.endTurn() items.boardModel.enabled = false - gameEnded = true + items.gameEnded = true } - items.boardModel.enabled = true - if (!items.playerOneTurn && !gameEnded) { + if (!items.playerOneTurn && !items.gameEnded) { items.playerOneLevelScore.endTurn() items.playerTwoLevelScore.beginTurn() - } else if (!gameEnded) { + items.boardModel.enabled = true + } else if (!items.gameEnded) { items.playerTwoLevelScore.endTurn() items.playerOneLevelScore.beginTurn() + items.boardModel.enabled = true } } function sowSeeds(index, board, scoreHouse, nextPlayer) { var currentPlayer = (nextPlayer + 1) % 2 var nextIndex = index lastMove = index // The seeds are sown until the picked seeds are equal to zero while (board[index]) { nextIndex = (nextIndex + 1) % 12 // If there are more than or equal to 12 seeds than we don't sow the in the pit from where we picked the seeds. if (index == nextIndex) { nextIndex = (nextIndex + 1) % 12 } // Decrement the count of seeds and sow it in the nextIndex board[index]--; board[nextIndex]++; } // The nextIndex now contains the seeds in the last pit sown. var capture = []; // The opponent's seeds are captured if they are equal to 2 or 3 if (((board[nextIndex] == 2 || board[nextIndex] == 3)) && ((currentPlayer == 1 && nextIndex > 5 && nextIndex < 12) || (currentPlayer == 0 && nextIndex >= 0 && nextIndex < 6))) { capture[nextIndex % 6] = true; } /* The seeds previous to the captured seeds are checked. If they are equal to 2 or 3 then they are captured until a pit arrives which has more than 3 seeds or 1 seed. */ while (capture[nextIndex % 6] && nextIndex % 6) { nextIndex--; if (board[nextIndex] == 2 || board[nextIndex] == 3) { capture[nextIndex % 6] = true; } } var allSeedsCaptured = true; /* Now we check if all the seeds in opponents houses which were to be captured are captured or not. If any of the house is not yet captured we set allSeedsCaptured as false */ for (var j = currentPlayer * 6; j < (currentPlayer * 6 + 6); j++) { if (!capture[j % 6] && board[j]) allSeedsCaptured = false; } // Now capture the seeds for the houses for which capture[houseIndex] = true if all seeds are not captured if (!allSeedsCaptured) { for (var j = currentPlayer * 6; j < (currentPlayer * 6 + 6); j++) { /* If opponent's houses capture is true we set the no of seeds in that house as 0 and give the seeds to the opponent. */ if (capture[j % 6]) { scoreHouse[nextPlayer] = scoreHouse[nextPlayer] + board[j]; board[j] = 0; } } } nextPlayer = currentPlayer var obj = { board: board, scoreHouse: scoreHouse, nextPlayer: nextPlayer, lastMove: lastMove } return obj } diff --git a/src/activities/oware/resource/tutorial3.png b/src/activities/oware/resource/tutorial3.png index b1490aeec..6b97e3205 100644 Binary files a/src/activities/oware/resource/tutorial3.png and b/src/activities/oware/resource/tutorial3.png differ diff --git a/src/activities/oware/resource/tutorial4.png b/src/activities/oware/resource/tutorial4.png index d8f7296c4..fd7d65078 100644 Binary files a/src/activities/oware/resource/tutorial4.png and b/src/activities/oware/resource/tutorial4.png differ