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.
404 lines
10 KiB
C#
404 lines
10 KiB
C#
/// @name Snake Auto (AutoCalib)
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
|
|
const int GAME_MIN_X = 9;
|
|
const int GAME_MAX_X = 28;
|
|
const int GAME_MIN_Y = 11;
|
|
const int GAME_MAX_Y = 30;
|
|
|
|
const int BLOCKED_MIN_X = 17;
|
|
const int BLOCKED_MAX_X = 20;
|
|
const int BLOCKED_MIN_Y = 19;
|
|
const int BLOCKED_MAX_Y = 22;
|
|
|
|
const int KIND_SNAKE = 7100;
|
|
const int KIND_FOOD = 5068;
|
|
|
|
// Fallback controls for this room layout
|
|
const int FALLBACK_UP = 769407748;
|
|
const int FALLBACK_RIGHT = 769407103;
|
|
const int FALLBACK_DOWN = 769407216;
|
|
const int FALLBACK_LEFT = 769407068;
|
|
|
|
int CTRL_UP = FALLBACK_UP;
|
|
int CTRL_RIGHT = FALLBACK_RIGHT;
|
|
int CTRL_DOWN = FALLBACK_DOWN;
|
|
int CTRL_LEFT = FALLBACK_LEFT;
|
|
|
|
List<(int x, int y)> body = new List<(int x, int y)>(256);
|
|
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>();
|
|
(int x, int y) food = (-1, -1);
|
|
|
|
string dir = "DOWN";
|
|
string lastSentDir = "";
|
|
DateTime lastCmdTime = DateTime.MinValue;
|
|
bool ate = false;
|
|
|
|
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
|
|
|
bool Wall(int x, int y) => x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y;
|
|
bool Blk(int x, int y) => x >= BLOCKED_MIN_X && x <= BLOCKED_MAX_X && y >= BLOCKED_MIN_Y && y <= BLOCKED_MAX_Y;
|
|
|
|
void Step(int x, int y, int d, out int nx, out int ny)
|
|
{
|
|
nx = x + (d == 3 ? 1 : d == 2 ? -1 : 0);
|
|
ny = y + (d == 1 ? 1 : d == 0 ? -1 : 0);
|
|
}
|
|
|
|
int Opp(int d) => d ^ 1;
|
|
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
|
|
|
bool Blocked(int x, int y, bool ignoreTail)
|
|
{
|
|
if (Wall(x, y) || Blk(x, y)) return true;
|
|
if (body.Count == 0) return false;
|
|
|
|
int checkLen = ignoreTail && !ate ? body.Count - 1 : body.Count;
|
|
for (int i = 0; i < checkLen; i++)
|
|
if (body[i].x == x && body[i].y == y) return true;
|
|
return false;
|
|
}
|
|
|
|
void SendDir(int dIdx)
|
|
{
|
|
string d = DIRS[dIdx];
|
|
if (d == lastSentDir && (DateTime.Now - lastCmdTime).TotalMilliseconds < 80) return;
|
|
|
|
int id = dIdx == 0 ? CTRL_UP : dIdx == 1 ? CTRL_DOWN : dIdx == 2 ? CTRL_LEFT : CTRL_RIGHT;
|
|
Send(Out["ClickFurni"], id, 0);
|
|
|
|
lastCmdTime = DateTime.Now;
|
|
lastSentDir = d;
|
|
dir = d;
|
|
}
|
|
|
|
int OpenSpace(int sx, int sy, int cap)
|
|
{
|
|
if (Blocked(sx, sy, false)) return 0;
|
|
var seen = new HashSet<(int, int)>();
|
|
var q = new Queue<(int, int)>();
|
|
|
|
q.Enqueue((sx, sy));
|
|
seen.Add((sx, sy));
|
|
int c = 0;
|
|
|
|
while (q.Count > 0 && c < cap)
|
|
{
|
|
var p = q.Dequeue();
|
|
c++;
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Step(p.Item1, p.Item2, d, out int nx, out int ny);
|
|
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
|
|
q.Enqueue((nx, ny));
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
int DistTo(int sx, int sy, int tx, int ty, int cap = 180)
|
|
{
|
|
if (sx == tx && sy == ty) return 0;
|
|
var seen = new HashSet<(int, int)>();
|
|
var q = new Queue<((int x, int y) pos, int dist)>();
|
|
|
|
q.Enqueue(((sx, sy), 0));
|
|
seen.Add((sx, sy));
|
|
|
|
while (q.Count > 0)
|
|
{
|
|
var cur = q.Dequeue();
|
|
if (cur.dist >= cap) return 999;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Step(cur.pos.x, cur.pos.y, d, out int nx, out int ny);
|
|
if (nx == tx && ny == ty) return cur.dist + 1;
|
|
if (!Blocked(nx, ny, false) && seen.Add((nx, ny)))
|
|
q.Enqueue(((nx, ny), cur.dist + 1));
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int BestMove(int hx, int hy, int fx, int fy, int curDir)
|
|
{
|
|
int best = curDir;
|
|
int bestScore = int.MinValue;
|
|
int minSpace = Math.Max(body.Count / 4, 8);
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == Opp(curDir)) continue;
|
|
Step(hx, hy, d, out int nx, out int ny);
|
|
if (Blocked(nx, ny, true)) continue;
|
|
|
|
int space = OpenSpace(nx, ny, 180);
|
|
if (space < minSpace) continue;
|
|
|
|
int score = 0;
|
|
if (nx == fx && ny == fy) score += 10000;
|
|
|
|
int distFood = DistTo(nx, ny, fx, fy, 180);
|
|
if (distFood > 0 && distFood < 999) score += (220 - distFood) * 7;
|
|
else if (distFood == -1) score -= 150;
|
|
|
|
score += Math.Min(space, 100) * 4;
|
|
|
|
if (score > bestScore)
|
|
{
|
|
bestScore = score;
|
|
best = d;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
int Emergency(int hx, int hy, int curDir)
|
|
{
|
|
int best = curDir;
|
|
int bestSpace = -1;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == Opp(curDir)) continue;
|
|
Step(hx, hy, d, out int nx, out int ny);
|
|
if (Blocked(nx, ny, true)) continue;
|
|
|
|
int space = OpenSpace(nx, ny, 80);
|
|
if (space > bestSpace)
|
|
{
|
|
bestSpace = space;
|
|
best = d;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
List<List<(long id, int x, int y)>> GetComponents(List<(long id, int kind, int x, int y)> points)
|
|
{
|
|
var comps = new List<List<(long id, int x, int y)>>();
|
|
var used = new HashSet<long>();
|
|
|
|
foreach (var p in points)
|
|
{
|
|
if (!used.Add(p.id)) continue;
|
|
|
|
var comp = new List<(long id, int x, int y)>();
|
|
var q = new Queue<(long id, int x, int y)>();
|
|
q.Enqueue((p.id, p.x, p.y));
|
|
|
|
while (q.Count > 0)
|
|
{
|
|
var cur = q.Dequeue();
|
|
comp.Add(cur);
|
|
|
|
foreach (var n in points)
|
|
{
|
|
if (used.Contains(n.id)) continue;
|
|
if (Math.Abs(cur.x - n.x) <= 1 && Math.Abs(cur.y - n.y) <= 1)
|
|
{
|
|
used.Add(n.id);
|
|
q.Enqueue((n.id, n.x, n.y));
|
|
}
|
|
}
|
|
}
|
|
|
|
comps.Add(comp);
|
|
}
|
|
|
|
return comps;
|
|
}
|
|
|
|
bool TryAutoCalibrateControls()
|
|
{
|
|
if (Self == null) return false;
|
|
|
|
int sx = Self.Location.X;
|
|
int sy = Self.Location.Y;
|
|
|
|
var nearby = FloorItems
|
|
.Where(i => i != null)
|
|
.Where(i => Math.Abs(i.Location.X - sx) <= 14 && Math.Abs(i.Location.Y - sy) <= 14)
|
|
.Where(i => i.Kind != KIND_SNAKE && i.Kind != KIND_FOOD)
|
|
.Select(i => (id: (long)i.Id, kind: (int)i.Kind, x: i.Location.X, y: i.Location.Y))
|
|
.ToList();
|
|
|
|
if (nearby.Count == 0) return false;
|
|
|
|
List<(long id, int x, int y)> best = null;
|
|
double bestScore = double.MaxValue;
|
|
|
|
foreach (var g in nearby.GroupBy(p => p.kind))
|
|
{
|
|
if (g.Count() < 4) continue;
|
|
|
|
var components = GetComponents(g.ToList());
|
|
foreach (var comp in components)
|
|
{
|
|
if (comp.Count < 4) continue;
|
|
|
|
int minX = comp.Min(p => p.x);
|
|
int maxX = comp.Max(p => p.x);
|
|
int minY = comp.Min(p => p.y);
|
|
int maxY = comp.Max(p => p.y);
|
|
|
|
int w = maxX - minX;
|
|
int h = maxY - minY;
|
|
if (w > 6 || h > 6) continue;
|
|
|
|
double cx = comp.Average(p => p.x);
|
|
double cy = comp.Average(p => p.y);
|
|
double dist = Math.Abs(cx - sx) + Math.Abs(cy - sy);
|
|
double score = dist + Math.Abs(comp.Count - 4) * 0.5;
|
|
|
|
if (score < bestScore)
|
|
{
|
|
bestScore = score;
|
|
best = comp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best == null || best.Count < 4) return false;
|
|
|
|
double centerX = best.Average(p => p.x);
|
|
double centerY = best.Average(p => p.y);
|
|
|
|
var used = new HashSet<long>();
|
|
|
|
long Pick(IEnumerable<(long id, int x, int y)> list)
|
|
{
|
|
foreach (var p in list)
|
|
{
|
|
if (used.Add(p.id)) return p.id;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
var upList = best.OrderBy(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
|
|
var downList = best.OrderByDescending(p => p.y).ThenBy(p => Math.Abs(p.x - centerX)).ToList();
|
|
var leftList = best.OrderBy(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
|
|
var rightList = best.OrderByDescending(p => p.x).ThenBy(p => Math.Abs(p.y - centerY)).ToList();
|
|
|
|
long up = Pick(upList);
|
|
long down = Pick(downList);
|
|
long left = Pick(leftList);
|
|
long right = Pick(rightList);
|
|
|
|
if (up <= 0 || down <= 0 || left <= 0 || right <= 0) return false;
|
|
|
|
CTRL_UP = (int)up;
|
|
CTRL_RIGHT = (int)right;
|
|
CTRL_DOWN = (int)down;
|
|
CTRL_LEFT = (int)left;
|
|
|
|
return true;
|
|
}
|
|
|
|
Log("Snake Auto (AutoCalib) started");
|
|
|
|
bool calibrated = TryAutoCalibrateControls();
|
|
if (calibrated)
|
|
Log($"Auto-calibrated controls: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
|
else
|
|
Log($"Auto-calibration failed, using fallback: U={CTRL_UP} R={CTRL_RIGHT} D={CTRL_DOWN} L={CTRL_LEFT}");
|
|
|
|
Log("Waiting for snake + food spawn...");
|
|
|
|
while (Run)
|
|
{
|
|
Delay(15);
|
|
|
|
var curSnake = new HashSet<(int x, int y)>();
|
|
food = (-1, -1);
|
|
|
|
foreach (var item in FloorItems)
|
|
{
|
|
if (item == null) continue;
|
|
int k;
|
|
try { k = (int)item.Kind; } catch { continue; }
|
|
|
|
if (k == KIND_SNAKE && !Wall(item.Location.X, item.Location.Y))
|
|
curSnake.Add((item.Location.X, item.Location.Y));
|
|
else if (k == KIND_FOOD)
|
|
food = (item.Location.X, item.Location.Y);
|
|
}
|
|
|
|
int curLen = curSnake.Count;
|
|
if (curLen == 0)
|
|
{
|
|
body.Clear();
|
|
bodySet.Clear();
|
|
continue;
|
|
}
|
|
|
|
if (body.Count == 0)
|
|
{
|
|
var sorted = curSnake.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
|
|
body = new List<(int x, int y)>(sorted);
|
|
bodySet = new HashSet<(int x, int y)>(sorted);
|
|
continue;
|
|
}
|
|
|
|
var head = body[0];
|
|
(int x, int y) newHead = (-1, -1);
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Step(head.x, head.y, d, out int nx, out int ny);
|
|
if (curSnake.Contains((nx, ny)) && !bodySet.Contains((nx, ny)))
|
|
{
|
|
newHead = (nx, ny);
|
|
if ((DateTime.Now - lastCmdTime).TotalMilliseconds > 400)
|
|
dir = DIRS[d];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newHead.x != -1)
|
|
{
|
|
int prevLen = body.Count;
|
|
body.Insert(0, newHead);
|
|
bodySet.Add(newHead);
|
|
ate = curLen > prevLen;
|
|
|
|
while (body.Count > curLen)
|
|
{
|
|
var tail = body[body.Count - 1];
|
|
bodySet.Remove(tail);
|
|
body.RemoveAt(body.Count - 1);
|
|
}
|
|
}
|
|
|
|
if (body.Count == 0 || food.x == -1) continue;
|
|
|
|
head = body[0];
|
|
int curDir = DirToIdx(dir);
|
|
|
|
Step(head.x, head.y, curDir, out int fx, out int fy);
|
|
if (Blocked(fx, fy, true))
|
|
{
|
|
SendDir(Emergency(head.x, head.y, curDir));
|
|
continue;
|
|
}
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == Opp(curDir)) continue;
|
|
Step(head.x, head.y, d, out int nx, out int ny);
|
|
if (nx == food.x && ny == food.y && !Blocked(nx, ny, true))
|
|
{
|
|
SendDir(d);
|
|
goto nextLoop;
|
|
}
|
|
}
|
|
|
|
SendDir(BestMove(head.x, head.y, food.x, food.y, curDir));
|
|
|
|
nextLoop:;
|
|
} |