C.A.S (Chess Assistance System)

Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system

目前为 2023-02-19 提交的版本。查看 最新版本

// ==UserScript==
// @name        C.A.S (Chess Assistance System)
// @namespace   sayfpack
// @author      sayfpack
// @version     2.2
// @homepageURL https://github.com/sayfpack13/chess-analysis-bot
// @supportURL  https://mmgc.life/
// @match       https://www.chess.com/*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @grant       GM_getResourceText
// @grant       GM_registerMenuCommand
// @description Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system
// @require     https://greasyfork.org/scripts/459136-usergui/code/UserGui.js?version=1143683
// @resource    jquery.js       https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @resource    chessboard.js   https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/chessboard.js
// @resource    chessboard.css  https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/chessboard.css
// @resource    lozza.js        https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/lozza.js
// @resource    stockfish.js    https://github.com/exoticorn/stockfish-js/releases/download/sf_5_js/stockfish.js
// @resource    stockfish2.js   https://github.com/lichess-org/stockfish.js/releases/download/ddugovic-250718/stockfish.js
// @run-at      document-start
// @inject-into content
// ==/UserScript==

/*
  e88~-_            e           ,d88~~\
 d888   \          d8b          8888
 8888             /Y88b         `Y88b
 8888            /  Y88b         `Y88b,
 Y888   / d88b  /____Y88b  d88b    8888
  "88_-~  Y88P /      Y88b Y88P \__88P'

Chess Assistance System (C.A.S) v1 | Q1 2023

[WARNING]
- Please be advised that the use of C.A.S may violate the rules and lead to disqualification or banning from tournaments and online platforms.
- The developers of C.A.S and related systems will NOT be held accountable for any consequences resulting from its use.
- We strongly advise to use C.A.S only in a controlled environment ethically.*/

let node_engine_id=[3];
let show_opposite_moves=false;
let use_book_moves=false;
let engineIndex=0;		// engine index
let reload_every=5;	// reload engine after x moves
let enableUserLog=true;		// enable engine log



let reload_count=1;
const repositoryRawURL = 'https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S';
const repositoryURL = 'https://github.com/sayfpack13/chess-analysis-bot';
const LICHESS_API="https://lichess.org/api/cloud-eval";

const dbValues = {
    engineDepthQuery: 'engineDepthQuery',
    displayMovesOnSite: 'displayMovesOnSite',
    openGuiAutomatically: 'openGuiAutomatically',
    subtleMode: 'subtleMode',
    subtletiness: 'subtletiness'
};




let Interface = null;
let LozzaUtils = null;

let initialized = false;
let firstMoveMade = false;


let engine = null;
let engine2 = null;
let engineObjectURL = null;
let lastEngine=engineIndex;
let engineObjectURL2 = null;

let chessBoardElem = null;
let turn = '-';
let playerColor = null;
let lastFen = null;

let uiChessBoard = null;

let activeGuiMoveHighlights = [];
let activeSiteMoveHighlights = [];

let engineLogNum = 1;
let userscriptLogNum = 1;
let enemyScore=0;
let myScore=0;


function moveResult(from,to,power,clear=true){
	if(clear){
		removeSiteMoveMarkings();
		Interface.boardUtils.removeBestMarkings();
	}
	
	const isPlayerTurn = playerColor == turn;


	if(isPlayerTurn) // my turn
		myScore=myScore+Number(power);
	else
		enemyScore=enemyScore+Number(power);

	Interface.boardUtils.updateBoardPower(myScore,enemyScore);
			
	if(GM_getValue(dbValues.displayMovesOnSite)) {
		markMoveToSite(from, to, isPlayerTurn,clear);
	}



	Interface.boardUtils.markMove(from, to, isPlayerTurn,clear);
	Interface.stopBestMoveProcessingAnimation();


}


function getBookMoves(fen,lichess,turn,depth="",movetime=""){
		GM_xmlhttpRequest ({
        method:     "GET",
        url:        LICHESS_API+"?fen="+fen+"&multiPv=3&variant=fromPosition",
        headers:    {
            "Content-Type": "application/json"
        },
        onload:     function (response) {
			if(response.response.includes("error")){

				getBestMoves(fen,lichess,turn,depth,movetime);
				
			}else{
				let data=JSON.parse(response.response);
				let nextMove=data.pvs[0].moves.split(' ')[0];
				let score=depth;
				

				moveResult(nextMove.slice(0,2),nextMove.slice(2,4),score,true);
            }
			
			
        },onerror:	function(error){
			getBestMoves(fen,lichess,turn,depth,movetime);
				
		}
    });

}

