using System; using System.Collections.Generic; using System.Linq; // ============================================================ // COLOR PUZZLE AUTO-SOLVER (Loopover 4x4) // Layer-by-layer Ansatz: loest ALLE 4 Reihen zuverlaessig. // Behaelt Anti-Desync + Auto-Flip-Erkennung bei. // ============================================================ 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; } } Log("=== Color Puzzle Auto-Solver (Layer-by-Layer) ==="); // ── 1. Read grid as array ───────────────────────────────── 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, 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; g[y - GRID_Y_MIN, x - GRID_X_MIN] = GetState(item); found[y - GRID_Y_MIN, x - GRID_X_MIN] = 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) return null; return g; } 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]}]")); } var grid = ReadGridFromRoom(); if (grid == null) { Log("ERROR: Konnte Grid nicht lesen (nicht alle 16 Tiles gefunden)."); return; } Log($"Start: {GridDump(grid)}"); // ── 2. Read arrows ──────────────────────────────────────── 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($"Pfeile: {arrowIds.Count}/16"); if (arrowIds.Count < 16) { Log("ERROR: Nicht alle Pfeile gefunden!"); return; } // ── 3. Read target ──────────────────────────────────────── 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 }; Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}"); // ── 4. Layer-by-Layer Solver ────────────────────────────── // Move encoding: 0-3=RowLeft(0-3), 4-7=RowRight(0-3), // 8-11=ColUp(0-3), 12-15=ColDown(0-3) 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"; } // Simulate a single move on a grid copy void SimMove(int[,] g, int m) { if (m < 4) { // RowLeft 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) { // RowRight 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) { // ColUp 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 { // ColDown 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) { // Work on a copy 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; } // 1x RowLeft is cheaper 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; } // 1x ColDown is cheaper 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); } // ── Phase 1: Solve Row 0 ───────────────────────────── // Use free column rotations + row shifts on rows 1-3. int C0 = tgtRows[0]; for (int c = 0; c < 4; c++) { if (g[0,c] == C0) continue; // Look in same column 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 { // Find C0 anywhere in rows 1-3 bool found = false; for (int r = 1; r <= 3 && !found; r++) for (int c2 = 0; c2 < 4 && !found; c2++) { if (c2 == c) continue; if (g[r,c2] == C0) { DoRowRight(r, (c - c2 + 4) % 4); DoColUp(c, r); found = true; } } if (!found) return null; // should never happen } } // ── Phase 2: Solve Row 1 (protecting Row 0) ────────── // Commutator [RowLeft(1,k1), ColUp(c,k2), RowRight(1,k1), ColDown(c,k2)] // creates a 3-cycle in rows 1+ only. Row 0 stays intact. int C1 = tgtRows[1]; for (int pass = 0; pass < 4; 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; // Move C1 to (srcR, colW) via row shift (safe: rows 2-3 only) if (srcC != colW) DoRowRight(srcR, (colW - srcC + 4) % 4); int k2 = srcR - 1; // 1 or 2 DoRowLeft(1, 1); DoColUp(colW, k2); DoRowRight(1, 1); DoColDown(colW, k2); } // ── Phase 3: Solve Rows 2-3 (protecting Rows 0-1) ─── // Commutator with r1=2, k2=1 only touches rows 2-3. int C2 = tgtRows[2]; for (int pass = 0; pass < 4; 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); } // Verify for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (g[r,c] != tgtRows[r]) return null; // Optimize: remove consecutive inverse pairs 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; } // ── 5. Check if already solved ──────────────────────────── bool IsGridSolved(int[,] g) { for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) if (g[r,c] != targetRows[r]) return false; return true; } if (IsGridSolved(grid)) { Log("Puzzle ist bereits geloest!"); return; } // ── 6. Solve ────────────────────────────────────────────── var solution = SolveLayerByLayer(grid, targetRows); if (solution == null || solution.Count == 0) { Log("ERROR: Solver konnte keine Loesung finden!"); Log("Moegliche Gruende: Farb-Verteilung nicht 4x je Farbe, oder falsche Ziel-Zuordnung."); return; } Log($"Loesung gefunden: {solution.Count} Moves"); for (int i = 0; i < solution.Count; i++) Log($" {i+1}. {MoveName(solution[i])}"); // ── 7. Execute with verification ────────────────────────── // Track arrow direction flips (auto-detect reversed arrows) 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}"; } // Encode grid as uint for quick comparison 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; } // Compute expected state after a move (using bit ops for speed) uint ApplyMoveBits(uint s, int m) { if (m < 4) { // RowLeft 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) { // RowRight 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) { // ColUp 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, v1=(s>>(b+8))&3u, v2=(s>>(b+16))&3u, v3=(s>>(b+24))&3u; uint mask = ~(3u<= MAX_RETRIES) { Log("WARN: Klick ohne Effekt nach 3 Versuchen, re-plane..."); grid = newGrid; solution = SolveLayerByLayer(grid, targetRows); if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; } moveIdx = 0; retries = 0; } else { Log(" Klick ohne Effekt, retry..."); Delay(300); } continue; } // Desync: grid changed unexpectedly (maybe another player or lag) Log($" Desync! Neuer Zustand: {GridDump(newGrid)}"); Log(" Re-plane von neuem Zustand..."); grid = newGrid; currentState = afterState; if (IsGridSolved(grid)) { Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ==="); return; } solution = SolveLayerByLayer(grid, targetRows); if (solution == null) { Log("ERROR: Re-Plan fehlgeschlagen!"); return; } moveIdx = 0; retries = 0; Log($" Neuer Plan: {solution.Count} Moves"); } if (currentState == goalState) Log("=== Puzzle geloest! Alle 4 Reihen korrekt! ==="); else Log("Alle Moves ausgefuehrt. Grid pruefen ob geloest.");