using System; using System.Collections.Generic; using System.Linq; // Color Puzzle Solver (fix): solves all 4 rows reliably. // Keeps desync handling + auto direction flip detection. 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_DELAY_MS = 950; 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; } } int[,] ReadGridFromRoom() { int[,] g = 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 r = y - GRID_Y_MIN; int c = x - GRID_X_MIN; g[r, c] = GetState(item); found[r, c] = true; } int cnt = 0; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (found[r, c]) cnt++; return cnt == 16 ? g : null; } string GridDump(int[,] g) { return string.Join(" | ", Enumerable.Range(0, 4).Select(r => $"R{r}[{g[r,0]},{g[r,1]},{g[r,2]},{g[r,3]}]")); } void SimMove(int[,] g, int m) { if (m < 4) { int r = m; int t = g[r, 0]; g[r, 0] = g[r, 1]; g[r, 1] = g[r, 2]; g[r, 2] = g[r, 3]; g[r, 3] = t; } else if (m < 8) { int r = m - 4; int t = g[r, 3]; g[r, 3] = g[r, 2]; g[r, 2] = g[r, 1]; g[r, 1] = g[r, 0]; g[r, 0] = t; } else if (m < 12) { int c = m - 8; int t = g[0, c]; g[0, c] = g[1, c]; g[1, c] = g[2, c]; g[2, c] = g[3, c]; g[3, c] = t; } else { int c = m - 12; int t = g[3, c]; g[3, c] = g[2, c]; g[2, c] = g[1, c]; g[1, c] = g[0, c]; g[0, c] = t; } } List SolveLayerByLayer(int[,] srcGrid, int[] tgtRows) { int[,] g = new int[4, 4]; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) g[r, c] = srcGrid[r, c]; var moves = new List(); void Do(int m) { moves.Add(m); SimMove(g, m); } void DoRowRight(int r, int times) { times = ((times % 4) + 4) % 4; if (times == 3) { Do(r); return; } for (int i = 0; i < times; i++) Do(r + 4); } void DoRowLeft(int r, int times) { times = ((times % 4) + 4) % 4; if (times == 3) { Do(r + 4); return; } for (int i = 0; i < times; i++) Do(r); } void DoColUp(int c, int times) { times = ((times % 4) + 4) % 4; if (times == 3) { Do(c + 12); return; } for (int i = 0; i < times; i++) Do(c + 8); } void DoColDown(int c, int times) { times = ((times % 4) + 4) % 4; if (times == 3) { Do(c + 8); return; } for (int i = 0; i < times; i++) Do(c + 12); } int C0 = tgtRows[0]; for (int c = 0; c < 4; c++) { if (g[0, c] == C0) continue; int foundRow = -1; for (int r = 1; r <= 3; r++) if (g[r, c] == C0) { foundRow = r; break; } if (foundRow >= 0) { DoColUp(c, foundRow); } else { bool found = false; for (int r = 1; r <= 3 && !found; r++) for (int c2 = 0; c2 < 4 && !found; c2++) if (c2 != c && g[r, c2] == C0) { DoRowRight(r, (c - c2 + 4) % 4); DoColUp(c, r); found = true; } if (!found) return null; } } int C1 = tgtRows[1]; for (int pass = 0; pass < 8; pass++) { int colW = -1; for (int c = 0; c < 4; c++) if (g[1, c] != C1) { colW = c; break; } if (colW < 0) break; int srcR = -1, srcC = -1; for (int r = 2; r <= 3 && srcR < 0; r++) for (int c = 0; c < 4; c++) if (g[r, c] == C1) { srcR = r; srcC = c; break; } if (srcR < 0) return null; if (srcC != colW) DoRowRight(srcR, (colW - srcC + 4) % 4); int k2 = srcR - 1; DoRowLeft(1, 1); DoColUp(colW, k2); DoRowRight(1, 1); DoColDown(colW, k2); } int C2 = tgtRows[2]; for (int pass = 0; pass < 8; pass++) { int colW = -1; for (int c = 0; c < 4; c++) if (g[2, c] != C2) { colW = c; break; } if (colW < 0) break; int srcC = -1; for (int c = 0; c < 4; c++) if (g[3, c] == C2) { srcC = c; break; } if (srcC < 0) return null; if (srcC != colW) DoRowRight(3, (colW - srcC + 4) % 4); DoRowLeft(2, 1); DoColUp(colW, 1); DoRowRight(2, 1); DoColDown(colW, 1); } for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (g[r, c] != tgtRows[r]) return null; bool changed = true; while (changed) { changed = false; for (int i = 0; i < moves.Count - 1; i++) { int a = moves[i], b = moves[i + 1]; bool cancel = false; if (a < 4 && b == a + 4) cancel = true; if (a >= 4 && a < 8 && b == a - 4) cancel = true; if (a >= 8 && a < 12 && b == a + 4) cancel = true; if (a >= 12 && b == a - 4) cancel = true; if (cancel) { moves.RemoveAt(i + 1); moves.RemoveAt(i); changed = true; break; } } } return moves; } bool IsSolvedForTarget(int[,] g, int[] targetRows) { for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (g[r, c] != targetRows[r]) return false; return true; } bool TryReadTargetRows(out int[] targetRows) { targetRows = null; var byX = new Dictionary(); 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; if (y < GRID_Y_MIN || y > GRID_Y_MAX) continue; if (x >= GRID_X_MIN && x <= GRID_X_MAX) continue; if (!byX.ContainsKey(x)) byX[x] = (new int[4], new bool[4], 0); var entry = byX[x]; int r = y - GRID_Y_MIN; if (!entry.found[r]) { entry.states[r] = GetState(item); entry.found[r] = true; entry.count++; byX[x] = entry; } } if (byX.Count == 0) return false; var best = byX .Select(kvp => new { X = kvp.Key, States = kvp.Value.states, Count = kvp.Value.count, Dist = kvp.Key < GRID_X_MIN ? (GRID_X_MIN - kvp.Key) : (kvp.Key - GRID_X_MAX) }) .OrderByDescending(x => x.Count) .ThenBy(x => x.Dist) .First(); if (best.Count < 4) return false; targetRows = new int[4]; for (int r = 0; r < 4; r++) targetRows[r] = best.States[r]; return true; } 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; } uint ApplyMoveBits(uint s, int m) { if (m < 4) { int sh = m * 8; uint row = (s >> sh) & 0xFFu; uint rot = ((row >> 2) | (row << 6)) & 0xFFu; return (s & ~(0xFFu << sh)) | (rot << sh); } if (m < 8) { int sh = (m - 4) * 8; uint row = (s >> sh) & 0xFFu; uint rot = ((row << 2) | (row >> 6)) & 0xFFu; return (s & ~(0xFFu << sh)) | (rot << sh); } if (m < 12) { int b = (m - 8) * 2; uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, 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)); } { int b = (m - 12) * 2; uint v0 = (s >> b) & 3u, v1 = (s >> (b + 8)) & 3u, v2 = (s >> (b + 16)) & 3u, 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)); } } 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[,] ApplyMovesToCopy(int[,] src, List moves) { int[,] g = new int[4, 4]; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) g[r, c] = src[r, c]; foreach (int m in moves) SimMove(g, m); return g; } Log("=== Color Puzzle Solver (fix all rows) ==="); var grid = ReadGridFromRoom(); if (grid == null) { Log("ERROR: Could not read full 4x4 grid."); return; } Log($"Start: {GridDump(grid)}"); var arrowIds = new Dictionary(); foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != ARROW_KIND) continue; int x = item.Location.X, 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; } if (arrowIds.Count < 16) { Log($"ERROR: Missing arrows ({arrowIds.Count}/16)."); return; } int[] detectedTarget; if (!TryReadTargetRows(out detectedTarget)) { detectedTarget = new[] { 1, 2, 3, 0 }; Log("WARN: Target tiles not fully detected, using fallback target rows 1,2,3,0."); } var candidateTargets = new List(); void AddTargetCandidate(int[] t) { if (t == null || t.Length != 4) return; if (!candidateTargets.Any(x => x[0] == t[0] && x[1] == t[1] && x[2] == t[2] && x[3] == t[3])) candidateTargets.Add(new[] { t[0], t[1], t[2], t[3] }); } AddTargetCandidate(detectedTarget); AddTargetCandidate(new[] { detectedTarget[3], detectedTarget[2], detectedTarget[1], detectedTarget[0] }); AddTargetCandidate(new[] { 1, 2, 3, 0 }); AddTargetCandidate(new[] { 0, 3, 2, 1 }); List solution = null; int[] targetRows = null; foreach (var candidate in candidateTargets) { var s = SolveLayerByLayer(grid, candidate); if (s == null || s.Count == 0) continue; var check = ApplyMovesToCopy(grid, s); if (!IsSolvedForTarget(check, candidate)) continue; if (solution == null || s.Count < solution.Count) { solution = s; targetRows = candidate; } } if (solution == null || targetRows == null) { Log("ERROR: Could not build a valid full 4-row plan."); return; } Log($"Target rows chosen: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}"); Log($"Plan length: {solution.Count} moves"); bool[] rowFlip = new bool[4]; bool[] colFlip = new bool[4]; string KeyForMove(int m) { if (m < 4) { int r = m; return rowFlip[r] ? $"right_{r}" : $"left_{r}"; } if (m < 8) { int r = m - 4; return rowFlip[r] ? $"left_{r}" : $"right_{r}"; } if (m < 12) { int c = m - 8; return colFlip[c] ? $"down_{c}" : $"up_{c}"; } int cc = m - 12; return colFlip[cc] ? $"up_{cc}" : $"down_{cc}"; } int[,] tgtGrid = new int[4, 4]; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) tgtGrid[r, c] = targetRows[r]; uint goalState = EncodeGrid(tgtGrid); uint currentState = EncodeGrid(grid); int moveIdx = 0; int retries = 0; const int MAX_RETRIES = 3; while (moveIdx < solution.Count) { if (currentState == goalState) { Log("=== Solved: all 4 rows complete ==="); return; } int move = solution[moveIdx]; uint expected = ApplyMoveBits(currentState, move); string key = KeyForMove(move); if (!arrowIds.ContainsKey(key)) { Log($"ERROR: Arrow '{key}' not found."); return; } long id = arrowIds[key]; Log($"[{moveIdx + 1}/{solution.Count}] {MoveName(move)} via {key}"); Send(Out["ClickFurni"], (int)id, 0); Delay(CLICK_DELAY_MS); var newGrid = ReadGridFromRoom(); if (newGrid == null) { Log("WARN: Grid read failed, retry..."); Delay(400); continue; } uint afterState = EncodeGrid(newGrid); if (afterState == expected) { currentState = afterState; moveIdx++; retries = 0; continue; } uint invExpected = ApplyMoveBits(currentState, InverseMove(move)); if (afterState == invExpected) { if (move < 8) { int r = move < 4 ? move : move - 4; rowFlip[r] = !rowFlip[r]; Log($"Auto-fix: Row {r} direction flipped."); } else { int c = move < 12 ? move - 8 : move - 12; colFlip[c] = !colFlip[c]; Log($"Auto-fix: Col {c} direction flipped."); } grid = newGrid; currentState = afterState; var replan = SolveLayerByLayer(grid, targetRows); if (replan == null) { Log("ERROR: Replan failed after direction flip."); return; } solution = replan; moveIdx = 0; retries = 0; Log($"Replan: {solution.Count} moves"); continue; } if (afterState == currentState) { retries++; if (retries >= MAX_RETRIES) { Log("WARN: Click had no effect multiple times, replan."); grid = newGrid; var replan = SolveLayerByLayer(grid, targetRows); if (replan == null) { Log("ERROR: Replan failed after no-effect clicks."); return; } solution = replan; moveIdx = 0; retries = 0; } else { Log("Click no effect, retrying..."); Delay(300); } continue; } Log($"Desync detected. New grid: {GridDump(newGrid)}"); grid = newGrid; currentState = afterState; if (IsSolvedForTarget(grid, targetRows)) { Log("=== Solved: all 4 rows complete ==="); return; } var desyncReplan = SolveLayerByLayer(grid, targetRows); if (desyncReplan == null) { Log("ERROR: Replan failed after desync."); return; } solution = desyncReplan; moveIdx = 0; retries = 0; Log($"Replan after desync: {solution.Count} moves"); } if (currentState == goalState) Log("=== Solved: all 4 rows complete ==="); else Log("Moves done. Please check if room state changed externally.");