b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
82 lines
2.7 KiB
C#
82 lines
2.7 KiB
C#
namespace Theriapolis.Core.World.Generation;
|
|
|
|
/// <summary>
|
|
/// Computes 8-connected land-component IDs and sizes via flood fill. Settlement
|
|
/// placement uses this to confine settlements to the main landmass — roads
|
|
/// can't cross ocean, so a settlement on a disconnected island would either be
|
|
/// unreachable or get a sea-crossing straight-line connector stub from
|
|
/// <c>EnsureSettlementConnectivity</c>. 8-connected matches A*'s movement
|
|
/// model so "reachable by road" and "same component" coincide.
|
|
/// </summary>
|
|
internal static class LandmassMap
|
|
{
|
|
private const int W = C.WORLD_WIDTH_TILES;
|
|
private const int H = C.WORLD_HEIGHT_TILES;
|
|
|
|
private static readonly (int dx, int dy)[] Neighbors =
|
|
{
|
|
( 0,-1), ( 1,-1), ( 1, 0), ( 1, 1),
|
|
( 0, 1), (-1, 1), (-1, 0), (-1,-1),
|
|
};
|
|
|
|
/// <summary>
|
|
/// Returns a pair of arrays: <c>componentId[x,y]</c> gives the 8-connected
|
|
/// land component ID at (x,y), or -1 if (x,y) is ocean; <c>componentSize[id]</c>
|
|
/// gives the tile count of component <paramref name="id"/>.
|
|
/// </summary>
|
|
public static (int[,] componentId, int[] componentSize) Compute(WorldState world)
|
|
{
|
|
var compId = new int[W, H];
|
|
for (int y = 0; y < H; y++)
|
|
for (int x = 0; x < W; x++)
|
|
compId[x, y] = -1;
|
|
|
|
var sizes = new List<int>();
|
|
var queue = new Queue<(int x, int y)>();
|
|
|
|
for (int y = 0; y < H; y++)
|
|
for (int x = 0; x < W; x++)
|
|
{
|
|
if (compId[x, y] != -1) continue;
|
|
if (world.Tiles[x, y].Biome == BiomeId.Ocean) continue;
|
|
|
|
int id = sizes.Count;
|
|
int size = 0;
|
|
compId[x, y] = id;
|
|
queue.Enqueue((x, y));
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
var (cx, cy) = queue.Dequeue();
|
|
size++;
|
|
foreach (var (dx, dy) in Neighbors)
|
|
{
|
|
int nx = cx + dx, ny = cy + dy;
|
|
if ((uint)nx >= W || (uint)ny >= H) continue;
|
|
if (compId[nx, ny] != -1) continue;
|
|
if (world.Tiles[nx, ny].Biome == BiomeId.Ocean) continue;
|
|
compId[nx, ny] = id;
|
|
queue.Enqueue((nx, ny));
|
|
}
|
|
}
|
|
|
|
sizes.Add(size);
|
|
}
|
|
|
|
return (compId, sizes.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the component ID of the largest land component, or -1 if the
|
|
/// world has no land.
|
|
/// </summary>
|
|
public static int LargestComponentId(int[] componentSizes)
|
|
{
|
|
int best = -1;
|
|
int bestSize = 0;
|
|
for (int i = 0; i < componentSizes.Length; i++)
|
|
if (componentSizes[i] > bestSize) { bestSize = componentSizes[i]; best = i; }
|
|
return best;
|
|
}
|
|
}
|