Initial commit: Theriapolis baseline at port/godot branch point
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>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user