Warning, file /education/gcompris/src/activities/checkers/checkers.js was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* GCompris - checkers.js 0002 * 0003 * SPDX-FileCopyrightText: 2017 Johnny Jazeix <jazeix@gmail.com> 0004 * 0005 * Authors: 0006 * Johnny Jazeix <jazeix@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 .import "engine.js" as Engine 0014 0015 var url = "qrc:/gcompris/src/activities/checkers/resource/" 0016 0017 var numberOfLevel = 5 0018 var items 0019 var state 0020 0021 function start(items_) { 0022 items = items_ 0023 items.currentLevel = Core.getInitialLevel(numberOfLevel) 0024 initLevel() 0025 } 0026 0027 function stop() { 0028 items.trigComputerMove.stop(); 0029 items.redoTimer.stop(); 0030 } 0031 0032 function initLevel() { 0033 state = new Engine.Draughts('W:W31-50:B1-20') 0034 state.resetGame() 0035 items.from = -1 0036 items.gameOver = false 0037 items.redo_stack = [] 0038 refresh() 0039 items.positions = 0 // Force a model reload 0040 items.positions = simplifiedState(state.position()) 0041 clearAcceptMove() 0042 } 0043 0044 function nextLevel() { 0045 items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel); 0046 initLevel(); 0047 } 0048 0049 function previousLevel() { 0050 items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel); 0051 initLevel(); 0052 } 0053 0054 function simplifiedState(position) { 0055 var newState = new Array() 0056 for(var i = 0; i < position.length; ++i) { 0057 var img = position[i] !== '0' && position[i] !== '?' ? position[i] : '' 0058 newState.push( 0059 { 0060 'pos': engineToViewPos(i), 0061 'img': img 0062 }) 0063 } 0064 return newState 0065 } 0066 0067 function updateMessage() { 0068 items.gameOver = false 0069 items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") 0070 0071 if(state.gameOver()) { 0072 items.message = items.blackTurn ? qsTr("White wins") : qsTr("Black wins") 0073 items.gameOver = true 0074 if(!items.twoPlayer) 0075 if(items.blackTurn) 0076 items.bonus.good('gnu') 0077 else 0078 items.bonus.good('tux') 0079 else 0080 items.bonus.good('flower') 0081 } 0082 } 0083 0084 function refresh() { 0085 items.blackTurn = (state.getTurn() == 'b') 0086 items.history = state.history() 0087 updateMessage() 0088 items.positions = 0 // Force a model reload 0089 items.positions = simplifiedState(state.position()) 0090 } 0091 0092 // Convert view position (QML) to the chess engine coordinate 0093 // 0094 // engine view: 0095 // | 01 02 03 04 05| 0096 // |06 07 08 09 10 | 0097 // | 11 12 13 14 15| 0098 // |16 17 18 19 20 | 0099 // | 21 22 23 24 25| 0100 // |26 27 28 29 30 | 0101 // | 31 32 33 34 35| 0102 // |36 37 38 39 40 | 0103 // | 41 42 43 44 45| 0104 // |46 47 48 49 50 | 0105 // 0106 // QML view: 0107 // | 91 93 95 97 99| 0108 // |80 82 84 86 88 | 0109 // | 71 73 75 77 79| 0110 // |60 62 64 66 68 | 0111 // | 51 53 55 57 59| 0112 // |40 42 44 46 48 | 0113 // | 31 33 35 37 39| 0114 // |20 22 24 26 28 | 0115 // | 11 13 15 17 19| 0116 // |00 02 04 06 08 | 0117 // 0118 function viewPosToEngine(pos) { 0119 var casesNumber = items.numberOfCases*items.numberOfCases 0120 var a = 10 * Math.floor((casesNumber - pos) / 10 + 1); 0121 var b = 20; 0122 if(pos % 10 !== 0) { 0123 b = (casesNumber - pos) % 10; 0124 } 0125 var newPos = (a - b + 1) 0126 newPos = Math.floor(newPos / 2 + 0.5) 0127 return newPos 0128 } 0129 0130 // Convert chess engine coordinate to view position (QML) 0131 function engineToViewPos(pos) { 0132 var newPos = 90-10*Math.floor((2*pos-1)/10)+2*((pos-1)%5)+1+((-1+Math.pow(-1, Math.floor((2*pos-1)/10)))/2) 0133 return newPos 0134 } 0135 0136 // move is the result from the engine move 0137 function visibleMove(move, from, to) { 0138 var piece = items.pieces.getPieceAt(from); 0139 items.pieces.moveTo(from, to, move) 0140 0141 // promotion 0142 if (move.to <= 5 && piece.img === 'w') 0143 piece.promotion() 0144 else if (move.to >= 46 && piece.img === 'b') 0145 piece.promotion() 0146 } 0147 0148 function findBestMove(currentState, depth, sign) { 0149 if(depth <= 0) { 0150 return getScore(currentState); 0151 } 0152 0153 var moves = currentState.moves() 0154 if(moves.length === 0) { 0155 return [100, 0]; 0156 } 0157 var bestScore = -1000; 0158 var bestScores; 0159 for(var move in moves) { 0160 currentState.move(moves[move]); 0161 var newScore = findBestMove(currentState, depth-1, -1.0*sign) 0162 currentState.undo() 0163 var score = sign*(newScore[0] - newScore[1]) 0164 if(score > bestScore) { 0165 bestScore = score; 0166 bestScores = newScore; 0167 } 0168 } 0169 return bestScores 0170 } 0171 0172 function computerMove() { 0173 var moves = state.moves() 0174 var bestScore = -1000 0175 var bestMoves = [] 0176 var newState = new Engine.Draughts(state.fen()) 0177 // 0 is b, 1 is b -> w, 2 is b -> w -> b guesses 0178 var depth = items.currentLevel === 5 ? 2 : 1; 0179 0180 for(var move in moves) { 0181 newState.move(moves[move]); 0182 var newScore = findBestMove(newState, depth, 1) 0183 newState.undo() 0184 var score = newScore[1] - newScore[0] 0185 if(bestMoves.length == 0 || bestScore < score) { 0186 bestScore = score 0187 bestMoves = [] 0188 bestMoves.push(moves[move]) 0189 } 0190 else if(bestScore == score) { 0191 bestMoves.push(moves[move]) 0192 } 0193 } 0194 bestMoves = Core.shuffle(bestMoves) 0195 0196 var computer = bestMoves[0] 0197 var move = state.move({"from": computer.from, "to": computer.to}) 0198 if(move) { 0199 visibleMove(move, engineToViewPos(computer.from), engineToViewPos(computer.to)) 0200 } 0201 return move 0202 } 0203 0204 function moveTo(from, to) { 0205 items.noPieceAnimation = false 0206 var move = state.move({"from": viewPosToEngine(from), "to": viewPosToEngine(to)}) 0207 if(move) { 0208 visibleMove(move, from, to) 0209 clearAcceptMove() 0210 items.redo_stack = [] 0211 if(!items.twoPlayer && !state.gameOver()) 0212 items.trigComputerMove.start() 0213 items.from = -1; 0214 } else { 0215 // Probably a check makes the move is invalid 0216 updateMessage(move) 0217 } 0218 return move 0219 } 0220 0221 function undo() { 0222 var move = state.undo(); 0223 var redo_stack = items.redo_stack 0224 redo_stack.push(move) 0225 // In computer mode, the white always starts, take care 0226 // of undo after a mate which requires us to revert on 0227 // a white play 0228 if(!items.twoPlayer && state.getTurn() === 'b') { 0229 move = state.undo(); 0230 redo_stack.push(move) 0231 } 0232 items.redo_stack = redo_stack 0233 refresh() 0234 } 0235 0236 function moveByEngine(engineMove) { 0237 items.noPieceAnimation = false 0238 if(!engineMove) 0239 return 0240 var move = state.move(engineMove) 0241 visibleMove(move, engineToViewPos(engineMove.from), engineToViewPos(engineMove.to)) 0242 } 0243 0244 function redo() { 0245 var redo_stack = items.redo_stack 0246 var move = redo_stack.pop() 0247 moveByEngine(move) 0248 // In computer mode, the white always starts, take care 0249 // of undo after a mate which requires us to revert on 0250 // a white play 0251 if(!items.twoPlayer && state.getTurn() === 'b') { 0252 move = redo_stack.pop() 0253 moveByEngine(move) 0254 } 0255 0256 // Force refresh 0257 items.redo_stack = [] 0258 items.redo_stack = redo_stack 0259 clearAcceptMove() 0260 } 0261 0262 // Random move depending on the level 0263 function randomMove() { 0264 if(!items.difficultyByLevel) { 0265 computerMove() 0266 return 0267 } 0268 // Disable random move if the situation is too bad for the user 0269 // This avoid having the computer playing bad against a user 0270 // with too few pieces making the game last too long 0271 var score = getScore(state) 0272 if(score[0] / score[1] < 0.7) { 0273 computerMove() 0274 return 0275 } 0276 0277 // At level 2 we let the computer play 20% of the time 0278 // and 80% of the time we make a random move. 0279 if(Math.random() < items.currentLevel / (numberOfLevel - 1)) { 0280 computerMove() 0281 return 0282 } 0283 // Get all possible moves 0284 var moves = state.moves() 0285 moves = Core.shuffle(moves) 0286 var move = state.move(moves[0]) 0287 if(move) { 0288 visibleMove(move, engineToViewPos(moves[0].from), engineToViewPos(moves[0].to)) 0289 } else { 0290 // Bad move, should not happen 0291 computerMove() 0292 } 0293 } 0294 0295 // Clear all accept move marker from the chessboard 0296 function clearAcceptMove() { 0297 for(var i=0; i < 50; ++i) { 0298 var square = items.squares.getSquareAt(engineToViewPos(i)) 0299 if(square) { 0300 square['acceptMove'] = false 0301 square['jumpable'] = false 0302 } 0303 } 0304 } 0305 0306 // Highlight the possible moves for the piece at position 'from' 0307 function showPossibleMoves(from) { 0308 var fromEngine = viewPosToEngine(from) 0309 var result = state.moves(); 0310 clearAcceptMove() 0311 for(var i=0; i < result.length; ++i) { 0312 if(fromEngine === result[i].from) { 0313 var pos = engineToViewPos(result[i].to) 0314 items.squares.getSquareAt(pos)['acceptMove'] = true 0315 for(var v = 1; v < result[i].jumps.length; ++ v) { 0316 items.squares.getSquareAt(engineToViewPos(result[i].jumps[v]))['jumpable'] = true 0317 } 0318 } 0319 } 0320 } 0321 0322 // Calculate the score for black and white 0323 // Count the number of pieces with each piece having a given weight 0324 // Piece pawn queen 0325 // Value 1 4 0326 // @return [white, black] 0327 function getScore(board) { 0328 var white = 0 0329 var black = 0 0330 var queenScore = 4 0331 var position = board.position() 0332 0333 for(var i = 0; i < position.length; ++i) { 0334 var img = position[i] !== '0' && position[i] !== '?' ? position[i] : '' 0335 if(img === '') 0336 continue; 0337 else if(img === 'w') { 0338 white ++; 0339 } 0340 else if(img === 'W') { 0341 white += queenScore; 0342 } 0343 if(img === 'b') { 0344 black ++; 0345 } 0346 else if(img === 'B') { 0347 black += queenScore; 0348 } 0349 } 0350 return [white, black] 0351 }