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