using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using static ProcGen;
using static UnityEditor.PlayerSettings;
public class ProcGen : MonoBehaviour
{
public Dictionary<Vector2Int, Cell> Grid = new Dictionary<Vector2Int, Cell>();
public GameObject grassPrefab;
public GameObject sandPrefab;
public GameObject waterPrefab;
public GameObject forestPrefab;
//variables that determine grid size and distance between cells
public int chunks = 10;
public int amplitude = 100;
public List<Chunk> AllChunks = new List<Chunk>
{
new //Grass Chunk
(
"Grass",
new List<WeightedChunk> //AllowedLeft
{
new("Grass", 5f),
new("Forest", 2f),
new("Beach", 1f),
new ("Water", 1f),
},
new List<WeightedChunk> //AllowedRight
{
new("Grass", 5f),
new("Forest", 2f),
new("Beach", 1f),
new ("Water", 1f),
},
new List<WeightedChunk>
{
new("Grass", 5f),
new("Forest", 2f),
new("Beach", 1f),
new ("Water", 1f),
},
new List<WeightedChunk>
{
new("Grass", 5f),
new("Forest", 2f),
new("Beach", 1f),
new ("Water", 1f),
}
),
new //Forest Chunk
(
"Forest",
new List<WeightedChunk> //AllowedLeft
{
new("Grass", 2f),
new("Forest", 5f),
new ("Water", 1f),
new ("Beach", 1f),
},
new List<WeightedChunk> //AllowedRight
{
new("Grass", 2f),
new("Forest", 5f),
new ("Water", 1f),
new ("Beach", 1f),
},
new List<WeightedChunk>
{
new("Grass", 2f),
new("Forest", 5f),
new ("Water", 1f),
new ("Beach", 1f),
},
new List<WeightedChunk>
{
new("Grass", 2f),
new("Forest", 5f),
new ("Water", 1f),
new ("Beach", 1f),
}
),
new //Beach Chunk
(
"Beach",
new List<WeightedChunk> //AllowedLeft
{
new ("Grass", 1f),
new ("Forest", 1f),
new ("Water", 30f),
new ("Beach", 10f),
},
new List<WeightedChunk> //AllowedRight
{
new ("Grass", 1f),
new ("Water", 30f),
new ("Beach", 10f),
new ("Forest", 1f),
},
new List<WeightedChunk>
{
new ("Grass", 1f),
new ("Water", 30f),
new ("Forest", 1f),
new ("Beach", 10f),
},
new List<WeightedChunk>
{
new ("Grass", 1f),
new ("Water", 30f),
new ("Beach", 10f),
new ("Forest", 1f),
}
),
new //Water Chunk
(
"Water",
new List<WeightedChunk> //AllowedLeft
{
new ("Grass", 1f),
new ("Water", 10f),
new ("Forest", 1f),
new ("Beach", 3f),
},
new List<WeightedChunk> //AllowedRight
{
new ("Grass", 1f),
new ("Water", 10f),
new ("Forest", 1f),
new ("Beach", 3f),
},
new List<WeightedChunk>
{
new ("Grass", 1f),
new ("Water", 10f),
new ("Forest", 1f),
new ("Beach", 3f),
},
new List<WeightedChunk>
{
new ("Grass", 1f),
new ("Water", 10f),
new ("Forest", 1f),
new ("Beach", 3f),
}
)
};
readonly Vector2Int[] directions = new Vector2Int[]
{
Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right
};
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
for(var x = 0; x<chunks; x++) //creates grid
{
for (var y = 0; y<chunks; y++)
{
Grid[new Vector2Int(x, y)] = new Cell();
Grid[new Vector2Int(x, y)].worldPos = new Vector2(x * amplitude, y * amplitude);
print("Current cell at " + x + ", " + y + " is now set to worldPos " + x*amplitude + ", " + y*amplitude);
}
}
int safety = 1000;
while (Grid.Values.Any(cell => cell.placedChunk == null) && safety-- > 0)
{
GenerateChunks();
}
if (safety <= 0)
{
Debug.LogError("Infinite loop detected — generation did not finish.");
}
}
// Update is called once per frame
void Update()
{
}
void GenerateChunks()
{
if (Grid.Values.All(cell => cell.possibleChunks.Count == 0))
{
Debug.LogError("All remaining cells have no possible chunks — generation failed.");
return;
}
List<Vector2Int> lowestEntropyCells = new List<Vector2Int>();
foreach (var kvp in Grid) //step 1: determine possible chunks at each cell
{
Vector2Int pos = kvp.Key;
Cell cell = kvp.Value;
if (cell.placedChunk != null) continue;
cell.possibleChunks = GetPossibleChunksForCell(pos);
}
int entropy = int.MaxValue; //make a list of cells with lowest entropy
foreach (var kvp in Grid)
{
Vector2Int pos = kvp.Key;
Cell cell = kvp.Value;
if (cell.possibleChunks.Count == 0) continue;
if (cell.possibleChunks.Count < entropy)
{
entropy = cell.possibleChunks.Count;
lowestEntropyCells = new List<Vector2Int>
{
pos
};
}
else if (cell.possibleChunks.Count == entropy)
{
lowestEntropyCells.Add(pos);
}
}
if (lowestEntropyCells == null || lowestEntropyCells.Count == 0)
{
Debug.LogError("No cells with valid options left. Stopping generation.");
return;
}
//step 2: randomly pick a cell from the lowest possible cells
PickChunk(lowestEntropyCells[Random.Range(0, lowestEntropyCells.Count)]);
//step 4: determine possible chunks at said cell
//step 3: randomly generate a cell based on weightings
}
void PickChunk(Vector2Int pos)
{
List<WeightedChunk> validChunks = GetWeightedoptionsForCell(pos);
float weight = 0f;
foreach (var kvp in validChunks)
{
weight += kvp.weight;
kvp.weight = weight;
}
float rand = Random.Range(0, weight);
foreach (var kvp in validChunks)
{
if (rand <= kvp.weight)
{
Grid[pos].placedChunk = AllChunks.First(c => c.name == kvp.name);
print("Set chunk at " + pos.x + ", " + pos.y + " To " + kvp.name);
return;
}
}
}
List<string> GetPossibleChunksForCell(Vector2Int cellPos)
{
List<string> validChunks = null;
foreach(var dir in directions)
{
Vector2Int neighborPos = cellPos + dir;
if (!Grid.ContainsKey(neighborPos)) continue;
Chunk neighbor = Grid[neighborPos].placedChunk;
if (neighbor == null) continue;
List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir);
List<string> names = allowed.Select(wc => wc.name).ToList();
if (validChunks == null)
{
validChunks = new List<string>(names);
}
else
{
validChunks = validChunks.Intersect(names).ToList();
}
}
return validChunks ?? AllChunks.Select(wc => wc.name).ToList();
}
List<WeightedChunk> GetWeightedoptionsForCell(Vector2Int cellPos)
{
Dictionary<string, float> chunkWeights = new Dictionary<string, float>();
foreach (var dir in directions)
{
Vector2Int neighborPos = cellPos + dir; //gets position of neighbor in current direction
if (!Grid.ContainsKey(neighborPos)) continue; //skips if the selected grid position doesn't exist (edge)
Chunk neighbor = Grid[neighborPos].placedChunk; //sets chunk to currently placed chunk at location
if (neighbor == null) continue; //if there is no chunk there, skip it
List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir); //gets allowed chunk from neighbor if found
foreach (var wc in allowed) //for each allowed chunk, add weight to chunkweights
{
if (!chunkWeights.ContainsKey(wc.name)) //if chunk weights doesnt have this chunk, add it and set it to it's weight
chunkWeights[wc.name] = wc.weight;
else //if chunk weights has this chunk already, add weight to it from the selected cell
chunkWeights[wc.name] += wc.weight;
}
}
if (chunkWeights.Count == 0) //if no chunks were found in neighbors, failsafe to set all chunks equally, so it randomly chooses the first chunk
{
return AllChunks.Select(chunk => new WeightedChunk(chunk.name, 1f)).ToList();
}
List<string> valid = GetPossibleChunksForCell(cellPos); //filter weights to remove all weights that aren't allowed by all neighbors
return chunkWeights
.Where(kvp => valid.Contains(kvp.Key)) //removed any weighted option that doesn't exist in valid
.Select(kvp => new WeightedChunk(kvp.Key, kvp.Value)) //convert chunkWeights keys and values to weightedChunk format
.ToList(); //combine to a list to return
}
List<WeightedChunk> GetAllowedChunksFrom(Chunk neighbor, Vector2Int dir)
{
if (dir == Vector2Int.up) return neighbor.AllowedBottom; // Because we're above
if (dir == Vector2Int.down) return neighbor.AllowedTop; // Because we're below
if (dir == Vector2Int.left) return neighbor.AllowedRight;
if (dir == Vector2Int.right) return neighbor.AllowedLeft;
return new List<WeightedChunk>();
}
}
public class Chunk
{
public string name;
public List<WeightedChunk> AllowedLeft;
public List<WeightedChunk> AllowedRight;
public List<WeightedChunk> AllowedTop;
public List<WeightedChunk> AllowedBottom;
public Chunk(string name, List<WeightedChunk> allowedLeft, List<WeightedChunk> allowedRight, List<WeightedChunk> allowedTop, List<WeightedChunk> allowedBottom)
{
this.name = name;
AllowedLeft = allowedLeft;
AllowedRight = allowedRight;
AllowedTop = allowedTop;
AllowedBottom = allowedBottom;
}
}
public class Cell
{
public Vector2 worldPos;
public Chunk placedChunk;
public List<string> possibleChunks;
public Cell()
{
this.possibleChunks = new List<string> {"Grass", "Water", "Beach", "Forest" };
}
}
public class WeightedChunk
{
public string name;
public float weight;
public WeightedChunk(string name, float weight)
{
this.name = name;
this.weight = weight;
}
}