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