/// @name Snake Auto (Campaign) v5 using System; using System.Linq; using System.Collections.Generic; const long HEAD_ID = 769407982; const int TARGET_KIND = 3666; const int SNAKE_KIND = 5478; const int GAME_MIN_X = 37; const int GAME_MAX_X = 59; const int GAME_MIN_Y = 29; const int GAME_MAX_Y = 51; // Confirmed controls const int CTRL_UP = 769407385; const int CTRL_RIGHT = 769406820; const int CTRL_DOWN = 769407085; const int CTRL_LEFT = 769407116; static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" }; DateTime lastCmdTime = DateTime.MinValue; DateTime lastInfo = DateTime.MinValue; string lastSentDir = ""; int curDir = -1; // 0=UP 1=DOWN 2=LEFT 3=RIGHT (int x, int y) prevHead = (-1, -1); HashSet<(int x, int y)> fillVisited = new HashSet<(int, int)>(); Queue<(int x, int y)> fillQueue = new Queue<(int, int)>(); HashSet<(int x, int y)> pathVisited = new HashSet<(int, int)>(); Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>(); bool InBoard(int x, int y) => x >= GAME_MIN_X && x <= GAME_MAX_X && y >= GAME_MIN_Y && y <= GAME_MAX_Y; void Next(int x, int y, int d, out int nx, out int ny) { nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0); ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0); } int Opp(int d) => d ^ 1; int DirFromDelta(int dx, int dy) { if (Math.Abs(dx) >= Math.Abs(dy)) return dx < 0 ? 2 : 3; return dy < 0 ? 0 : 1; } int IdForDir(int d) => d == 0 ? CTRL_UP : d == 1 ? CTRL_DOWN : d == 2 ? CTRL_LEFT : CTRL_RIGHT; bool TryGetButtonPos(int id, out int x, out int y) { var b = GetFloorItem(id); if (b == null) { x = 0; y = 0; return false; } x = b.Location.X; y = b.Location.Y; return true; } void Cmd(int d) { string nd = DIRS[d]; if (nd == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 110) return; int id = IdForDir(d); if (TryGetButtonPos(id, out int bx, out int by)) Move(bx, by); Send(Out["ClickFurni"], id, 0); lastCmdTime = DateTime.Now; lastSentDir = nd; } bool Bad(int x, int y, HashSet<(int x, int y)> body) { if (!InBoard(x, y)) return true; return body.Contains((x, y)); } int Fill(int sx, int sy, HashSet<(int x, int y)> body, int cap) { if (Bad(sx, sy, body)) return 0; fillVisited.Clear(); fillQueue.Clear(); fillVisited.Add((sx, sy)); fillQueue.Enqueue((sx, sy)); int c = 0; while (fillQueue.Count > 0 && c < cap) { var p = fillQueue.Dequeue(); c++; for (int d = 0; d < 4; d++) { Next(p.x, p.y, d, out int nx, out int ny); if (!Bad(nx, ny, body) && fillVisited.Add((nx, ny))) fillQueue.Enqueue((nx, ny)); } } return c; } int Dist(int sx, int sy, int tx, int ty, HashSet<(int x, int y)> body, int cap) { if (sx == tx && sy == ty) return 0; pathVisited.Clear(); pathQueue.Clear(); pathVisited.Add((sx, sy)); pathQueue.Enqueue(((sx, sy), 0)); while (pathQueue.Count > 0) { var cur = pathQueue.Dequeue(); if (cur.dist >= cap) return 999; for (int d = 0; d < 4; d++) { Next(cur.pos.x, cur.pos.y, d, out int nx, out int ny); if (nx == tx && ny == ty) return cur.dist + 1; if (!Bad(nx, ny, body) && pathVisited.Add((nx, ny))) pathQueue.Enqueue(((nx, ny), cur.dist + 1)); } } return -1; } int Emergency((int x, int y) head, HashSet<(int x, int y)> body) { int bestDir = curDir >= 0 ? curDir : 0; int bestSpace = -1; for (int d = 0; d < 4; d++) { if (curDir >= 0 && d == Opp(curDir)) continue; Next(head.x, head.y, d, out int nx, out int ny); if (Bad(nx, ny, body)) continue; int space = Fill(nx, ny, body, 120); if (space > bestSpace) { bestSpace = space; bestDir = d; } } return bestDir; } int Decide((int x, int y) head, (int x, int y) target, HashSet<(int x, int y)> body) { int bestDir = curDir >= 0 ? curDir : 0; int bestScore = int.MinValue; for (int d = 0; d < 4; d++) { if (curDir >= 0 && d == Opp(curDir)) continue; Next(head.x, head.y, d, out int nx, out int ny); if (Bad(nx, ny, body)) continue; int space = Fill(nx, ny, body, 200); if (space < 10) continue; int dist = Dist(nx, ny, target.x, target.y, body, 200); int score = 0; if (nx == target.x && ny == target.y) score += 15000; if (dist > 0 && dist < 999) score += (240 - dist) * 8; else if (dist == -1) score -= 300; score += Math.Min(space, 120) * 4; if (curDir >= 0 && d == curDir) score += 2; if (score > bestScore) { bestScore = score; bestDir = d; } } return bestDir; } Log("Snake Auto (Campaign) v5 started"); Log($"Buttons: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}"); while (Run) { Delay(18); var headItem = GetFloorItem(HEAD_ID); if (headItem == null) { if ((DateTime.Now - lastInfo).TotalMilliseconds > 1500) { Log("waiting: head not visible"); lastInfo = DateTime.Now; } continue; } var head = (headItem.Location.X, headItem.Location.Y); if (!InBoard(head.Item1, head.Item2)) continue; if (prevHead.x != -1) { int dx = head.Item1 - prevHead.x; int dy = head.Item2 - prevHead.y; if (dx != 0 || dy != 0) curDir = DirFromDelta(dx, dy); } prevHead = head; var body = FloorItems .Where(i => i != null && i.Kind == SNAKE_KIND) .Where(i => InBoard(i.Location.X, i.Location.Y)) .Select(i => (i.Location.X, i.Location.Y)) .Where(p => !(p.Item1 == head.Item1 && p.Item2 == head.Item2)) .ToHashSet(); var targets = FloorItems .Where(i => i != null && i.Kind == TARGET_KIND) .Where(i => InBoard(i.Location.X, i.Location.Y)) .Select(i => (x: i.Location.X, y: i.Location.Y)) .ToList(); if (targets.Count == 0) { // No tile visible: stay safe, do not random reverse. int safe = Emergency(head, body); Cmd(safe); continue; } var target = targets .OrderBy(t => Math.Abs(t.x - head.Item1) + Math.Abs(t.y - head.Item2)) .First(); if (curDir >= 0) { Next(head.Item1, head.Item2, curDir, out int fx, out int fy); if (Bad(fx, fy, body)) { int emergency = Emergency(head, body); Cmd(emergency); continue; } } int best = Decide(head, target, body); Cmd(best); }