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 }