File indexing completed on 2024-04-28 15:08:05

0001 /* GCompris - TicTacToe.js
0002  *
0003  * SPDX-FileCopyrightText: 2014 Pulkit Gupta <pulkitgenius@gmail.com>
0004  *
0005  * Authors:
0006  *   Pulkit Gupta <pulkitgenius@gmail.com>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 .pragma library
0011 .import QtQuick 2.12 as Quick
0012 .import "qrc:/gcompris/src/core/core.js" as Core
0013 
0014 var numberOfLevel = 5
0015 var items
0016 
0017 var url = "qrc:/gcompris/src/activities/tic_tac_toe/resource/"
0018 
0019 var currentPiece
0020 var currentPlayer
0021 var currentLocation
0022 var twoPlayer
0023 var track = []      //For tracking moves
0024 var hx      //x co-ordinate needed for creating first image when playSecond is enabled
0025 var vy      //y co-ordinate needed for creating first image when playSecond is enabled
0026 var stopper     //For stopping game when doing reset
0027 
0028 function start(items_, twoPlayer_) {
0029     items = items_
0030     items.currentLevel = Core.getInitialLevel(numberOfLevel);
0031     currentPlayer = 1
0032     twoPlayer = twoPlayer_
0033     items.playSecond = 0
0034 
0035     initLevel()
0036 }
0037 
0038 function stop() {
0039 }
0040 
0041 function initLevel() {
0042     items.counter = 0
0043     items.gameDone = false
0044     items.pieces.clear()
0045     track = []
0046     for(var y = 0;  y < items.rows; y++) {
0047         for(var x = 0;  x < items.columns; x++) {
0048             items.pieces.append({'stateTemp': "invisible"})
0049         }
0050     }
0051     if (items.playSecond) 
0052         initiatePlayer2()
0053     else 
0054         initiatePlayer1()
0055     stopper = false
0056     if (items.playSecond)
0057         changePlayToSecond()
0058 }
0059 
0060 function nextLevel() {
0061     items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel);
0062     reset();
0063 }
0064 
0065 function previousLevel() {
0066     items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel);
0067     reset();
0068 }
0069 
0070 function reset() {
0071     stopper = true
0072     hx = items.repeater.itemAt(1).x     
0073     vy = items.repeater.itemAt(3).y
0074     stopAnimations()
0075     items.pieces.clear() // Clear the board
0076     if (items.playSecond)
0077         items.playSecond = 0
0078     else
0079         items.playSecond = 1
0080     initLevel()
0081 }
0082 
0083 function stopAnimations() {
0084     items.magnify.stop();
0085     items.player1score.endTurn();
0086     items.player2score.endTurn();
0087 }
0088 
0089 //Initial values at the start of game when its player 1 turn
0090 function initiatePlayer1() {
0091     items.player2score.endTurn();
0092     items.player1score.beginTurn();
0093 }
0094 
0095 //Initial values at the start of game when its player 2 turn
0096 function initiatePlayer2() {
0097     items.player1score.endTurn();
0098     items.player2score.beginTurn();
0099 }
0100 
0101 //Change scale of score boxes according to turns
0102 function changeScale() {
0103     if (items.playSecond) {
0104         if (items.counter%2 == 0) {
0105             initiatePlayer2()
0106             shouldComputerPlay()
0107         }
0108         else
0109             initiatePlayer1()
0110     }
0111     else {
0112         if (items.counter%2 == 0) {
0113             initiatePlayer1()
0114         }
0115         else {
0116             initiatePlayer2()
0117             shouldComputerPlay()
0118         }
0119     }
0120 }
0121 
0122 //Changing play to second in single player mode
0123 function changePlayToSecond() {
0124     if (items.playSecond == 0) {
0125         items.playSecond = 1
0126         reset()
0127         return 0
0128     }
0129     if (!twoPlayer) {
0130         var rand = Math.floor((Math.random() * 9))
0131         var y = parseInt(rand/3) * vy
0132         var x = parseInt(rand%3) * hx
0133         items.repeater.itemAt(rand).state = "DONE"
0134         currentPiece = rand
0135         items.createPiece.x = x
0136         items.createPiece.y = y
0137         items.demagnify.start()
0138         items.createPiece.opacity = 1
0139         items.magnify.start()
0140     }
0141 }
0142 
0143 //Changing play to second in single player mode
0144 function changePlayToFirst() {
0145     items.playSecond = 0
0146     reset()
0147 }
0148 
0149 //Get row of the corresponding square box (square boxes are defined in repeater)
0150 function getrowno(parentY) {
0151     for(var i = 0; i < items.rows - 1; i++) {
0152         if(parentY == items.repeater.itemAt(i*3).y) {
0153             return i
0154         }
0155     }
0156     return items.rows - 1
0157 }
0158 
0159 //Get column of the corresponding square box (square boxes are defined in repeater)
0160 function getcolno(parentX) {
0161     for(var i = 0; i < items.columns - 1; i++) {
0162         if(parentX == items.repeater.itemAt(i).x) {
0163             return i
0164         }
0165     }
0166     return items.columns - 1
0167 }
0168 
0169 //Create the piece (cross or circle) at given position
0170 function handleCreate(parent) {
0171     parent.state = "DONE"
0172     var rowno = getrowno(parent.y)
0173     var colno = getcolno(parent.x)
0174     currentPiece = (rowno * items.columns) + colno
0175     items.createPiece.x=parent.x
0176     items.createPiece.y=parent.y
0177     items.demagnify.start()
0178     items.createPiece.opacity = 1
0179     items.magnify.start()
0180 }
0181 
0182 //Return the state of piece (1 or 2), 1 and 2 corresponds to Player 1 and Player 2 respectively
0183 function getPieceState(col, row) {
0184     return items.pieces.get(row * items.columns + col).stateTemp
0185 }
0186 
0187 /* setMove() decides the move which is to be played by computer. It decides according to the current level of player:
0188  * At level 1 -> No if statements are parsed, and in the end, a random empty location is returned
0189  * At level 2 -> Only first 'if' statement is parsed, evaluateBoard(computerState) is called which checks if computer can 
0190  *               win in this turn, if there is an empty location in which computer can get three consecutive marks, then
0191  *               computer will play there, else computer will play on a random empty location. At level 2, computer will 
0192  *               not check if player can win in next turn or not, this increases the winning chance of player at level 2.
0193  * At level 3 -> First two 'if' statements are parsed, hence computer will first check if it can win in this turn by executing
0194  *               evaluateBoard(computerState). If not, then it will execute evaluateBoard(playerState) which will check if
0195  *               player can win in next turn or not, if player can win, then computer will play at that location to stop
0196  *               the player from winning. Else computer will play randomly. Therefore player can not win, unless he uses
0197  *               a double trick (Having two positions from where you can win).
0198  * At level 4 -> Same as level 3
0199  * At level 5 -> Along with evaluateBoard(computerState) and evaluateBoard(playerState), applyLogic(playerState) is called 
0200  *               which counters all the possibilities of double trick. Hence at level 5, player can not win, it will either
0201  *               be a draw or the player will lose.
0202  * setMove() returns the position where computer has to play its turn
0203 */
0204 function setMove() {
0205     //Assigning States -> Which state "1" or "2" is used for identifying player and computer  
0206     var playerState = items.playSecond ? "2" : "1"
0207     var computerState = items.playSecond ? "1" : "2"
0208 
0209     if (items.currentLevel > 0) {
0210         var value = evaluateBoard(computerState)
0211         if (value != -1) {
0212             return value
0213         }
0214     }
0215 
0216     if (items.currentLevel > 1) {
0217         var value = evaluateBoard(playerState)
0218         if (value != -1) {
0219             return value
0220         }
0221     }
0222 
0223     if (items.currentLevel > 4) {
0224         var value = applyLogic(playerState)
0225         if (value != -1) {
0226             return value
0227         }
0228     }
0229 
0230     var found = false
0231     while (!found) {
0232         var randno = Math.floor((Math.random() * 9));
0233         if (items.pieces.get(randno).stateTemp == "invisible")
0234             found = true
0235     }
0236     return randno
0237 }
0238 
0239 //Returns the position after analyzing such that no double trick is possible
0240 function applyLogic(player) {
0241     if (items.pieces.get(4).stateTemp == "invisible")
0242         return 4
0243     if (!items.playSecond) {    
0244         if (items.counter == 1 && track[0] == 4) {
0245             var temp = [0,2,6,8]
0246             var randno = Math.floor((Math.random() * 4));
0247             return temp[randno]
0248         }
0249         if (items.counter == 3 && track[0] == 4) {
0250             var temp = [0,2,6,8]
0251             var found = false
0252             while (!found) {
0253                 var randno = Math.floor((Math.random() * 4));
0254                 if (items.pieces.get(temp[randno]).stateTemp == "invisible")
0255                     found = true
0256             }
0257             return temp[randno]
0258         }
0259         var value = giveNearest()
0260         if (value != -1)
0261             return value
0262         return -1
0263     }
0264     else {
0265         if (items.counter == 2 && track[1] == 4) {
0266             var temp = [0,2,6,8]
0267             var found = false
0268             while (!found) {
0269                 var randno = Math.floor((Math.random() * 4));
0270                 if (items.pieces.get(temp[randno]).stateTemp == "invisible")
0271                     found = true
0272             }
0273         }
0274         else {
0275             var value = giveNearest()
0276             if (value != -1)
0277                 return value
0278             return -1
0279         }
0280     }
0281 }
0282 
0283 /* One of the function used by applyLogic, giveNearest() returns the immediate empty position (up, down, left or right) to the 
0284  * position at which player played his turn. The logic is, that in most cases if computer plays just immediate to where the 
0285  * player has played, then player wont be able to get three consecutive marks. 
0286  * Returns -1 if no immediate empty position is found
0287 */
0288 function giveNearest() {
0289     var currentRow = parseInt(currentPiece / items.columns)
0290     var currentCol = parseInt(currentPiece % items.columns)
0291     var temp = []
0292 
0293     if (currentRow + 1 < 3) {
0294         if(getPieceState(currentCol, currentRow + 1) == "invisible")
0295             temp.push((currentRow + 1) * items.columns + currentCol)
0296     }
0297     if (currentRow - 1 > 0) {
0298         if(getPieceState(currentCol, currentRow - 1) == "invisible")
0299             temp.push((currentRow - 1) * items.columns + currentCol)
0300     }
0301     if (currentCol + 1 < 3) {
0302         if(getPieceState(currentCol + 1, currentRow) == "invisible")
0303             temp.push(currentRow * items.columns + currentCol + 1)
0304     }
0305     if (currentCol - 1 > 0) {
0306         if(getPieceState(currentCol - 1, currentRow) == "invisible")
0307             temp.push(currentRow * items.columns + currentCol - 1)
0308     }
0309     if (temp.length != 0) {
0310         var randno = Math.floor((Math.random() * temp.length));
0311         return temp[randno]
0312     }
0313 
0314     return -1
0315 }
0316 
0317 //Starts the process of computer turn
0318 function doMove() {
0319     var pos = setMove()
0320     handleCreate(items.repeater.itemAt(pos))
0321 }
0322 
0323 /* evaluateBoard(player) checks if the player has marked two consecutive places and if third place is empty or not, if found
0324  * such a place, then return that place, else return -1
0325 */
0326 function evaluateBoard(player) {
0327     var countp, counti, invisibleX, invisibleY
0328     //Horizontal
0329     for(var i = 0; i < 3; i++) {
0330         countp=0
0331         counti=0
0332         for(var j=0; j<3; j++) {
0333             if(getPieceState(j,i) == player)
0334                 countp++
0335             else if(getPieceState(j,i) == "invisible") {
0336                 counti++
0337                 invisibleX=i
0338                 invisibleY=j
0339             }
0340         }
0341         if(countp==2 && counti==1) {
0342             return (invisibleX * items.columns + invisibleY)
0343         }
0344     }
0345     //Vertical
0346     for(var i = 0; i < 3; i++) {
0347         countp=0
0348         counti=0
0349         for(var j=0; j<3; j++) {
0350             if(getPieceState(i,j) == player)
0351                 countp++
0352             else if(getPieceState(i,j) == "invisible") {
0353                 counti++
0354                 invisibleX=j
0355                 invisibleY=i
0356             }
0357         }
0358         if(countp==2 && counti==1) {
0359             return (invisibleX * items.columns + invisibleY)
0360         }
0361     }
0362 
0363     // Diagonal top left / bottom right
0364     countp=0
0365     counti=0
0366     for(var i=0; i<3; i++) {
0367         if(getPieceState(i,i) == player)
0368             countp++
0369         else if(getPieceState(i,i) == "invisible") {
0370             counti++
0371             invisibleX=i
0372             invisibleY=i
0373         }
0374     }
0375     if(countp==2 && counti==1) {
0376         return (invisibleX * items.columns + invisibleY)
0377     }
0378 
0379     // Diagonal top right / bottom left
0380     countp=0
0381     counti=0
0382     var j=2
0383     for(var i=0; i<3; i++) {
0384         if(getPieceState(j,i) == player)
0385             countp++
0386         else if(getPieceState(j,i) == "invisible") {
0387             counti++
0388             invisibleX=i
0389             invisibleY=j
0390         }
0391         j--
0392     }
0393     if(countp==2 && counti==1) {
0394         return (invisibleX * items.columns + invisibleY)
0395     }
0396     return -1
0397 }
0398 
0399 //Checks the condition if game is won or not
0400 function checkGameWon(currentPieceRow, currentPieceColumn) {
0401     currentPlayer = getPieceState(currentPieceColumn, currentPieceRow)
0402 
0403     // Horizontal
0404     var sameColor = 0
0405     for(var col = 0; col < items.columns; col++) {
0406         if(getPieceState(col, currentPieceRow) === currentPlayer) {
0407             if(++sameColor == 3) {
0408                 items.repeater.itemAt(currentPieceRow * items.columns + col).visible = true
0409                 items.repeater.itemAt(currentPieceRow * items.columns + col - 1).visible = true
0410                 items.repeater.itemAt(currentPieceRow * items.columns + col - 2).visible = true
0411                 items.repeater.itemAt(currentPieceRow * items.columns + col).border.color = "#62db53"
0412                 items.repeater.itemAt(currentPieceRow * items.columns + col - 1).border.color = "#62db53"
0413                 items.repeater.itemAt(currentPieceRow * items.columns + col - 2).border.color = "#62db53"
0414                 return true
0415             }
0416         } 
0417         else {
0418             sameColor = 0
0419         }
0420     }
0421 
0422     // Vertical
0423     sameColor = 0
0424     for(var row = 0; row < items.rows; row++) {
0425         if(getPieceState(currentPieceColumn, row) === currentPlayer) {
0426             if(++sameColor == 3) {
0427                 items.repeater.itemAt(row * items.columns + currentPieceColumn).visible = true
0428                 items.repeater.itemAt((row - 1) * items.columns + currentPieceColumn).visible = true
0429                 items.repeater.itemAt((row -2) * items.columns + currentPieceColumn).visible = true
0430                 items.repeater.itemAt(row * items.columns + currentPieceColumn).border.color = "#62db53"
0431                 items.repeater.itemAt((row - 1) * items.columns + currentPieceColumn).border.color = "#62db53"
0432                 items.repeater.itemAt((row - 2) * items.columns + currentPieceColumn).border.color = "#62db53"
0433                 return true
0434             }
0435         } 
0436         else {
0437             sameColor = 0
0438         }
0439     }
0440 
0441     // Diagonal top left / bottom right
0442     if(getPieceState(0,0) === currentPlayer && getPieceState(1,1) === currentPlayer && getPieceState(2,2) === currentPlayer) {
0443         items.repeater.itemAt(0).visible = true
0444         items.repeater.itemAt(4).visible = true
0445         items.repeater.itemAt(8).visible = true
0446         items.repeater.itemAt(0).border.color = "#62db53"
0447         items.repeater.itemAt(4).border.color = "#62db53"
0448         items.repeater.itemAt(8).border.color = "#62db53"
0449         return true
0450     }
0451 
0452     // Diagonal top right / bottom left
0453     if(getPieceState(2,0) === currentPlayer && getPieceState(1,1) === currentPlayer && getPieceState(0,2) === currentPlayer) {
0454         items.repeater.itemAt(2).visible = true
0455         items.repeater.itemAt(4).visible = true
0456         items.repeater.itemAt(6).visible = true
0457         items.repeater.itemAt(2).border.color = "#62db53"
0458         items.repeater.itemAt(4).border.color = "#62db53"
0459         items.repeater.itemAt(6).border.color = "#62db53"
0460         return true
0461     }
0462     return false
0463 }
0464 
0465 //Checks if its Computer's turn or not, if its Computer's turn, then call doMove()
0466 function shouldComputerPlay() {
0467     if(!twoPlayer) {
0468         if(items.counter % 2 && items.playSecond == false && stopper == false) {
0469             doMove()
0470         }
0471         else if((items.counter % 2 == 0) && items.playSecond == true && stopper == false) {
0472             doMove()
0473         }
0474     }
0475 }
0476 
0477 //This function is called after every turn to proceed the game
0478 function continueGame() {
0479     items.createPiece.opacity = 0
0480     if (!items.playSecond)
0481         items.pieces.set(currentPiece, {"stateTemp": items.counter++ % 2 ? "2": "1"})
0482     else {
0483         if (items.counter++ % 2 == 0)
0484             items.pieces.set(currentPiece, {"stateTemp": "2"})
0485         else
0486             items.pieces.set(currentPiece, {"stateTemp": "1"})
0487     }
0488     track.push(currentPiece)
0489     /* Update score if game won */
0490     if(twoPlayer) {
0491         if(checkGameWon(parseInt(currentPiece / items.columns),
0492                         parseInt(currentPiece % items.columns))) {
0493             items.gameDone = true
0494             if(currentPlayer === "1") {
0495                 items.player1score.win()
0496             } 
0497             else {
0498                 items.player2score.win()
0499             }
0500             items.bonus.good("flower")
0501         }
0502         else if(items.counter == 9) {
0503             items.player1score.endTurn();
0504             items.player2score.endTurn();
0505             items.bonus.bad("flower")
0506         }
0507         else
0508             changeScale()
0509     } 
0510     else {
0511         if(checkGameWon(parseInt(currentPiece / items.columns),
0512                         parseInt(currentPiece % items.columns))) {
0513             items.gameDone = true
0514             if(currentPlayer == "1") {
0515                 items.player1score.win()
0516                 items.bonus.good("flower")
0517             } 
0518             else {
0519                 items.player2score.win()
0520                 items.bonus.bad("flower")
0521             }
0522         }
0523         else if(items.counter == 9) {
0524             items.player1score.endTurn();
0525             items.player2score.endTurn();
0526             items.bonus.bad("flower")
0527         }
0528         else 
0529             changeScale()
0530     }
0531 }