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})"; } string targetFurniName = "Number Tile Dark"; int stepDelayMilliseconds = 2000; Log("Dominosa Auto-Config Solver v7 Initialized."); Log("Phase 1: Scanning room to auto-detect board boundaries..."); var numberTiles = new Dictionary(); var connectionTiles = new Dictionary(); var floorMap = new Dictionary>(); int minX = int.MaxValue, minY = int.MaxValue; int maxX = int.MinValue, maxY = int.MinValue; try { if (FloorItems == null) { Log("ERROR: Cannot access FloorItems."); return; } 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 (item.GetName() == targetFurniName) { numberTiles[p] = item; if (p.X < minX) minX = p.X; if (p.Y < minY) minY = p.Y; if (p.X > maxX) maxX = p.X; if (p.Y > maxY) maxY = p.Y; } } if (numberTiles.Count == 0) { Log("No 'Number Tile Dark' furni found. Cannot determine board area. Stopping."); return; } Log($"Auto-detected board boundaries: X({minX}-{maxX}), Y({minY}-{maxY})."); var numberTileLocations = new HashSet(numberTiles.Keys); foreach (Point p1 in numberTileLocations) { Point p2_horiz = new Point(p1.X + 4, p1.Y); if (numberTileLocations.Contains(p2_horiz)) connectionTiles[new Point(p1.X + 2, p1.Y)] = (p1, p2_horiz); Point p2_vert = new Point(p1.X, p1.Y + 4); if (numberTileLocations.Contains(p2_vert)) connectionTiles[new Point(p1.X, p1.Y + 2)] = (p1, p2_vert); } } catch (Exception ex) { Log($"ERROR during analysis: {ex.Message}"); return; } Log($"Analysis complete. Found {numberTiles.Count} numbers and {connectionTiles.Count} connections."); Func isGapTileConnected = (p) => { if (floorMap.TryGetValue(p, out var stack)) return stack.Any(f => f.GetName() == "Dark Tile" && Math.Abs(f.Location.Z - 0.25) < 0.001); return false; }; var activeConnections = new HashSet(); foreach (var c in connectionTiles) { bool isHorizontal = numberTiles.ContainsKey(new Point(c.Key.X - 2, c.Key.Y)); if (isHorizontal) { if (isGapTileConnected(new Point(c.Key.X - 1, c.Key.Y)) && isGapTileConnected(c.Key) && isGapTileConnected(new Point(c.Key.X + 1, c.Key.Y))) activeConnections.Add(c.Key); } else { if (isGapTileConnected(new Point(c.Key.X, c.Key.Y - 1)) && isGapTileConnected(c.Key) && isGapTileConnected(new Point(c.Key.X, c.Key.Y + 1))) activeConnections.Add(c.Key); } } Log($"Detected {activeConnections.Count} currently active connections on the board."); var idealConnections = new HashSet(); var pairedTiles = new HashSet(); var usedDominoes = new HashSet<(int, int)>(); while (true) { bool moveMadeThisIteration = false; foreach (var tilePos in numberTiles.Keys.Where(p => !pairedTiles.Contains(p))) { var possiblePartners = new List(); Point[] neighborChecks = { new Point(tilePos.X - 4, tilePos.Y), new Point(tilePos.X + 4, tilePos.Y), new Point(tilePos.X, tilePos.Y - 4), new Point(tilePos.X, tilePos.Y + 4) }; foreach (var partnerPos in neighborChecks) { if (numberTiles.ContainsKey(partnerPos) && !pairedTiles.Contains(partnerPos)) { var domino = (Math.Min(numberTiles[tilePos].State, numberTiles[partnerPos].State), Math.Max(numberTiles[tilePos].State, numberTiles[partnerPos].State)); if (!usedDominoes.Contains(domino)) possiblePartners.Add(partnerPos); } } if (possiblePartners.Count == 1) { var partnerPos = possiblePartners.First(); var domino = (Math.Min(numberTiles[tilePos].State, numberTiles[partnerPos].State), Math.Max(numberTiles[tilePos].State, numberTiles[partnerPos].State)); var connection = connectionTiles.First(kvp => (kvp.Value.Item1.Equals(tilePos) && kvp.Value.Item2.Equals(partnerPos)) || (kvp.Value.Item1.Equals(partnerPos) && kvp.Value.Item2.Equals(tilePos))).Key; idealConnections.Add(connection); pairedTiles.Add(tilePos); pairedTiles.Add(partnerPos); usedDominoes.Add(domino); moveMadeThisIteration = true; break; } } if (moveMadeThisIteration) continue; var dominoPossibilities = new Dictionary<(int, int), List>(); foreach (var c in connectionTiles) { Point p1 = c.Value.Item1; Point p2 = c.Value.Item2; if (!pairedTiles.Contains(p1) && !pairedTiles.Contains(p2)) { var domino = (Math.Min(numberTiles[p1].State, numberTiles[p2].State), Math.Max(numberTiles[p1].State, numberTiles[p2].State)); if (!usedDominoes.Contains(domino)) { if (!dominoPossibilities.ContainsKey(domino)) dominoPossibilities[domino] = new List(); dominoPossibilities[domino].Add(c.Key); } } } var forcedDomino = dominoPossibilities.FirstOrDefault(kvp => kvp.Value.Count == 1); if (!forcedDomino.Equals(default(KeyValuePair<(int, int), List>))) { var connection = forcedDomino.Value.First(); var (p1, p2) = connectionTiles[connection]; var domino = forcedDomino.Key; idealConnections.Add(connection); pairedTiles.Add(p1); pairedTiles.Add(p2); usedDominoes.Add(domino); moveMadeThisIteration = true; continue; } if (!moveMadeThisIteration) break; } bool success = false; if (pairedTiles.Count == numberTiles.Count) { success = true; } else { var precomputedNeighbors = numberTiles.Keys.ToDictionary(p => p, p => new Point[] { new Point(p.X - 4, p.Y), new Point(p.X + 4, p.Y), new Point(p.X, p.Y - 4), new Point(p.X, p.Y + 4) }.Where(n => numberTiles.ContainsKey(n)).ToList()); bool SolveRecursive() { if (pairedTiles.Count == numberTiles.Count) return true; var firstUnpaired = numberTiles.Keys.First(p => !pairedTiles.Contains(p)); foreach (var neighbor in precomputedNeighbors[firstUnpaired]) { if (pairedTiles.Contains(neighbor)) continue; var domino = (Math.Min(numberTiles[firstUnpaired].State, numberTiles[neighbor].State), Math.Max(numberTiles[firstUnpaired].State, numberTiles[neighbor].State)); if (usedDominoes.Contains(domino)) continue; var connection = connectionTiles.First(kvp => (kvp.Value.Item1.Equals(firstUnpaired) && kvp.Value.Item2.Equals(neighbor)) || (kvp.Value.Item1.Equals(neighbor) && kvp.Value.Item2.Equals(firstUnpaired))).Key; pairedTiles.Add(firstUnpaired); pairedTiles.Add(neighbor); usedDominoes.Add(domino); idealConnections.Add(connection); if (SolveRecursive()) return true; idealConnections.Remove(connection); usedDominoes.Remove(domino); pairedTiles.Remove(neighbor); pairedTiles.Remove(firstUnpaired); } return false; } success = SolveRecursive(); } if (!success) { Log("CRITICAL ERROR: The solver could not find a valid solution."); return; } var movesToDisconnect = new List(); foreach(Point p in activeConnections) { if (!idealConnections.Contains(p)) movesToDisconnect.Add(p); } var movesToConnect = new List(); foreach(Point p in idealConnections) { if (!activeConnections.Contains(p)) movesToConnect.Add(p); } if (movesToDisconnect.Count == 0 && movesToConnect.Count == 0) { Log("Board is already solved."); return; } if (movesToDisconnect.Any()) { Log($"--- [PLAN: UNDOING {movesToDisconnect.Count} MOVES] ---"); movesToDisconnect.ForEach(p => Log($" -> Click {p}")); Delay(2000); for (int i = 0; i < movesToDisconnect.Count; i++) { var p = movesToDisconnect[i]; Send(Out["MoveAvatar"], p.X, p.Y); Delay(stepDelayMilliseconds); } } if (movesToConnect.Any()) { Log($"--- [PLAN: MAKING {movesToConnect.Count} MOVES] ---"); movesToConnect.ForEach(p => Log($" -> Click {p}")); Delay(2000); for (int i = 0; i < movesToConnect.Count; i++) { var p = movesToConnect[i]; Send(Out["MoveAvatar"], p.X, p.Y); Delay(stepDelayMilliseconds); } } Log("Execution complete.");