function getNodeBestMoves(engine_name,fen,lichess,turn,depth="",movetime=""){


	GM_xmlhttpRequest ({
        method:     "GET",
        url:        "http://localhost:5000/getBestMove?fen="+fen+"&depth="+depth+"&movetime="+movetime+"&turn="+turn+"&lichess="+lichess+"&engine_type=exe&engine_name="+engine_name,
        headers:    {
            "Content-Type": "application/json"
        },
        onload:     function (response) {
			if(response.response=="false"){
				return;
			}
			let data=JSON.parse(response.response);

			let depth=data.depth;
			let power=data.score;
			let nextMove=data.move;
			let oppositeMove=data.opposite_move;
			

			moveResult(nextMove.slice(0,2),nextMove.slice(2,4),power,true);
			if(oppositeMove!="false" && show_opposite_moves)
				moveResult(oppositeMove.slice(0,2),oppositeMove.slice(2,4),power,false);
			
			
        },onerror:	function(error){
			console.log("check node server");
			console.log("using local engine !!");
			loadRandomChessengine(fen);
				
		}
    });

}


function eloToTitle(elo) {
    return elo >= 2800 ? "Grand Master"
    : elo >= 2600 ? "International Master"
    : elo >= 2400 ? "Fide Master"
    : elo >= 2200 ? "National Master"
    : elo >= 2000 ? "Expert"
    : elo >= 1800 ? "Tournament Player"
    : elo >= 1700 ? "Experienced"
    : elo >= 1600 ? "Experienced"
    : elo >= 1400 ? "Intermediate"
    : elo >= 1200 ? "Average"
    : elo >= 1000 ? "Casual"
    : "Beginner";
}

const engineEloArr = [
    { elo: 1200, data: 'go depth 1' },
    { elo: 1300, data: 'go depth 2' },
    { elo: 1450, data: 'go depth 3' },
    { elo: 1750, data: 'go depth 4' },
    { elo: 2000, data: 'go depth 5' },
    { elo: 2200, data: 'go depth 6' },
    { elo: 2400, data: 'go depth 7' },
    { elo: 2600, data: 'go depth 8' },
    { elo: 2800, data: 'go depth 9' },
    { elo: 3000, data: 'go depth 10' },
    { elo: 3200, data: 'go depth 11' },
    { elo: 3300, data: 'go depth 12' },
    { elo: 3400, data: 'go depth 13' }
];

function getCurrentEngineElo() {
    return engineEloArr.find(x => x.data == GM_getValue(dbValues.engineDepthQuery))?.elo;
}

function getEloDescription(elo,depth){


	return `Power: ${elo}, Desc: (${eloToTitle(elo)}), DEPTH: ${Number(depth)+1}`;
}



const Gui = new UserGui;
Gui.settings.window.title = 'C.A.S';
Gui.settings.window.external = true;
Gui.settings.window.size.width = 500;
Gui.settings.gui.external.popup = false;
Gui.settings.gui.external.style += GM_getResourceText('chessboard.css');
Gui.settings.gui.external.style += `
div[class^='board'] {
    background-color: black;
}
.best-move-from {
    background-color: #31ff7f;
    transform: scale(0.85);
}
.best-move-to {
    background-color: #31ff7f;
}
.negative-best-move-from {
    background-color: #fd0000;
    transform: scale(0.85);
}
.negative-best-move-to {
    background-color: #fd0000;
}
body {
    display: block;
    margin-left: auto;
    margin-right: auto;
    width: 360px;
}
#fen {
    margin-left: 10px;
}
#engine-log-container {
    max-height: 35vh;
    overflow: auto!important;
}
#userscript-log-container {
    max-height: 35vh;
    overflow: auto!important;
}
.sideways-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.rendered-form .card {
    margin-bottom: 10px;
}
.hidden {
    display: none;
}
.main-title-bar {
    display: flex;
    justify-content: space-between;
}
@keyframes wiggle {
    0% { transform: scale(1); }
   80% { transform: scale(1); }
   85% { transform: scale(1.1); }
   95% { transform: scale(1); }
  100% { transform: scale(1); }
}

.wiggle {
  display: inline-block;
  animation: wiggle 1s infinite;
}
`;

