All checks were successful
Build project / Build for (StandaloneLinux64, 6000.0.37f1) (push) Successful in 5m1s
Build project / Build for (StandaloneWindows64, 6000.0.37f1) (push) Successful in 5m41s
Build project / Publish to itch.io (StandaloneLinux64) (push) Successful in 5s
Build project / Publish to itch.io (StandaloneWindows64) (push) Successful in 6s
348 lines
9.6 KiB
C#
348 lines
9.6 KiB
C#
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<Point> points = new List<Point>();
|
|
private List<Edge> edges = new List<Edge>();
|
|
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<Text>();
|
|
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<RectTransform>(),
|
|
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<Image>();
|
|
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<Text>();
|
|
if (text != null)
|
|
{
|
|
text.enabled = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnRegenerateClick()
|
|
{
|
|
GenerateGame();
|
|
}
|
|
} |