xabbo-scripts/Color Puzzle BFS-IDA Solver.csx
Administrator 7bfc390ed6 Initial commit: 68 Xabbo Scripter scripts
Habbo Hotel automation scripts including:
- Game solvers (Snake, Color Puzzle, Tetris, Flappy Bird, Flood-IT)
- Room utilities (Autogate, One-Way Door, Furni Scanner)
- Bot tools (Heal Bot, Pet Trainer, User Collector)
- Trading & economy (Furni-Matic, Seed Trade, Trade Spam)

Cleaned up: removed 5 duplicates and 2 broken scripts,
renamed 37 gibberish filenames to descriptive names.
2026-03-16 09:38:59 +01:00

419 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (3FarbenTile)
// 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<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;
}
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<int> solution = null;
// ── 6a. BFS ───────────────────────────────────────────────
Log($"Starte BFS (max {BFS_MAX_NODES:N0} Nodes)...");
var startBfs = DateTime.Now;
var visited = new Dictionary<uint, (uint parent, int move)>();
var queue = new Queue<uint>();
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<int>();
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<int> bestSol = null;
int bestLen = 30;
bool timeout = false;
bool DFS(uint state, List<int> 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<int>(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<int>(), 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) ===");