untangle-puzzlegame/Assets/Scripts/UntangleGameManager.cs
2025-04-17 17:33:08 -04:00

238 lines
6.6 KiB
C#

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<Point> points = new List<Point>();
public List<Connection> connections = new List<Connection>();
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<Point>();
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;
}
}