// ==UserScript==
// @name C.A.S (Chess Assistance System)
// @namespace sayfpack
// @author sayfpack
// @version 1.5
// @homepageURL https://github.com/Hakorr/Userscripts/tree/main/Other/A.C.A.S
// @supportURL https://github.com/Hakorr/Userscripts/issues/new
// @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://gf.qytechs.cn/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 lozza2.js https://raw.githubusercontent.com/op12no2/lozza/master/history/2.6/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'
Advanced 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.*/
// DANGER ZONE - DO NOT PROCEED IF YOU DON'T KNOW WHAT YOU'RE DOING //
/*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
//////////////////////////////////////////////////////////////////////
// DANGER ZONE - DO NOT PROCEED IF YOU DON'T KNOW WHAT YOU'RE DOING //
const ANALYS_API_URL="https://lichess.org/api/cloud-eval";
const TABLEBASE_API_URL="https://tablebase.lichess.ovh/standard";
const USE_API_LICHESS=false
const SEND_MOVES_TO_CHESSAPP=false;
let engineObjectURL2
let lichess_worked=false;
let pgn='';
function moveResult(from,to,power){
removeSiteMoveMarkings();
Interface.boardUtils.removeBestMarkings();
const isPlayerTurn = playerColor == turn;
let enemyScore=0;
let myScore=0;
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);
}
Interface.boardUtils.markMove(from, to, isPlayerTurn);
Interface.stopBestMoveProcessingAnimation();
}
function getRandomMove(fen){
GM_xmlhttpRequest ({
method: "GET",
url: TABLEBASE_API_URL+"?fen="+fen,
headers: {
"Content-Type": "application/json"
},
onload: function (response) {
let data=JSON.parse(response.response);
let moves=data.moves;
let random_int=Math.floor(Math.random() * moves.length);
let nextMove=data.moves[random_int].uci;
const [from, to] = [nextMove.slice(0,2),nextMove.slice(2,4)];
moveResult(from,to,0);
}
});
}
function getPGN () {
let pgn='';
for (const el of Array.from(document.querySelectorAll('vertical-move-list > .move'))) {
const number = el.dataset.wholeMoveNumber
pgn += `${number}. `
for (const node of Array.from(el.querySelectorAll('.node'))) {
const figurine = node.querySelector('span')
if (figurine) {
pgn += `${figurine.dataset.figurine}`
}
pgn += `${node.innerText} `
}
}
return pgn;
}
function getBestMoves2(fen,depth="",movetime=""){
GM_xmlhttpRequest ({
method: "GET",
url: "http://localhost:5000/getBestMove?fen="+fen+"&depth="+depth+"&movetime="+movetime,
headers: {
"Content-Type": "application/json"
},
onload: function (response) {
let data=JSON.parse(response.response);
let depth=data.depth;
let power=data.score;
let nextMove=data.move;
const [from,to] = [nextMove.slice(0,2),nextMove.slice(2,4)];
moveResult(from,to,power);
},onerror: function(error){
console.log("check api server");
console.log("using local engine !!");
loadRandomChessengine(fen);
}
});
}
function getBestMoves(fen){
GM_xmlhttpRequest ({
method: "GET",
url: ANALYS_API_URL+"?fen="+fen+"&variant=fromPosition",
headers: {
"Content-Type": "application/json"
},
onload: function (response) {
if(response.responseText.includes("error")){
lichess_worked=false;
if(USE_API_LICHESS){
console.log("using lichess api engine !!");
if(playerColor == null || turn == playerColor)
getBestMoves2(fen);
}else{
console.log("using local engine !!");
loadRandomChessengine(fen);
}
}else {
lichess_worked=true;
console.log("using lichess engine !!");
let data=JSON.parse(response.response);
let move=((data.pvs[0]).moves).split(' ');
let power=((data.pvs[0]).cp);
let nextMove=move[0];
const [from, to] = [nextMove.slice(0,2),nextMove.slice(2,4)];
moveResult(from,to,power);
}
}
});
}
function sendMoves(){
const black = document.querySelector('chess-board')?.classList.contains('flipped') || false
pgn=getPGN();
GM_xmlhttpRequest ({
method: "POST",
url: "http://localhost:30012",
data: JSON.stringify({black,pgn}),
headers: {
"Content-Type": "application/json"
},
onload: function (response) {
}
});
}
const reload_every=40;
const enableLog=1;
const repositoryRawURL = 'https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S';
const repositoryURL = 'https://github.com/Hakorr/Userscripts/tree/main/Other/A.C.A.S';
let engineIndex=4;
const dbValues = {
engineDepthQuery: 'engineDepthQuery',
displayMovesOnSite: 'displayMovesOnSite',
openGuiAutomatically: 'openGuiAutomatically',
subtleMode: 'subtleMode',
subtletiness: 'subtletiness'
};
let reload_count=0;
let Interface = null;
let LozzaUtils = null;
let initialized = false;
let firstMoveMade = false;
let engine = null;
let engine2 = null;
let engineObjectURL = 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;
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) => {
if(!Gui?.document) return;
const [fromElem, toElem] = [this.boardUtils.findSquareElem(fromSquare), this.boardUtils.findSquareElem(toSquare)];
if(isPlayerTurn) {
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 || enableLog==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 || enableLog==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) {
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); border: 4px solid rgb(0 0 0 / 50%);';
const defaultToSquareStyle = 'background-color: rgb(129 129 129); 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}%);` : defaultFromSquareStyle);
highlight(toSquare, subtleMode ? `background-color: rgb(0 0 0 / ${subtletiness}%);` : defaultToSquareStyle);
}
function removeSiteMoveMarkings() {
activeSiteMoveHighlights.forEach(elem => {
elem?.remove();
});
activeSiteMoveHighlights = [];
}
function updateBestMove(mutationArr) {
if(SEND_MOVES_TO_CHESSAPP)
sendMoves();
const Fen = new FenUtils();
let currentFen = Fen.getFen();
let currentPgn = getPGN();
if(currentFen != lastFen || currentPgn!=pgn) {
lastFen = currentFen;
pgn = currentPgn;
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}!`);
}
}
if(engineIndex!=4)
reloadChessEngine();
Interface.stopBestMoveProcessingAnimation();
currentFen = Fen.getFen();
Interface.boardUtils.removeBestMarkings();
removeSiteMoveMarkings();
Interface.boardUtils.updateBoardFen(currentFen);
Interface.log('Sending best move request to the engine!');
if(engineIndex!=4){
engine.postMessage(`position fen ${currentFen}`);
if(playerColor == null || turn == playerColor) {
engine.postMessage(GM_getValue(dbValues.engineDepthQuery));
}
}else{
// lichess engine
if(playerColor == null || turn == playerColor) {
let depth = engineEloArr.findIndex(x => x.data == GM_getValue(dbValues.engineDepthQuery))+1;
getBestMoves2(currentFen,depth);
}
}
}
}
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-lozza2" id="select-engine-1">Lozza 2</option>
<option value="option-stockfish" id="select-engine-2">Stockfish</option>
<option value="option-stockfish2" id="select-engine-3">Stockfish 2</option>
<option value="option-nodeserver" id="select-engine-4">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">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>
`);
Gui.addPage('About', `
<div class="rendered-form">
<div class="card">
<div class="card-body">
<p class="lead">
<b>C.A.S</b> <i>(Advanced Chess Assistance System)</i> is an advanced chess assistance system which enhances your chess performance with cutting-edge real-time move analysis.
</p>
</div>
<div class="card-footer sideways-card">Developers <small>Haka</small></div>
<div class="card-footer sideways-card">Version <small>${GM_info.script.version}</small></div>
<div class="card-footer sideways-card">Repository <a href="${repositoryURL}" target="_blank">C.A.S</a></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 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');
engineElem.onchange = () =>{
engineIndex=engineElem.selectedIndex;
if(engineObjectURL){
URL.revokeObjectURL(engineObjectURL);
engineObjectURL=null;
}
Interface.stopBestMoveProcessingAnimation();
Interface.boardUtils.removeBestMarkings();
removeSiteMoveMarkings();
if(engineIndex==4){
Interface.boardUtils.updateBoardPower(0,0);
}else{
if(engine!=null)
engine.terminate();
loadChessEngine();
}
}
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;
};
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() {
if(reload_count>=reload_every){
reload_count=0;
Interface.log(`Reloading the chess engine!`);
engine.terminate();
loadChessEngine();
}
else{
reload_count=reload_count+1;
}
}
function loadRandomChessengine(fen){
if(!engineObjectURL2)
engineObjectURL2 = URL.createObjectURL(new Blob([GM_getResourceText('stockfish2.js')], {type: 'application/javascript'}));
if(!engine2)
engine2 = new Worker(engineObjectURL2);
if(engineObjectURL2){
engine2.onmessage = e => {
if(e.data.includes('bestmove')) {
const [from, to] = LozzaUtils.separateMoveCodes(e.data);
if(!lichess_worked)
moveResult(from,to,0);
}
};
engine2.postMessage('ucinewgame');
engine2.postMessage(`position fen ${fen}`);
engine2.postMessage(GM_getValue(dbValues.engineDepthQuery));
}
}
function loadChessEngine() {
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('lozza2.js')], {type: 'application/javascript'}));
else if(engineIndex==2)
engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('stockfish.js')], {type: 'application/javascript'}));
else if(engineIndex==3)
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')) {
removeSiteMoveMarkings();
Interface.boardUtils.removeBestMarkings();
const [from, to] = LozzaUtils.separateMoveCodes(e.data);
moveResult(from,to,0);
}
else if(e.data.includes('info')) {
removeSiteMoveMarkings();
Interface.boardUtils.removeBestMarkings();
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!`);
}
}
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);