using System; using System.Collections.Generic; using System.Linq; // Color Puzzle Solver v2 // - Auto calibration of arrow -> move mapping // - Waits for real state change after every click const int TILE_KIND = 3696; const int ARROW_KIND = 17851; const int GRID_X_MIN = 36; const int GRID_X_MAX = 39; const int GRID_Y_MIN = 27; const int GRID_Y_MAX = 30; const int CLICK_SETTLE_DELAY_MS = 250; const int WAIT_CHANGE_TIMEOUT_MS = 6000; const int WAIT_CHANGE_POLL_MS = 120; const int MAX_STEPS = 140; const int BFS_MAX_NODES = 4_000_000; const int IDA_MAX_SEC = 12; const bool AUTO_QUEUE_START = true; const long TRANSPORTER_ID = 759030883; const int QUEUE_CLICK_INTERVAL_MS = 5000; const int WAIT_PUZZLE_POLL_MS = 250; const int WAIT_PUZZLE_LOG_MS = 5000; const bool REQUIRE_SELF_IN_PLAYZONE = true; const int PLAY_X_MIN = 34; const int PLAY_X_MAX = 41; const int PLAY_Y_MIN = 26; const int PLAY_Y_MAX = 31; const bool REQUIRE_SELF_MIN_Z = true; const double SELF_MIN_Z = 17.0; const bool REQUIRE_PLAYER_QUEUE_CLEAR = true; const string WATCH_PLAYER_NAME = "gracie"; const int WATCH_QUEUE_X = 33; const int WATCH_QUEUE_Y = 19; const double WATCH_QUEUE_Z = 4.5; const double WATCH_QUEUE_Z_TOL = 1.0; int GetState(dynamic item) { try { return int.Parse(item.State?.ToString() ?? "0"); } catch { return 0; } } int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } } uint EncodeGrid(int[,] g) { uint s = 0; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) s |= ((uint)(g[r, c] & 3)) << (2 * (r * 4 + c)); return s; } bool TryReadGrid(out uint state, out string dump) { int[,] grid = new int[4, 4]; bool[,] found = new bool[4, 4]; foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != TILE_KIND) continue; int x = item.Location.X; int y = item.Location.Y; double z = item.Location.Z; if (x < GRID_X_MIN || x > GRID_X_MAX) continue; if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue; if (z < 18.4) continue; int row = y - GRID_Y_MIN; int col = x - GRID_X_MIN; grid[row, col] = GetState(item); found[row, col] = true; } int cnt = 0; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (found[r, c]) cnt++; if (cnt < 16) { state = 0; dump = ""; return false; } state = EncodeGrid(grid); dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r => $"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]")); return true; } uint RowLeft(uint s, int r) { int sh = r * 8; uint row = (s >> sh) & 0xFFu; uint rot = ((row >> 2) | (row << 6)) & 0xFFu; return (s & ~(0xFFu << sh)) | (rot << sh); } uint RowRight(uint s, int r) { int sh = r * 8; uint row = (s >> sh) & 0xFFu; uint rot = ((row << 2) | (row >> 6)) & 0xFFu; return (s & ~(0xFFu << sh)) | (rot << sh); } uint ColUp(uint s, int c) { int b = c * 2; uint v0 = (s >> b) & 3u; uint v1 = (s >> (b + 8)) & 3u; uint v2 = (s >> (b + 16)) & 3u; uint v3 = (s >> (b + 24)) & 3u; uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24)); return (s & mask) | (v1 << b) | (v2 << (b + 8)) | (v3 << (b + 16)) | (v0 << (b + 24)); } uint ColDown(uint s, int c) { int b = c * 2; uint v0 = (s >> b) & 3u; uint v1 = (s >> (b + 8)) & 3u; uint v2 = (s >> (b + 16)) & 3u; uint v3 = (s >> (b + 24)) & 3u; uint mask = ~(3u << b | 3u << (b + 8) | 3u << (b + 16) | 3u << (b + 24)); return (s & mask) | (v3 << b) | (v0 << (b + 8)) | (v1 << (b + 16)) | (v2 << (b + 24)); } uint ApplyMove(uint s, int m) { if (m < 4) return RowLeft(s, m); if (m < 8) return RowRight(s, m - 4); if (m < 12) return ColUp(s, m - 8); return ColDown(s, m - 12); } int InverseMove(int m) { if (m < 4) return m + 4; if (m < 8) return m - 4; if (m < 12) return m + 4; return m - 4; } string MoveName(int m) { if (m < 4) return $"Row{m} LEFT"; if (m < 8) return $"Row{m - 4} RIGHT"; if (m < 12) return $"Col{m - 8} UP"; return $"Col{m - 12} DOWN"; } int DetectMove(uint before, uint after) { int hit = -1; for (int m = 0; m < 16; m++) { if (ApplyMove(before, m) != after) continue; if (hit != -1) return -2; hit = m; } return hit; } List SolveBfs(uint start, uint goal) { if (start == goal) return new List(); var visited = new Dictionary(); var queue = new Queue(); visited[start] = (start, -1); queue.Enqueue(start); int nodes = 0; bool found = false; while (queue.Count > 0 && nodes < BFS_MAX_NODES) { uint cur = queue.Dequeue(); nodes++; for (int m = 0; m < 16; m++) { uint nxt = ApplyMove(cur, m); if (visited.ContainsKey(nxt)) continue; visited[nxt] = (cur, m); if (nxt == goal) { found = true; queue.Clear(); break; } queue.Enqueue(nxt); } } if (!found) return null; var sol = new List(); uint s = goal; while (s != start) { var p = visited[s]; sol.Add(p.move); s = p.parent; } sol.Reverse(); return sol; } List SolveIda(uint start, uint goal) { if (start == goal) return new List(); var t0 = DateTime.Now; int H(uint st) { int mis = 0; for (int i = 0; i < 16; i++) { int a = (int)((st >> (i * 2)) & 3u); int b = (int)((goal >> (i * 2)) & 3u); if (a != b) mis++; } return (mis + 3) / 4; } List best = null; bool timeout = false; bool Dfs(uint st, List path, int maxDepth) { if (timeout) return false; if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC) { timeout = true; return false; } if (st == goal) { best = new List(path); return true; } int h = H(st); if (path.Count + h > maxDepth) return false; int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1; for (int m = 0; m < 16; m++) { if (m == block) continue; path.Add(m); if (Dfs(ApplyMove(st, m), path, maxDepth)) return true; path.RemoveAt(path.Count - 1); if (timeout) return false; } return false; } int d0 = H(start); for (int d = d0; d <= 22 && !timeout; d++) { if (Dfs(start, new List(), d)) break; } return best; } List Solve(uint start, uint goal) { var bfs = SolveBfs(start, goal); if (bfs != null) return bfs; return SolveIda(start, goal); } bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter) { Send(Out["ClickFurni"], (int)furniId, 0); Delay(CLICK_SETTLE_DELAY_MS); int waited = 0; while (waited < WAIT_CHANGE_TIMEOUT_MS) { if (TryReadGrid(out after, out dumpAfter) && after != before) return true; Delay(WAIT_CHANGE_POLL_MS); waited += WAIT_CHANGE_POLL_MS; } after = before; dumpAfter = ""; return false; } Dictionary ReadArrowIds() { var arrowIds = new Dictionary(); foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != ARROW_KIND) continue; int x = item.Location.X; int y = item.Location.Y; if (y == GRID_Y_MIN - 1 && x >= GRID_X_MIN && x <= GRID_X_MAX) arrowIds[$"up_{x - GRID_X_MIN}"] = item.Id; else if (y == GRID_Y_MAX + 1 && x >= GRID_X_MIN && x <= GRID_X_MAX) arrowIds[$"down_{x - GRID_X_MIN}"] = item.Id; else if (x == GRID_X_MIN - 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX) arrowIds[$"left_{y - GRID_Y_MIN}"] = item.Id; else if (x == GRID_X_MAX + 1 && y >= GRID_Y_MIN && y <= GRID_Y_MAX) arrowIds[$"right_{y - GRID_Y_MIN}"] = item.Id; } return arrowIds; } bool IsSelfInPlayZone() { try { int x = Self.Location.X; int y = Self.Location.Y; double z = Self.Location.Z; bool inRect = x >= PLAY_X_MIN && x <= PLAY_X_MAX && y >= PLAY_Y_MIN && y <= PLAY_Y_MAX; bool inZ = !REQUIRE_SELF_MIN_Z || z >= SELF_MIN_Z; return inRect && inZ; } catch { return false; } } bool IsWatchedPlayerAtQueueSpot() { try { var u = Users.FirstOrDefault(x => x != null && x.Name != null && x.Name.Equals(WATCH_PLAYER_NAME, StringComparison.OrdinalIgnoreCase)); if (u == null || u.Location == null) return false; int x = u.Location.X; int y = u.Location.Y; double z = u.Location.Z; return x == WATCH_QUEUE_X && y == WATCH_QUEUE_Y && Math.Abs(z - WATCH_QUEUE_Z) <= WATCH_QUEUE_Z_TOL; } catch { return false; } } Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ==="); Dictionary arrowIds = null; uint current; string dumpNow; int sinceQueueClick = QUEUE_CLICK_INTERVAL_MS; int sinceLog = WAIT_PUZZLE_LOG_MS; while (true) { bool hasGrid = TryReadGrid(out current, out dumpNow); var probeArrows = ReadArrowIds(); bool hasArrows = probeArrows.Count == 16; bool inPlayZone = !REQUIRE_SELF_IN_PLAYZONE || IsSelfInPlayZone(); bool queueClear = !REQUIRE_PLAYER_QUEUE_CLEAR || !IsWatchedPlayerAtQueueSpot(); if (hasGrid && hasArrows && inPlayZone && queueClear) { arrowIds = probeArrows; break; } if (AUTO_QUEUE_START && sinceQueueClick >= QUEUE_CLICK_INTERVAL_MS) { Send(Out["ClickFurni"], (int)TRANSPORTER_ID, 0); Log($"Queue: Klick Transporter {TRANSPORTER_ID}..."); sinceQueueClick = 0; } if (sinceLog >= WAIT_PUZZLE_LOG_MS) { string selfPos = "?"; try { selfPos = $"{Self.Location.X},{Self.Location.Y},{Self.Location.Z:F2}"; } catch { } Log($"Warte auf Spielstart... Grid={(hasGrid ? "ok" : "no")}, Pfeile={probeArrows.Count}/16, InZone={(inPlayZone ? "yes" : "no")}, QueueClear={(queueClear ? "yes" : "no")}, Self={selfPos}"); sinceLog = 0; } Delay(WAIT_PUZZLE_POLL_MS); sinceQueueClick += WAIT_PUZZLE_POLL_MS; sinceLog += WAIT_PUZZLE_POLL_MS; } Log("Puzzle erkannt. Starte Solver..."); Log($"Pfeile: {arrowIds.Count}/16"); int[] targetRows = new int[4]; bool targetFound = false; foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != TILE_KIND) continue; if (item.Location.X != 41) continue; int y = item.Location.Y; if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue; targetRows[y - GRID_Y_MIN] = GetState(item); targetFound = true; } if (!targetFound) targetRows = new[] { 1, 2, 3, 0 }; int[,] tgt = new int[4, 4]; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) tgt[r, c] = targetRows[r]; uint goal = EncodeGrid(tgt); Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}"); Log($"Start: {dumpNow}"); var moveToKey = new Dictionary(); var keyToMove = new Dictionary(); var allKeys = arrowIds.Keys.OrderBy(k => k).ToList(); for (int step = 1; step <= MAX_STEPS; step++) { if (current == goal) { Log("=== Geloest: alle 4 Reihen korrekt ==="); return; } var plan = Solve(current, goal); if (plan == null || plan.Count == 0) { Log("ERROR: Kein Plan vom aktuellen Zustand."); return; } int wanted = plan[0]; string key; bool probing = false; if (moveToKey.ContainsKey(wanted)) { key = moveToKey[wanted]; } else { key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k)); if (key == null) { key = allKeys[0]; } probing = true; } long id = arrowIds[key]; Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : "")); if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter)) { Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal."); continue; } int actual = DetectMove(current, after); if (actual >= 0) { moveToKey[actual] = key; keyToMove[key] = actual; if (actual != wanted) Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})"); } else if (actual == -1) { Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}"); } else { Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}"); } current = after; if (step % 10 == 0) Log($" Calib: {moveToKey.Count}/16 Moves gemappt"); } Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");