function FenUtils() {
    this.board = [
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
            [1,1,1,1,1,1,1,1],
        ];

    this.pieceCodeToFen = pieceStr => {
        const [pieceColor, pieceName] = pieceStr.split('');

        return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }

    this.getFenCodeFromPieceElem = pieceElem => {
        return this.pieceCodeToFen([...pieceElem.classList].find(x => x.match(/^(b|w)[prnbqk]{1}$/)));
    }

    this.getPieceColor = pieceFenStr => {
        return pieceFenStr == pieceFenStr.toUpperCase() ? 'w' : 'b';
    }

    this.getPieceOppositeColor = pieceFenStr => {
        return this.getPieceColor(pieceFenStr) == 'w' ? 'b' : 'w';
    }

    this.squeezeEmptySquares = fenStr => {
        return fenStr.replace(/11111111/g, '8')
            .replace(/1111111/g, '7')
            .replace(/111111/g, '6')
            .replace(/11111/g, '5')
            .replace(/1111/g, '4')
            .replace(/111/g, '3')
            .replace(/11/g, '2');
    }

    this.posToIndex = pos => {
        const [x, y] = pos.split('');

        return { 'y': 8 - y, 'x': 'abcdefgh'.indexOf(x) };
    }

    this.getBoardPiece = pos => {
        const indexObj = this.posToIndex(pos);

        return this.board[indexObj.y][indexObj.x];
    }

    this.getRights = () => {
        let rights = '';

        // check for white
        const e1 = this.getBoardPiece('e1'),
              h1 = this.getBoardPiece('h1'),
              a1 = this.getBoardPiece('a1');

        if(e1 == 'K' && h1 == 'R') rights += 'K';
        if(e1 == 'K' && a1 == 'R') rights += 'Q';

        //check for black
        const e8 = this.getBoardPiece('e8'),
              h8 = this.getBoardPiece('h8'),
              a8 = this.getBoardPiece('a8');

        if(e8 == 'k' && h8 == 'r') rights += 'k';
        if(e8 == 'k' && a8 == 'r') rights += 'q';

        return rights ? rights : '-';
    }
	
	

    this.getBasicFen = () => {
        const pieceElems = [...chessBoardElem.querySelectorAll('.piece')];

        pieceElems.forEach(pieceElem => {
            const pieceFenCode = this.getFenCodeFromPieceElem(pieceElem);
            const [xPos, yPos] = pieceElem.classList.toString().match(/square-(\d)(\d)/).slice(1);

            this.board[8 - yPos][xPos - 1] = pieceFenCode;
        });

        const basicFen = this.squeezeEmptySquares(this.board.map(x => x.join('')).join('/'));

        return basicFen;
    }

    this.getFen = () => {
        const basicFen = this.getBasicFen();
        const rights = this.getRights();
		
	
        return `${basicFen} ${turn} ${rights} - 0 1`;
    }
}

function InterfaceUtils() {
    this.boardUtils = {
        findSquareElem: (squareCode) => {
            if(!Gui?.document) return;

            return Gui.document.querySelector(`.square-${squareCode}`);
        },
        markMove: (fromSquare, toSquare, isPlayerTurn,clear=true) => {
            if(!Gui?.document) return;

            const [fromElem, toElem] = [this.boardUtils.findSquareElem(fromSquare), this.boardUtils.findSquareElem(toSquare)];

            if(isPlayerTurn && clear) {
                fromElem.classList.add('best-move-from');
                toElem.classList.add('best-move-to');
            } else {
                fromElem.classList.add('negative-best-move-from');
                toElem.classList.add('negative-best-move-to');
            }

            activeGuiMoveHighlights.push(fromElem);
            activeGuiMoveHighlights.push(toElem);
        },
        removeBestMarkings: () => {
            if(!Gui?.document) return;

            activeGuiMoveHighlights.forEach(elem => {
                elem.classList.remove('best-move-from', 'best-move-to', 'negative-best-move-from', 'negative-best-move-to');
            });

            activeGuiMoveHighlights = [];
        },
        updateBoardFen: fen => {
            if(!Gui?.document) return;

            Gui.document.querySelector('#fen').textContent = fen;
        },
		updateBoardPower: (myScore,enemyScore) => {
            if(!Gui?.document) return;

            Gui.document.querySelector('#enemy-score').textContent = enemyScore;
            Gui.document.querySelector('#my-score').textContent = myScore;
        },
        updateBoardOrientation: orientation => {
            if(!Gui?.document) return;

            const orientationElem = Gui?.document?.querySelector('#orientation');

            if(orientationElem) {
                orientationElem.textContent = orientation;
            }
        }
    }

    this.engineLog = str => {
        if(!Gui?.document || enableUserLog==0) return;

        const logElem = document.createElement('div');
        logElem.classList.add('list-group-item');

        if(str.includes('info')) logElem.classList.add('list-group-item-info');
        if(str.includes('bestmove')) logElem.classList.add('list-group-item-success');

        logElem.innerText = `#${engineLogNum++} ${str}`;

        Gui.document.querySelector('#engine-log-container').prepend(logElem);
    }

    this.log = str => {
        if(!Gui?.document || enableUserLog==0) return;

        const logElem = document.createElement('div');
        logElem.classList.add('list-group-item');

        if(str.includes('info')) logElem.classList.add('list-group-item-info');
        if(str.includes('bestmove')) logElem.classList.add('list-group-item-success');

        const container = Gui?.document?.querySelector('#userscript-log-container');

        if(container) {
            logElem.innerText = `#${userscriptLogNum++} ${str}`;

            container.prepend(logElem);
        }
    }

    this.getBoardOrientation = () => {
        return document.querySelector('.board.flipped') ? 'b' : 'w';
    }

    this.updateBestMoveProgress = text => {
        if(!Gui?.document) return;

        const progressBarElem = Gui.document.querySelector('#best-move-progress');

        progressBarElem.innerText = text;

        progressBarElem.classList.remove('hidden');
        progressBarElem.classList.add('wiggle');
    }

    this.stopBestMoveProcessingAnimation = () => {
        if(!Gui?.document) return;

        const progressBarElem = Gui.document.querySelector('#best-move-progress');

        progressBarElem.classList.remove('wiggle');
    }

    this.hideBestMoveProgress = () => {
        if(!Gui?.document) return;

        const progressBarElem = Gui.document.querySelector('#best-move-progress');

        if(!progressBarElem.classList.contains('hidden')) {
            progressBarElem.classList.add('hidden');
            this.stopBestMoveProcessingAnimation();
        }
    }
}

