using System; using System.Collections.Generic; using System.Linq; const int CLICK_DELAY = 2500; const int GAME_MIN_X = 9; const int GAME_MAX_X = 26; const int GAME_MIN_Y = 12; const int GAME_MAX_Y = 29; const int GRID_W = 18; const int GRID_H = 18; const int TOTAL = 324; const int KIND_TILE = 3666; Dictionary tileIdByColor = new Dictionary(); int[,] grid = new int[GRID_W, GRID_H]; int GetState(dynamic item) { try { return int.Parse(item.State?.ToString() ?? "0"); } catch { return 0; } } int GetId(dynamic item) { try { return (int)item.Id; } catch { return 0; } } int GetKind(dynamic item) { try { return (int)item.Kind; } catch { return -1; } } void ReadGrid() { Array.Clear(grid, 0, grid.Length); tileIdByColor.Clear(); foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != KIND_TILE) continue; int x = item.Location.X, y = item.Location.Y; if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue; int gx = x - GAME_MIN_X, gy = GAME_MAX_Y - y; int state = GetState(item), id = GetId(item); if (gx >= 0 && gx < GRID_W && gy >= 0 && gy < GRID_H) { grid[gx, gy] = state; if (!tileIdByColor.ContainsKey(state)) tileIdByColor[state] = id; } } } HashSet<(int,int)> Flood(HashSet<(int,int)> area, int color) { var result = new HashSet<(int,int)>(area); var q = new Queue<(int,int)>(); foreach (var p in area) q.Enqueue(p); while (q.Count > 0) { var (x, y) = q.Dequeue(); foreach (var (nx, ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}) { if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue; if (result.Contains((nx,ny))) continue; if (grid[nx,ny] == color) { result.Add((nx,ny)); q.Enqueue((nx,ny)); } } } return result; } HashSet<(int,int)> InitArea() { var area = new HashSet<(int,int)>{(0,0)}; return Flood(area, grid[0,0]); } HashSet BorderColors(HashSet<(int,int)> area) { var c = new HashSet(); foreach (var (x,y) in area) { if (x > 0 && !area.Contains((x-1,y))) c.Add(grid[x-1,y]); if (x < GRID_W-1 && !area.Contains((x+1,y))) c.Add(grid[x+1,y]); if (y > 0 && !area.Contains((x,y-1))) c.Add(grid[x,y-1]); if (y < GRID_H-1 && !area.Contains((x,y+1))) c.Add(grid[x,y+1]); } return c; } HashSet RemainingColors(HashSet<(int,int)> area) { var colors = new HashSet(); for (int x = 0; x < GRID_W; x++) for (int y = 0; y < GRID_H; y++) if (!area.Contains((x,y))) colors.Add(grid[x,y]); return colors; } int CountRegions(HashSet<(int,int)> area) { var visited = new bool[GRID_W, GRID_H]; foreach (var (x,y) in area) visited[x,y] = true; int regions = 0; for (int sx = 0; sx < GRID_W; sx++) { for (int sy = 0; sy < GRID_H; sy++) { if (visited[sx,sy]) continue; regions++; var q = new Queue<(int,int)>(); q.Enqueue((sx,sy)); visited[sx,sy] = true; int c = grid[sx,sy]; while (q.Count > 0) { var (x,y) = q.Dequeue(); foreach (var (nx,ny) in new[]{(x-1,y),(x+1,y),(x,y-1),(x,y+1)}) { if (nx < 0 || nx >= GRID_W || ny < 0 || ny >= GRID_H) continue; if (visited[nx,ny]) continue; if (grid[nx,ny] == c) { visited[nx,ny] = true; q.Enqueue((nx,ny)); } } } } } return regions; } int Heuristic(HashSet<(int,int)> area) { var remaining = RemainingColors(area); int regions = CountRegions(area); return Math.Max(remaining.Count, (regions + 1) / 2); } bool EliminatesColor(HashSet<(int,int)> area, int color) { var newArea = Flood(area, color); for (int x = 0; x < GRID_W; x++) for (int y = 0; y < GRID_H; y++) if (!newArea.Contains((x,y)) && grid[x,y] == color) return false; return true; } int Eval(HashSet<(int,int)> area, int depth, int alpha) { if (area.Count == TOTAL) return 1000 - depth; if (depth <= 0) return area.Count - Heuristic(area) * 10; var borders = BorderColors(area); int best = -999; var moves = borders.Select(c => { var next = Flood(area, c); bool elim = EliminatesColor(area, c); return (c, next.Count - area.Count, elim, next); }).OrderByDescending(m => m.Item3 ? 1000 : 0) .ThenByDescending(m => m.Item2).ToList(); foreach (var (c, gain, elim, next) in moves) { int score = Eval(next, depth - 1, best); if (score > best) best = score; if (best >= alpha) break; } return best; } (int color, int score) BestMove(HashSet<(int,int)> area, int depth) { var borders = BorderColors(area); int bestC = 0, bestS = -9999; var moves = borders.Select(c => { var next = Flood(area, c); bool elim = EliminatesColor(area, c); return (c, next.Count - area.Count, elim, next); }).OrderByDescending(m => m.Item3 ? 1000 : 0) .ThenByDescending(m => m.Item2).ToList(); foreach (var (c, gain, elim, next) in moves) { int score; if (next.Count == TOTAL) score = 10000; else score = Eval(next, depth - 1, bestS) + (elim ? 50 : 0); if (score > bestS) { bestS = score; bestC = c; } } return (bestC, bestS); } List Solve() { var moves = new List(); var area = InitArea(); while (area.Count < TOTAL && moves.Count < 32) { int remaining = TOTAL - area.Count; int depth = remaining > 200 ? 3 : remaining > 100 ? 4 : remaining > 50 ? 5 : 6; var (c, s) = BestMove(area, depth); if (c == 0) break; moves.Add(c); area = Flood(area, c); } return moves; } void Click(int color) { foreach (var item in FloorItems) { if (item == null) continue; if (GetKind(item) != KIND_TILE) continue; int x = item.Location.X, y = item.Location.Y; if (x < GAME_MIN_X || x > GAME_MAX_X || y < GAME_MIN_Y || y > GAME_MAX_Y) continue; int state = GetState(item); if (state == color) { Send(Out["ClickFurni"], (int)item.Id, 0); return; } } } Log("═══════════════════════════════════════════"); Log(" FLOOD-IT BOT - RESEARCH BASED"); Log("═══════════════════════════════════════════"); while (Run) { ReadGrid(); var area = InitArea(); Log($"Start: {area.Count}/{TOTAL}"); if (area.Count == TOTAL) { Log("COMPLETE!"); Delay(2000); continue; } Log("Solving..."); var sw = System.Diagnostics.Stopwatch.StartNew(); var solution = Solve(); sw.Stop(); Log($"Solution: {string.Join(",", solution)} ({solution.Count} moves) in {sw.ElapsedMilliseconds}ms"); foreach (var c in solution) { Click(c); Log($"Click {c}"); Delay(CLICK_DELAY); } Delay(1000); }