File indexing completed on 2024-04-28 15:07:46
0001 /* GCompris - chess.js 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 * 0009 * SPDX-License-Identifier: GPL-3.0-or-later 0010 */ 0011 .pragma library 0012 .import QtQuick 2.12 as Quick 0013 .import "qrc:/gcompris/src/core/core.js" as Core 0014 .import "engine.js" as Engine 0015 0016 var url = "qrc:/gcompris/src/activities/chess/resource/" 0017 0018 var numberOfLevel 0019 var items 0020 var state 0021 0022 function start(items_) { 0023 items = items_ 0024 numberOfLevel = items.fen.length 0025 items.currentLevel = Core.getInitialLevel(numberOfLevel) 0026 initLevel() 0027 } 0028 0029 function stop() { 0030 items.trigComputerMove.stop(); 0031 items.redoTimer.stop(); 0032 items.timerSwap.stop(); 0033 } 0034 0035 function initLevel() { 0036 state = Engine.p4_fen2state(items.fen[items.currentLevel][1]) 0037 items.from = -1 0038 items.gameOver = false 0039 items.redo_stack = [] 0040 refresh() 0041 Engine.p4_prepare(state) 0042 items.positions = 0 // Force a model reload 0043 items.positions = simplifiedState(state['board']) 0044 clearAcceptMove() 0045 items.whiteTakenPieceModel.clear() 0046 items.blackTakenPieceModel.clear() 0047 } 0048 0049 function nextLevel() { 0050 items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel); 0051 initLevel(); 0052 } 0053 0054 function previousLevel() { 0055 items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel); 0056 initLevel(); 0057 } 0058 0059 function simplifiedState(state) { 0060 var newState = new Array() 0061 for(var i = state.length - 1; i >= 0; --i) { 0062 if(state[i] !== 16) { 0063 var img = "" 0064 switch(state[i]) { 0065 case 2: 0066 img = "wp" 0067 break 0068 case 3: 0069 img = "bp" 0070 break 0071 case 4: 0072 img = "wr" 0073 break 0074 case 5: 0075 img = "br" 0076 break 0077 case 6: 0078 img = "wn" 0079 break 0080 case 7: 0081 img = "bn" 0082 break 0083 case 8: 0084 img = "wb" 0085 break 0086 case 9: 0087 img = "bb" 0088 break 0089 case 10: 0090 img = "wk" 0091 break 0092 case 11: 0093 img = "bk" 0094 break 0095 case 12: 0096 img = "wq" 0097 break 0098 case 13: 0099 img = "bq" 0100 break 0101 default: 0102 break 0103 } 0104 newState.push( 0105 { 0106 'pos': engineToViewPos(i), 0107 'img': img 0108 }) 0109 } 0110 } 0111 return newState 0112 } 0113 0114 function updateMessage(move) { 0115 items.isWarningMessage = false 0116 items.gameOver = false 0117 items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") 0118 if(!move) 0119 return 0120 if((move.flags & (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) 0121 == (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) { 0122 items.message = items.blackTurn ? qsTr("White mates", "white wins") : qsTr("Black mates", "black wins") 0123 items.gameOver = true 0124 if(!items.twoPlayer) 0125 if(state.to_play !== 0) 0126 items.bonus.good('gnu') 0127 else 0128 items.bonus.good('tux') 0129 else 0130 items.bonus.good('flower') 0131 } else if((move.flags & Engine.P4_MOVE_FLAG_MATE) == Engine.P4_MOVE_FLAG_MATE) { 0132 items.message = qsTr("Drawn game") 0133 items.gameOver = true 0134 items.bonus.good('flower') 0135 } else if((move.flags & Engine.P4_MOVE_FLAG_CHECK) == Engine.P4_MOVE_FLAG_CHECK) { 0136 items.message = items.blackTurn ? qsTr("White checks", "black king is under attack") : qsTr("Black checks", "white king is under attack") 0137 } else if(move.flags === Engine.P4_MOVE_ILLEGAL) { 0138 items.isWarningMessage = true 0139 items.message = qsTr("Invalid, your king may be in check") 0140 } 0141 } 0142 0143 function refresh(move) { 0144 items.blackTurn = state.to_play // 0=w 1=b 0145 items.history = state.history 0146 updateMessage(move) 0147 } 0148 0149 // Convert view position (QML) to the chess engine coordinate 0150 // 0151 // The engine manages coordinate into a 120 element array, which is conceptually 0152 // a 10x12 board, with the 8x8 board placed at the centre, thus: 0153 // + 0123456789 0154 // 0 ########## 0155 // 10 ########## 0156 // 20 #RNBQKBNR# 0157 // 30 #PPPPPPPP# 0158 // 40 #........# 0159 // 50 #........# 0160 // 60 #........# 0161 // 70 #........# 0162 // 80 #pppppppp# 0163 // 90 #rnbqkbnr# 0164 //100 ########## 0165 //110 ########## 0166 // 0167 // In QML each cell is in the regular range [0-63] 0168 // 0169 function viewPosToEngine(pos) { 0170 return (Math.floor(pos / 8) + 2) * 10 + pos % 8 + 1 0171 } 0172 0173 // Convert chess engine coordinate to view position (QML) 0174 function engineToViewPos(pos) { 0175 var newpos = pos - 21 - (Math.floor((pos - 20) / 10) * 2) 0176 return newpos 0177 } 0178 0179 // move is the result from the engine move 0180 function visibleMove(move, from, to) { 0181 items.pieces.moveTo(from, to) 0182 // Castle move 0183 if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_KING) 0184 items.pieces.moveTo(from + 3, to - 1) 0185 else if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_QUEEN) 0186 items.pieces.moveTo(from - 4, to + 1) 0187 else if(items.pieces.getPieceAt(to).img === 'wp' && to > 55) 0188 items.pieces.promotion(to) 0189 else if(items.pieces.getPieceAt(to).img === 'bp' && to < 8) 0190 items.pieces.promotion(to) 0191 } 0192 0193 function computerMove() { 0194 var computer = state.findmove(3) 0195 var move = state.move(computer[0], computer[1]) 0196 if(move.ok) { 0197 visibleMove(move, engineToViewPos(computer[0]), engineToViewPos(computer[1])) 0198 refresh(move) 0199 } 0200 return move 0201 } 0202 0203 function moveTo(from, to) { 0204 items.noPieceAnimation = false 0205 var move = state.move(viewPosToEngine(from), viewPosToEngine(to)) 0206 if(move.ok) { 0207 visibleMove(move, from, to) 0208 refresh(move) 0209 clearAcceptMove() 0210 items.redo_stack = [] 0211 if(!items.twoPlayer) 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.ok 0219 } 0220 0221 function undo() { 0222 var redo_stack = items.redo_stack 0223 redo_stack.push(state.history[state.moveno - 1]) 0224 state.jump_to_moveno(state.moveno - 1) 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.to_play !== 0) { 0229 redo_stack.push(state.history[state.moveno - 1]) 0230 state.jump_to_moveno(state.moveno - 1) 0231 } 0232 // without it, you can't move again the same piece 0233 Engine.p4_prepare(state) 0234 items.redo_stack = redo_stack 0235 refresh() 0236 items.positions = [] // Force a model reload 0237 items.positions = simplifiedState(state['board']) 0238 } 0239 0240 function moveByEngine(engineMove) { 0241 items.noPieceAnimation = false 0242 if(!engineMove) 0243 return 0244 var move = state.move(engineMove[0], engineMove[1]) 0245 visibleMove(move, engineToViewPos(engineMove[0]), engineToViewPos(engineMove[1])) 0246 refresh(move) 0247 } 0248 0249 function redo() { 0250 var redo_stack = items.redo_stack 0251 moveByEngine(items.redo_stack.pop()) 0252 // In computer mode, the white always starts, take care 0253 // of undo after a mate which requires us to revert on 0254 // a white play 0255 if(!items.twoPlayer && state.to_play !== 0) { 0256 items.redoTimer.moveByEngine(items.redo_stack.pop()) 0257 } 0258 0259 // Force refresh 0260 items.redo_stack = [] 0261 items.redo_stack = redo_stack 0262 } 0263 0264 // Random move depending on the level 0265 function randomMove() { 0266 if(!items.difficultyByLevel) { 0267 computerMove() 0268 return 0269 } 0270 // Disable random move if the situation is too bad for the user 0271 // This avoid having the computer playing bad against a user 0272 // with too few pieces making the game last too long 0273 var score = getScore() 0274 if(score[0] / score[1] < 0.7) { 0275 computerMove() 0276 return 0277 } 0278 0279 // At level 2 we let the computer play 20% of the time 0280 // and 80% of the time we make a random move. 0281 if(Math.random() < items.currentLevel / (numberOfLevel - 1)) { 0282 computerMove() 0283 return 0284 } 0285 // Get all possible moves 0286 var moves = Engine.p4_parse(state, state.to_play, state.enpassant, 0) 0287 moves = Core.shuffle(moves) 0288 var move = state.move(moves[0][1], moves[0][2]) 0289 if(move.ok) { 0290 visibleMove(move, engineToViewPos(moves[0][1]), engineToViewPos(moves[0][2])) 0291 refresh(move) 0292 } else { 0293 // Bad move, should not happens 0294 computerMove() 0295 } 0296 } 0297 0298 // Clear all accept move marker from the chessboard 0299 function clearAcceptMove() { 0300 for(var i=0; i < items.positions.length; ++i) 0301 items.squares.getSquareAt(i)['acceptMove'] = false 0302 } 0303 0304 // Highlight the possible moves for the piece at position 'from' 0305 function showPossibleMoves(from) { 0306 var result = Engine.p4_parse(state, state.to_play, state.enpassant, 0) 0307 clearAcceptMove() 0308 var fromEngine = viewPosToEngine(from) 0309 for(var i=0; i < result.length; ++i) { 0310 if(fromEngine === result[i][1]) { 0311 // we don't want to display invalid moves 0312 var move = Engine.p4_make_move(state, result[i][1], result[i][2]); 0313 //is it check? 0314 if (Engine.p4_check_check(state, state.to_play)) { 0315 Engine.p4_unmake_move(state, move); 0316 continue; 0317 } 0318 Engine.p4_unmake_move(state, move); 0319 var pos = engineToViewPos(result[i][2]) 0320 items.squares.getSquareAt(pos)['acceptMove'] = true 0321 } 0322 } 0323 } 0324 0325 // Calculate the score for black and white 0326 // Count the number of pieces with each piece having a given weight 0327 // Piece pawn knight bishop rook queen 0328 // Value 1 3 3 5 9 0329 // @return [white, black] 0330 function getScore() { 0331 var lut = {2: 1, 4: 5, 6: 3, 8: 3, 12: 9} 0332 var white = 0 0333 var black = 0 0334 for(var i=0; i < state['board'].length; ++i) { 0335 var score = lut[state['board'][i] & 0xFE] 0336 if(score) 0337 if(state['board'][i] & 0x01) 0338 black += score 0339 else 0340 white += score 0341 } 0342 return [white, black] 0343 } 0344