function LozzaUtility() {
    this.separateMoveCodes = moveCode => {
        moveCode = moveCode.trim();

        let move = moveCode.split(' ')[1];

        return [move.slice(0,2), move.slice(2,4)];
    }

    this.extractInfo = str => {
        const keys = ['time', 'nps', 'depth'];

        return keys.reduce((acc, key) => {
            const match = str.match(`${key} (\\d+)`);

            if (match) {
                acc[key] = Number(match[1]);
            }

            return acc;
        }, {});
    }
}

function fenSquareToChessComSquare(fenSquareCode) {
    const [x, y] = fenSquareCode.split('');

    return `square-${['abcdefgh'.indexOf(x) + 1]}${y}`;
}

function markMoveToSite(fromSquare, toSquare, isPlayerTurn,clear) {
    const highlight = (fenSquareCode, style) => {
        const squareClass = fenSquareToChessComSquare(fenSquareCode);

        const highlightElem = document.createElement('div');
            highlightElem.classList.add('highlight');
            highlightElem.classList.add(squareClass);
            highlightElem.dataset.testElement = 'highlight';
            highlightElem.style = style;

        activeSiteMoveHighlights.push(highlightElem);

        const existingHighLight = document.querySelector(`.highlight.${squareClass}`);

        if(existingHighLight) {
            existingHighLight.remove();
        }

        chessBoardElem.prepend(highlightElem);
    }

    const defaultFromSquareStyle = 'background-color: rgb(249 121 255 / 90%); border: 4px solid rgb(0 0 0 / 50%);';
    const defaultToSquareStyle = 'background-color: rgb(129 129 129 / 90%); border: 4px dashed rgb(0 0 0 / 50%);';
    const negativeFromSquareStyle = 'background-color: rgb(255 0 0 / 20%); border: 4px solid rgb(0 0 0 / 50%);';
    const negativeToSquareStyle = 'background-color: rgb(255 0 0 / 20%); border: 4px dashed rgb(0 0 0 / 50%);';

    const subtleMode = GM_getValue(dbValues.subtleMode);
    const subtletiness = GM_getValue(dbValues.subtletiness);

    highlight(fromSquare, subtleMode ? `background-color: rgb(0 0 0 / ${subtletiness}%);` : (clear ? defaultFromSquareStyle : negativeFromSquareStyle));
    highlight(toSquare, subtleMode ? `background-color: rgb(0 0 0 / ${subtletiness}%);` : (clear ? defaultToSquareStyle : negativeToSquareStyle));
}

function removeSiteMoveMarkings() {
    activeSiteMoveHighlights.forEach(elem => {
        elem?.remove();
    });

    activeSiteMoveHighlights = [];
}

