Greasy Fork is available in English.

Lichess - Detailed Moves

Show brillant, excellent, great and book moves on lichess.org as chess.com does, an updated version of Thomas Sihapanya's version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            Lichess - Detailed Moves
// @license         GPL-3.0-only 
// @namespace       https://github.com/sealldeveloper/lichess-better-moves
// @contributionURL https://github.com/sealldeveloper/lichess-better-moves
// @version         0.5
// @description     Show brillant, excellent, great and book moves on lichess.org as chess.com does, an updated version of Thomas Sihapanya's version.
// @author          Seall.DEV & Thomas Sihapnya
// @require         https://greasyfork.org/scripts/47911-font-awesome-all-js/code/Font-awesome%20AllJs.js?version=275337
// @include         /^https\:\/\/lichess\.org\/[a-zA-Z0-9]{8,}/
// @grant           GM.xmlHttpRequest
// @grant           unsafeWindow
// @inject-into     content
// ==/UserScript==
// ==OpenUserJS==
// @author          sealldeveloper
// ==/OpenUserJS==

(function() {
    'use strict';
    const GOOD_MOVE_THRESOLD = 0.6;
    const EXCELLENT_MOVE_THRESOLD = 1;
    const BRILLANT_MOVE_THRESOLD = 2;
    const CHECKMATE_IN_X_MOVES_VALUE = 100;

    let goodMoves = {
        white : {
            'book' : 0,
            'good' : 0,
            'excellent': 0,
            'brillant': 0,
        },
        black : {
            'book' : 0,
            'good' : 0,
            'excellent': 0,
            'brillant': 0,
        }
    }


    /**
     * Wait for the page to load all its elements before running the script
     */
    window.addEventListener('load', function() {

        /**
         * Create click event since .click on elements doesn't work quite well in Chrome
         */
        let clickEvent = document.createEvent('MouseEvents');
        clickEvent.initMouseEvent('mousedown', true, true);

        /**
         * Moves explorer must be opened to detect book moves. If not, user is prompted to run an analysis and reload the page.
         */
        const formAnalysis = document.getElementsByClassName('future-game-analysis')[0];

        if (typeof(formAnalysis) !== 'undefined') {
            alert('Lichess Detailed Moves: Please run an analysis and reload the page when finished. You will then be prompted to accept cross-origin resource : you can safely allow it (it will fetch data for an opening API) !');
        }

        /**
         * Check if users is in analysis page and analysis has been run
         */
        if (document.getElementsByClassName('analyse__tools').length > 0
            && document.getElementsByClassName('future-game-analysis').length === 0
            && document.getElementsByClassName('computer-analysis active').length > 0) {

            const ecoCodesApiUrl = 'https://github.com/sealldeveloper/lichess-detailed-moves/raw/main/data/eco.json';

            /**
             * Loads ECO codes API
             * https://github.com/sealldeveloper/lichess-detailed-moves/raw/main/data/eco.json
             */
            function loadEcoCodesApi() {
                GM.xmlHttpRequest({
                    method: "GET",
                    url:ecoCodesApiUrl,
                    onload: function(response) {
                        lichessGoodMoves(JSON.parse(response.responseText));
                    },
                    onerror: function(err) {
                        alert('Lichess Detailed Moves: The script cannot be launched (maybe you have forbid the access to a cross-origin resource ?) - Refresh the page if you want to start again.');
                    }
                });
            }

            function loadMoves(ecoCodes) {
                let domMoves = document.getElementsByTagName('move');
                let moves = [];
                let previousEval = {
                    value: '+0.0',
                    symbol: '+',
                    absVal: '0.0'
                };

                Object.values(domMoves).forEach(domMove => {
                    if (!domMove.classList.contains('empty')) {
                        if ("undefined" !== typeof(domMove.childNodes)) {

                            domMove.childNodes.forEach(node => {

                                if ('SAN' === node.tagName) {
                                    /**
                                     * Handle opening
                                     */
                                    moves.push(node.innerHTML);

                                    let currentColor = checkColor(moves.length-1);
                                    let currentPgn = createPgnMoves(moves);
                                    let foundOpening = ecoCodes.find(eco => eco.moves.toLowerCase().trim() == currentPgn.toLowerCase().trim());

                                    if (typeof(foundOpening) !== 'undefined') {
                                        handleOpeningMove(node, foundOpening, currentColor);
                                    }

                                    /**
                                     * Handle evaluation
                                     */
                                 		if (!node.innerHTML.endsWith('#')){
                                      let currentEval = {
                                          textValue: domMove.getElementsByTagName('eval')[0].innerHTML,
                                          symbol: domMove.getElementsByTagName('eval')[0].innerHTML.charAt(0)
                                      }

                                      if (currentEval.symbol == '#') {
                                          currentEval.value = currentColor == 'white' ? - CHECKMATE_IN_X_MOVES_VALUE : CHECKMATE_IN_X_MOVES_VALUE;
                                      }
                                      else {
                                          currentEval = {
                                              textValue: currentEval.textValue,
                                              symbol: currentEval.symbol,
                                              value: (currentEval.symbol == '+') ? parseFloat(currentEval.textValue.substring(1)) : 0 - parseFloat(currentEval.textValue.substring(1))
                                          }
                                      }

                                      let delta = currentEval.value - previousEval.value;

                                      let moveText = node.innerHTML;

                                      if ("white" === currentColor) {
                                          if (delta >= GOOD_MOVE_THRESOLD && delta < EXCELLENT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #b2f196;">'+
                                                                      moveText+'!?'+
                                                              '</span>';

                                              goodMoves.white.good++;
                                          }
                                          if (delta >= EXCELLENT_MOVE_THRESOLD && delta < BRILLANT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #96bc4b;">'+
                                                                      moveText+'!'+
                                                              '</span>';

                                              goodMoves.white.excellent++;
                                          }
                                          if (delta >= BRILLANT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #1baca6;">'+
                                                                      moveText+'!!'+
                                                              '</span>';

                                              goodMoves.white.brillant++;
                                          }
                                      }

                                      if ("black" === currentColor) {
                                          if (delta <= -GOOD_MOVE_THRESOLD && delta > -EXCELLENT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #b2f196;">'+
                                                                      moveText+'!?'+
                                                              '</span>';

                                              goodMoves.black.good++;
                                          }
                                          if (delta <= -EXCELLENT_MOVE_THRESOLD && delta > -BRILLANT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #96bc4b;">'+
                                                                      moveText+'!'+
                                                              '</span>';

                                              goodMoves.black.excellent++;
                                          }
                                          if (delta <= -BRILLANT_MOVE_THRESOLD) {
                                              node.innerHTML = '<span style="color: #1baca6;">'+
                                                                      moveText+'!!'+
                                                              '</span>';

                                              goodMoves.black.brillant++;
                                          }
                                      }

                                      previousEval = currentEval;
                                    }

                                }
                            })
                        }
                    }
                });

                return moves;
            }

            function handleOpeningMove(node, opening, currentColor) {
                const moveText = node.innerHTML;

                node.innerHTML = '<span style="color: #a88865;">'+
                                        moveText+
                                        ' <i class="fas fa-book" style="font-size: 0.7em"></i>'+
                                '</span>'+
                                '<pre style="font-size:0.7em; width:0">'+opening.name+'</pre>';

                node.parentElement.title = opening.name;

                goodMoves[currentColor].book++;
            }

            function checkColor(index) {
                if (0 === index%2) {
                    return "white";
                }
                if (0 !== index%2) {
                    return "black";
                }
            }

            function createPgnMoves(moves) {

                let pgn = '';

                moves.forEach((move, index) => {
                    if ("white" === checkColor(index)) {
                        pgn += (index/2+1) + '. ' + move;
                    }
                    if ("black" === checkColor(index)) {
                        pgn += ' ' + move + ' ';
                    }
                });

                return pgn;
            }

            function showDataInTable() {

                const whiteTable = document.getElementsByClassName('advice-summary__side')[0]
                const blackTable = document.getElementsByClassName('advice-summary__side')[1]
              	function dataPoint(colour,symbol,data,text,table,coloured) {
                    var first = true
                    table.childNodes.forEach(node => {
                        if (node.innerHTML.includes('inaccuracies') && first === true) {
                            const before=node
                            const div = document.createElement('div');
                            if (data !== 0){
                                div.style='color: '+coloured;
                            }
                            div.classList.add('symbol');
                            div.classList.add('advice-summary__error');
                            div.setAttribute('data-color', colour);
                            div.setAttribute('data-symbol', symbol);
                            const strong = document.createElement('strong')
                            strong.innerHTML = data;
                            div.append(strong)
                            div.innerHTML = div.innerHTML+text;
                            table.insertBefore(div,before);
                            first = false
                        }
                    })
                }
                // White
              	dataPoint('white','!!',goodMoves.white.brillant,' brilliancies',whiteTable,'#1baca6')   
              	dataPoint('white','!',goodMoves.white.excellent,' excellencies',whiteTable,'#96bc4b')
              	dataPoint('white','!?',goodMoves.white.good,' greats',whiteTable,'#b2f196')
              	dataPoint('white','Book',goodMoves.white.book,' book',whiteTable,'#a88865')
			
                // Black
                dataPoint('black','!!',goodMoves.black.brillant,' brilliancies',blackTable,'#1baca6')
              	dataPoint('black','!',goodMoves.black.excellent,' excellencies',blackTable,'#96bc4b')
              	dataPoint('black','!?',goodMoves.black.good,' greats',blackTable,'#b2f196')
              	dataPoint('black','Book',goodMoves.black.book,' book',blackTable,'#a88865')
              	
                
            }

            function lichessGoodMoves(ecoCodes) {

                console.log('Lichess Detailed Moves: Successfully started!');

                loadMoves(ecoCodes);
                showDataInTable();
            }

            // Start the app !
            loadEcoCodesApi();

        } // check if users is in analysis page
    }, false); // addEventListener('load', callback)
})(); // Immediately-Invoked Function Expression (function() {})())