Greasy Fork 支持简体中文。

Automated moves

14/04/2025, 16:54:06

  1. // ==UserScript==
  2. // @name Automated moves
  3. // @namespace Violentmonkey Scripts
  4. // @match https://chess.ytdraws.win/*
  5. // @grant none
  6. // @version 1.0
  7. // @author -
  8. // @description 14/04/2025, 16:54:06
  9. // @license CC0
  10. // ==/UserScript==
  11.  
  12.  
  13. let basicBotInterval = null;
  14.  
  15. // --- Helper Functions (Existing + New) ---
  16.  
  17. /**
  18. * Calculates Manhattan distance between two points.
  19. * @param {number} x1
  20. * @param {number} y1
  21. * * @param {number} x2
  22. * @param {number} y2
  23. * @returns {number}
  24. */
  25. function manhattanDistance(x1, y1, x2, y2) {
  26. return Math.abs(x1 - x2) + Math.abs(y1 - y2);
  27. }
  28.  
  29. /**
  30. * Finds all coordinates of the player's pieces of a specific type.
  31. * @param {number} pieceType - The type of piece (1-6).
  32. * @returns {Array<Array<number>>} An array of [x, y] coordinates.
  33. */
  34. function findMyPiecesOfType(pieceType) {
  35. const pieces = [];
  36. if (typeof selfId === 'undefined' || selfId === -1) return pieces;
  37.  
  38. for (let x = 0; x < boardW; x++) {
  39. for (let y = 0; y < boardH; y++) {
  40. if (board[x][y] === pieceType && teams[x][y] === selfId) {
  41. pieces.push([x, y]);
  42. }
  43. }
  44. }
  45. return pieces;
  46. }
  47.  
  48. /**
  49. * Finds all coordinates of the player's King(s).
  50. * @returns {Array<Array<number>>} An array of [x, y] coordinates.
  51. */
  52. function findMyKings() {
  53. return findMyPiecesOfType(6); // 6 is King
  54. }
  55.  
  56. /**
  57. * Checks if the player only has King(s) left.
  58. * @returns {boolean} True if only Kings remain, false otherwise.
  59. */
  60. function isOnlyKingLeft() {
  61. if (typeof selfId === 'undefined' || selfId === -1) return false;
  62. for (let x = 0; x < boardW; x++) {
  63. for (let y = 0; y < boardH; y++) {
  64. // If we find a piece belonging to us that is NOT a King (type 6)
  65. if (teams[x][y] === selfId && board[x][y] !== 6 && board[x][y] !== 0) {
  66. return false; // Found a non-king piece
  67. }
  68. }
  69. }
  70. return true; // No non-king pieces found
  71. }
  72.  
  73. /**
  74. * Checks if a specific square is attacked by any enemy piece.
  75. * (Same as before - potentially inefficient)
  76. * @param {number} targetX - The x-coordinate of the square to check.
  77. * @param {number} targetY - The y-coordinate of the square to check.
  78. * @returns {boolean} True if the square is under attack, false otherwise.
  79. */
  80. function isSquareAttacked(targetX, targetY) {
  81. if (typeof selfId === 'undefined' || selfId === -1) return false;
  82. if (typeof generateLegalMoves === 'undefined') return false; // Safety check
  83.  
  84. for (let x = 0; x < boardW; x++) {
  85. for (let y = 0; y < boardH; y++) {
  86. if (board[x][y] !== 0 && teams[x][y] !== selfId && teams[x][y] !== 0) {
  87. try {
  88. const enemyMoves = generateLegalMoves(x, y, board, teams);
  89. for (const move of enemyMoves) {
  90. if (move[0] === targetX && move[1] === targetY) {
  91. return true;
  92. }
  93. }
  94. } catch (e) {
  95. // console.error(`Error generating moves for enemy at ${x},${y} when checking attack on ${targetX},${targetY}:`, e);
  96. }
  97. }
  98. }
  99. }
  100. return false;
  101. }
  102.  
  103. /**
  104. * Finds the coordinates of the nearest neutral (Team 0) piece.
  105. * @param {number} fromX - The starting X coordinate.
  106. * @param {number} fromY - The starting Y coordinate.
  107. * @returns {Array<number> | null} Coordinates [x, y] of the nearest neutral piece, or null if none found.
  108. */
  109. function findNearestNeutralPiece(fromX, fromY) {
  110. let nearestPos = null;
  111. let minDistance = Infinity;
  112.  
  113. for (let x = 0; x < boardW; x++) {
  114. for (let y = 0; y < boardH; y++) {
  115. // Team 0 is assumed to be neutral/white/unclaimed
  116. if (teams[x][y] === 0 && board[x][y] !== 0) {
  117. const dist = manhattanDistance(fromX, fromY, x, y);
  118. if (dist < minDistance) {
  119. minDistance = dist;
  120. nearestPos = [x, y];
  121. }
  122. }
  123. }
  124. }
  125. return nearestPos;
  126. }
  127.  
  128.  
  129. /**
  130. * Checks if a player's piece is "idle" (not attacking anything and not under attack).
  131. * @param {number} pieceX The piece's X coordinate.
  132. * @param {number} pieceY The piece's Y coordinate.
  133. * @param {Array<Array<number>>} legalMoves The pre-calculated legal moves for this piece.
  134. * @returns {boolean} True if the piece is idle, false otherwise.
  135. */
  136. function isPieceIdle(pieceX, pieceY, legalMoves) {
  137. // 1. Check if under attack
  138. if (isSquareAttacked(pieceX, pieceY)) {
  139. return false;
  140. }
  141.  
  142. // 2. Check if any legal move is a capture
  143. for (const move of legalMoves) {
  144. const [toX, toY] = move;
  145. // Is the destination occupied by an enemy (not empty, not ours, not neutral)?
  146. if (board[toX][toY] !== 0 && teams[toX][toY] !== selfId && teams[toX][toY] !== 0) {
  147. return false; // This piece can attack
  148. }
  149. }
  150.  
  151. // If not attacked and cannot attack, it's idle
  152. return true;
  153. }
  154.  
  155. /**
  156. * Finds the best move from a list of legal moves towards a target coordinate.
  157. * Handles Knights (type 2) separately to mitigate looping issues.
  158. * "Best" minimizes Manhattan distance, with tie-breaking for Knights.
  159. * @param {number} pieceType The type (1-6) of the moving piece.
  160. * @param {number} currentX The current X of the moving piece.
  161. * @param {number} currentY The current Y of the moving piece.
  162. * @param {Array<Array<number>>} legalMoves List of [toX, toY] moves.
  163. * @param {number} targetX Target X coordinate.
  164. * @param {number} targetY Target Y coordinate.
  165. * @returns {Array<number> | null} The best move [toX, toY] or null if no suitable legal moves.
  166. */
  167. function findBestMoveTowards(pieceType, currentX, currentY, legalMoves, targetX, targetY) {
  168. let bestMove = null;
  169. let minDistance = Infinity;
  170. let madeProgress = false; // Did any move reduce distance?
  171.  
  172. const currentTargetDist = manhattanDistance(currentX, currentY, targetX, targetY);
  173.  
  174. // --- Knight-Specific Logic (Type 2) ---
  175. if (pieceType === 2) {
  176. let potentialMoves = [];
  177. for (const move of legalMoves) {
  178. const [toX, toY] = move;
  179. // Skip moves onto own pieces
  180. if (teams[toX]?.[toY] === selfId) continue;
  181.  
  182. const newDist = manhattanDistance(toX, toY, targetX, targetY);
  183. potentialMoves.push({ move: move, dist: newDist });
  184. }
  185.  
  186. // Sort potential moves: prioritize smaller distance, then break ties
  187. potentialMoves.sort((a, b) => {
  188. if (a.dist !== b.dist) {
  189. return a.dist - b.dist; // Closer is better
  190. } else {
  191. // Tie-breaker: Prioritize move that reduces the LARGER axis difference more
  192. const [ax, ay] = a.move;
  193. const [bx, by] = b.move;
  194. const aDiffX = Math.abs(ax - targetX);
  195. const aDiffY = Math.abs(ay - targetY);
  196. const bDiffX = Math.abs(bx - targetX);
  197. const bDiffY = Math.abs(by - targetY);
  198. const currentDiffX = Math.abs(currentX - targetX);
  199. const currentDiffY = Math.abs(currentY - targetY);
  200.  
  201. if (currentDiffX > currentDiffY) { // Further in X direction
  202. return aDiffX - bDiffX; // Smaller X difference is better
  203. } else if (currentDiffY > currentDiffX) { // Further in Y direction
  204. return aDiffY - bDiffY; // Smaller Y difference is better
  205. } else {
  206. return 0; // No preference if equidistant on axes
  207. }
  208. }
  209. });
  210.  
  211. // Return the best valid move found
  212. if (potentialMoves.length > 0) {
  213. bestMove = potentialMoves[0].move;
  214. // Basic loop avoidance: if best move doesn't decrease distance, maybe pick 2nd best if it does?
  215. if(potentialMoves.length > 1 && potentialMoves[0].dist >= currentTargetDist && potentialMoves[1].dist < currentTargetDist) {
  216. bestMove = potentialMoves[1].move;
  217. // console.log(`Knight: Avoiding non-progress move, taking 2nd best.`);
  218. }
  219. }
  220. return bestMove;
  221.  
  222. } else {
  223. // --- Standard Logic (Non-Knights) ---
  224. for (const move of legalMoves) {
  225. const [toX, toY] = move;
  226. // Skip moves onto own pieces
  227. if (teams[toX]?.[toY] === selfId) continue;
  228.  
  229. const dist = manhattanDistance(toX, toY, targetX, targetY);
  230.  
  231. if (dist < minDistance) {
  232. minDistance = dist;
  233. bestMove = move;
  234. if (dist < currentTargetDist) madeProgress = true;
  235. }
  236. }
  237. // If the best move doesn't actually get closer, maybe reconsider?
  238. // For non-knights, usually direct path is fine, but this could be added if needed.
  239. // If best move doesn't make progress, but other moves *do*, prefer one that does?
  240. if(bestMove && !madeProgress) {
  241. let progressMove = null;
  242. let progressMinDist = Infinity;
  243. for (const move of legalMoves) {
  244. const [toX, toY] = move;
  245. if (teams[toX]?.[toY] === selfId) continue;
  246. const dist = manhattanDistance(toX, toY, targetX, targetY);
  247. if (dist < currentTargetDist && dist < progressMinDist) { // Found a move that makes progress
  248. progressMinDist = dist;
  249. progressMove = move;
  250. }
  251. }
  252. if(progressMove) {
  253. // console.log(`Non-Knight: Prioritizing progress move over non-progress best move.`);
  254. bestMove = progressMove;
  255. }
  256. }
  257.  
  258. return bestMove;
  259. }
  260. }
  261.  
  262. /**
  263. * Checks if moving a high-value piece (Rook/Queen) to a square is acceptably safe.
  264. * Currently just checks if the destination square is attacked.
  265. * @param {number} toX Destination X.
  266. * @param {number} toY Destination Y.
  267. * @returns {boolean} True if the move is considered safe enough, false otherwise.
  268. */
  269. function isHunterMoveSafe(toX, toY) {
  270. // For now, "safe enough" means the destination isn't directly attacked.
  271. // Could be expanded (e.g., allow if capturing equally valuable piece).
  272. return !isSquareAttacked(toX, toY);
  273. }
  274.  
  275. /**
  276. * Counts the number of Rooks (4) and Queens (5) the player controls.
  277. * @returns {number} The total count of Rooks and Queens.
  278. */
  279. function countMyHunterPieces() {
  280. let count = 0;
  281. if (typeof selfId === 'undefined' || selfId === -1) return 0;
  282.  
  283. for (let x = 0; x < boardW; x++) {
  284. for (let y = 0; y < boardH; y++) {
  285. if (teams[x][y] === selfId && (board[x][y] === 4 || board[x][y] === 5)) {
  286. count++;
  287. }
  288. }
  289. }
  290. return count;
  291. }
  292.  
  293. /**
  294. * Finds the coordinates of the nearest enemy King (piece type 6).
  295. * @param {number} fromX - The starting X coordinate.
  296. * @param {number} fromY - The starting Y coordinate.
  297. * @returns {Array<number> | null} Coordinates [x, y] of the nearest enemy king, or null if none found.
  298. */
  299. function findNearestEnemyKing(fromX, fromY) {
  300. let nearestPos = null;
  301. let minDistance = Infinity;
  302.  
  303. for (let x = 0; x < boardW; x++) {
  304. for (let y = 0; y < boardH; y++) {
  305. // Check for King (6) that is NOT ours and NOT neutral (0)
  306. if (board[x][y] === 6 && teams[x][y] !== selfId && teams[x][y] !== 0) {
  307. const dist = manhattanDistance(fromX, fromY, x, y);
  308. if (dist < minDistance) {
  309. minDistance = dist;
  310. nearestPos = [x, y];
  311. }
  312. }
  313. }
  314. }
  315. return nearestPos;
  316. }
  317.  
  318.  
  319. /**
  320. * Finds the coordinates of the nearest *prioritized* neutral (Team 0) piece.
  321. * Priority: Queens/Rooks > Other Pieces. Distance breaks ties within priority.
  322. * @param {number} fromX - The starting X coordinate.
  323. * @param {number} fromY - The starting Y coordinate.
  324. * @returns {Array<number> | null} Coordinates [x, y] of the best neutral target, or null if none found.
  325. */
  326. function findPrioritizedNearestNeutralPiece(fromX, fromY) {
  327. let bestHighPriorityTarget = { pos: null, dist: Infinity }; // Rooks (4), Queens (5)
  328. let bestLowPriorityTarget = { pos: null, dist: Infinity }; // Others (1, 2, 3, 6)
  329.  
  330. for (let x = 0; x < boardW; x++) {
  331. for (let y = 0; y < boardH; y++) {
  332. // Team 0 is assumed to be neutral/white/unclaimed
  333. if (teams[x][y] === 0 && board[x][y] !== 0) {
  334. const pieceType = board[x][y];
  335. const dist = manhattanDistance(fromX, fromY, x, y);
  336.  
  337. // Check if it's a high-priority target (Rook or Queen)
  338. if (pieceType === 4 || pieceType === 5) {
  339. if (dist < bestHighPriorityTarget.dist) {
  340. bestHighPriorityTarget.dist = dist;
  341. bestHighPriorityTarget.pos = [x, y];
  342. }
  343. } else { // Low priority target
  344. if (dist < bestLowPriorityTarget.dist) {
  345. bestLowPriorityTarget.dist = dist;
  346. bestLowPriorityTarget.pos = [x, y];
  347. }
  348. }
  349. }
  350. }
  351. }
  352.  
  353. // Return the high-priority target if found, otherwise the low-priority one
  354. if (bestHighPriorityTarget.pos) {
  355. // console.log(`Prioritized Neutral Target: High Prio [${bestHighPriorityTarget.pos}] at dist ${bestHighPriorityTarget.dist}`);
  356. return bestHighPriorityTarget.pos;
  357. } else if (bestLowPriorityTarget.pos) {
  358. // console.log(`Prioritized Neutral Target: Low Prio [${bestLowPriorityTarget.pos}] at dist ${bestLowPriorityTarget.dist}`);
  359. return bestLowPriorityTarget.pos;
  360. }
  361.  
  362. return null; // No neutral pieces found
  363. }
  364.  
  365.  
  366. /**
  367. * The main logic function for the bot, run periodically.
  368. */
  369. function basicBotTurnLogic() {
  370. // --- Pre-computation Checks ---
  371. 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') {
  372. return; // Not ready
  373. }
  374. if (curMoveCooldown > 220) {
  375. return; // On cooldown
  376. }
  377.  
  378. let actionTakenThisTurn = false; // Flag to prevent multiple actions
  379. const pieceMovesCache = {}; // Cache moves for potential reuse
  380.  
  381. // --- Priority 1: King Safety ---
  382. const myKings = findMyKings();
  383. for (const kingPos of myKings) {
  384. const [kingX, kingY] = kingPos;
  385. if (isSquareAttacked(kingX, kingY)) {
  386. console.log(`BasicBot: King at ${kingX},${kingY} is under attack! Finding safe move.`);
  387. try {
  388. const kingMoves = generateLegalMoves(kingX, kingY, board, teams);
  389. let safeMove = null;
  390. let desperationMove = null;
  391. for (const move of kingMoves) { // Find best escape
  392. const [toX, toY] = move;
  393. if (teams[toX]?.[toY] === selfId) continue;
  394. if (!isSquareAttacked(toX, toY)) { safeMove = move; break; }
  395. else if (!desperationMove) { desperationMove = move; }
  396. }
  397. if (safeMove) { // Execute safe move
  398. console.log(`BasicBot: Moving King from ${kingX},${kingY} to SAFE square ${safeMove[0]},${safeMove[1]}.`);
  399. send(new Uint16Array([kingX, kingY, safeMove[0], safeMove[1]]));
  400. actionTakenThisTurn = true;
  401. } else if (desperationMove) { // Execute desperation move
  402. console.warn(`BasicBot: No safe escape! Moving King from ${kingX},${kingY} to potentially UNSAFE square ${desperationMove[0]},${desperationMove[1]}.`);
  403. send(new Uint16Array([kingX, kingY, desperationMove[0], desperationMove[1]]));
  404. actionTakenThisTurn = true;
  405. } else { console.error(`BasicBot: King at ${kingX},${kingY} attacked, NO legal moves!`); }
  406. } catch (e) { console.error(`BasicBot: Error processing King escape @ ${kingX},${kingY}:`, e); }
  407. if (actionTakenThisTurn) return; // End turn
  408. }
  409. }
  410.  
  411. // --- Priority 2: Standard Capture Opportunity ---
  412. if (actionTakenThisTurn) return;
  413. for (let x = 0; x < boardW; x++) {
  414. for (let y = 0; y < boardH; y++) {
  415. if (teams[x]?.[y] === selfId && board[x][y] !== 0 && board[x][y] !== 6) { // Our piece, not King
  416. const pieceType = board[x][y];
  417. try {
  418. const legalMoves = pieceMovesCache[`${x},${y}`] || generateLegalMoves(x, y, board, teams);
  419. if (!pieceMovesCache[`${x},${y}`]) pieceMovesCache[`${x},${y}`] = legalMoves;
  420.  
  421. for (const move of legalMoves) {
  422. const [toX, toY] = move;
  423. const targetPiece = board[toX]?.[toY];
  424. const targetTeam = teams[toX]?.[toY];
  425.  
  426. // Is it a capture of an enemy piece?
  427. if (targetPiece !== 0 && targetTeam !== selfId && targetTeam !== 0) {
  428. // ** Hunter Safety Check for Capture **
  429. if (pieceType === 4 || pieceType === 5) { // Is the capturing piece a Rook or Queen?
  430. if (!isHunterMoveSafe(toX, toY)) {
  431. console.log(`BasicBot: Hunter (${pieceType}) at ${x},${y} avoiding UNSAFE capture at ${toX},${toY}.`);
  432. continue; // Skip this specific capture attempt
  433. }
  434. }
  435.  
  436. // If safe (or not a hunter), proceed with capture
  437. console.log(`BasicBot: Capture! Moving ${pieceType} from ${x},${y} to ${toX},${toY}`);
  438. send(new Uint16Array([x, y, toX, toY]));
  439. actionTakenThisTurn = true;
  440. break; // Exit inner loop (capture found)
  441. }
  442. }
  443. } catch (e) { /* Handle error */ }
  444. }
  445. if (actionTakenThisTurn) break; // Exit outer loop
  446. }
  447. if (actionTakenThisTurn) break; // Exit loops if standard capture made
  448. }
  449. if (actionTakenThisTurn) return;
  450.  
  451.  
  452. // --- Priority 3: Lone King Moves Towards Prioritized Neutral (If Safe) ---
  453. if (actionTakenThisTurn) return;
  454. const onlyKingLeft = isOnlyKingLeft();
  455. if (onlyKingLeft && myKings.length > 0) {
  456. const [kingX, kingY] = myKings[0];
  457. if (!isSquareAttacked(kingX, kingY)) { // Only if King is currently safe
  458. const nearestNeutral = findPrioritizedNearestNeutralPiece(kingX, kingY);
  459. if (nearestNeutral) {
  460. const [targetX, targetY] = nearestNeutral;
  461. const targetType = board[targetX]?.[targetY] || '?';
  462. try {
  463. const kingMoves = pieceMovesCache[`${kingX},${kingY}`] || generateLegalMoves(kingX, kingY, board, teams);
  464. // Use the modified findBestMoveTowards (though King is not type 2)
  465. const bestMove = findBestMoveTowards(6, kingX, kingY, kingMoves, targetX, targetY);
  466. if (bestMove) {
  467. const [toX, toY] = bestMove;
  468. if (!isSquareAttacked(toX, toY)) { // Check destination safety
  469. console.log(`BasicBot: Lone King moving from ${kingX},${kingY} to SAFE square ${toX},${toY} towards neutral type ${targetType}.`);
  470. send(new Uint16Array([kingX, kingY, toX, toY]));
  471. actionTakenThisTurn = true;
  472. } else { /* console.log(`BasicBot: Lone King best move to ${toX},${toY} is UNSAFE. Holding.`); */ }
  473. }
  474. } catch (e) { console.error(`BasicBot: Error processing Lone King move towards neutral:`, e); }
  475. }
  476. }
  477. }
  478. if (actionTakenThisTurn) return;
  479.  
  480. // --- Priority 4: Idle Pieces Move Towards Prioritized Neutral ---
  481. if (actionTakenThisTurn) return;
  482. if (onlyKingLeft) return; // Don't run if only king left
  483.  
  484. for (let x = 0; x < boardW; x++) {
  485. for (let y = 0; y < boardH; y++) {
  486. if (teams[x]?.[y] === selfId && board[x][y] !== 0 && board[x][y] !== 6) { // Our piece, not King
  487. const pieceType = board[x][y];
  488. try {
  489. const legalMoves = pieceMovesCache[`${x},${y}`] || generateLegalMoves(x, y, board, teams);
  490. if (!pieceMovesCache[`${x},${y}`]) pieceMovesCache[`${x},${y}`] = legalMoves;
  491.  
  492. if (isPieceIdle(x, y, legalMoves)) { // Check if idle first
  493. const nearestNeutral = findPrioritizedNearestNeutralPiece(x, y);
  494. if (nearestNeutral) {
  495. const [targetX, targetY] = nearestNeutral;
  496. const targetType = board[targetX]?.[targetY] || '?';
  497. // Use the modified findBestMoveTowards
  498. const bestMove = findBestMoveTowards(pieceType, x, y, legalMoves, targetX, targetY);
  499. if (bestMove) {
  500. const [toX, toY] = bestMove;
  501.  
  502. // ** Hunter Safety Check for Idle Move **
  503. if (pieceType === 4 || pieceType === 5) { // Is the moving piece a Rook or Queen?
  504. if (!isHunterMoveSafe(toX, toY)) {
  505. console.log(`BasicBot: Hunter (${pieceType}) at ${x},${y} avoiding move to UNSAFE idle target square ${toX},${toY}.`);
  506. continue; // Skip moving this piece this turn, maybe another idle piece can move.
  507. }
  508. }
  509.  
  510. // If safe (or not a hunter), proceed with move
  511. console.log(`BasicBot: Moving idle piece ${pieceType} from ${x},${y} to ${toX},${toY} towards neutral type ${targetType}.`);
  512. send(new Uint16Array([x, y, toX, toY]));
  513. actionTakenThisTurn = true;
  514. break; // Exit inner loop (idle piece moved)
  515. }
  516. }
  517. }
  518. } catch (e) { /* Handle error */ }
  519. }
  520. if (actionTakenThisTurn) break; // Exit outer loop
  521. }
  522. if (actionTakenThisTurn) break; // Exit loops if idle piece moved
  523. }
  524.  
  525. // if (!actionTakenThisTurn) console.log("BasicBot: No actions taken this cycle.");
  526. }
  527.  
  528.  
  529. // --- startBasicBot and stopBasicBot functions remain the same ---
  530.  
  531. /**
  532. * Starts the bot's periodic execution.
  533. */
  534. function startBasicBot(intervalMs = 300) {
  535. if (basicBotInterval) { console.log("BasicBot: Already running."); return; }
  536. if (typeof selfId === 'undefined' || selfId === -1) {
  537. console.warn("BasicBot: Cannot start, game not fully initialized (selfId unknown). Try again shortly.");
  538. setTimeout(() => startBasicBot(intervalMs), 2000); return;
  539. }
  540. console.log(`BasicBot: Starting bot with ${intervalMs}ms interval. R/Q safety enabled.`);
  541. if (basicBotInterval) clearInterval(basicBotInterval);
  542. basicBotInterval = setInterval(basicBotTurnLogic, intervalMs);
  543. }
  544.  
  545. /**
  546. * Stops the bot's periodic execution.
  547. */
  548. function stopBasicBot() {
  549. if (basicBotInterval) { console.log("BasicBot: Stopping bot."); clearInterval(basicBotInterval); basicBotInterval = null; }
  550. else { console.log("BasicBot: Bot is not running."); }
  551. }
  552.  
  553. startBasicBot()