Net-Game/Assets/Scripts/MapGenerator.cs

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;
}
}