xabbo-scripts/Color Pattern Walker Fixed.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

636 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
class Cell
{
public long Id;
public int X;
public int Y;
public int State;
public int Kind;
public double Z;
}
const int SOL_MIN_X = 4;
const int SOL_MAX_X = 9;
const int SOL_MIN_Y = 1;
const int SOL_MAX_Y = 8;
const int PLAY_MIN_X = 8;
const int PLAY_MAX_X = 13;
const int PLAY_MIN_Y = 14;
const int PLAY_MAX_Y = 21;
const int SPAWN_X = 13;
const int SPAWN_Y = 13;
const int SPAWN_WAIT_MS = 180000;
const int FORCED_CYCLE = 5;
const bool ASSUME_START_ALL_ZERO = true;
const int STEP_WAIT_MS = 420;
const int MOVE_COMMAND_INTERVAL_MS = 70;
const int MOVE_SETTLE_MS = 0;
const int PERIODIC_SYNC_EVERY_STEPS = 12;
const int PATH_BURST_MAX_STEPS = 10;
const int MAX_STEPS = 5000;
string K(int x, int y) => x + "," + y;
int GetKind(dynamic item)
{
try { return (int)item.Kind; }
catch { return -1; }
}
int GetState(dynamic item)
{
try { return int.Parse(item.State?.ToString() ?? "0"); }
catch { return 0; }
}
string GetNameSafe(dynamic item)
{
try
{
string n = item.GetName();
return string.IsNullOrWhiteSpace(n) ? "<unknown>" : n;
}
catch { return "<unknown>"; }
}
bool InRect(int x, int y, int minX, int maxX, int minY, int maxY)
{
return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
bool InPlay(int x, int y)
{
return InRect(x, y, PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
}
void FullRoomScanLog()
{
var all = new List<dynamic>();
foreach (var it in FloorItems)
{
if (it == null) continue;
all.Add(it);
}
Log("=== Full Room Scan ===");
Log($"FloorItems total: {all.Count}");
var byKind = all.GroupBy(x => GetKind(x))
.Select(g => new {
Kind = g.Key,
Count = g.Count(),
States = string.Join(",", g.Select(x => GetState(x)).Distinct().OrderBy(x => x)),
Name = g.Select(x => GetNameSafe(x)).FirstOrDefault()
})
.OrderByDescending(x => x.Count)
.Take(25)
.ToList();
foreach (var k in byKind)
Log($"Kind {k.Kind} x{k.Count} states[{k.States}] name={k.Name}");
}
bool WaitForSpawn()
{
Log($"Waiting for round spawn on {SPAWN_X}:{SPAWN_Y}...");
int elapsed = 0;
while (elapsed < SPAWN_WAIT_MS)
{
if (Self != null && Self.Location != null && Self.Location.X == SPAWN_X && Self.Location.Y == SPAWN_Y)
{
Log("Spawn detected, starting solver.");
Delay(300);
return true;
}
Delay(200);
elapsed += 200;
}
Log("Spawn timeout. Starting anyway.");
return false;
}
List<Cell> CollectCellsInRect(int minX, int maxX, int minY, int maxY)
{
var raw = new List<Cell>();
foreach (var it in FloorItems)
{
if (it == null) continue;
int x = it.Location.X;
int y = it.Location.Y;
if (!InRect(x, y, minX, maxX, minY, maxY)) continue;
raw.Add(new Cell {
Id = it.Id,
X = x,
Y = y,
State = GetState(it),
Kind = GetKind(it),
Z = it.Location.Z
});
}
if (raw.Count == 0) return new List<Cell>();
int targetCount = (maxX - minX + 1) * (maxY - minY + 1);
var bestKind = raw.GroupBy(c => c.Kind)
.Select(g => new {
Kind = g.Key,
CoordCount = g.Select(c => K(c.X, c.Y)).Distinct().Count(),
Count = g.Count()
})
.OrderByDescending(x => x.CoordCount)
.ThenByDescending(x => x.Count)
.First();
var cellsOfKind = raw.Where(c => c.Kind == bestKind.Kind).ToList();
var bestPerCoord = new List<Cell>();
foreach (var g in cellsOfKind.GroupBy(c => K(c.X, c.Y)))
{
var top = g.OrderByDescending(c => c.Z).First();
bestPerCoord.Add(top);
}
Log($"Rect X[{minX}-{maxX}] Y[{minY}-{maxY}] -> kind {bestKind.Kind}, coords {bestPerCoord.Count}/{targetCount}");
return bestPerCoord;
}
Dictionary<string, Cell> IndexCells(List<Cell> cells)
{
var d = new Dictionary<string, Cell>();
foreach (var c in cells) d[K(c.X, c.Y)] = c;
return d;
}
Dictionary<long, int> ReadCurrentPlayStates(HashSet<long> ids)
{
var d = new Dictionary<long, int>();
foreach (var it in FloorItems)
{
if (it == null) continue;
long id = it.Id;
if (!ids.Contains(id)) continue;
d[id] = GetState(it);
}
return d;
}
int Need(int current, int target, int cycle)
{
int d = (target - current) % cycle;
if (d < 0) d += cycle;
return d;
}
int Objective(Dictionary<long, int> cur, Dictionary<long, int> target, int cycle)
{
int sum = 0;
foreach (var kv in target)
{
if (!cur.ContainsKey(kv.Key)) continue;
sum += Need(cur[kv.Key], kv.Value, cycle);
}
return sum;
}
int HardObjectiveFromRoom(List<Cell> playCells, Dictionary<long, int> targetByPlayId, int cycle, int[] needs)
{
var ids = new HashSet<long>(targetByPlayId.Keys);
var cur = ReadCurrentPlayStates(ids);
for (int i = 0; i < playCells.Count; i++)
{
long id = playCells[i].Id;
int val = cur.ContainsKey(id) ? cur[id] : 0;
needs[i] = Need(val, targetByPlayId[id], cycle);
}
return needs.Sum();
}
int ApplyNeedStep(int[] needs, int idx, int cycle)
{
int d = needs[idx];
if (d > 0)
{
needs[idx] = d - 1;
return -1;
}
needs[idx] = cycle - 1;
return cycle - 1;
}
bool WaitUntilAt(int tx, int ty)
{
int elapsed = 0;
while (elapsed < STEP_WAIT_MS)
{
if (Self != null && Self.Location != null && Self.Location.X == tx && Self.Location.Y == ty)
return true;
Delay(40);
elapsed += 40;
}
return false;
}
List<(int x, int y)> Neigh4(int x, int y)
{
var list = new List<(int, int)> {
(x + 1, y),
(x - 1, y),
(x, y + 1),
(x, y - 1),
(x + 1, y + 1),
(x + 1, y - 1),
(x - 1, y + 1),
(x - 1, y - 1)
};
return list.Where(p => InPlay(p.Item1, p.Item2)).ToList();
}
int Dist(int x1, int y1, int x2, int y2)
{
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
}
int ReadSelfX(int fallback)
{
try { return Self.Location.X; }
catch { return fallback; }
}
int ReadSelfY(int fallback)
{
try { return Self.Location.Y; }
catch { return fallback; }
}
DateTime _lastMoveCmd = DateTime.MinValue;
void FastMove(int x, int y)
{
int since = (int)(DateTime.UtcNow - _lastMoveCmd).TotalMilliseconds;
if (since < MOVE_COMMAND_INTERVAL_MS)
Delay(MOVE_COMMAND_INTERVAL_MS - since);
Move(x, y);
_lastMoveCmd = DateTime.UtcNow;
}
(int nx, int ny)? NextStepToTarget(int sx, int sy, int tx, int ty)
{
(int x, int y) start = (sx, sy);
(int x, int y) goal = (tx, ty);
if (start == goal) return null;
var q = new Queue<(int x, int y)>();
var vis = new HashSet<string>();
var prev = new Dictionary<string, (int x, int y)>();
q.Enqueue(start);
vis.Add(K(start.x, start.y));
while (q.Count > 0)
{
var cur = q.Dequeue();
foreach (var n in Neigh4(cur.x, cur.y))
{
string nk = K(n.x, n.y);
if (vis.Contains(nk)) continue;
vis.Add(nk);
prev[nk] = cur;
if (n == goal)
{
var node = goal;
while (true)
{
var pk = K(node.x, node.y);
var pnode = prev[pk];
if (pnode == start) return node;
node = pnode;
}
}
q.Enqueue(n);
}
}
return null;
}
List<(int x, int y)> BuildPathToTarget(int sx, int sy, int tx, int ty)
{
(int x, int y) start = (sx, sy);
(int x, int y) goal = (tx, ty);
var empty = new List<(int x, int y)>();
if (start == goal) return empty;
var q = new Queue<(int x, int y)>();
var vis = new HashSet<string>();
var prev = new Dictionary<string, (int x, int y)>();
q.Enqueue(start);
vis.Add(K(start.x, start.y));
while (q.Count > 0)
{
var cur = q.Dequeue();
foreach (var n in Neigh4(cur.x, cur.y))
{
string nk = K(n.x, n.y);
if (vis.Contains(nk)) continue;
vis.Add(nk);
prev[nk] = cur;
if (n == goal)
{
var rev = new List<(int x, int y)>();
var node = goal;
while (node != start)
{
rev.Add(node);
node = prev[K(node.x, node.y)];
}
rev.Reverse();
return rev;
}
q.Enqueue(n);
}
}
return empty;
}
Log("=== Color Pattern Walker Solver (fixed bounds) ===");
WaitForSpawn();
FullRoomScanLog();
var solCells = CollectCellsInRect(SOL_MIN_X, SOL_MAX_X, SOL_MIN_Y, SOL_MAX_Y);
var playCells = CollectCellsInRect(PLAY_MIN_X, PLAY_MAX_X, PLAY_MIN_Y, PLAY_MAX_Y);
int expectedSol = (SOL_MAX_X - SOL_MIN_X + 1) * (SOL_MAX_Y - SOL_MIN_Y + 1);
int expectedPlay = (PLAY_MAX_X - PLAY_MIN_X + 1) * (PLAY_MAX_Y - PLAY_MIN_Y + 1);
if (solCells.Count < expectedSol || playCells.Count < expectedPlay)
{
Log($"ERROR: Board incomplete. Solution {solCells.Count}/{expectedSol}, Play {playCells.Count}/{expectedPlay}");
return;
}
var solMap = IndexCells(solCells);
var playMap = IndexCells(playCells);
var targetByPlayId = new Dictionary<long, int>();
foreach (var p in playCells)
{
int sx = p.X - 4;
int sy = p.Y - 13;
string sk = K(sx, sy);
if (!solMap.ContainsKey(sk)) continue;
targetByPlayId[p.Id] = solMap[sk].State;
}
if (targetByPlayId.Count != expectedPlay)
{
Log($"ERROR: Could not map all play cells to solution cells ({targetByPlayId.Count}/{expectedPlay}).");
return;
}
var ids = new HashSet<long>(targetByPlayId.Keys);
var cur = new Dictionary<long, int>();
if (ASSUME_START_ALL_ZERO)
{
foreach (var id in ids) cur[id] = 0;
Log("Using round-start baseline: all play tiles = state 0.");
}
else
{
cur = ReadCurrentPlayStates(ids);
if (cur.Count != expectedPlay)
{
Log($"ERROR: Could not read all current play states ({cur.Count}/{expectedPlay}).");
return;
}
}
int cycle = FORCED_CYCLE;
if (cycle < 2) cycle = 5;
Log($"State cycle: {cycle}");
var coordToIdx = new Dictionary<string, int>();
var idToIdx = new Dictionary<long, int>();
for (int i = 0; i < playCells.Count; i++)
{
var c = playCells[i];
coordToIdx[K(c.X, c.Y)] = i;
idToIdx[c.Id] = i;
}
int[] needs = new int[playCells.Count];
for (int i = 0; i < playCells.Count; i++)
{
long id = playCells[i].Id;
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
}
int obj = needs.Sum();
Log($"Initial objective: {obj}");
if (obj == 0) { Log("Already solved."); return; }
if (Self == null || Self.Location == null)
{
Log("ERROR: No self location.");
return;
}
int cx = Self.Location.X;
int cy = Self.Location.Y;
if (!InPlay(cx, cy))
{
var bestEntry = playCells
.OrderBy(c => Dist(cx, cy, c.X, c.Y))
.First();
FastMove(bestEntry.X, bestEntry.Y);
WaitUntilAt(bestEntry.X, bestEntry.Y);
cur = ReadCurrentPlayStates(ids);
for (int i = 0; i < playCells.Count; i++)
{
long id = playCells[i].Id;
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
}
cx = ReadSelfX(bestEntry.X);
cy = ReadSelfY(bestEntry.Y);
obj = needs.Sum();
Log($"After entry objective: {obj}");
}
int stagnation = 0;
int prevX = -999;
int prevY = -999;
int sinceResync = 0;
var burstPath = new List<(int x, int y)>();
int burstIndex = 0;
for (int step = 1; step <= MAX_STEPS; step++)
{
if (!InPlay(cx, cy))
{
Log("WARN: Left play field unexpectedly, moving back.");
var back = playCells.OrderBy(c => Dist(cx, cy, c.X, c.Y)).First();
FastMove(back.X, back.Y);
WaitUntilAt(back.X, back.Y);
cx = ReadSelfX(back.X);
cy = ReadSelfY(back.Y);
cur = ReadCurrentPlayStates(ids);
for (int i = 0; i < playCells.Count; i++)
{
long id = playCells[i].Id;
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
}
obj = needs.Sum();
sinceResync = 0;
burstPath.Clear();
burstIndex = 0;
continue;
}
if (obj == 0)
{
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
int standNeed = 999;
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
standNeed = needs[coordToIdx[K(cx, cy)]];
if (obj == 0 && standNeed == 0)
{
Log("=== Done: bottom matches solution (hard check OK) ===");
return;
}
}
var neighbors = Neigh4(cx, cy);
if (neighbors.Count == 0)
{
Log("ERROR: No neighbors on play field.");
return;
}
int unresolved = 0;
for (int i = 0; i < needs.Length; i++)
if (needs[i] > 0) unresolved++;
if (burstIndex >= burstPath.Count)
{
var needy = playCells
.Select(c => new {
Cell = c,
Need = needs[idToIdx[c.Id]],
D = Dist(cx, cy, c.X, c.Y)
})
.Where(x => x.Need > 0)
.OrderByDescending(x => x.Need)
.ThenBy(x => x.D)
.FirstOrDefault();
if (needy != null)
{
var path = BuildPathToTarget(cx, cy, needy.Cell.X, needy.Cell.Y);
if (path.Count > 0)
{
burstPath = path.Take(PATH_BURST_MAX_STEPS).ToList();
burstIndex = 0;
}
}
if (burstIndex >= burstPath.Count)
{
var fallback = neighbors
.Select(n => new {
N = n,
Need = needs[coordToIdx[K(n.x, n.y)]],
Back = (n.x == prevX && n.y == prevY) ? 1 : 0
})
.OrderByDescending(x => x.Need)
.ThenBy(x => x.Back)
.First();
burstPath = new List<(int x, int y)> { fallback.N };
burstIndex = 0;
}
}
(int x, int y) bestN = burstPath[burstIndex];
burstIndex++;
int idxChosen = coordToIdx[K(bestN.x, bestN.y)];
if (needs[idxChosen] == 0 && unresolved <= 8)
{
stagnation++;
}
else
{
stagnation = 0;
}
if (stagnation >= 12)
{
burstPath.Clear();
burstIndex = 0;
stagnation = 0;
}
prevX = cx;
prevY = cy;
int oldObj = obj;
FastMove(bestN.x, bestN.y);
if (MOVE_SETTLE_MS > 0) Delay(MOVE_SETTLE_MS);
// Predictive advance: keep running without stop-go per step.
cx = bestN.x;
cy = bestN.y;
if (InPlay(cx, cy) && coordToIdx.ContainsKey(K(cx, cy)))
{
int landedIdx = coordToIdx[K(cx, cy)];
obj += ApplyNeedStep(needs, landedIdx, cycle);
}
else
{
sinceResync = 20;
}
sinceResync++;
if (sinceResync >= PERIODIC_SYNC_EVERY_STEPS || stagnation >= 8)
{
Delay(120);
cx = ReadSelfX(cx);
cy = ReadSelfY(cy);
cur = ReadCurrentPlayStates(ids);
for (int i = 0; i < playCells.Count; i++)
{
long id = playCells[i].Id;
needs[i] = Need(cur[id], targetByPlayId[id], cycle);
}
obj = needs.Sum();
sinceResync = 0;
}
int newObj = obj;
Log($"[{step}] ({cx},{cy}) objective {oldObj} -> {newObj}");
}
obj = HardObjectiveFromRoom(playCells, targetByPlayId, cycle, needs);
int finalStandNeed = 999;
if (Self != null && Self.Location != null)
{
int fx = ReadSelfX(-9999);
int fy = ReadSelfY(-9999);
if (InPlay(fx, fy) && coordToIdx.ContainsKey(K(fx, fy)))
finalStandNeed = needs[coordToIdx[K(fx, fy)]];
}
if (obj == 0 && finalStandNeed == 0)
Log("=== Done: bottom matches solution (hard check OK) ===");
else
Log($"Stopped after max steps. Remaining objective: {obj}");