function updateBestMove(mutationArr) {

	
    const Fen = new FenUtils();
	
    let currentFen = Fen.getFen();


    if(currentFen != lastFen) {
        lastFen = currentFen;


        if(mutationArr) {
            const attributeMutationArr = mutationArr.filter(m => m.target.classList.contains('piece') && m.attributeName == 'class');

            if(attributeMutationArr?.length) {
                turn = Fen.getPieceOppositeColor(Fen.getFenCodeFromPieceElem(attributeMutationArr[0].target));
                Interface.log(`Turn updated to ${turn}!`);
	
            }
        }
		
		

				Interface.stopBestMoveProcessingAnimation();

				currentFen = Fen.getFen();

				Interface.boardUtils.removeBestMarkings();

				removeSiteMoveMarkings();

				Interface.boardUtils.updateBoardFen(currentFen);


				if(engineIndex!=0)
					reloadChessEngine(false,()=>{

				

				// send engine only when it's my turn
				if(playerColor == null || turn == playerColor) {
					Interface.log('Sending best move request to the engine!');

					let depth = engineEloArr.findIndex(x => x.data == GM_getValue(dbValues.engineDepthQuery))+1;

					if(use_book_moves){
						getBookMoves(currentFen,false,turn,depth);
					}else{
						getBestMoves(currentFen,false,turn,depth);
					}

				}

				});

    }
}
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function getBestMoves(fen,lichess,turn,depth=0,movetime=""){
	if(!node_engine_id.includes(engineIndex)){
		// local engines
		while(!engine){
			sleep(100);
		}
		engine.postMessage(`position fen ${fen}`);
		engine.postMessage(GM_getValue(dbValues.engineDepthQuery));
	}else{
		// node server
		console.log("using node server");
		
		var engine_name="";
		if(engineIndex==3)
			engine_name="stockfish-15.exe"
		
		getNodeBestMoves(engine_name,fen,lichess,turn,depth,movetime);
	}
}

function observeNewMoves() {
    updateBestMove();

    const boardObserver = new MutationObserver(mutationArr => {
        const lastPlayerColor = playerColor;

        updatePlayerColor();
		

        if(playerColor != lastPlayerColor) {
            Interface.log(`Player color changed from ${lastPlayerColor} to ${playerColor}!`);
			
            updateBestMove();
        } else {
            updateBestMove(mutationArr);
        }
    });

    boardObserver.observe(chessBoardElem, { childList: true, subtree: true, attributes: true });
}

