using System; using System.Collections.Generic; using System.Linq; class Cell { public long Id; public int X; public int Y; public int State; public int Kind; public double Z; } const int SOL_MIN_X = 4; const int SOL_MAX_X = 9; const int SOL_MIN_Y = 1; const int SOL_MAX_Y = 8; const int PLAY_MIN_X = 8; const int PLAY_MAX_X = 13; const int PLAY_MIN_Y = 14; const int PLAY_MAX_Y = 21; const int SPAWN_X = 13; const int SPAWN_Y = 13; const int SPAWN_WAIT_MS = 180000; const int FORCED_CYCLE = 5; const bool ASSUME_START_ALL_ZERO = true; const int STEP_WAIT_MS = 420; const int MOVE_COMMAND_INTERVAL_MS = 70; const int MOVE_SETTLE_MS = 0; const int PERIODIC_SYNC_EVERY_STEPS = 12; const int PATH_BURST_MAX_STEPS = 10; const int MAX_STEPS = 5000; string K(int x, int y) => x + "," + y; int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } } int GetState(dynamic item) { try { return int.Parse(item.State?.ToString() ?? "0"); } catch { return 0; } } string GetNameSafe(dynamic item) { try { string n = item.GetName(); return string.IsNullOrWhiteSpace(n) ? "" : n; } catch { return ""; } } bool InRect(int x, int y, int minX, int maxX, int minY, int maxY) { return x >= minX && x <= maxX && y >= minY && y <= maxY; } bool InPlay(int x, int y) { return InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y); } void FullRoomScanLog() { var all = new List(); foreach (var it in FloorItems) { if (it == null) continue; all.Add(it); } Log("=== Full Room Scan ==="); Log($"FloorItems total: {all.Count}"); var byKind = all.GroupBy(x => GetKind(x)) .Select(g => new { Kind = g.Key, Count = g.Count(), States = string.Join(",", g.Select(x => GetState(x)).Distinct().OrderBy(x => x)), Name = g.Select(x => GetNameSafe(x)).FirstOrDefault() }) .OrderByDescending(x => x.Count) .Take(25) .ToList(); foreach (var k in byKind) Log($"Kind {k.Kind} x{k.Count} states[{k.States}] name={k.Name}"); } bool WaitForSpawn() { Log($"Waiting for round spawn on {SPAWN_X}:{SPAWN_Y}..."); int elapsed = 0; while (elapsed < SPAWN_WAIT_MS) { if (Self != null && Self.Location != null && Self.Location.X == SPAWN_X && Self.Location.Y == SPAWN_Y) { Log("Spawn detected, starting solver."); Delay(300); return true; } Delay(200); elapsed += 200; } Log("Spawn timeout. Starting anyway."); return false; } List CollectCellsInRect(int minX, int maxX, int minY, int maxY) { var raw = new List(); foreach (var it in FloorItems) { if (it == null) continue; int x = it.Location.X; int y = it.Location.Y; if (!InRect(x, y, minX, maxX, minY, maxY)) continue; raw.Add(new Cell { Id = it.Id, X = x, Y = y, State = GetState(it), Kind = GetKind(it), Z = it.Location.Z }); } if (raw.Count == 0) return new List(); int targetCount = (maxX - minX + 1) * (maxY - minY + 1); var bestKind = raw.GroupBy(c => c.Kind) .Select(g => new { Kind = g.Key, CoordCount = g.Select(c => K(c.X, c.Y)).Distinct().Count(), Count = g.Count() }) .OrderByDescending(x => x.CoordCount) .ThenByDescending(x => x.Count) .First(); var cellsOfKind = raw.Where(c => c.Kind == bestKind.Kind).ToList(); var bestPerCoord = new List(); foreach (var g in cellsOfKind.GroupBy(c => K(c.X, c.Y))) { var top = g.OrderByDescending(c => c.Z).First(); bestPerCoord.Add(top); } Log($"Rect X[{minX}-{maxX}] Y[{minY}-{maxY}] -> kind {bestKind.Kind}, coords {bestPerCoord.Count}/{targetCount}"); return bestPerCoord; } Dictionary IndexCells(List cells) { var d = new Dictionary(); foreach (var c in cells) d[K(c.X, c.Y)] = c; return d; } Dictionary ReadCurrentPlayStates(HashSet ids) { var d = new Dictionary(); foreach (var it in FloorItems) { if (it == null) continue; long id = it.Id; if (!ids.Contains(id)) continue; d[id] = GetState(it); } return d; } int Need(int current, int target, int cycle) { int d = (target - current) % cycle; if (d < 0) d += cycle; return d; } int Objective(Dictionary cur, Dictionary target, int cycle) { int sum = 0; foreach (var kv in target) { if (!cur.ContainsKey(kv.Key)) continue; sum += Need(cur[kv.Key], kv.Value, cycle); } return sum; } int HardObjectiveFromRoom(List playCells, Dictionary targetByPlayId, int cycle, int[] needs) { var ids = new HashSet(targetByPlayId.Keys); var cur = ReadCurrentPlayStates(ids); for (int i = 0; i < playCells.Count; i++) { long id = playCells[i].Id; int val = cur.ContainsKey(id) ? cur[id] : 0; needs[i] = Need(val, targetByPlayId[id], cycle); } return needs.Sum(); } int ApplyNeedStep(int[] needs, int idx, int cycle) { int d = needs[idx]; if (d > 0) { needs[idx] = d - 1; return -1; } needs[idx] = cycle - 1; return cycle - 1; } bool WaitUntilAt(int tx, int ty) { int elapsed = 0; while (elapsed < STEP_WAIT_MS) { if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty) return true; Delay(40); elapsed += 40; } return false; } List<(int x, int y)> Neigh4(int x, int y) { var list = new List<(int, int)> { (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1), (x + 1, y + 1), (x + 1, y - 1), (x - 1, y + 1), (x - 1, y - 1) }; return list.Where(p => InPlay(p.Item1, p.Item2)).ToList(); } int Dist(int x1, int y1, int x2, int y2) { return Math.Abs(x1 - x2) + Math.Abs(y1 - y2); } int ReadSelfX(int fallback) { try { return Self.Location.X; } catch { return fallback; } } int ReadSelfY(int fallback) { try { return Self.Location.Y; } catch { return fallback; } } DateTime _lastMoveCmd = DateTime.MinValue; void FastMove(int x, int y) { int since = (int)(DateTime.UtcNow - _lastMoveCmd).TotalMilliseconds; if (since < MOVE_COMMAND_INTERVAL_MS) Delay(MOVE_COMMAND_INTERVAL_MS - since); Move(x, y); _lastMoveCmd = DateTime.UtcNow; } (int nx, int ny)? NextStepToTarget(int sx, int sy, int tx, int ty) { (int x, int y) start = (sx, sy); (int x, int y) goal = (tx, ty); if (start == goal) return null; var q = new Queue<(int x, int y)>(); var vis = new HashSet(); var prev = new Dictionary(); q.Enqueue(start); vis.Add(K(start.x, start.y)); while (q.Count > 0) { var cur = q.Dequeue(); foreach (var n in Neigh4(cur.x, cur.y)) { string nk = K(n.x, n.y); if (vis.Contains(nk)) continue; vis.Add(nk); prev[nk] = cur; if (n == goal) { var node = goal; while (true) { var pk = K(node.x, node.y); var pnode = prev[pk]; if (pnode == start) return node; node = pnode; } } q.Enqueue(n); } } return null; } List<(int x, int y)> BuildPathToTarget(int sx, int sy, int tx, int ty) { (int x, int y) start = (sx, sy); (int x, int y) goal = (tx, ty); var empty = new List<(int x, int y)>(); if (start == goal) return empty; var q = new Queue<(int x, int y)>(); var vis = new HashSet(); var prev = new Dictionary(); q.Enqueue(start); vis.Add(K(start.x, start.y)); while (q.Count > 0) { var cur = q.Dequeue(); foreach (var n in Neigh4(cur.x, cur.y)) { string nk = K(n.x, n.y); if (vis.Contains(nk)) continue; vis.Add(nk); prev[nk] = cur; if (n == goal) { var rev = new List<(int x, int y)>(); var node = goal; while (node != start) { rev.Add(node); node = prev[K(node.x, node.y)]; } rev.Reverse(); return rev; } q.Enqueue(n); } } return empty; } Log("=== Color Pattern Walker Solver (fixed bounds) ==="); WaitForSpawn(); FullRoomScanLog(); var solCells = CollectCellsInRect(SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y); var playCells = CollectCellsInRect(PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y); int expectedSol = (SOL_MAX_X - SOL_MIN_X + 1) * (SOL_MAX_Y - SOL_MIN_Y + 1); int expectedPlay = (PLAY_MAX_X - PLAY_MIN_X + 1) * (PLAY_MAX_Y - PLAY_MIN_Y + 1); if (solCells.Count < expectedSol || playCells.Count < expectedPlay) { Log($"ERROR: Board incomplete. Solution {solCells.Count}/{expectedSol}, Play {playCells.Count}/{expectedPlay}"); return; } var solMap = IndexCells(solCells); var playMap = IndexCells(playCells); var targetByPlayId = new Dictionary(); foreach (var p in playCells) { int sx = p.X - 4; int sy = p.Y - 13; string sk = K(sx, sy); if (!solMap.ContainsKey(sk)) continue; targetByPlayId[p.Id] = solMap[sk].State; } if (targetByPlayId.Count != expectedPlay) { Log($"ERROR: Could not map all play cells to solution cells ({targetByPlayId.Count}/{expectedPlay})."); return; } var ids = new HashSet(targetByPlayId.Keys); var cur = new Dictionary(); if (ASSUME_START_ALL_ZERO) { foreach (var id in ids) cur[id] = 0; Log("Using round-start baseline: all play tiles = state 0."); } else { cur = ReadCurrentPlayStates(ids); if (cur.Count != expectedPlay) { Log($"ERROR: Could not read all current play states ({cur.Count}/{expectedPlay})."); return; } } int cycle = FORCED_CYCLE; if (cycle < 2) cycle = 5; Log($"State cycle: {cycle}"); var coordToIdx = new Dictionary(); var idToIdx = new Dictionary(); for (int i = 0; i < playCells.Count; i++) { var c = playCells[i]; coordToIdx[K(c.X, c.Y)] = i; idToIdx[c.Id] = i; } int[] needs = new int[playCells.Count]; for (int i = 0; i < playCells.Count; i++) { long id = playCells[i].Id; needs[i] = Need(cur[id], targetByPlayId[id], cycle); } int obj = needs.Sum(); Log($"Initial objective: {obj}"); if (obj == 0) { Log("Already solved."); return; } if (Self == null || Self.Location == null) { Log("ERROR: No self location."); return; } int cx = Self.Location.X; int cy = Self.Location.Y; if (!InPlay(cx, cy)) { var bestEntry = playCells .OrderBy(c => Dist(cx, cy, c.X, c.Y)) .First(); FastMove(bestEntry.X, bestEntry.Y); WaitUntilAt(bestEntry.X, bestEntry.Y); cur = ReadCurrentPlayStates(ids); for (int i = 0; i < playCells.Count; i++) { long id = playCells[i].Id; needs[i] = Need(cur[id], targetByPlayId[id], cycle); } cx = ReadSelfX(bestEntry.X); cy = ReadSelfY(bestEntry.Y); obj = needs.Sum(); Log($"After entry objective: {obj}"); } int stagnation = 0; int prevX = -999; int prevY = -999; int sinceResync = 0; var burstPath = new List<(int x, int y)>(); int burstIndex = 0; for (int step = 1; step <= MAX_STEPS; step++) { if (!InPlay(cx, cy)) { Log("WARN: Left play field unexpectedly, moving back."); var back = playCells.OrderBy(c => Dist(cx, cy, c.X, c.Y)).First(); FastMove(back.X, back.Y); WaitUntilAt(back.X, back.Y); cx = ReadSelfX(back.X); cy = ReadSelfY(back.Y); cur = ReadCurrentPlayStates(ids); for (int i = 0; i < playCells.Count; i++) { long id = playCells[i].Id; needs[i] = Need(cur[id], targetByPlayId[id], cycle); } obj = needs.Sum(); sinceResync = 0; burstPath.Clear(); burstIndex = 0; continue; } if (obj == 0) { obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs); int standNeed = 999; if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy))) standNeed = needs[coordToIdx[K(cx, cy)]]; if (obj == 0 && standNeed == 0) { Log("=== Done: bottom matches solution (hard check OK) ==="); return; } } var neighbors = Neigh4(cx, cy); if (neighbors.Count == 0) { Log("ERROR: No neighbors on play field."); return; } int unresolved = 0; for (int i = 0; i < needs.Length; i++) if (needs[i] > 0) unresolved++; if (burstIndex >= burstPath.Count) { var needy = playCells .Select(c => new { Cell = c, Need = needs[idToIdx[c.Id]], D = Dist(cx, cy, c.X, c.Y) }) .Where(x => x.Need > 0) .OrderByDescending(x => x.Need) .ThenBy(x => x.D) .FirstOrDefault(); if (needy != null) { var path = BuildPathToTarget(cx, cy, needy.Cell.X, needy.Cell.Y); if (path.Count > 0) { burstPath = path.Take(PATH_BURST_MAX_STEPS).ToList(); burstIndex = 0; } } if (burstIndex >= burstPath.Count) { var fallback = neighbors .Select(n => new { N = n, Need = needs[coordToIdx[K(n.x, n.y)]], Back = (n.x == prevX && n.y == prevY) ? 1 : 0 }) .OrderByDescending(x => x.Need) .ThenBy(x => x.Back) .First(); burstPath = new List<(int x, int y)> { fallback.N }; burstIndex = 0; } } (int x, int y) bestN = burstPath[burstIndex]; burstIndex++; int idxChosen = coordToIdx[K(bestN.x, bestN.y)]; if (needs[idxChosen] == 0 && unresolved <= 8) { stagnation++; } else { stagnation = 0; } if (stagnation >= 12) { burstPath.Clear(); burstIndex = 0; stagnation = 0; } prevX = cx; prevY = cy; int oldObj = obj; FastMove(bestN.x, bestN.y); if (MOVE_SETTLE_MS > 0) Delay(MOVE_SETTLE_MS); // Predictive advance: keep running without stop-go per step. cx = bestN.x; cy = bestN.y; if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy))) { int landedIdx = coordToIdx[K(cx, cy)]; obj += ApplyNeedStep(needs, landedIdx, cycle); } else { sinceResync = 20; } sinceResync++; if (sinceResync >= PERIODIC_SYNC_EVERY_STEPS || stagnation >= 8) { Delay(120); cx = ReadSelfX(cx); cy = ReadSelfY(cy); cur = ReadCurrentPlayStates(ids); for (int i = 0; i < playCells.Count; i++) { long id = playCells[i].Id; needs[i] = Need(cur[id], targetByPlayId[id], cycle); } obj = needs.Sum(); sinceResync = 0; } int newObj = obj; Log($"[{step}] ({cx},{cy}) objective {oldObj} -> {newObj}"); } obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs); int finalStandNeed = 999; if (Self != null && Self.Location != null) { int fx = ReadSelfX(-9999); int fy = ReadSelfY(-9999); if (InPlay(fx, fy) && coordToIdx.ContainsKey(K(fx, fy))) finalStandNeed = needs[coordToIdx[K(fx, fy)]]; } if (obj == 0 && finalStandNeed == 0) Log("=== Done: bottom matches solution (hard check OK) ==="); else Log($"Stopped after max steps. Remaining objective: {obj}");