using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; using TMPro; public class UntangleGame : MonoBehaviour { public class Point { public float x, y; public GameObject visual; public int index; } public class Edge { public int a, b; // indices of connected points public bool isCrossed; public LineRenderer line; } [Header("Game Objects")] public GameObject pointPrefab; public LineRenderer edgePrefab; [Header("Game Settings")] public int pointCount = 10; public float playAreaSize = 8f; [Header("Colors")] public Color normalEdgeColor = Color.black; public Color crossedEdgeColor = Color.red; public Color pointColor = Color.blue; public Color dragPointColor = Color.white; public Color cursorPointColor = Color.gray; public Color neighborPointColor = Color.red; [Header("Visual Settings")] public float pointRadius = 0.2f; public float dragThreshold = 0.5f; [Header("Options")] public bool snapToGrid = false; public bool showCrossedEdges = true; public bool showVertexNumbers = false; private List points = new List(); private List edges = new List(); private int draggedPoint = -1; private int cursorPoint = -1; private bool isSolved = false; private Camera mainCamera; private Material lineMaterial; void Start() { mainCamera = Camera.main; // Create material for lines to prevent pink color lineMaterial = new Material(Shader.Find("UI/Default")); lineMaterial.color = normalEdgeColor; GenerateGame(); } void GenerateGame() { // Clear existing game foreach (Point p in points) { if (p.visual != null) Destroy(p.visual); } foreach (Edge e in edges) { if (e.line != null) Destroy(e.line.gameObject); } points.Clear(); edges.Clear(); isSolved = false; // Create points in a circle (local space) for (int i = 0; i < pointCount; i++) { float angle = i * 2 * Mathf.PI / pointCount; Point p = new Point { x = Mathf.Sin(angle) * playAreaSize * 0.4f, y = Mathf.Cos(angle) * playAreaSize * 0.4f, index = i }; points.Add(p); } // Create edges (simplified planar graph) for (int i = 0; i < pointCount; i++) { int connections = Mathf.Min(3, pointCount - i - 1); for (int j = 1; j <= connections; j++) { int k = (i + j) % pointCount; edges.Add(new Edge { a = i, b = k }); } } CreateVisuals(); CheckCrossings(); } void CreateVisuals() { // Create point visuals foreach (Point p in points) { p.visual = Instantiate(pointPrefab, transform); p.visual.transform.localPosition = new Vector3(p.x, p.y, 0); p.visual.transform.localScale = Vector3.one * pointRadius * 2; if (showVertexNumbers) { Text text = p.visual.GetComponentInChildren(); if (text != null) { text.text = p.index.ToString(); text.enabled = showVertexNumbers; } } } // Create edge visuals foreach (Edge e in edges) { e.line = Instantiate(edgePrefab, transform); e.line.useWorldSpace = false; e.line.material = lineMaterial; e.line.startWidth = 0.1f; e.line.endWidth = 0.1f; UpdateEdgeVisual(e); } } void UpdateEdgeVisual(Edge e) { Point a = points[e.a]; Point b = points[e.b]; e.line.startColor = e.isCrossed && showCrossedEdges ? crossedEdgeColor : normalEdgeColor; e.line.endColor = e.line.startColor; e.line.SetPosition(0, new Vector3(a.x, a.y, 0)); e.line.SetPosition(1, new Vector3(b.x, b.y, 0)); } void Update() { HandleInput(); UpdateVisuals(); } void HandleInput() { Vector2 localMousePos; RectTransformUtility.ScreenPointToLocalPointInRectangle( GetComponent(), Input.mousePosition, mainCamera, out localMousePos); Debug.Log($"Mouse Position: {localMousePos}"); if (Input.GetMouseButtonDown(0)) { int closest = FindClosestPoint(localMousePos); if (closest != -1) draggedPoint = closest; } else if (Input.GetMouseButton(0) && draggedPoint != -1) { if (snapToGrid) { float gridSize = playAreaSize / (pointCount - 1); points[draggedPoint].x = Mathf.Round(localMousePos.x / gridSize) * gridSize; points[draggedPoint].y = Mathf.Round(localMousePos.y / gridSize) * gridSize; } else { points[draggedPoint].x = localMousePos.x; points[draggedPoint].y = localMousePos.y; } // Keep within bounds points[draggedPoint].x = Mathf.Clamp(points[draggedPoint].x, -playAreaSize/2, playAreaSize/2); points[draggedPoint].y = Mathf.Clamp(points[draggedPoint].y, -playAreaSize/2, playAreaSize/2); CheckCrossings(); } else if (Input.GetMouseButtonUp(0)) { draggedPoint = -1; } else { cursorPoint = FindClosestPoint(localMousePos); } } int FindClosestPoint(Vector2 localPosition) { int closest = -1; float minDist = float.MaxValue; for (int i = 0; i < points.Count; i++) { float dist = Vector2.Distance(localPosition, new Vector2(points[i].x, points[i].y)); Debug.Log($"Point {i}: Distance {dist}"); if (dist < dragThreshold && dist < minDist) { minDist = dist; closest = i; } } Debug.Log($"Closest point: {closest} at distance {minDist}"); return closest; } void UpdateVisuals() { // Update point positions for (int i = 0; i < points.Count; i++) { Point p = points[i]; p.visual.transform.localPosition = new Vector3(p.x, p.y, 0); // Update point color Image img = p.visual.GetComponent(); if (img != null) { if (i == draggedPoint) img.color = dragPointColor; else if (i == cursorPoint) img.color = cursorPointColor; else if (draggedPoint != -1 && IsConnected(draggedPoint, i)) img.color = neighborPointColor; else img.color = pointColor; } } // Update edges foreach (Edge e in edges) { UpdateEdgeVisual(e); } } bool IsConnected(int a, int b) { foreach (Edge e in edges) { if ((e.a == a && e.b == b) || (e.a == b && e.b == a)) return true; } return false; } void CheckCrossings() { bool anyCrossings = false; // Reset all edges foreach (Edge e in edges) { e.isCrossed = false; } // Check all edge pairs for crossings for (int i = 0; i < edges.Count; i++) { Edge e1 = edges[i]; Point a1 = points[e1.a]; Point a2 = points[e1.b]; for (int j = i + 1; j < edges.Count; j++) { Edge e2 = edges[j]; Point b1 = points[e2.a]; Point b2 = points[e2.b]; // Skip if edges share a point if (e1.a == e2.a || e1.a == e2.b || e1.b == e2.a || e1.b == e2.b) continue; if (DoLinesIntersect(a1, a2, b1, b2)) { e1.isCrossed = true; e2.isCrossed = true; anyCrossings = true; } } } isSolved = !anyCrossings; } bool DoLinesIntersect(Point a1, Point a2, Point b1, Point b2) { Vector2 p1 = new Vector2(a1.x, a1.y); Vector2 p2 = new Vector2(a2.x, a2.y); Vector2 p3 = new Vector2(b1.x, b1.y); Vector2 p4 = new Vector2(b2.x, b2.y); float d = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x); if (d == 0) return false; float u = ((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d; float v = ((p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x)) / d; return (u >= 0) && (u <= 1) && (v >= 0) && (v <= 1); } public void OnSnapToGridToggle(bool value) { snapToGrid = value; } public void OnShowCrossedEdgesToggle(bool value) { showCrossedEdges = value; foreach (Edge e in edges) { UpdateEdgeVisual(e); } } public void OnVertexNumbersToggle(bool value) { showVertexNumbers = value; foreach (Point p in points) { Text text = p.visual.GetComponentInChildren(); if (text != null) { text.enabled = value; } } } public void OnRegenerateClick() { GenerateGame(); } }