Highlight squares

14/04/2025, 16:51:53

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name        Highlight squares
// @namespace   Violentmonkey Scripts
// @match       https://chess.ytdraws.win/*
// @grant       none
// @version     1.0
// @author      -
// @description 14/04/2025, 16:51:53
// @license CC-NC-SA
// ==/UserScript==
// == Pathfinding & Attack Visualization Script ==

(function() { // Encapsulate to avoid polluting global scope too much

    // --- Configuration ---
    const ATTACKED_SQUARE_COLOR = 'rgba(255, 0, 0, 0.25)'; // Red, semi-transparent
    const PATH_COLOR = 'rgba(0, 0, 255, 0.3)'; // Blue, semi-transparent
    const PATH_STEP_INTERVAL_MS = 50; // How often to check if we can take the next step (doesn't override game cooldown)
    const MOVE_COOLDOWN_THRESHOLD = 220; // From original game code, ms

    // --- State Variables ---
    let visualizeAttacksEnabled = false;
    let attackedSquares = new Set(); // Stores "x,y" strings of attacked squares
    let pathfindingData = {
        active: false,
        pieceType: 0,
        startX: -1,
        startY: -1,
        targetX: -1,
        targetY: -1,
        currentPath: [], // Array of [x, y] steps
        pathIndex: 0,
        intervalId: null
    };

    // --- Attack Calculation ---

    /**
     * Calculates all squares attacked by enemy pieces.
     * Assumes generateLegalMoves works correctly for all piece types.
     * @returns {Set<string>} A set of "x,y" strings representing attacked squares.
     */
    function calculateAllEnemyAttacks() {
        const attacked = new Set();
        if (typeof board === 'undefined' || typeof teams === 'undefined' || typeof selfId === 'undefined' || typeof generateLegalMoves !== 'function') {
            return attacked;
        }

        for (let x = 0; x < boardW; x++) {
            for (let y = 0; y < boardH; y++) {
                const team = teams[x]?.[y];
                const piece = board[x]?.[y];
                // Check if it's an enemy piece (not ours, not neutral, not empty)
                if (piece && team && team !== selfId && team !== 0) {
                    try {
                        const moves = generateLegalMoves(x, y, board, teams);
                        for (const move of moves) {
                            attacked.add(`${move[0]},${move[1]}`);
                        }
                    } catch (e) {
                        // console.warn(`Error generating moves for enemy at ${x},${y}:`, e);
                    }
                }
            }
        }
        return attacked;
    }

    // --- Pathfinding (BFS) ---

    /**
     * Finds the shortest path using Breadth-First Search.
     * @param {number} startX
     * @param {number} startY
     * @param {number} targetX
     * @param {number} targetY
     * @param {number} pieceType The type of piece trying to move (for generateLegalMoves)
     * @returns {Array<Array<number>> | null} Path as array of [x,y] steps, or null if no path.
     */
    function findPathBFS(startX, startY, targetX, targetY, pieceType) {
        console.log(`Pathfinding: Type ${pieceType} from ${startX},${startY} to ${targetX},${targetY}`);
        if (typeof board === 'undefined' || typeof teams === 'undefined' || typeof selfId === 'undefined' || typeof generateLegalMoves !== 'function') {
            console.error("Pathfinding failed: Missing game variables or functions.");
            return null;
        }

        const queue = [[startX, startY]];
        const visited = new Set([`${startX},${startY}`]);
        const parentMap = {}; // Store "childX,childY": [parentX, parentY]

        while (queue.length > 0) {
            const [currX, currY] = queue.shift();

            // Target found?
            if (currX === targetX && currY === targetY) {
                // Reconstruct path
                const path = [];
                let step = [targetX, targetY];
                while (step[0] !== startX || step[1] !== startY) {
                    path.push(step);
                    const parentKey = `${step[0]},${step[1]}`;
                    if (!parentMap[parentKey]) break; // Should not happen if target found
                    step = parentMap[parentKey];
                }
                path.push([startX, startY]); // Add start
                console.log("Path found:", path.reverse()); // Reverse to get start -> end
                return path;
            }

            // Get legal moves *as if* the piece were at currX, currY
            // We need a way to call generateLegalMoves assuming the piece *type*
            // is at currX, currY, temporarily ignoring the actual board state there
            // for planning purposes, *except* for blocking friendly pieces.
            // This requires modifying generateLegalMoves or having a variant.
            // --- SIMPLIFICATION for this example: ---
            // We'll call generateLegalMoves normally. This means it might fail if
            // the intermediate square is occupied inappropriately for the real piece.
            // A more robust solution needs a planning-specific move generator.
            // We also *simulate* the piece being there to check moves.
            const originalPiece = board[currX]?.[currY];
            const originalTeam = teams[currX]?.[currY];
            board[currX][currY] = pieceType; // Temporarily place piece for move generation
            teams[currX][currY] = selfId;
            let legalMoves = [];
            try {
                 legalMoves = generateLegalMoves(currX, currY, board, teams);
            } catch (e) { /* handle error */ }
            // Restore original board state
            board[currX][currY] = originalPiece === undefined ? 0 : originalPiece;
            teams[currX][currY] = originalTeam === undefined ? 0 : originalTeam;
            // --- End Simplification ---


            for (const move of legalMoves) {
                const [nextX, nextY] = move;
                const nextKey = `${nextX},${nextY}`;

                // Check bounds and if already visited
                if (nextX < 0 || nextX >= boardW || nextY < 0 || nextY >= boardH || visited.has(nextKey)) {
                    continue;
                }

                // Check if destination is blocked by a friendly piece (crucial!)
                // Allow moving *to* the target square even if occupied (capture/overwrite)
                if (teams[nextX]?.[nextY] === selfId && !(nextX === targetX && nextY === targetY)) {
                    continue;
                }

                // Mark visited, store parent, enqueue
                visited.add(nextKey);
                parentMap[nextKey] = [currX, currY];
                queue.push([nextX, nextY]);
            }
        }

        console.log("Pathfinding failed: No path found.");
        return null; // No path found
    }

    // --- Path Execution ---

    function executePathStep() {
        if (!pathfindingData.active || typeof send !== 'function' || typeof curMoveCooldown === 'undefined') {
            stopPathfinding();
            return;
        }

        // Check game cooldown
        if (curMoveCooldown > MOVE_COOLDOWN_THRESHOLD) {
            return; // Wait for cooldown
        }

        // Check if path is complete
        if (pathfindingData.pathIndex >= pathfindingData.currentPath.length - 1) {
            console.log("Path complete.");
            stopPathfinding();
            return;
        }

        const currentStep = pathfindingData.currentPath[pathfindingData.pathIndex];
        const nextStep = pathfindingData.currentPath[pathfindingData.pathIndex + 1];

        console.log(`Executing path step: [${currentStep[0]}, ${currentStep[1]}] -> [${nextStep[0]}, ${nextStep[1]}]`);

        try {
            // Send the move for the *next* step
            const buf = new Uint16Array(4);
            buf[0] = currentStep[0]; // From
            buf[1] = currentStep[1];
            buf[2] = nextStep[0];   // To
            buf[3] = nextStep[1];
            send(buf);

            // Advance path index (will be used in next interval check)
            pathfindingData.pathIndex++;
             // We assume the 'send' action triggers the game's cooldown mechanism.
             // Our check of `curMoveCooldown` at the start handles waiting.

        } catch (e) {
            console.error("Error sending path step move:", e);
            stopPathfinding();
        }
    }

    function startPathfinding(targetX, targetY) {
        stopPathfinding(); // Stop any previous path

        if (typeof selectedSquareX === 'undefined' || typeof selectedSquareY === 'undefined') {
            console.log("Pathfinding: No piece selected.");
            return;
        }
        if (selectedSquareX === targetX && selectedSquareY === targetY) {
            console.log("Pathfinding: Target is the same as start.");
            return;
        }
         const pieceType = board[selectedSquareX]?.[selectedSquareY];
         if (!pieceType) {
             console.error("Pathfinding: Cannot determine selected piece type.");
             return;
         }


        const path = findPathBFS(selectedSquareX, selectedSquareY, targetX, targetY, pieceType);

        if (path && path.length > 1) { // Need at least start and one step
            pathfindingData = {
                active: true,
                pieceType: pieceType,
                startX: selectedSquareX,
                startY: selectedSquareY,
                targetX: targetX,
                targetY: targetY,
                currentPath: path,
                pathIndex: 0, // Start at the beginning of the path
                intervalId: setInterval(executePathStep, PATH_STEP_INTERVAL_MS)
            };
            console.log("Pathfinding started.");
            // Deselect piece visually maybe? Or keep selected? User choice.
            // selectedSquareX = selectedSquareY = undefined; // Optional deselect
        } else {
            console.log("Pathfinding: No valid path found or path too short.");
            // Optionally provide user feedback (e.g., flash screen red)
        }
    }

    function stopPathfinding() {
        if (pathfindingData.intervalId) {
            clearInterval(pathfindingData.intervalId);
        }
        pathfindingData = { active: false, intervalId: null, currentPath: [], pathIndex: 0 }; // Reset state
        console.log("Pathfinding stopped.");
    }

    // --- Drawing ---

    function drawAttackedSquares() {
        if (!visualizeAttacksEnabled || typeof ctx === 'undefined' || typeof camera === 'undefined') return;

        // Recalculate attacks (can be optimized)
        attackedSquares = calculateAllEnemyAttacks();

        if (attackedSquares.size === 0) return;

        const originalTransform = ctx.getTransform(); // Save original transform
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.scale(camera.scale, camera.scale);
        ctx.translate(camera.x, camera.y);

        ctx.fillStyle = ATTACKED_SQUARE_COLOR;

        // Get visible bounds (using canvasPos - slightly adapted)
        const topLeftView = canvasPos ? canvasPos({ x: 0, y: 0 }) : { x: -camera.x * camera.scale, y: -camera.y * camera.scale }; // Fallback if canvasPos missing
        const bottomRightView = canvasPos ? canvasPos({ x: innerWidth, y: innerHeight }) : { x: (innerWidth / camera.scale) - camera.x, y: (innerHeight / camera.scale) - camera.y }; // Fallback

        const startCol = Math.max(0, Math.floor(topLeftView.x / squareSize) - 1);
        const endCol = Math.min(boardW, Math.ceil(bottomRightView.x / squareSize) + 1);
        const startRow = Math.max(0, Math.floor(topLeftView.y / squareSize) - 1);
        const endRow = Math.min(boardH, Math.ceil(bottomRightView.y / squareSize) + 1);


        attackedSquares.forEach(key => {
            const [x, y] = key.split(',').map(Number);
            // Only draw if within rough view bounds
            if (x >= startCol && x < endCol && y >= startRow && y < endRow) {
                 ctx.fillRect(x * squareSize, y * squareSize, squareSize, squareSize);
            }
        });

        ctx.setTransform(originalTransform); // Restore original transform
    }

     function drawCurrentPath() {
        if (!pathfindingData.active || pathfindingData.currentPath.length === 0 || typeof ctx === 'undefined' || typeof camera === 'undefined') return;

        const originalTransform = ctx.getTransform();
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.scale(camera.scale, camera.scale);
        ctx.translate(camera.x, camera.y);

        ctx.fillStyle = PATH_COLOR;
        ctx.strokeStyle = 'rgba(0, 0, 150, 0.5)';
        ctx.lineWidth = 3 / camera.scale; // Make line width scale invariant

        // Draw path lines/squares
        for (let i = pathfindingData.pathIndex; i < pathfindingData.currentPath.length; i++) {
             const [x, y] = pathfindingData.currentPath[i];
             // Draw square highlight
             ctx.fillRect(x * squareSize, y * squareSize, squareSize, squareSize);

             // Draw line to next segment (optional)
             if (i < pathfindingData.currentPath.length - 1) {
                const [nextX, nextY] = pathfindingData.currentPath[i+1];
                ctx.beginPath();
                ctx.moveTo(x * squareSize + squareSize / 2, y * squareSize + squareSize / 2);
                ctx.lineTo(nextX * squareSize + squareSize / 2, nextY * squareSize + squareSize / 2);
                ctx.stroke();
             }
        }
        ctx.setTransform(originalTransform);
    }


    // --- Integration with Game Loop & Events ---

    // Monkey-patch the render function (use MutationObserver if render is complex/obfuscated)
    if (typeof render === 'function') {
        const originalRender = render;
        window.render = function(...args) {
            originalRender.apply(this, args); // Call original render first
            // Add our drawing functions
            try {
                drawAttackedSquares();
                drawCurrentPath();
            } catch (e) {
                 console.error("Error in visualization drawing:", e);
                 // Stop visuals if they error out?
                 visualizeAttacksEnabled = false;
                 stopPathfinding();
            }
        }
        console.log("Visualization hooks added to render function.");
    } else {
        console.error("Could not find global 'render' function to hook into.");
    }

    // Monkey-patch mousedown (or add event listener if preferred)
     if (typeof onmousedown === 'function') {
         const originalMouseDown = onmousedown;
         window.onmousedown = function(e) {
             // Stop any ongoing pathfinding if user clicks anywhere
             if (pathfindingData.active) {
                  console.log("User click interrupted pathfinding.");
                  stopPathfinding();
                  // Let the original handler decide if a new selection/move happens
             }

            // Calculate potential target square BEFORE calling original handler
             let targetX, targetY;
             let mousePosDown; // Store mouse pos for pathfinding check
             try {
                 const t = ctx.getTransform(); // Need context for transform
                 ctx.translate(canvas.width/2, canvas.height/2);
                 ctx.scale(camera.scale, camera.scale);
                 ctx.translate(camera.x, camera.y);
                 mousePosDown = canvasPos({ x: e.clientX, y: e.clientY }); // Use clientX/Y
                 ctx.setTransform(t); // Restore immediately
                 targetX = Math.floor(mousePosDown.x / squareSize);
                 targetY = Math.floor(mousePosDown.y / squareSize);
             } catch (err) {
                // console.error("Could not calculate target square on mousedown:", err);
                originalMouseDown.call(this, e); // Still call original
                return;
            }

            // --- Pathfinding Check ---
            let isImmediateLegalMove = false;
            if (typeof selectedSquareX !== 'undefined' && typeof selectedSquareY !== 'undefined' && typeof legalMoves !== 'undefined' && Array.isArray(legalMoves)) {
                for (let i = 0; i < legalMoves.length; i++) {
                    if (legalMoves[i][0] === targetX && legalMoves[i][1] === targetY) {
                        isImmediateLegalMove = true;
                        break;
                    }
                }
            }

            // Call the original handler *first* to handle selection/deselection/immediate moves
            originalMouseDown.call(this, e);

            // --- Initiate Pathfinding AFTER original handler ---
            // Check if:
            // 1. A piece *is still* selected (original handler didn't deselect or move successfully).
            // 2. The click was NOT an immediate legal move for the originally selected piece.
            // 3. The click is within board bounds.
            // 4. Pathfinding isn't already active (should have been stopped above, but double check).
            if (typeof selectedSquareX !== 'undefined' && typeof selectedSquareY !== 'undefined' &&
                !isImmediateLegalMove &&
                targetX >= 0 && targetX < boardW && targetY >= 0 && targetY < boardH &&
                !pathfindingData.active)
            {
                // Check cooldown just before starting pathfinding
                if (curMoveCooldown <= MOVE_COOLDOWN_THRESHOLD) {
                    console.log("Initiating pathfinding to", targetX, targetY);
                    startPathfinding(targetX, targetY);
                } else {
                    console.log("Cannot start pathfinding, move cooldown active.");
                }
            }
         }
         console.log("Pathfinding hook added to onmousedown function.");
     } else {
        console.error("Could not find global 'onmousedown' function to hook into.");
     }


    // --- Global Control Functions ---
    window.toggleAttackVisualization = function(enable = !visualizeAttacksEnabled) {
        visualizeAttacksEnabled = enable;
        console.log("Attack Visualization " + (visualizeAttacksEnabled ? "Enabled" : "Disabled"));
        if (!visualizeAttacksEnabled) {
            attackedSquares.clear(); // Clear stored squares when disabled
            // Request a redraw if possible (difficult without direct access to game loop flags)
        }
    }

    window.cancelCurrentPath = function() {
        stopPathfinding();
    }

    console.log("Pathfinding and Attack Visualization script loaded.");
    console.log("Use toggleAttackVisualization() to turn red squares on/off.");
    console.log("Click an invalid square after selecting a piece to pathfind.");
    console.log("Use cancelCurrentPath() to stop active pathfinding.");
    toggleAttackVisualization()

})(); // End IIFE