Lichess Auto Chess Player

Pro-level chess autoplay with Stockfish engine and human-like behavior

Size

15.3 KB

Version

2.1.1

Created

Nov 2, 2025

Updated

13 days ago

1// ==UserScript==
2// @name		Lichess Auto Chess Player
3// @description		Pro-level chess autoplay with Stockfish engine and human-like behavior
4// @version		2.1.1
5// @match		https://*.lichess.org/*
6// @icon		https://lichess1.org/assets/logo/lichess-favicon-32.png
7// @require		https://cdn.jsdelivr.net/npm/stockfish@16.0.0/src/stockfish.js
8// @require		https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js
9// @grant		GM.getValue
10// @grant		GM.setValue
11// ==/UserScript==
12(function() {
13    'use strict';
14
15    console.log('Lichess Pro Auto Chess Player loaded');
16
17    // Configuration
18    const CONFIG = {
19        enabled: true,
20        minThinkTime: 3000,  // Minimum thinking time in ms (3 seconds)
21        maxThinkTime: 10000, // Maximum thinking time in ms (10 seconds)
22        moveVariation: 0.4,  // 40% variation in move timing
23        enableMouseMovement: true,
24        stockfishDepth: 18,  // Analysis depth (higher = stronger, 18 is pro level)
25        skillLevel: 20,      // Stockfish skill level (0-20, 20 is maximum)
26    };
27
28    // Chess game instance and Stockfish engine
29    let chessGame = null;
30    let stockfishEngine = null;
31    let isAutoplayActive = false;
32    let isMyTurn = false;
33    let playerColor = null;
34    let engineReady = false;
35
36    // Initialize Stockfish engine
37    function initStockfish() {
38        return new Promise((resolve) => {
39            console.log('Initializing Stockfish engine...');
40            
41            if (typeof STOCKFISH === 'function') {
42                stockfishEngine = STOCKFISH();
43                
44                stockfishEngine.onmessage = function(event) {
45                    const message = event.data || event;
46                    console.log('Stockfish:', message);
47                    
48                    if (message === 'uciok') {
49                        engineReady = true;
50                        console.log('Stockfish engine ready!');
51                        resolve();
52                    }
53                };
54                
55                // Initialize UCI protocol
56                stockfishEngine.postMessage('uci');
57                stockfishEngine.postMessage(`setoption name Skill Level value ${CONFIG.skillLevel}`);
58                stockfishEngine.postMessage('setoption name MultiPV value 1');
59            } else {
60                console.error('Stockfish not available');
61                resolve();
62            }
63        });
64    }
65
66    // Utility: Random delay with human-like variation
67    function getHumanDelay() {
68        const base = CONFIG.minThinkTime + Math.random() * (CONFIG.maxThinkTime - CONFIG.minThinkTime);
69        const variation = base * CONFIG.moveVariation * (Math.random() - 0.5);
70        return Math.floor(base + variation);
71    }
72
73    // Utility: Sleep function
74    function sleep(ms) {
75        return new Promise(resolve => setTimeout(resolve, ms));
76    }
77
78    // Utility: Simulate human-like mouse movement
79    function simulateMouseMovement(element) {
80        if (!CONFIG.enableMouseMovement || !element) return;
81        
82        const rect = element.getBoundingClientRect();
83        const x = rect.left + rect.width / 2 + (Math.random() - 0.5) * 10;
84        const y = rect.top + rect.height / 2 + (Math.random() - 0.5) * 10;
85        
86        const mouseoverEvent = new MouseEvent('mouseover', {
87            bubbles: true,
88            cancelable: true,
89            clientX: x,
90            clientY: y
91        });
92        element.dispatchEvent(mouseoverEvent);
93    }
94
95    // Get current board state from Lichess
96    function getBoardState() {
97        try {
98            // Try to get FEN from the page
99            const fenElement = document.querySelector('[data-fen]');
100            if (fenElement) {
101                return fenElement.getAttribute('data-fen');
102            }
103
104            // Alternative: Try to extract from the board element
105            const mainBoard = document.querySelector('main.round');
106            if (mainBoard && mainBoard.dataset && mainBoard.dataset.fen) {
107                return mainBoard.dataset.fen;
108            }
109
110            // Try to get from Lichess global object
111            if (window.lichess && window.lichess.analysis) {
112                return window.lichess.analysis.node.fen;
113            }
114
115            return null;
116        } catch (error) {
117            console.error('Error getting board state:', error);
118            return null;
119        }
120    }
121
122    // Check if it's our turn
123    function checkIfMyTurn() {
124        try {
125            const turnIndicator = document.querySelector('.rclock-turn');
126            if (!turnIndicator) return false;
127
128            const myColorClass = playerColor === 'white' ? '.rclock-bottom' : '.rclock-top';
129            const myClockElement = document.querySelector(myColorClass);
130            
131            if (myClockElement && myClockElement.classList.contains('rclock-turn')) {
132                return true;
133            }
134
135            // Alternative check: Look for the turn class on the board
136            const board = document.querySelector('cg-board');
137            if (board && board.parentElement) {
138                const wrapper = board.closest('.cg-wrap');
139                if (wrapper) {
140                    const orientation = wrapper.classList.contains('orientation-white') ? 'white' : 'black';
141                    const turnElement = document.querySelector('.turn');
142                    if (turnElement) {
143                        const turnText = turnElement.textContent.toLowerCase();
144                        return turnText.includes(orientation);
145                    }
146                }
147            }
148
149            return false;
150        } catch (error) {
151            console.error('Error checking turn:', error);
152            return false;
153        }
154    }
155
156    // Determine player color
157    function determinePlayerColor() {
158        try {
159            const board = document.querySelector('.cg-wrap');
160            if (!board) return null;
161
162            if (board.classList.contains('orientation-white')) {
163                return 'white';
164            } else if (board.classList.contains('orientation-black')) {
165                return 'black';
166            }
167
168            return null;
169        } catch (error) {
170            console.error('Error determining color:', error);
171            return null;
172        }
173    }
174
175    // Calculate best move using Stockfish
176    function calculateBestMoveWithStockfish(fen) {
177        return new Promise((resolve) => {
178            if (!stockfishEngine || !engineReady) {
179                console.error('Stockfish engine not ready');
180                resolve(null);
181                return;
182            }
183
184            let bestMove = null;
185            
186            const messageHandler = function(event) {
187                const message = event.data || event;
188                
189                // Look for bestmove in the output
190                if (typeof message === 'string' && message.startsWith('bestmove')) {
191                    const parts = message.split(' ');
192                    if (parts.length >= 2) {
193                        bestMove = parts[1];
194                        console.log('Stockfish best move:', bestMove);
195                        stockfishEngine.onmessage = null;
196                        resolve(bestMove);
197                    }
198                }
199            };
200            
201            stockfishEngine.onmessage = messageHandler;
202            
203            // Send position and calculate
204            stockfishEngine.postMessage('ucinewgame');
205            stockfishEngine.postMessage(`position fen ${fen}`);
206            stockfishEngine.postMessage(`go depth ${CONFIG.stockfishDepth}`);
207            
208            // Timeout fallback
209            setTimeout(() => {
210                if (!bestMove) {
211                    console.log('Stockfish timeout, using fallback');
212                    stockfishEngine.onmessage = null;
213                    resolve(null);
214                }
215            }, 15000);
216        });
217    }
218
219    // Parse UCI move format (e.g., "e2e4") to chess.js format
220    function parseUCIMove(uciMove, fen) {
221        if (!uciMove || uciMove.length < 4) return null;
222        
223        try {
224            if (!chessGame) {
225                chessGame = new Chess(fen);
226            } else {
227                chessGame.load(fen);
228            }
229            
230            const from = uciMove.substring(0, 2);
231            const to = uciMove.substring(2, 4);
232            const promotion = uciMove.length > 4 ? uciMove[4] : undefined;
233            
234            const move = chessGame.move({
235                from: from,
236                to: to,
237                promotion: promotion
238            });
239            
240            chessGame.undo();
241            return move;
242        } catch (error) {
243            console.error('Error parsing UCI move:', error);
244            return null;
245        }
246    }
247
248    // Make a move on the board
249    async function makeMove(move) {
250        try {
251            console.log('Making move:', move.from, '->', move.to);
252
253            // Find the source square
254            const fromSquare = document.querySelector(`cg-board square[data-key="${move.from}"]`);
255            if (!fromSquare) {
256                console.error('Source square not found:', move.from);
257                return false;
258            }
259
260            // Simulate mouse movement to source
261            await sleep(100 + Math.random() * 200);
262            simulateMouseMovement(fromSquare);
263            await sleep(50 + Math.random() * 100);
264
265            // Click source square
266            fromSquare.click();
267            console.log('Clicked source square:', move.from);
268            
269            await sleep(200 + Math.random() * 300);
270
271            // Find the destination square
272            const toSquare = document.querySelector(`cg-board square[data-key="${move.to}"]`);
273            if (!toSquare) {
274                console.error('Destination square not found:', move.to);
275                return false;
276            }
277
278            // Simulate mouse movement to destination
279            simulateMouseMovement(toSquare);
280            await sleep(50 + Math.random() * 100);
281
282            // Click destination square
283            toSquare.click();
284            console.log('Clicked destination square:', move.to);
285
286            // Handle promotion if needed
287            if (move.promotion) {
288                await sleep(300);
289                const promotionPiece = document.querySelector(`[data-role="promotion"] piece[data-piece="${move.promotion}"]`);
290                if (promotionPiece) {
291                    promotionPiece.click();
292                    console.log('Selected promotion piece:', move.promotion);
293                }
294            }
295
296            return true;
297        } catch (error) {
298            console.error('Error making move:', error);
299            return false;
300        }
301    }
302
303    // Main autoplay loop
304    async function autoplayLoop() {
305        if (!isAutoplayActive) return;
306
307        try {
308            // Check if we're in an active game
309            const gameElement = document.querySelector('main.round');
310            if (!gameElement) {
311                console.log('No active game detected');
312                setTimeout(autoplayLoop, 2000);
313                return;
314            }
315
316            // Determine player color if not set
317            if (!playerColor) {
318                playerColor = determinePlayerColor();
319                console.log('Player color:', playerColor);
320            }
321
322            // Check if it's our turn
323            isMyTurn = checkIfMyTurn();
324            
325            if (!isMyTurn) {
326                console.log('Waiting for our turn...');
327                setTimeout(autoplayLoop, 1000);
328                return;
329            }
330
331            console.log('It\'s our turn! Calculating move...');
332
333            // Get current board state
334            const fen = getBoardState();
335            if (!fen) {
336                console.error('Could not get board state');
337                setTimeout(autoplayLoop, 2000);
338                return;
339            }
340
341            console.log('Current FEN:', fen);
342
343            // Calculate best move using Stockfish
344            const uciMove = await calculateBestMoveWithStockfish(fen);
345            if (!uciMove) {
346                console.log('No move calculated by Stockfish');
347                setTimeout(autoplayLoop, 2000);
348                return;
349            }
350
351            const bestMove = parseUCIMove(uciMove, fen);
352            if (!bestMove) {
353                console.log('Could not parse move');
354                setTimeout(autoplayLoop, 2000);
355                return;
356            }
357
358            // Wait with human-like delay
359            const thinkTime = getHumanDelay();
360            console.log(`Thinking for ${thinkTime}ms...`);
361            await sleep(thinkTime);
362
363            // Make the move
364            const moveSuccess = await makeMove(bestMove);
365            
366            if (moveSuccess) {
367                console.log('Move executed successfully');
368                isMyTurn = false;
369            } else {
370                console.error('Failed to execute move');
371            }
372
373            // Continue the loop
374            setTimeout(autoplayLoop, 1500);
375
376        } catch (error) {
377            console.error('Error in autoplay loop:', error);
378            setTimeout(autoplayLoop, 2000);
379        }
380    }
381
382    // Create UI toggle button
383    function createToggleButton() {
384        const button = document.createElement('button');
385        button.id = 'autoplay-toggle';
386        button.textContent = '🤖 Pro Auto: ON';
387        button.style.cssText = `
388            position: fixed;
389            top: 10px;
390            right: 10px;
391            z-index: 10000;
392            padding: 10px 15px;
393            background: #3893E8;
394            color: white;
395            border: none;
396            border-radius: 5px;
397            font-weight: bold;
398            cursor: pointer;
399            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
400            font-size: 14px;
401        `;
402
403        button.addEventListener('click', () => {
404            isAutoplayActive = !isAutoplayActive;
405            button.textContent = isAutoplayActive ? '🤖 Pro Auto: ON' : '🤖 Pro Auto: OFF';
406            button.style.background = isAutoplayActive ? '#3893E8' : '#666';
407            
408            if (isAutoplayActive) {
409                console.log('Pro Autoplay enabled');
410                autoplayLoop();
411            } else {
412                console.log('Pro Autoplay disabled');
413            }
414        });
415
416        document.body.appendChild(button);
417        console.log('Toggle button created');
418    }
419
420    // Initialize the extension
421    async function init() {
422        console.log('Initializing Lichess Pro Auto Chess Player...');
423        
424        // Wait for page to be ready
425        if (document.readyState === 'loading') {
426            document.addEventListener('DOMContentLoaded', init);
427            return;
428        }
429
430        // Wait for body to be available
431        if (!document.body) {
432            setTimeout(init, 100);
433            return;
434        }
435
436        // Initialize Stockfish engine
437        await initStockfish();
438
439        // Create toggle button
440        try {
441            createToggleButton();
442        } catch (error) {
443            console.error('Error creating toggle button:', error);
444        }
445
446        // Start autoplay if enabled
447        if (CONFIG.enabled) {
448            isAutoplayActive = true;
449            setTimeout(autoplayLoop, 2000);
450        }
451
452        console.log('Lichess Pro Auto Chess Player initialized');
453    }
454
455    // Start the extension
456    if (document.readyState === 'loading') {
457        document.addEventListener('DOMContentLoaded', init);
458    } else {
459        init();
460    }
461
462})();
Lichess Auto Chess Player | Robomonkey