using System; using System.Collections.Generic; using System.Linq; // ============================================================ // COLOR PUZZLE AUTO-SOLVER (Loopover 4x4) // Liest Grid + Ziel aus dem Raum, loest per BFS/IDA*, // klickt die Pfeil-Buttons automatisch. // ============================================================ 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 = 700; const int BFS_MAX_NODES = 8_000_000; const int IDA_MAX_SEC = 15; // Set true if arrows push tiles INTO the grid (opposite direction) const bool REVERSE_ARROWS = false; 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; } } Log("=== Color Puzzle Auto-Solver ==="); // ── 1. Read puzzle grid from room ────────────────────────── int[,] grid = new int[4, 4]; bool[,] gridFound = new bool[4, 4]; foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != TILE_KIND) continue; int x = item.Location.X, 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 col = x - GRID_X_MIN; int row = y - GRID_Y_MIN; grid[row, col] = GetState(item); gridFound[row, col] = true; } int foundCount = 0; for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (gridFound[r, c]) foundCount++; if (foundCount < 16) { Log($"ERROR: Nur {foundCount}/16 Grid-Tiles gefunden!"); Log("Bist du im richtigen Raum?"); return; } Log("Aktuelles Grid:"); for (int r = 0; r < 4; r++) Log($" Row {r}: [{grid[r,0]}, {grid[r,1]}, {grid[r,2]}, {grid[r,3]}]"); // ── 2. Define *fixed* target pattern (ignore room indicators) ────────── // Mapping from Scanner (Floor:3696): // State 1 = grün, State 2 = rot, State 3 = blau, State 0 = bunt (3‑Farben‑Tile) // Gewünschtes Endbild (von oben nach unten): // Row 0: alles grün (1) // Row 1: alles rot (2) // Row 2: alles blau (3) // Row 3: alles bunt (0) int[,] target = new int[4, 4]; for (int c = 0; c < 4; c++) { target[0, c] = 1; // grün target[1, c] = 2; // rot target[2, c] = 3; // blau target[3, c] = 0; // bunt } Log("Ziel-Grid (fest vorgegeben):"); for (int r = 0; r < 4; r++) Log($" Row {r}: [{target[r,0]}, {target[r,1]}, {target[r,2]}, {target[r,3]}]"); // ── 3. Read arrow button IDs ────────────────────────────── 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; } Log($"Arrow-Buttons gefunden: {arrowIds.Count}/16"); if (arrowIds.Count < 16) { Log("WARN: Nicht alle 16 Pfeile gefunden!"); foreach (var kv in arrowIds) Log($" {kv.Key} = {kv.Value}"); } // ── 4. State encoding (2 bits per cell, 32 bits total) ──── // Bits 0-1: grid[0,0], Bits 2-3: grid[0,1], ... Bits 30-31: grid[3,3] // Row r = bits [r*8 .. r*8+7] uint Encode(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; } // ── 5. Bit-manipulation move functions ──────────────────── // RowLeft: [c0,c1,c2,c3] → [c1,c2,c3,c0] = rotate byte RIGHT by 2 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); } // RowRight: [c0,c1,c2,c3] → [c3,c0,c1,c2] = rotate byte LEFT by 2 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); } // ColUp: [r0,r1,r2,r3] → [r1,r2,r3,r0] 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)); } // ColDown: [r0,r1,r2,r3] → [r3,r0,r1,r2] 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)); } // Move encoding: 0-3=RowLeft(0-3), 4-7=RowRight(0-3), 8-11=ColUp(0-3), 12-15=ColDown(0-3) 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"; } // ── 6. Solve ────────────────────────────────────────────── uint startState = Encode(grid); uint goalState = Encode(target); if (startState == goalState) { Log("Puzzle ist bereits geloest!"); return; } List solution = null; // ── 6a. BFS ─────────────────────────────────────────────── Log($"Starte BFS (max {BFS_MAX_NODES:N0} Nodes)..."); var startBfs = DateTime.Now; var visited = new Dictionary(); var queue = new Queue(); visited[startState] = (startState, -1); queue.Enqueue(startState); bool solved = false; int nodesExplored = 0; while (queue.Count > 0 && !solved && nodesExplored < BFS_MAX_NODES) { uint current = queue.Dequeue(); nodesExplored++; if (nodesExplored % 2_000_000 == 0) Log($" BFS: {nodesExplored:N0} States, Queue: {queue.Count:N0}"); for (int m = 0; m < 16; m++) { uint next = ApplyMove(current, m); if (visited.ContainsKey(next)) continue; visited[next] = (current, m); if (next == goalState) { solved = true; break; } queue.Enqueue(next); } } if (solved) { solution = new List(); uint s = goalState; while (s != startState) { var (parent, move) = visited[s]; solution.Add(move); s = parent; } solution.Reverse(); var bfsTime = (DateTime.Now - startBfs).TotalMilliseconds; Log($"BFS Loesung: {solution.Count} Moves in {bfsTime:F0}ms ({nodesExplored:N0} States)"); } else { Log($"BFS: Keine Loesung in {nodesExplored:N0} Nodes."); visited.Clear(); visited = null; queue.Clear(); queue = null; // ── 6b. IDA* Fallback ───────────────────────────────── Log($"Starte IDA* (max {IDA_MAX_SEC}s)..."); var startIda = DateTime.Now; int Heuristic(uint st) { int mis = 0; for (int i = 0; i < 16; i++) { int sv = (int)((st >> (i * 2)) & 3u); int gv = (int)((goalState >> (i * 2)) & 3u); if (sv != gv) mis++; } return (mis + 3) / 4; } List bestSol = null; int bestLen = 30; bool timeout = false; bool DFS(uint state, List moves, int maxDepth) { if (timeout) return false; if ((DateTime.Now - startIda).TotalSeconds > IDA_MAX_SEC) { timeout = true; return false; } if (state == goalState) { if (moves.Count < bestLen) { bestLen = moves.Count; bestSol = new List(moves); } return true; } int h = Heuristic(state); if (moves.Count + h > maxDepth) return false; if (moves.Count >= bestLen - 1) return false; int lastInv = moves.Count > 0 ? InverseMove(moves[moves.Count - 1]) : -1; bool found = false; for (int m = 0; m < 16; m++) { if (m == lastInv) continue; uint next = ApplyMove(state, m); moves.Add(m); if (DFS(next, moves, maxDepth)) found = true; moves.RemoveAt(moves.Count - 1); if (timeout) break; } return found; } int startH = Heuristic(startState); for (int depth = startH; depth <= 20 && !timeout; depth++) { Log($" IDA* Tiefe {depth}..."); DFS(startState, new List(), depth); if (bestSol != null) break; } if (bestSol != null) { solution = bestSol; var idaTime = (DateTime.Now - startIda).TotalMilliseconds; Log($"IDA* Loesung: {solution.Count} Moves in {idaTime:F0}ms"); } else { Log("ERROR: Keine Loesung gefunden!"); Log("Moegliche Gruende:"); Log(" - Puzzle-State hat sich geaendert"); Log(" - Target-Zuordnung ist falsch"); return; } } // ── 7. Show solution ────────────────────────────────────── Log("Loesungs-Schritte:"); for (int i = 0; i < solution.Count; i++) Log($" {i+1}. {MoveName(solution[i])}"); // ── 8. Execute moves via ClickFurni ────────────────────── Log("Fuehre Moves aus..."); foreach (int m in solution) { string dir; int idx; if (REVERSE_ARROWS) { // Reversed: solver says LEFT → click RIGHT arrow (push from right) if (m < 4) { dir = "right"; idx = m; } else if (m < 8) { dir = "left"; idx = m - 4; } else if (m < 12) { dir = "down"; idx = m - 8; } else { dir = "up"; idx = m - 12; } } else { // Normal: solver says LEFT → click LEFT arrow if (m < 4) { dir = "left"; idx = m; } else if (m < 8) { dir = "right"; idx = m - 4; } else if (m < 12) { dir = "up"; idx = m - 8; } else { dir = "down"; idx = m - 12; } } string key = $"{dir}_{idx}"; if (!arrowIds.ContainsKey(key)) { Log($"ERROR: Arrow '{key}' nicht gefunden!"); return; } long arrowId = arrowIds[key]; Log($" Click: {MoveName(m)} -> {key} (ID: {arrowId})"); Send(Out["ClickFurni"], (int)arrowId, 0); Delay(CLICK_DELAY); } // ── 9. Verify final grid ──────────────────────────────────── int[,] finalGrid = new int[4, 4]; bool[,] finalFound = new bool[4, 4]; foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != TILE_KIND) continue; int x = item.Location.X, 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 col = x - GRID_X_MIN; int row = y - GRID_Y_MIN; finalGrid[row, col] = GetState(item); finalFound[row, col] = true; } Log("Finales Grid nach Ausfuehrung:"); for (int r = 0; r < 4; r++) Log($" Row {r}: [{finalGrid[r,0]}, {finalGrid[r,1]}, {finalGrid[r,2]}, {finalGrid[r,3]}]"); Log("=== Puzzle geloest (internes Ziel erreicht) ===");