xabbo-scripts/Snake Auto (AutoCalib).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

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:;
}