function addGuiPages() {
    if(Gui?.document) return;

    Gui.addPage("Main", `
    <div class="rendered-form">
        <script>${GM_getResourceText('jquery.js')}</script>
        <script>${GM_getResourceText('chessboard.js')}</script>
        <div class="card">
            <div class="card-body">
                <div class="main-title-bar">
                    <h5 class="card-title">Live Chessboard</h5>
                    <p id="best-move-progress"></p>
                </div>

                <div id="board" style="width: 447px"></div>
            </div>
            <div id="orientation" class="hidden"></div>
            <div class="card-footer sideways-card">FEN :<small class="text-muted"><div id="fen"></div></small></div>
            <div class="card-footer sideways-card">ENEMY SCORE :<div id="enemy-score"></div></div>
            <div class="card-footer sideways-card">MY SCORE : <div id="my-score"></div></div>
        </div>
        <script>
        const orientationElem = document.querySelector('#orientation');
        const fenElem = document.querySelector('#fen');

        let board = ChessBoard('board', {
            pieceTheme: '${repositoryRawURL}/content/chesspieces/{piece}.svg',
            position: 'start',
            orientation: '${playerColor == 'b' ? 'black' : 'white'}'
        });

        const orientationObserver = new MutationObserver(() => {
            board = ChessBoard('board', {
                pieceTheme: '${repositoryRawURL}/content/chesspieces/{piece}.svg',
                position: fenElem.textContent,
                orientation: orientationElem.textContent == 'b' ? 'black' : 'white'
            });
        });

        const fenObserver = new MutationObserver(() => {
            board.position(fenElem.textContent);
        });

        orientationObserver.observe(orientationElem, { attributes: true,  childList: true,  characterData: true });
        fenObserver.observe(fenElem, { attributes: true,  childList: true,  characterData: true });
        </script>
    </div>
    `);

    Gui.addPage('Log', `
    <div class="rendered-form">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Userscript Log</h5>
                <ul class="list-group" id="userscript-log-container"></ul>
            </div>
        </div>
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Engine Log</h5>
                <ul class="list-group" id="engine-log-container"></ul>
            </div>
        </div>
    </div>
    `);

    const depth = engineEloArr.findIndex(x => x.data == GM_getValue(dbValues.engineDepthQuery));
    const subtletiness = GM_getValue(dbValues.subtletiness);

    const displayMovesOnSite = GM_getValue(dbValues.displayMovesOnSite) == true;
    const openGuiAutomatically = GM_getValue(dbValues.openGuiAutomatically) == true;
    const subtleMode = GM_getValue(dbValues.subtleMode) == true;




    Gui.addPage('Settings', `
    <div class="rendered-form">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Engine</h5>
                <div class="form-group field-select-engine">
                    <select class="form-control" name="select-engine" id="select-engine">
                        <option value="option-lozza" id="select-engine-0">Lozza</option>
                        <option value="option-stockfish" id="select-engine-2">Stockfish 5</option>
                        <option value="option-stockfish2" id="select-engine-3">Stockfish 2018</option>
                        <option value="option-nodeserver" id="select-engine-4">Stockfish 15 (Node Server)</option>
                    </select>
                </div>
            </div>
        </div>
		
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Engine Strength</h5>
                <input type="range" class="form-range" min="0" max="${engineEloArr.length - 1}" value="${depth}" id="depth-range">
            </div>
            <div class="card-footer sideways-card" id="depth-elo">Elo <small id="elo">${getEloDescription(getCurrentEngineElo(),depth)}</small></div>
        </div>

        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Other</h5>
				
				        <div>
                            <input type="checkbox" id="enable-user-log" ${enableUserLog ? 'checked' : ''}>
                            <label for="enable-user-log">Enable User Scripts Log</label>
                        </div>
						
						
                        <div>
                            <input type="checkbox" id="use-book-moves" ${use_book_moves ? 'checked' : ''}>
                            <label for="use-book-moves">Use book moves</label>
                        </div>
						

				        <div>
                            <input type="checkbox" id="show-opposite-moves" ${show_opposite_moves ? 'checked' : ''}>
                            <label for="show-opposite-moves">Show Opponent best moves</label>
                        </div>		
						
						<div>
                            <label for="reload-count">Reload Engine every</label>
                            <input type="number" id="reload-count" value="${reload_every}">
							<label for="reload-count"> moves</label>
                        </div>
        </div>

		
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Visual</h5>
                <div id="display-moves-on-site-warning" class="alert alert-danger ${displayMovesOnSite ? '' : 'hidden'}">
                    <strong>Highly risky!</strong> DOM manipulation (moves displayed on site) is easily detectable! Use with caution.
                </div>
                <input type="checkbox" id="display-moves-on-site" ${displayMovesOnSite ? 'checked' : ''}>
                <label for="display-moves-on-site">Display moves on site</label>
                <!-- Display moves on site additional settings -->
                <div class="card ${displayMovesOnSite ? '' : 'hidden'}" id="display-moves-on-site-additional">
                    <div class="card-body">
                        <!-- Open GUI automatically checkbox -->
                        <div>
                            <input type="checkbox" id="open-gui-automatically" ${openGuiAutomatically ? 'checked' : ''}>
                            <label for="open-gui-automatically">Open GUI automatically</label>
                        </div>
                        <!-- Subtle mode settinngs -->
                        <div>
                            <!-- Subtle mode checkbox -->
                            <div>
                                <input type="checkbox" id="subtle-mode" ${subtleMode ? 'checked' : ''}>
                                <label for="subtle-mode">Subtle mode</label>
                            </div>
                            <!-- Subtle mode additional settings -->
                            <div>
                                <div class="card ${subtleMode ? '' : 'hidden'}" id="subtletiness-range-container">
                                    <div class="card-body">
                                        <!-- Subtletiness range -->
                                        <h6 class="card-title">Visibility</h6>
                                        <input type="range" class="form-range" min="1" max="50" value="${subtletiness}" id="subtletiness-range">
                                    </div>
                                    <div class="card-footer sideways-card">Percentage <small id="subtletiness-info">${subtletiness}%</small></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    `);

   
}

