using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class UntangleGameManager : MonoBehaviour { public GameObject pointPrefab; public LineRenderer linePrefab; public Text statusText; public int pointCount = 10; public Button newGameButton, solveButton; private List points = new List(); public List connections = new List(); private Vector3[] solutionPositions; void Start() { newGameButton.onClick.AddListener(GenerateRandomGame); solveButton.onClick.AddListener(AutoSolve); GenerateRandomGame(); } void GenerateRandomGame() { ClearOldGame(); statusText.text = ""; points.Clear(); connections.Clear(); solutionPositions = new Vector3[pointCount]; // Step 1: Place points randomly (solution layout) for (int i = 0; i < pointCount; i++) { Vector2 pos; int tries = 0; do { pos = Random.insideUnitCircle * 4f; tries++; if (tries > 1000) break; } while (IsTooClose(pos)); GameObject obj = Instantiate(pointPrefab, pos, Quaternion.identity); Point p = obj.GetComponent(); p.id = i; p.manager = this; points.Add(p); solutionPositions[i] = pos; } // Step 2: Create a Minimum Spanning Tree (MST) List<(int, int, float)> edges = new List<(int, int, float)>(); for (int i = 0; i < pointCount; i++) { for (int j = i + 1; j < pointCount; j++) { float dist = Vector2.Distance(points[i].transform.position, points[j].transform.position); edges.Add((i, j, dist)); } } edges.Sort((a, b) => a.Item3.CompareTo(b.Item3)); int[] parent = new int[pointCount]; for (int i = 0; i < pointCount; i++) parent[i] = i; int Find(int x) => parent[x] == x ? x : parent[x] = Find(parent[x]); void Union(int x, int y) => parent[Find(x)] = Find(y); int edgeCount = 0; foreach (var (a, b, _) in edges) { if (Find(a) != Find(b)) { Union(a, b); AddConnection(a, b); edgeCount++; if (edgeCount == pointCount - 1) break; } } // Step 3: Ensure every point has at least degree 2 int[] degrees = new int[pointCount]; foreach (var c in connections) { degrees[c.a.id]++; degrees[c.b.id]++; } for (int i = 0; i < pointCount; i++) { while (degrees[i] < 2) { bool added = false; // Try to add a non-crossing edge for (int j = 0; j < pointCount; j++) { if (i == j || IsConnected(i, j)) continue; Connection temp = new Connection { a = points[i], b = points[j] }; if (DoesIntersectAny(temp)) continue; AddConnection(i, j); degrees[i]++; degrees[j]++; added = true; break; } // If no valid non-crossing edge found, allow one forced connection (rare case) if (!added) { for (int j = 0; j < pointCount; j++) { if (i == j || IsConnected(i, j)) continue; AddConnection(i, j); degrees[i]++; degrees[j]++; break; } } } } // Step 4: Shuffle point positions to create the puzzle foreach (var p in points) { p.transform.position = Random.insideUnitCircle * 4f; } foreach (var c in connections) c.UpdateLine(); } void AddConnection(int a, int b) { Connection c = new Connection(); c.a = points[a]; c.b = points[b]; c.line = Instantiate(linePrefab); c.UpdateLine(); connections.Add(c); } bool IsConnected(int a, int b) { foreach (var c in connections) { if ((c.a.id == a && c.b.id == b) || (c.a.id == b && c.b.id == a)) return true; } return false; } bool DoesIntersectAny(Connection newC) { foreach (var c in connections) { if (newC.SharesPointWith(c)) continue; if (LinesIntersect(newC, c)) return true; } return false; } bool IsTooClose(Vector2 pos) { foreach (var p in points) { if (Vector2.Distance(p.transform.position, pos) < 0.5f) return true; } return false; } public void CheckIfSolved() { foreach (var c1 in connections) { foreach (var c2 in connections) { if (c1 == c2 || c1.SharesPointWith(c2)) continue; if (LinesIntersect(c1, c2)) { statusText.text = ""; return; } } } statusText.text = "Completed!"; } void AutoSolve() { for (int i = 0; i < points.Count; i++) { points[i].transform.position = solutionPositions[i]; } foreach (var c in connections) c.UpdateLine(); CheckIfSolved(); } void ClearOldGame() { foreach (var p in points) Destroy(p.gameObject); foreach (var c in connections) Destroy(c.line.gameObject); } bool LinesIntersect(Connection c1, Connection c2) { Vector2 p1 = c1.a.transform.position; Vector2 q1 = c1.b.transform.position; Vector2 p2 = c2.a.transform.position; Vector2 q2 = c2.b.transform.position; return DoIntersect(p1, q1, p2, q2); } bool DoIntersect(Vector2 p1, Vector2 q1, Vector2 p2, Vector2 q2) { int o1 = Orientation(p1, q1, p2); int o2 = Orientation(p1, q1, q2); int o3 = Orientation(p2, q2, p1); int o4 = Orientation(p2, q2, q1); return (o1 != o2 && o3 != o4); } int Orientation(Vector2 a, Vector2 b, Vector2 c) { float val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); if (Mathf.Abs(val) < 0.0001f) return 0; return (val > 0) ? 1 : 2; } }