using System; using System.Collections.Generic; using System.Linq; using Xabbo.Core; public struct Point : IEquatable { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public bool Equals(Point other) => X == other.X && Y == other.Y; public override bool Equals(object obj) => obj is Point other && Equals(other); public override int GetHashCode() => HashCode.Combine(X, Y); public override string ToString() => $"({X}, {Y})"; } int pickupClickDelayMilliseconds = 25; int placementDelayMilliseconds = 30; var floorMap = new Dictionary>(); var blockSourceIds = new Dictionary(); var rowConstraints = new int[6, 4]; var colConstraints = new int[6, 4]; var hasCircleRow = new bool[6, 4]; var hasCircleCol = new bool[6, 4]; var grid = new int[6, 6]; string[] colors = { "Red", "Pink", "Blue", "Green" }; var blockNamesToColors = new Dictionary { { "Großer Bauklotz 5", 0 }, { "Großer Bauklotz 4", 1 }, { "Großer Bauklotz 11", 2 }, { "Großer Bauklotz 8", 3 } }; Log("Sudoku Solver v11 Initialized."); foreach (IFloorItem item in FloorItems) { if (item == null) continue; var p = new Point(item.Location.X, item.Location.Y); if (!floorMap.ContainsKey(p)) floorMap[p] = new List(); floorMap[p].Add(item); if (p.X == 19 && p.Y == 19 && blockNamesToColors.TryGetValue(item.GetName(), out int colorIndex)) { blockSourceIds[colors[colorIndex].ToLower()] = item.Id; } } if (blockSourceIds.Count < 4) { Log("ERROR: Could not find all four source blocks at (19,19)."); return; } Func hasCircleMarker = p => floorMap.TryGetValue(p, out var items) && items.Any(i => i.GetName() == "Nummernbauklotz") && items.Any(i => i.GetName().StartsWith("Großer Bauklotz")); for (int i = 0; i < 6; i++) { int y = 23 + i; int x = 23 + i; int[][] rC = { new[] { 16, y }, new[] { 18, y }, new[] { 20, y }, new[] { 22, y } }; int[][] cC = { new[] { x, 16 }, new[] { x, 18 }, new[] { x, 20 }, new[] { x, 22 } }; for (int color = 0; color < 4; color++) { var rP = new Point(rC[color][0], rC[color][1]); if (floorMap.TryGetValue(rP, out var rI)) { var t = rI.FirstOrDefault(it => it.GetName() == "Nummernbauklotz"); if (t != null) { rowConstraints[i, color] = t.State; hasCircleRow[i, color] = hasCircleMarker(rP); } } var cP = new Point(cC[color][0], cC[color][1]); if (floorMap.TryGetValue(cP, out var cI)) { var t = cI.FirstOrDefault(it => it.GetName() == "Nummernbauklotz"); if (t != null) { colConstraints[i, color] = t.State; hasCircleCol[i, color] = hasCircleMarker(cP); } } } } var initialPlacements = new HashSet(); for (int y = 0; y < 6; y++) for (int x = 0; x < 6; x++) { grid[y, x] = -1; var gP = new Point(23 + x, 23 + y); if (floorMap.TryGetValue(gP, out var items)) { var b = items.FirstOrDefault(i => i.GetName().StartsWith("Großer Bauklotz")); if (b != null && blockNamesToColors.TryGetValue(b.GetName(), out int cV)) { grid[y, x] = cV; initialPlacements.Add(new Point(x,y)); } } } Func IsValid = (r, c) => { var rowCounts = new int[4]; bool rowIsFull = true; for (int i = 0; i < 6; i++) { if (grid[r, i] == -1) rowIsFull = false; else rowCounts[grid[r, i]]++; } if (rowIsFull) { for (int color = 0; color < 4; color++) { if (rowCounts[color] != rowConstraints[r, color]) return false; var indices = Enumerable.Range(0, 6).Where(i => grid[r,i] == color).ToList(); if(indices.Count > 1 && (hasCircleRow[r, color] != (indices.Last() - indices.First() + 1 == indices.Count))) return false; } } else { for (int color = 0; color < 4; color++) { if (rowCounts[color] > rowConstraints[r, color]) return false; } } var colCounts = new int[4]; bool colIsFull = true; for (int i = 0; i < 6; i++) { if (grid[i, c] == -1) colIsFull = false; else colCounts[grid[i, c]]++; } if (colIsFull) { for (int color = 0; color < 4; color++) { if (colCounts[color] != colConstraints[c, color]) return false; var indices = Enumerable.Range(0, 6).Where(i => grid[i,c] == color).ToList(); if(indices.Count > 1 && (hasCircleCol[c, color] != (indices.Last() - indices.First() + 1 == indices.Count))) return false; } } else { for (int color = 0; color < 4; color++) { if (colCounts[color] > colConstraints[c, color]) return false; } } return true; }; Func solve = null; solve = () => { int nextR = -1, nextC = -1; for (int r = 0; r < 6; r++) for (int c = 0; c < 6; c++) if (grid[r, c] == -1) { nextR = r; nextC = c; goto found; } found:; if (nextR == -1) return true; for (int color = 0; color < 4; color++) { grid[nextR, nextC] = color; if (IsValid(nextR, nextC) && solve()) return true; } grid[nextR, nextC] = -1; return false; }; Log("\nSolving..."); if (solve()) { Log("Solution found. Placing blocks..."); var placementsByColor = Enumerable.Range(0,4).ToDictionary(i => colors[i].ToLower(), i => new List()); for (int y = 0; y < 6; y++) for (int x = 0; x < 6; x++) { if (!initialPlacements.Contains(new Point(x,y))) placementsByColor[colors[grid[y, x]].ToLower()].Add(new Point(x, y)); } int totalPlacements = placementsByColor.Sum(kvp => kvp.Value.Count); if (totalPlacements == 0) { Log("Puzzle is already solved."); } else { foreach(var kvp in placementsByColor.Where(kvp => kvp.Value.Any())) { Log($"--- Placing {kvp.Value.Count} {kvp.Key.ToUpper()} blocks ---"); Send(Out["ClickFurni"], blockSourceIds[kvp.Key], 0); Delay(pickupClickDelayMilliseconds); Send(Out["ClickFurni"], blockSourceIds[kvp.Key], 0); Delay(pickupClickDelayMilliseconds); foreach(Point p in kvp.Value) { Send(Out["MoveAvatar"], 23 + p.X, 23 + p.Y); Delay(placementDelayMilliseconds); } } Log($"\nPlaced {totalPlacements} blocks."); } } else { Log("\nERROR: No solution found."); } Log("Execution complete.");