function openGUI() {
    Interface.log(`Opening GUI!`);

    const hide = elem => elem.classList.add('hidden');
    const show = elem => elem.classList.remove('hidden');

    Gui.open(() => {
		const depthEloElem = Gui.document.querySelector('#depth-elo');
        const depthRangeElem = Gui.document.querySelector('#depth-range');
        const eloElem = Gui.document.querySelector('#elo');
		const engineElem = Gui.document.querySelector('#select-engine');
		engineElem.selectedIndex=engineIndex;

        const useLocalEngineElem = Gui.document.querySelector('#use-book-moves');
        const showOppositeMovesElem = Gui.document.querySelector('#show-opposite-moves');
		
		
		

        const displayMovesOnSiteElem = Gui.document.querySelector('#display-moves-on-site');
        const displayMovesOnSiteWarningElem = Gui.document.querySelector('#display-moves-on-site-warning');

        const openGuiAutomaticallyElem = Gui.document.querySelector('#open-gui-automatically');
        const openGuiAutomaticallyAdditionalElem = Gui.document.querySelector('#display-moves-on-site-additional');

        const subtleModeElem = Gui.document.querySelector('#subtle-mode');
        const subtletinessRangeContainerElem = Gui.document.querySelector('#subtletiness-range-container');
        const subtletinessRange = Gui.document.querySelector('#subtletiness-range');

        const subtletinessInfo = Gui.document.querySelector('#subtletiness-info');
        
		
		const reloadEveryElem = Gui.document.querySelector('#reload-count');
		
		const enableUserLogElem = Gui.document.querySelector('#enable-user-log');

		
		enableUserLogElem.onchange=()=>{
			const isChecked=enableUserLogElem.checked;
			
			if(isChecked)
				enableUserLog=true;
			else
				enableUserLog=false;
		}
		
		reloadEveryElem.onchange=()=>{
			reload_every=reloadEveryElem.value;
		}

		engineElem.onchange = () =>{
			lastEngine=engineIndex;
			engineIndex=engineElem.selectedIndex;
			
			

			if(engineObjectURL){
				URL.revokeObjectURL(engineObjectURL);
				engineObjectURL=null;
				URL.revokeObjectURL(engineObjectURL2);
				engineObjectURL2=null;
			}




				reloadChessEngine(true,()=>{
					Interface.boardUtils.removeBestMarkings();

					removeSiteMoveMarkings();
					
					Interface.boardUtils.updateBoardPower(0,0);
				});
			
		}

        depthRangeElem.onchange = () => {
            const depth = depthRangeElem.value;
            const engineEloObj = engineEloArr[depth];

            const description = getEloDescription(engineEloObj.elo,depth);
            const engineQuery = engineEloObj.data;

            GM_setValue(dbValues.engineDepthQuery, engineQuery);

            eloElem.innerText = description;
        };
		
		showOppositeMovesElem.onchange=()=>{
			const isChecked = showOppositeMovesElem.checked;
			
			if(isChecked){
				show_opposite_moves=true;
			}else{
				show_opposite_moves=false;
			}
		}
		
		useLocalEngineElem.onchange=()=>{
			const isChecked = useLocalEngineElem.checked;
			
			if(isChecked){
				use_book_moves=true;
			}else{
				use_book_moves=false;
			}
		}

        displayMovesOnSiteElem.onchange = () => {
            const isChecked = displayMovesOnSiteElem.checked;

            if(isChecked) {
                GM_setValue(dbValues.displayMovesOnSite, true);

                show(displayMovesOnSiteWarningElem);
                show(openGuiAutomaticallyAdditionalElem);

                openGuiAutomaticallyElem.checked = GM_getValue(dbValues.openGuiAutomatically);
            } else {
                GM_setValue(dbValues.displayMovesOnSite, false);
                GM_setValue(dbValues.openGuiAutomatically, true);

                hide(displayMovesOnSiteWarningElem);
                hide(openGuiAutomaticallyAdditionalElem);
            }
        };

        openGuiAutomaticallyElem.onchange = () => {
            GM_setValue(dbValues.openGuiAutomatically, openGuiAutomaticallyElem.checked);
        };

        subtleModeElem.onchange = () => {
           const isChecked = subtleModeElem.checked;

            if(isChecked) {
                GM_setValue(dbValues.subtleMode, true);
                show(subtletinessRangeContainerElem);
            } else {
                GM_setValue(dbValues.subtleMode, false);
                hide(subtletinessRangeContainerElem);
            }
        };

        subtletinessRange.onchange = () => {
            GM_setValue(dbValues.subtletiness, subtletinessRange.value);
            subtletinessInfo.innerText = `${subtletinessRange.value}%`;
        };

        window.onunload = () => {
            if(Gui.window && !Gui.window.closed) {
                Gui.window.close();
            }
        };

        const isWindowClosed = setInterval(() => {
            if(Gui.window.closed) {
                clearInterval(isWindowClosed);
				if(engine!=null)
					engine.terminate();
            }
        }, 1000);

        observeNewMoves();

        Interface.log('Initialized!');
    });
}

