您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
14/04/2025, 16:54:06
// ==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()