xabbo-scripts/Scripts/DominosolveV4-FINAL.csx
Administrator 7a548130a3 Move all scripts into Scripts/ subfolder
Keeps the repo root clean - only README.md visible on landing page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 09:49:37 +01:00

216 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Xabbo.Core;
public struct Point : IEquatable<Point>
{
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<Point, IFloorItem>();
var connectionTiles = new Dictionary<Point, (Point, Point)>();
var floorMap = new Dictionary<Point, List<IFloorItem>>();
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<IFloorItem>();
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<Point>(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<Point, bool> 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<Point>();
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<Point>();
var pairedTiles = new HashSet<Point>();
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>();
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<Point>>();
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<Point>();
dominoPossibilities[domino].Add(c.Key);
}
}
}
var forcedDomino = dominoPossibilities.FirstOrDefault(kvp => kvp.Value.Count == 1);
if (!forcedDomino.Equals(default(KeyValuePair<(int, int), List<Point>>)))
{
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<Point>();
foreach(Point p in activeConnections) { if (!idealConnections.Contains(p)) movesToDisconnect.Add(p); }
var movesToConnect = new List<Point>();
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.");