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.
343 lines
9.4 KiB
C#
343 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
// --- KONFIGURATION ---
|
|
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;
|
|
|
|
const int CTRL_UP = 2147418172;
|
|
const int CTRL_RIGHT = 2147418173;
|
|
const int CTRL_DOWN = 2147418174;
|
|
const int CTRL_LEFT = 2147418175;
|
|
|
|
// --- GLOBALE VARIABLEN ---
|
|
List<(int x, int y)> body = new List<(int x, int y)>(200);
|
|
HashSet<(int x, int y)> bodySet = new HashSet<(int x, int y)>(200);
|
|
(int x, int y) food = (-1, -1);
|
|
|
|
string dir = "DOWN";
|
|
string lastSentDir = "";
|
|
DateTime lastCmdTime = DateTime.MinValue;
|
|
|
|
int len = 0;
|
|
bool ate = false;
|
|
|
|
static readonly string[] DIRS = { "UP", "DOWN", "LEFT", "RIGHT" };
|
|
HashSet<(int, int)> fillVisited = new HashSet<(int, int)>(300);
|
|
Queue<(int, int)> fillQueue = new Queue<(int, int)>(300);
|
|
HashSet<(int, int)> pathVisited = new HashSet<(int, int)>(200);
|
|
Queue<((int x, int y) pos, int dist)> pathQueue = new Queue<((int, int), int)>(200);
|
|
|
|
// --- HILFSFUNKTIONEN ---
|
|
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 Nxt(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 OppIdx(int d) => d ^ 1;
|
|
|
|
bool Bad(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 Cmd(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 Fill(int sx, int sy, int limit)
|
|
{
|
|
if (Bad(sx, sy, false)) return 0;
|
|
|
|
fillVisited.Clear();
|
|
fillQueue.Clear();
|
|
|
|
fillQueue.Enqueue((sx, sy));
|
|
fillVisited.Add((sx, sy));
|
|
int c = 0;
|
|
|
|
while (fillQueue.Count > 0 && c < limit)
|
|
{
|
|
var (x, y) = fillQueue.Dequeue();
|
|
c++;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Nxt(x, y, d, out int nx, out int ny);
|
|
if (!Bad(nx, ny, false) && fillVisited.Add((nx, ny)))
|
|
fillQueue.Enqueue((nx, ny));
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// Pathfinding mit höherem Default-Limit
|
|
int GetPathDistance(int sx, int sy, int tx, int ty, int limit = 150)
|
|
{
|
|
if (sx == tx && sy == ty) return 0;
|
|
|
|
pathVisited.Clear();
|
|
pathQueue.Clear();
|
|
|
|
pathQueue.Enqueue(((sx, sy), 0));
|
|
pathVisited.Add((sx, sy));
|
|
|
|
while (pathQueue.Count > 0)
|
|
{
|
|
var curr = pathQueue.Dequeue();
|
|
if (curr.dist >= limit) return 999;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Nxt(curr.pos.x, curr.pos.y, d, out int nx, out int ny);
|
|
|
|
if (nx == tx && ny == ty) return curr.dist + 1;
|
|
if (!Bad(nx, ny, false) && pathVisited.Add((nx, ny)))
|
|
pathQueue.Enqueue(((nx, ny), curr.dist + 1));
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int DecideMove(int hx, int hy, int fx, int fy, int curDirIdx)
|
|
{
|
|
int bestDir = curDirIdx;
|
|
int bestScore = int.MinValue;
|
|
|
|
// Höhere Limits für besseres Pathfinding
|
|
int fillLimit = len > 100 ? 100 : len > 50 ? 150 : 200;
|
|
int pathLimit = len > 100 ? 80 : len > 50 ? 120 : 150;
|
|
int minSpace = Math.Max(len / 4, 10); // Noch toleranter
|
|
|
|
var tail = body.Count > 0 ? body[body.Count - 1] : (-1, -1);
|
|
|
|
// Prüfe ob Food am Rand ist
|
|
bool foodAtEdge = fx <= GAME_MIN_X + 1 || fx >= GAME_MAX_X - 1 ||
|
|
fy <= GAME_MIN_Y + 1 || fy >= GAME_MAX_Y - 1;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == OppIdx(curDirIdx)) continue;
|
|
|
|
Nxt(hx, hy, d, out int nx, out int ny);
|
|
if (Bad(nx, ny, true)) continue;
|
|
|
|
int space = Fill(nx, ny, fillLimit);
|
|
if (space < minSpace) continue;
|
|
|
|
int score = 0;
|
|
|
|
// ESSEN DIREKT DANEBEN = HÖCHSTE PRIORITÄT!
|
|
if (nx == fx && ny == fy)
|
|
{
|
|
score += 10000;
|
|
}
|
|
else
|
|
{
|
|
int distFood = GetPathDistance(nx, ny, fx, fy, pathLimit);
|
|
if (distFood > 0 && distFood < 999)
|
|
{
|
|
// Stärkerer Food-Bonus - Essen ist wichtiger!
|
|
score += (200 - distFood) * 8;
|
|
}
|
|
else if (distFood == -1)
|
|
{
|
|
// Kein Weg zum Essen gefunden - leichter Malus aber nicht zu hart
|
|
score -= 100;
|
|
}
|
|
}
|
|
|
|
// Platz-Bonus (weniger gewichtet als vorher)
|
|
score += Math.Min(space, 100) * 5;
|
|
|
|
// Safety: Schwanz erreichbar?
|
|
if (tail.Item1 != -1 && space < fillLimit)
|
|
{
|
|
int distTail = GetPathDistance(nx, ny, tail.Item1, tail.Item2, 30);
|
|
if (distTail > 0) score += 150;
|
|
else if (space < 40) score -= 300;
|
|
}
|
|
|
|
// Wandnähe - NUR wenn Food NICHT am Rand ist!
|
|
// Wenn Food am Rand -> kein Penalty für Randnähe
|
|
if (!foodAtEdge && len > 50)
|
|
{
|
|
if (nx <= GAME_MIN_X + 1 || nx >= GAME_MAX_X - 1) score -= 20;
|
|
if (ny <= GAME_MIN_Y + 1 || ny >= GAME_MAX_Y - 1) score -= 20;
|
|
}
|
|
|
|
if (score > bestScore)
|
|
{
|
|
bestScore = score;
|
|
bestDir = d;
|
|
}
|
|
}
|
|
|
|
return bestDir;
|
|
}
|
|
|
|
int EmergencyMove(int hx, int hy, int curDirIdx)
|
|
{
|
|
int bestDir = curDirIdx;
|
|
int bestSpace = -1;
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == OppIdx(curDirIdx)) continue;
|
|
Nxt(hx, hy, d, out int nx, out int ny);
|
|
if (Bad(nx, ny, true)) continue;
|
|
|
|
int space = Fill(nx, ny, 60);
|
|
if (space > bestSpace)
|
|
{
|
|
bestSpace = space;
|
|
bestDir = d;
|
|
}
|
|
}
|
|
return bestDir;
|
|
}
|
|
|
|
int DirToIdx(string d) => d == "UP" ? 0 : d == "DOWN" ? 1 : d == "LEFT" ? 2 : 3;
|
|
|
|
Log("SNAKE V16 - EDGE FOOD FIX");
|
|
|
|
DateTime lastFullCalc = DateTime.MinValue;
|
|
int cachedMove = -1;
|
|
|
|
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(); len = 0; 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);
|
|
len = curLen;
|
|
continue;
|
|
}
|
|
|
|
var head = body[0];
|
|
(int x, int y) newHead = (-1, -1);
|
|
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
Nxt(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)
|
|
{
|
|
body.Insert(0, newHead);
|
|
bodySet.Add(newHead);
|
|
ate = curLen > len;
|
|
while (body.Count > curLen)
|
|
{
|
|
var tail = body[body.Count - 1];
|
|
bodySet.Remove(tail);
|
|
body.RemoveAt(body.Count - 1);
|
|
}
|
|
len = curLen;
|
|
cachedMove = -1;
|
|
}
|
|
|
|
if (body.Count == 0) continue;
|
|
head = body[0];
|
|
int curDirIdx = DirToIdx(dir);
|
|
|
|
// NOTFALL: Wand direkt vor uns?
|
|
Nxt(head.x, head.y, curDirIdx, out int frontX, out int frontY);
|
|
if (Bad(frontX, frontY, true))
|
|
{
|
|
int emergency = EmergencyMove(head.x, head.y, curDirIdx);
|
|
Cmd(emergency);
|
|
continue;
|
|
}
|
|
|
|
if (food.x == -1) continue;
|
|
|
|
// SOFORT ESSEN wenn direkt daneben!
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if (d == OppIdx(curDirIdx)) continue;
|
|
Nxt(head.x, head.y, d, out int nx, out int ny);
|
|
|
|
if (nx == food.x && ny == food.y && !Bad(nx, ny, true))
|
|
{
|
|
int spaceAfter = Fill(nx, ny, 80);
|
|
if (spaceAfter >= Math.Max(len / 4, 8))
|
|
{
|
|
Cmd(d);
|
|
goto nextLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normale KI - öfter neu berechnen für bessere Reaktion
|
|
double msSinceCalc = (DateTime.Now - lastFullCalc).TotalMilliseconds;
|
|
int recalcInterval = len > 100 ? 100 : len > 50 ? 70 : 50;
|
|
|
|
if (cachedMove == -1 || msSinceCalc > recalcInterval)
|
|
{
|
|
cachedMove = DecideMove(head.x, head.y, food.x, food.y, curDirIdx);
|
|
lastFullCalc = DateTime.Now;
|
|
}
|
|
|
|
if (cachedMove != curDirIdx)
|
|
Cmd(cachedMove);
|
|
|
|
nextLoop:;
|
|
} |