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.
416 lines
10 KiB
C#
416 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
// Color Puzzle Solver v2
|
|
// - Auto calibration of arrow -> move mapping
|
|
// - Waits for real state change after every click
|
|
|
|
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_SETTLE_DELAY_MS = 250;
|
|
const int WAIT_CHANGE_TIMEOUT_MS = 6000;
|
|
const int WAIT_CHANGE_POLL_MS = 120;
|
|
const int MAX_STEPS = 140;
|
|
const int BFS_MAX_NODES = 4_000_000;
|
|
const int IDA_MAX_SEC = 12;
|
|
|
|
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; }
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool TryReadGrid(out uint state, out string dump)
|
|
{
|
|
int[,] grid = 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 row = y - GRID_Y_MIN;
|
|
int col = x - GRID_X_MIN;
|
|
grid[row, col] = GetState(item);
|
|
found[row, col] = 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)
|
|
{
|
|
state = 0;
|
|
dump = "";
|
|
return false;
|
|
}
|
|
|
|
state = EncodeGrid(grid);
|
|
dump = string.Join(" | ", Enumerable.Range(0, 4).Select(r =>
|
|
$"R{r}[{grid[r,0]},{grid[r,1]},{grid[r,2]},{grid[r,3]}]"));
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
int DetectMove(uint before, uint after)
|
|
{
|
|
int hit = -1;
|
|
for (int m = 0; m < 16; m++)
|
|
{
|
|
if (ApplyMove(before, m) != after) continue;
|
|
if (hit != -1) return -2;
|
|
hit = m;
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
List<int> SolveBfs(uint start, uint goal)
|
|
{
|
|
if (start == goal) return new List<int>();
|
|
|
|
var visited = new Dictionary<uint, (uint parent, int move)>();
|
|
var queue = new Queue<uint>();
|
|
visited[start] = (start, -1);
|
|
queue.Enqueue(start);
|
|
int nodes = 0;
|
|
bool found = false;
|
|
|
|
while (queue.Count > 0 && nodes < BFS_MAX_NODES)
|
|
{
|
|
uint cur = queue.Dequeue();
|
|
nodes++;
|
|
|
|
for (int m = 0; m < 16; m++)
|
|
{
|
|
uint nxt = ApplyMove(cur, m);
|
|
if (visited.ContainsKey(nxt)) continue;
|
|
visited[nxt] = (cur, m);
|
|
if (nxt == goal)
|
|
{
|
|
found = true;
|
|
queue.Clear();
|
|
break;
|
|
}
|
|
queue.Enqueue(nxt);
|
|
}
|
|
}
|
|
|
|
if (!found) return null;
|
|
|
|
var sol = new List<int>();
|
|
uint s = goal;
|
|
while (s != start)
|
|
{
|
|
var p = visited[s];
|
|
sol.Add(p.move);
|
|
s = p.parent;
|
|
}
|
|
sol.Reverse();
|
|
return sol;
|
|
}
|
|
|
|
List<int> SolveIda(uint start, uint goal)
|
|
{
|
|
if (start == goal) return new List<int>();
|
|
var t0 = DateTime.Now;
|
|
|
|
int H(uint st)
|
|
{
|
|
int mis = 0;
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
int a = (int)((st >> (i * 2)) & 3u);
|
|
int b = (int)((goal >> (i * 2)) & 3u);
|
|
if (a != b) mis++;
|
|
}
|
|
return (mis + 3) / 4;
|
|
}
|
|
|
|
List<int> best = null;
|
|
bool timeout = false;
|
|
|
|
bool Dfs(uint st, List<int> path, int maxDepth)
|
|
{
|
|
if (timeout) return false;
|
|
if ((DateTime.Now - t0).TotalSeconds > IDA_MAX_SEC)
|
|
{
|
|
timeout = true;
|
|
return false;
|
|
}
|
|
|
|
if (st == goal)
|
|
{
|
|
best = new List<int>(path);
|
|
return true;
|
|
}
|
|
|
|
int h = H(st);
|
|
if (path.Count + h > maxDepth) return false;
|
|
|
|
int block = path.Count > 0 ? InverseMove(path[path.Count - 1]) : -1;
|
|
for (int m = 0; m < 16; m++)
|
|
{
|
|
if (m == block) continue;
|
|
path.Add(m);
|
|
if (Dfs(ApplyMove(st, m), path, maxDepth)) return true;
|
|
path.RemoveAt(path.Count - 1);
|
|
if (timeout) return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int d0 = H(start);
|
|
for (int d = d0; d <= 22 && !timeout; d++)
|
|
{
|
|
if (Dfs(start, new List<int>(), d)) break;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
List<int> Solve(uint start, uint goal)
|
|
{
|
|
var bfs = SolveBfs(start, goal);
|
|
if (bfs != null) return bfs;
|
|
return SolveIda(start, goal);
|
|
}
|
|
|
|
bool ClickAndWaitChange(long furniId, uint before, out uint after, out string dumpAfter)
|
|
{
|
|
Send(Out["ClickFurni"], (int)furniId, 0);
|
|
Delay(CLICK_SETTLE_DELAY_MS);
|
|
|
|
int waited = 0;
|
|
while (waited < WAIT_CHANGE_TIMEOUT_MS)
|
|
{
|
|
if (TryReadGrid(out after, out dumpAfter) && after != before)
|
|
return true;
|
|
Delay(WAIT_CHANGE_POLL_MS);
|
|
waited += WAIT_CHANGE_POLL_MS;
|
|
}
|
|
|
|
after = before;
|
|
dumpAfter = "";
|
|
return false;
|
|
}
|
|
|
|
Log("=== Color Puzzle Auto-Solver (AutoCalib + WaitChange) ===");
|
|
|
|
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;
|
|
int 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 16 Pfeile gefunden.");
|
|
return;
|
|
}
|
|
|
|
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 };
|
|
|
|
int[,] tgt = new int[4, 4];
|
|
for (int r = 0; r < 4; r++)
|
|
for (int c = 0; c < 4; c++)
|
|
tgt[r, c] = targetRows[r];
|
|
|
|
uint goal = EncodeGrid(tgt);
|
|
Log($"Ziel: R0={targetRows[0]}, R1={targetRows[1]}, R2={targetRows[2]}, R3={targetRows[3]}");
|
|
|
|
if (!TryReadGrid(out uint current, out string dumpNow))
|
|
{
|
|
Log("ERROR: Grid nicht lesbar.");
|
|
return;
|
|
}
|
|
Log($"Start: {dumpNow}");
|
|
|
|
var moveToKey = new Dictionary<int, string>();
|
|
var keyToMove = new Dictionary<string, int>();
|
|
var allKeys = arrowIds.Keys.OrderBy(k => k).ToList();
|
|
|
|
for (int step = 1; step <= MAX_STEPS; step++)
|
|
{
|
|
if (current == goal)
|
|
{
|
|
Log("=== Geloest: alle 4 Reihen korrekt ===");
|
|
return;
|
|
}
|
|
|
|
var plan = Solve(current, goal);
|
|
if (plan == null || plan.Count == 0)
|
|
{
|
|
Log("ERROR: Kein Plan vom aktuellen Zustand.");
|
|
return;
|
|
}
|
|
|
|
int wanted = plan[0];
|
|
string key;
|
|
bool probing = false;
|
|
|
|
if (moveToKey.ContainsKey(wanted))
|
|
{
|
|
key = moveToKey[wanted];
|
|
}
|
|
else
|
|
{
|
|
key = allKeys.FirstOrDefault(k => !keyToMove.ContainsKey(k));
|
|
if (key == null)
|
|
{
|
|
key = allKeys[0];
|
|
}
|
|
probing = true;
|
|
}
|
|
|
|
long id = arrowIds[key];
|
|
Log($"[{step}] want {MoveName(wanted)} | click {key}" + (probing ? " (probe)" : ""));
|
|
|
|
if (!ClickAndWaitChange(id, current, out uint after, out string dumpAfter))
|
|
{
|
|
Log(" Kein Move erkannt (Timeout), gleicher Schritt nochmal.");
|
|
continue;
|
|
}
|
|
|
|
int actual = DetectMove(current, after);
|
|
if (actual >= 0)
|
|
{
|
|
moveToKey[actual] = key;
|
|
keyToMove[key] = actual;
|
|
if (actual != wanted)
|
|
Log($" AutoCalib: {key} == {MoveName(actual)} (nicht {MoveName(wanted)})");
|
|
}
|
|
else if (actual == -1)
|
|
{
|
|
Log($" Unbekannter Transition-Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
|
}
|
|
else
|
|
{
|
|
Log($" Mehrdeutiger Delta, weiter mit Re-Plan. State: {dumpAfter}");
|
|
}
|
|
|
|
current = after;
|
|
|
|
if (step % 10 == 0)
|
|
Log($" Calib: {moveToKey.Count}/16 Moves gemappt");
|
|
}
|
|
|
|
Log("Nicht fertig in MAX_STEPS. Script einfach nochmal starten.");
|