function reloadChessEngine(forced,callback) {
	if(reload_count>=reload_every || forced==true){
		reload_count=1;
		Interface.log(`Reloading the chess engine!`);
		
		if(engine)
			engine.terminate();
		if(engine2)
			engine2.terminate();
		loadChessEngine(callback);
	}
	else{
		reload_count=reload_count+1;
		callback();
	}
}

function loadRandomChessengine(fen){
	if(!engineObjectURL2)
		engineObjectURL2 = URL.createObjectURL(new Blob([GM_getResourceText('stockfish2.js')], {type: 'application/javascript'}));
	


	
	if(engineObjectURL2){
		engine2 = new Worker(engineObjectURL2);
		engine2.onmessage = e => {
            if(e.data.includes('bestmove')) {


             
				let move = e.data.split(' ')[1];
				let move2 = e.data.split(' ')[3];
				
				moveResult(move.slice(0,2),move.slice(2,4),0,true);
				if((move2!=undefined || move2!="") && show_opposite_moves){
					moveResult(move2.slice(0,2),move2.slice(2,4),0,false);
				}
            }

         
        };
		
		while(!engine2){
			sleep(1000);
		}
		engine2.postMessage('ucinewgame');
		
		engine2.postMessage(`position fen ${fen}`);
		
	
		engine2.postMessage(GM_getValue(dbValues.engineDepthQuery));
		
	}
}

function loadChessEngine(callback) {
    if(!engineObjectURL) {
		if(engineIndex==0)
			engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('lozza.js')], {type: 'application/javascript'}));
		else if(engineIndex==1)
			engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('stockfish.js')], {type: 'application/javascript'}));
		else if(engineIndex==2)
			engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('stockfish2.js')], {type: 'application/javascript'}));
    }

    if(engineObjectURL) {
		engine = new Worker(engineObjectURL);

        engine.onmessage = e => {
            if(e.data.includes('bestmove')) {

   
				let move = e.data.split(' ')[1];
				let move2 = e.data.split(' ')[3];
				
				moveResult(move.slice(0,2),move.slice(2,4),0,true);
				if((move2!=undefined || move2!="") && show_opposite_moves){
					moveResult(move2.slice(0,2),move2.slice(2,4),0,false);
				}
            }

            else if(e.data.includes('info')) {

                const infoObj = LozzaUtils.extractInfo(e.data);

                if(infoObj?.depth) {
                    Interface.updateBestMoveProgress(`Depth ${infoObj.depth}`);
                }
            }
			Interface.engineLog(e.data);
        };

        engine.postMessage('ucinewgame');

        Interface.log(`Loaded the chess engine!`);
    }
	
	callback();
}

function initializeDatabase() {
    const initValue = (name, value) => {
        if(GM_getValue(name) == undefined) {
            GM_setValue(name, value);
        }
    };

    initValue(dbValues.engineDepthQuery, 'go depth 5');
    initValue(dbValues.displayMovesOnSite, false);
    initValue(dbValues.subtleMode, false);
    initValue(dbValues.openGuiAutomatically, true);
    initValue(dbValues.subtletiness, 25);

    Interface.log(`Initialized the database!`);
}

async function updatePlayerColor() {
    const boardOrientation = Interface.getBoardOrientation();

    playerColor = boardOrientation;
    turn = boardOrientation;

    Interface.boardUtils.updateBoardOrientation(playerColor);
}

async function initialize(openInterface) {
    Interface = new InterfaceUtils();
    LozzaUtils = new LozzaUtility();

    const boardOrientation = Interface.getBoardOrientation();
    turn = boardOrientation;

    initializeDatabase();

    loadChessEngine(()=>{
		
	});

    updatePlayerColor();

    if(openInterface) {
        addGuiPages();
        openGUI();
    } else {
        observeNewMoves();
    }
}

if(typeof GM_registerMenuCommand == 'function') {
    GM_registerMenuCommand("Open C.A.S", e => {
        if(chessBoardElem) {
            initialize(true);
        }
    }, 's');
}

const waitForChessBoard = setInterval(() => {
    const boardElem = document.querySelector('chess-board');
    const firstPieceElem = document.querySelector('.piece');

    if(boardElem && firstPieceElem && chessBoardElem != boardElem) {
        chessBoardElem = boardElem;

        if(window.location.href != 'https://www.chess.com/play') {
            const openGuiAutomatically = GM_getValue(dbValues.openGuiAutomatically);

            if(openGuiAutomatically == undefined) {
                initialize(true);
            } else {
                initialize(openGuiAutomatically);
            }
        }
    }
}, 1000);