- // ==UserScript==
- // @name Automated moves
- // @namespace Violentmonkey Scripts
- // @match https://chess.ytdraws.win/*
- // @grant none
- // @version 1.0
- // @author -
- // @description 14/04/2025, 16:54:06
- // @license CC0
- // ==/UserScript==
-
-
- let basicBotInterval = null;
-
- // --- Helper Functions (Existing + New) ---
-
- /**
- * Calculates Manhattan distance between two points.
- * @param {number} x1
- * @param {number} y1
- * * @param {number} x2
- * @param {number} y2
- * @returns {number}
- */
- function manhattanDistance(x1, y1, x2, y2) {
- return Math.abs(x1 - x2) + Math.abs(y1 - y2);
- }
-
- /**
- * Finds all coordinates of the player's pieces of a specific type.
- * @param {number} pieceType - The type of piece (1-6).
- * @returns {Array<Array<number>>} An array of [x, y] coordinates.
- */
- function findMyPiecesOfType(pieceType) {
- const pieces = [];
- if (typeof selfId === 'undefined' || selfId === -1) return pieces;
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- if (board[x][y] === pieceType && teams[x][y] === selfId) {
- pieces.push([x, y]);
- }
- }
- }
- return pieces;
- }
-
- /**
- * Finds all coordinates of the player's King(s).
- * @returns {Array<Array<number>>} An array of [x, y] coordinates.
- */
- function findMyKings() {
- return findMyPiecesOfType(6); // 6 is King
- }
-
- /**
- * Checks if the player only has King(s) left.
- * @returns {boolean} True if only Kings remain, false otherwise.
- */
- function isOnlyKingLeft() {
- if (typeof selfId === 'undefined' || selfId === -1) return false;
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- // If we find a piece belonging to us that is NOT a King (type 6)
- if (teams[x][y] === selfId && board[x][y] !== 6 && board[x][y] !== 0) {
- return false; // Found a non-king piece
- }
- }
- }
- return true; // No non-king pieces found
- }
-
- /**
- * Checks if a specific square is attacked by any enemy piece.
- * (Same as before - potentially inefficient)
- * @param {number} targetX - The x-coordinate of the square to check.
- * @param {number} targetY - The y-coordinate of the square to check.
- * @returns {boolean} True if the square is under attack, false otherwise.
- */
- function isSquareAttacked(targetX, targetY) {
- if (typeof selfId === 'undefined' || selfId === -1) return false;
- if (typeof generateLegalMoves === 'undefined') return false; // Safety check
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- if (board[x][y] !== 0 && teams[x][y] !== selfId && teams[x][y] !== 0) {
- try {
- const enemyMoves = generateLegalMoves(x, y, board, teams);
- for (const move of enemyMoves) {
- if (move[0] === targetX && move[1] === targetY) {
- return true;
- }
- }
- } catch (e) {
- // console.error(`Error generating moves for enemy at ${x},${y} when checking attack on ${targetX},${targetY}:`, e);
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Finds the coordinates of the nearest neutral (Team 0) piece.
- * @param {number} fromX - The starting X coordinate.
- * @param {number} fromY - The starting Y coordinate.
- * @returns {Array<number> | null} Coordinates [x, y] of the nearest neutral piece, or null if none found.
- */
- function findNearestNeutralPiece(fromX, fromY) {
- let nearestPos = null;
- let minDistance = Infinity;
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- // Team 0 is assumed to be neutral/white/unclaimed
- if (teams[x][y] === 0 && board[x][y] !== 0) {
- const dist = manhattanDistance(fromX, fromY, x, y);
- if (dist < minDistance) {
- minDistance = dist;
- nearestPos = [x, y];
- }
- }
- }
- }
- return nearestPos;
- }
-
-
- /**
- * Checks if a player's piece is "idle" (not attacking anything and not under attack).
- * @param {number} pieceX The piece's X coordinate.
- * @param {number} pieceY The piece's Y coordinate.
- * @param {Array<Array<number>>} legalMoves The pre-calculated legal moves for this piece.
- * @returns {boolean} True if the piece is idle, false otherwise.
- */
- function isPieceIdle(pieceX, pieceY, legalMoves) {
- // 1. Check if under attack
- if (isSquareAttacked(pieceX, pieceY)) {
- return false;
- }
-
- // 2. Check if any legal move is a capture
- for (const move of legalMoves) {
- const [toX, toY] = move;
- // Is the destination occupied by an enemy (not empty, not ours, not neutral)?
- if (board[toX][toY] !== 0 && teams[toX][toY] !== selfId && teams[toX][toY] !== 0) {
- return false; // This piece can attack
- }
- }
-
- // If not attacked and cannot attack, it's idle
- return true;
- }
-
- /**
- * Finds the best move from a list of legal moves towards a target coordinate.
- * Handles Knights (type 2) separately to mitigate looping issues.
- * "Best" minimizes Manhattan distance, with tie-breaking for Knights.
- * @param {number} pieceType The type (1-6) of the moving piece.
- * @param {number} currentX The current X of the moving piece.
- * @param {number} currentY The current Y of the moving piece.
- * @param {Array<Array<number>>} legalMoves List of [toX, toY] moves.
- * @param {number} targetX Target X coordinate.
- * @param {number} targetY Target Y coordinate.
- * @returns {Array<number> | null} The best move [toX, toY] or null if no suitable legal moves.
- */
- function findBestMoveTowards(pieceType, currentX, currentY, legalMoves, targetX, targetY) {
- let bestMove = null;
- let minDistance = Infinity;
- let madeProgress = false; // Did any move reduce distance?
-
- const currentTargetDist = manhattanDistance(currentX, currentY, targetX, targetY);
-
- // --- Knight-Specific Logic (Type 2) ---
- if (pieceType === 2) {
- let potentialMoves = [];
- for (const move of legalMoves) {
- const [toX, toY] = move;
- // Skip moves onto own pieces
- if (teams[toX]?.[toY] === selfId) continue;
-
- const newDist = manhattanDistance(toX, toY, targetX, targetY);
- potentialMoves.push({ move: move, dist: newDist });
- }
-
- // Sort potential moves: prioritize smaller distance, then break ties
- potentialMoves.sort((a, b) => {
- if (a.dist !== b.dist) {
- return a.dist - b.dist; // Closer is better
- } else {
- // Tie-breaker: Prioritize move that reduces the LARGER axis difference more
- const [ax, ay] = a.move;
- const [bx, by] = b.move;
- const aDiffX = Math.abs(ax - targetX);
- const aDiffY = Math.abs(ay - targetY);
- const bDiffX = Math.abs(bx - targetX);
- const bDiffY = Math.abs(by - targetY);
- const currentDiffX = Math.abs(currentX - targetX);
- const currentDiffY = Math.abs(currentY - targetY);
-
- if (currentDiffX > currentDiffY) { // Further in X direction
- return aDiffX - bDiffX; // Smaller X difference is better
- } else if (currentDiffY > currentDiffX) { // Further in Y direction
- return aDiffY - bDiffY; // Smaller Y difference is better
- } else {
- return 0; // No preference if equidistant on axes
- }
- }
- });
-
- // Return the best valid move found
- if (potentialMoves.length > 0) {
- bestMove = potentialMoves[0].move;
- // Basic loop avoidance: if best move doesn't decrease distance, maybe pick 2nd best if it does?
- if(potentialMoves.length > 1 && potentialMoves[0].dist >= currentTargetDist && potentialMoves[1].dist < currentTargetDist) {
- bestMove = potentialMoves[1].move;
- // console.log(`Knight: Avoiding non-progress move, taking 2nd best.`);
- }
- }
- return bestMove;
-
- } else {
- // --- Standard Logic (Non-Knights) ---
- for (const move of legalMoves) {
- const [toX, toY] = move;
- // Skip moves onto own pieces
- if (teams[toX]?.[toY] === selfId) continue;
-
- const dist = manhattanDistance(toX, toY, targetX, targetY);
-
- if (dist < minDistance) {
- minDistance = dist;
- bestMove = move;
- if (dist < currentTargetDist) madeProgress = true;
- }
- }
- // If the best move doesn't actually get closer, maybe reconsider?
- // For non-knights, usually direct path is fine, but this could be added if needed.
- // If best move doesn't make progress, but other moves *do*, prefer one that does?
- if(bestMove && !madeProgress) {
- let progressMove = null;
- let progressMinDist = Infinity;
- for (const move of legalMoves) {
- const [toX, toY] = move;
- if (teams[toX]?.[toY] === selfId) continue;
- const dist = manhattanDistance(toX, toY, targetX, targetY);
- if (dist < currentTargetDist && dist < progressMinDist) { // Found a move that makes progress
- progressMinDist = dist;
- progressMove = move;
- }
- }
- if(progressMove) {
- // console.log(`Non-Knight: Prioritizing progress move over non-progress best move.`);
- bestMove = progressMove;
- }
- }
-
- return bestMove;
- }
- }
-
- /**
- * Checks if moving a high-value piece (Rook/Queen) to a square is acceptably safe.
- * Currently just checks if the destination square is attacked.
- * @param {number} toX Destination X.
- * @param {number} toY Destination Y.
- * @returns {boolean} True if the move is considered safe enough, false otherwise.
- */
- function isHunterMoveSafe(toX, toY) {
- // For now, "safe enough" means the destination isn't directly attacked.
- // Could be expanded (e.g., allow if capturing equally valuable piece).
- return !isSquareAttacked(toX, toY);
- }
-
- /**
- * Counts the number of Rooks (4) and Queens (5) the player controls.
- * @returns {number} The total count of Rooks and Queens.
- */
- function countMyHunterPieces() {
- let count = 0;
- if (typeof selfId === 'undefined' || selfId === -1) return 0;
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- if (teams[x][y] === selfId && (board[x][y] === 4 || board[x][y] === 5)) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Finds the coordinates of the nearest enemy King (piece type 6).
- * @param {number} fromX - The starting X coordinate.
- * @param {number} fromY - The starting Y coordinate.
- * @returns {Array<number> | null} Coordinates [x, y] of the nearest enemy king, or null if none found.
- */
- function findNearestEnemyKing(fromX, fromY) {
- let nearestPos = null;
- let minDistance = Infinity;
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- // Check for King (6) that is NOT ours and NOT neutral (0)
- if (board[x][y] === 6 && teams[x][y] !== selfId && teams[x][y] !== 0) {
- const dist = manhattanDistance(fromX, fromY, x, y);
- if (dist < minDistance) {
- minDistance = dist;
- nearestPos = [x, y];
- }
- }
- }
- }
- return nearestPos;
- }
-
-
- /**
- * Finds the coordinates of the nearest *prioritized* neutral (Team 0) piece.
- * Priority: Queens/Rooks > Other Pieces. Distance breaks ties within priority.
- * @param {number} fromX - The starting X coordinate.
- * @param {number} fromY - The starting Y coordinate.
- * @returns {Array<number> | null} Coordinates [x, y] of the best neutral target, or null if none found.
- */
- function findPrioritizedNearestNeutralPiece(fromX, fromY) {
- let bestHighPriorityTarget = { pos: null, dist: Infinity }; // Rooks (4), Queens (5)
- let bestLowPriorityTarget = { pos: null, dist: Infinity }; // Others (1, 2, 3, 6)
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- // Team 0 is assumed to be neutral/white/unclaimed
- if (teams[x][y] === 0 && board[x][y] !== 0) {
- const pieceType = board[x][y];
- const dist = manhattanDistance(fromX, fromY, x, y);
-
- // Check if it's a high-priority target (Rook or Queen)
- if (pieceType === 4 || pieceType === 5) {
- if (dist < bestHighPriorityTarget.dist) {
- bestHighPriorityTarget.dist = dist;
- bestHighPriorityTarget.pos = [x, y];
- }
- } else { // Low priority target
- if (dist < bestLowPriorityTarget.dist) {
- bestLowPriorityTarget.dist = dist;
- bestLowPriorityTarget.pos = [x, y];
- }
- }
- }
- }
- }
-
- // Return the high-priority target if found, otherwise the low-priority one
- if (bestHighPriorityTarget.pos) {
- // console.log(`Prioritized Neutral Target: High Prio [${bestHighPriorityTarget.pos}] at dist ${bestHighPriorityTarget.dist}`);
- return bestHighPriorityTarget.pos;
- } else if (bestLowPriorityTarget.pos) {
- // console.log(`Prioritized Neutral Target: Low Prio [${bestLowPriorityTarget.pos}] at dist ${bestLowPriorityTarget.dist}`);
- return bestLowPriorityTarget.pos;
- }
-
- return null; // No neutral pieces found
- }
-
-
- /**
- * The main logic function for the bot, run periodically.
- */
- function basicBotTurnLogic() {
- // --- Pre-computation Checks ---
- if (gameOver || typeof selfId === 'undefined' || selfId === -1 || typeof board === 'undefined' || typeof teams === 'undefined' || typeof generateLegalMoves === 'undefined' || typeof send === 'undefined' || typeof boardW === 'undefined' || typeof curMoveCooldown === 'undefined') {
- return; // Not ready
- }
- if (curMoveCooldown > 220) {
- return; // On cooldown
- }
-
- let actionTakenThisTurn = false; // Flag to prevent multiple actions
- const pieceMovesCache = {}; // Cache moves for potential reuse
-
- // --- Priority 1: King Safety ---
- const myKings = findMyKings();
- for (const kingPos of myKings) {
- const [kingX, kingY] = kingPos;
- if (isSquareAttacked(kingX, kingY)) {
- console.log(`BasicBot: King at ${kingX},${kingY} is under attack! Finding safe move.`);
- try {
- const kingMoves = generateLegalMoves(kingX, kingY, board, teams);
- let safeMove = null;
- let desperationMove = null;
- for (const move of kingMoves) { // Find best escape
- const [toX, toY] = move;
- if (teams[toX]?.[toY] === selfId) continue;
- if (!isSquareAttacked(toX, toY)) { safeMove = move; break; }
- else if (!desperationMove) { desperationMove = move; }
- }
- if (safeMove) { // Execute safe move
- console.log(`BasicBot: Moving King from ${kingX},${kingY} to SAFE square ${safeMove[0]},${safeMove[1]}.`);
- send(new Uint16Array([kingX, kingY, safeMove[0], safeMove[1]]));
- actionTakenThisTurn = true;
- } else if (desperationMove) { // Execute desperation move
- console.warn(`BasicBot: No safe escape! Moving King from ${kingX},${kingY} to potentially UNSAFE square ${desperationMove[0]},${desperationMove[1]}.`);
- send(new Uint16Array([kingX, kingY, desperationMove[0], desperationMove[1]]));
- actionTakenThisTurn = true;
- } else { console.error(`BasicBot: King at ${kingX},${kingY} attacked, NO legal moves!`); }
- } catch (e) { console.error(`BasicBot: Error processing King escape @ ${kingX},${kingY}:`, e); }
- if (actionTakenThisTurn) return; // End turn
- }
- }
-
- // --- Priority 2: Standard Capture Opportunity ---
- if (actionTakenThisTurn) return;
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- if (teams[x]?.[y] === selfId && board[x][y] !== 0 && board[x][y] !== 6) { // Our piece, not King
- const pieceType = board[x][y];
- try {
- const legalMoves = pieceMovesCache[`${x},${y}`] || generateLegalMoves(x, y, board, teams);
- if (!pieceMovesCache[`${x},${y}`]) pieceMovesCache[`${x},${y}`] = legalMoves;
-
- for (const move of legalMoves) {
- const [toX, toY] = move;
- const targetPiece = board[toX]?.[toY];
- const targetTeam = teams[toX]?.[toY];
-
- // Is it a capture of an enemy piece?
- if (targetPiece !== 0 && targetTeam !== selfId && targetTeam !== 0) {
- // ** Hunter Safety Check for Capture **
- if (pieceType === 4 || pieceType === 5) { // Is the capturing piece a Rook or Queen?
- if (!isHunterMoveSafe(toX, toY)) {
- console.log(`BasicBot: Hunter (${pieceType}) at ${x},${y} avoiding UNSAFE capture at ${toX},${toY}.`);
- continue; // Skip this specific capture attempt
- }
- }
-
- // If safe (or not a hunter), proceed with capture
- console.log(`BasicBot: Capture! Moving ${pieceType} from ${x},${y} to ${toX},${toY}`);
- send(new Uint16Array([x, y, toX, toY]));
- actionTakenThisTurn = true;
- break; // Exit inner loop (capture found)
- }
- }
- } catch (e) { /* Handle error */ }
- }
- if (actionTakenThisTurn) break; // Exit outer loop
- }
- if (actionTakenThisTurn) break; // Exit loops if standard capture made
- }
- if (actionTakenThisTurn) return;
-
-
- // --- Priority 3: Lone King Moves Towards Prioritized Neutral (If Safe) ---
- if (actionTakenThisTurn) return;
- const onlyKingLeft = isOnlyKingLeft();
- if (onlyKingLeft && myKings.length > 0) {
- const [kingX, kingY] = myKings[0];
- if (!isSquareAttacked(kingX, kingY)) { // Only if King is currently safe
- const nearestNeutral = findPrioritizedNearestNeutralPiece(kingX, kingY);
- if (nearestNeutral) {
- const [targetX, targetY] = nearestNeutral;
- const targetType = board[targetX]?.[targetY] || '?';
- try {
- const kingMoves = pieceMovesCache[`${kingX},${kingY}`] || generateLegalMoves(kingX, kingY, board, teams);
- // Use the modified findBestMoveTowards (though King is not type 2)
- const bestMove = findBestMoveTowards(6, kingX, kingY, kingMoves, targetX, targetY);
- if (bestMove) {
- const [toX, toY] = bestMove;
- if (!isSquareAttacked(toX, toY)) { // Check destination safety
- console.log(`BasicBot: Lone King moving from ${kingX},${kingY} to SAFE square ${toX},${toY} towards neutral type ${targetType}.`);
- send(new Uint16Array([kingX, kingY, toX, toY]));
- actionTakenThisTurn = true;
- } else { /* console.log(`BasicBot: Lone King best move to ${toX},${toY} is UNSAFE. Holding.`); */ }
- }
- } catch (e) { console.error(`BasicBot: Error processing Lone King move towards neutral:`, e); }
- }
- }
- }
- if (actionTakenThisTurn) return;
-
- // --- Priority 4: Idle Pieces Move Towards Prioritized Neutral ---
- if (actionTakenThisTurn) return;
- if (onlyKingLeft) return; // Don't run if only king left
-
- for (let x = 0; x < boardW; x++) {
- for (let y = 0; y < boardH; y++) {
- if (teams[x]?.[y] === selfId && board[x][y] !== 0 && board[x][y] !== 6) { // Our piece, not King
- const pieceType = board[x][y];
- try {
- const legalMoves = pieceMovesCache[`${x},${y}`] || generateLegalMoves(x, y, board, teams);
- if (!pieceMovesCache[`${x},${y}`]) pieceMovesCache[`${x},${y}`] = legalMoves;
-
- if (isPieceIdle(x, y, legalMoves)) { // Check if idle first
- const nearestNeutral = findPrioritizedNearestNeutralPiece(x, y);
- if (nearestNeutral) {
- const [targetX, targetY] = nearestNeutral;
- const targetType = board[targetX]?.[targetY] || '?';
- // Use the modified findBestMoveTowards
- const bestMove = findBestMoveTowards(pieceType, x, y, legalMoves, targetX, targetY);
- if (bestMove) {
- const [toX, toY] = bestMove;
-
- // ** Hunter Safety Check for Idle Move **
- if (pieceType === 4 || pieceType === 5) { // Is the moving piece a Rook or Queen?
- if (!isHunterMoveSafe(toX, toY)) {
- console.log(`BasicBot: Hunter (${pieceType}) at ${x},${y} avoiding move to UNSAFE idle target square ${toX},${toY}.`);
- continue; // Skip moving this piece this turn, maybe another idle piece can move.
- }
- }
-
- // If safe (or not a hunter), proceed with move
- console.log(`BasicBot: Moving idle piece ${pieceType} from ${x},${y} to ${toX},${toY} towards neutral type ${targetType}.`);
- send(new Uint16Array([x, y, toX, toY]));
- actionTakenThisTurn = true;
- break; // Exit inner loop (idle piece moved)
- }
- }
- }
- } catch (e) { /* Handle error */ }
- }
- if (actionTakenThisTurn) break; // Exit outer loop
- }
- if (actionTakenThisTurn) break; // Exit loops if idle piece moved
- }
-
- // if (!actionTakenThisTurn) console.log("BasicBot: No actions taken this cycle.");
- }
-
-
- // --- startBasicBot and stopBasicBot functions remain the same ---
-
- /**
- * Starts the bot's periodic execution.
- */
- function startBasicBot(intervalMs = 300) {
- if (basicBotInterval) { console.log("BasicBot: Already running."); return; }
- if (typeof selfId === 'undefined' || selfId === -1) {
- console.warn("BasicBot: Cannot start, game not fully initialized (selfId unknown). Try again shortly.");
- setTimeout(() => startBasicBot(intervalMs), 2000); return;
- }
- console.log(`BasicBot: Starting bot with ${intervalMs}ms interval. R/Q safety enabled.`);
- if (basicBotInterval) clearInterval(basicBotInterval);
- basicBotInterval = setInterval(basicBotTurnLogic, intervalMs);
- }
-
- /**
- * Stops the bot's periodic execution.
- */
- function stopBasicBot() {
- if (basicBotInterval) { console.log("BasicBot: Stopping bot."); clearInterval(basicBotInterval); basicBotInterval = null; }
- else { console.log("BasicBot: Bot is not running."); }
- }
-
- startBasicBot()