xabbo-scripts/Scripts/Color Puzzle Solver 4-Row Fix.csx
Administrator 7a548130a3 Move all scripts into Scripts/ subfolder
Keeps the repo root clean - only README.md visible on landing page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 09:49:37 +01:00

578 lines
15 KiB
C#

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<int> 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<int>();
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<int, (int[] states, bool[] found, int count)>();
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<int> 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<string, long>();
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<int[]>();
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<int> 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.");