308 lines
11 KiB
C#
308 lines
11 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
public class MapGenerator : MonoBehaviour
|
|
{
|
|
// Directions: Right, Up, Left, Down (matches Net game encoding)
|
|
private static readonly Vector2Int[] directions = {
|
|
new Vector2Int(1, 0), // R
|
|
new Vector2Int(0, 1), // U
|
|
new Vector2Int(-1, 0), // L
|
|
new Vector2Int(0, -1) // D
|
|
};
|
|
|
|
public static void Generate(Tile[,] tiles, GameObject[] prefabs, int width, int height, int seed,
|
|
bool wrapping = false, bool uniqueSolution = true, float barrierProbability = 0f)
|
|
{
|
|
Random.InitState(seed);
|
|
|
|
// 1. Generate the solution grid using Net's algorithm
|
|
var solution = GenerateSolution(width, height, wrapping);
|
|
|
|
// 2. Add barriers
|
|
var barriers = AddBarriers(solution, width, height, wrapping, barrierProbability);
|
|
|
|
// 3. Create tiles and apply solution
|
|
CreateTiles(tiles, prefabs, width, height, solution, barriers);
|
|
|
|
// 4. Shuffle (random rotation)
|
|
ShuffleTiles(tiles, width, height, seed);
|
|
}
|
|
|
|
private static byte[,] GenerateSolution(int width, int height, bool wrapping)
|
|
{
|
|
byte[,] tiles = new byte[width, height];
|
|
int cx = width / 2;
|
|
int cy = height / 2;
|
|
|
|
// Use a priority queue (simplified with List for this example)
|
|
var possibilities = new List<(int x, int y, int dir)>();
|
|
|
|
// Start from center
|
|
AddPossibleDirections(possibilities, cx, cy, width, height, wrapping);
|
|
while (possibilities.Count > 0)
|
|
{
|
|
// Get random possibility
|
|
int idx = Random.Range(0, possibilities.Count);
|
|
var (x1, y1, d1) = possibilities[idx];
|
|
possibilities.RemoveAt(idx);
|
|
|
|
// Calculate destination
|
|
int x2 = Wrap(x1 + directions[d1].x, width, wrapping);
|
|
int y2 = Wrap(y1 + directions[d1].y, height, wrapping);
|
|
int d2 = (d1 + 2) % 4; // Opposite direction
|
|
|
|
// Skip if destination already has connections
|
|
if (tiles[x2, y2] != 0)
|
|
continue;
|
|
|
|
|
|
|
|
// Make the connection
|
|
tiles[x1, y1] |= (byte)(1 << d1);
|
|
tiles[x2, y2] |= (byte)(1 << d2);
|
|
|
|
// Prevent full crosses (Net game rule)
|
|
if (CountBits(tiles[x1, y1]) == 3)
|
|
RemoveFullCrossPossibility(possibilities, x1, y1, tiles[x1, y1]);
|
|
|
|
// Add new possibilities from destination
|
|
AddPossibleDirections(possibilities, x2, y2, width, height, wrapping);
|
|
}
|
|
|
|
return tiles;
|
|
}
|
|
|
|
private static void AddPossibleDirections(List<(int x, int y, int dir)> possibilities,
|
|
int x, int y, int width, int height, bool wrapping)
|
|
{
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
int nx = Wrap(x + directions[d].x, width, wrapping);
|
|
int ny = Wrap(y + directions[d].y, height, wrapping);
|
|
|
|
// Skip if out of bounds (for non-wrapping)
|
|
if (!wrapping && (nx < 0 || nx >= width || ny < 0 || ny >= height))
|
|
continue;
|
|
|
|
possibilities.Add((x, y, d));
|
|
}
|
|
}
|
|
|
|
private static void RemoveFullCrossPossibility(List<(int x, int y, int dir)> possibilities,
|
|
int x, int y, byte tile)
|
|
{
|
|
// Find the missing direction (the one not in this 3-way tile)
|
|
int missingDir = 0;
|
|
for (; missingDir < 4; missingDir++)
|
|
if ((tile & (1 << missingDir)) == 0)
|
|
break;
|
|
|
|
// Remove any possibility that would create a full cross
|
|
for (int i = possibilities.Count - 1; i >= 0; i--)
|
|
{
|
|
if (possibilities[i].x == x && possibilities[i].y == y && possibilities[i].dir == missingDir)
|
|
possibilities.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
private static byte[,] AddBarriers(byte[,] solution, int width, int height, bool wrapping, float probability)
|
|
{
|
|
byte[,] barriers = new byte[width, height];
|
|
|
|
// Add border barriers if not wrapping
|
|
if (!wrapping)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
barriers[x, 0] |= (byte)(1 << 3); // Up barrier on bottom row
|
|
barriers[x, height - 1] |= (byte)(1 << 1); // Down barrier on top row
|
|
}
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
barriers[0, y] |= (byte)(1 << 2); // Left barrier on first column
|
|
barriers[width - 1, y] |= (byte)(1 << 0); // Right barrier on last column
|
|
}
|
|
}
|
|
|
|
// Add random barriers
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
// Only add barriers where there isn't already a connection
|
|
for (int d = 0; d < 4; d++)
|
|
{
|
|
if ((solution[x, y] & (1 << d)) == 0 && Random.value < probability)
|
|
{
|
|
int nx = Wrap(x + directions[d].x, width, wrapping);
|
|
int ny = Wrap(y + directions[d].y, height, wrapping);
|
|
|
|
barriers[x, y] |= (byte)(1 << d);
|
|
barriers[nx, ny] |= (byte)(1 << ((d + 2) % 4));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return barriers;
|
|
}
|
|
|
|
private static void CreateTiles(Tile[,] tiles, GameObject[] prefabs, int width, int height,
|
|
byte[,] solution, byte[,] barriers)
|
|
{
|
|
float offsetX = -((width - 1) / 2f);
|
|
float offsetY = ((height - 1) / 2f);
|
|
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
byte connections = solution[x, y];
|
|
string type = GetTileType(connections);
|
|
int rotation = GetRotation(connections, type);
|
|
|
|
GameObject prefab = FindPrefab(prefabs, type);
|
|
GameObject tileObj = Instantiate(prefab);
|
|
tileObj.transform.position = new Vector2(offsetX + x, offsetY - y);
|
|
|
|
Tile tile = tileObj.GetComponent<Tile>();
|
|
tile.X = x;
|
|
tile.Y = y;
|
|
|
|
tile.baseConnections = GetBaseConnections(type);
|
|
tile.graphicRotationOffset = GetGraphicOffset(type);
|
|
|
|
tile.ApplyRotation(rotation);
|
|
tile.SolutionRotation = rotation;
|
|
// if (type == "TriCorner")
|
|
// // roate plus 90 degrees
|
|
// tile.SolutionRotation += 1;
|
|
// else if (type == "Corner")
|
|
// // rotate 3 times
|
|
// tile.SolutionRotation += 4;
|
|
// // tile.SolutionRotation %= 4;
|
|
|
|
tile.SolutionConnections = connections;
|
|
tile.Barriers = barriers[x, y];
|
|
|
|
tile.SetConnectionState(true); // Show connected state (green)
|
|
tiles[x, y] = tile;
|
|
}
|
|
}
|
|
// Set center tile to true
|
|
tiles[width / 2, height / 2].isCenterTile = true;
|
|
tiles[width / 2, height / 2].SetConnectionState(true); // Show connected state (green)
|
|
}
|
|
|
|
private static void ShuffleTiles(Tile[,] tiles, int width, int height, int seed)
|
|
{
|
|
Random.InitState(seed);
|
|
foreach (var tile in tiles)
|
|
{
|
|
if (!tile.locked)
|
|
{
|
|
// Random rotation (0-3)
|
|
int rot = Random.Range(0, 4);
|
|
for (int i = 0; i < rot; i++)
|
|
tile.RotateClockwise();
|
|
|
|
tile.SetConnectionState(false); // Show disconnected state (black)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int Wrap(int value, int max, bool wrapping)
|
|
{
|
|
if (!wrapping) return value;
|
|
return (value + max) % max;
|
|
}
|
|
|
|
private static int CountBits(byte b)
|
|
{
|
|
int count = 0;
|
|
while (b != 0)
|
|
{
|
|
count++;
|
|
b &= (byte)(b - 1);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private static string GetTileType(byte connections)
|
|
{
|
|
int count = CountBits(connections);
|
|
switch (count)
|
|
{
|
|
case 1: return "Node";
|
|
case 2:
|
|
if ((connections & 0b0101) == 0b0101 || (connections & 0b1010) == 0b1010)
|
|
return "Straight"; // Opposite directions
|
|
return "Corner";
|
|
case 3: return "TriCorner";
|
|
default: return "Empty";
|
|
}
|
|
}
|
|
|
|
private static int GetRotation(byte connections, string type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case "Node":
|
|
// Find which direction is set
|
|
for (int i = 0; i < 4; i++)
|
|
if ((connections & (1 << i)) != 0)
|
|
return i;
|
|
return 0;
|
|
|
|
case "Straight":
|
|
return (connections & 0b0101) == 0b0101 ? 0 : 1; // Vertical or horizontal
|
|
|
|
case "Corner":
|
|
// Find first set bit
|
|
for (int i = 0; i < 4; i++)
|
|
if ((connections & (1 << i)) != 0)
|
|
return i;
|
|
return 0;
|
|
|
|
case "TriCorner":
|
|
// Find missing direction
|
|
for (int i = 0; i < 4; i++)
|
|
if ((connections & (1 << i)) == 0)
|
|
return i;
|
|
return 0;
|
|
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
private static GameObject FindPrefab(GameObject[] prefabs, string type)
|
|
{
|
|
foreach (var p in prefabs)
|
|
{
|
|
if (p.name.Contains(type))
|
|
return p;
|
|
}
|
|
Debug.LogError($"No prefab found for type: {type}");
|
|
return prefabs[0];
|
|
}
|
|
|
|
private static int[] GetBaseConnections(string type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case "Node": return new int[] { 0 }; // Right
|
|
case "Straight": return new int[] { 0, 2 }; // Right and Left
|
|
case "Corner": return new int[] { 0, 1 }; // Right and Up
|
|
case "TriCorner": return new int[] { 0, 1, 2 }; // Right, Up, Left
|
|
// case "Cross": return new int[] { 0, 1, 2, 3 }; // All directions
|
|
default: return new int[0];
|
|
}
|
|
}
|
|
|
|
private static int GetGraphicOffset(string type)
|
|
{
|
|
// Adjust if your tile graphics need rotation offsets
|
|
return 0;
|
|
}
|
|
} |