diff options
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/MapGenTests.fs | 112 |
1 files changed, 107 insertions, 5 deletions
diff --git a/src/LivingVillage.Desktop.Tests/MapGenTests.fs b/src/LivingVillage.Desktop.Tests/MapGenTests.fs index d83a144..3ff2904 100644 --- a/src/LivingVillage.Desktop.Tests/MapGenTests.fs +++ b/src/LivingVillage.Desktop.Tests/MapGenTests.fs @@ -36,6 +36,25 @@ type MapGenTests () = let nx, ny = x + dx, y + dy if nx >= 0 && nx < map.Width && ny >= 0 && ny < map.Height && map.Tiles.[ny * map.Width + nx] = int MapGen.GroundTile.Water then 1 else 0) + /// P30:把民居按「足迹邻近(两轴间隙 <=2 格)」聚成组团,返回各组规模。 + let houseGroups (houses: MapGen.Building list) : int list = + let arr = houses |> List.sortBy (fun b -> b.Top, b.Left) |> Array.ofList + let n = arr.Length + let parent = Array.init n id + let rec find i = if parent.[i] = i then i else (parent.[i] <- find parent.[i]; parent.[i]) + let union a b = + let ra = find a + let rb = find b + if ra <> rb then parent.[ra] <- rb + let near (a: MapGen.Building) (b: MapGen.Building) = + let xGap = max (a.Left - (b.Left + b.Width - 1)) (b.Left - (a.Left + a.Width - 1)) + let yGap = max (a.Top - (b.Top + b.Height - 1)) (b.Top - (a.Top + a.Height - 1)) + xGap <= 2 && yGap <= 2 + for i in 0 .. n - 1 do + for j in i + 1 .. n - 1 do + if near arr.[i] arr.[j] then union i j + [ for i in 0 .. n - 1 -> find i ] |> List.groupBy id |> List.map (fun (_, group) -> group.Length) + [<TestMethod>] member _.SameSeedGeneratesByteIdenticalTerrain () = let a = MapGen.generateWithSize 64 48 (uint64 4242) @@ -256,20 +275,20 @@ type MapGenTests () = Assert.IsTrue(a.Buildings = b.Buildings) Assert.AreEqual<string>(MapGen.serialize a, MapGen.serialize b) - /// P28:512 沿河民居由 10 -> 16 增密(+6),并出现 2/3/4 三档宽度(附属小筑/民居/大宅), - /// 形成聚落层次;默认图仍为 0,不消耗 RNG,故 64/256 checksum 锁定不变。 + /// P28/P30:512 沿河民居增密并出现 2/3/4 三档宽度(附属小筑/民居/大宅),形成聚落层次; + /// 默认图仍为 0,不消耗 RNG,故 64/256 checksum 锁定不变。 [<TestMethod>] member _.LargeMapHouseDensityHierarchyOnlyGrowsLargeMaps () = - Assert.AreEqual<int>(16, (MapGen.paramsForSize 512 384 (uint64 1)).ExtraRiversideHouses) + Assert.AreEqual<int>(30, (MapGen.paramsForSize 512 384 (uint64 1)).ExtraRiversideHouses) Assert.AreEqual<int>(0, (MapGen.paramsForSize 64 48 (uint64 1)).ExtraRiversideHouses) Assert.AreEqual<int>(0, (MapGen.paramsForSize 256 192 (uint64 1)).ExtraRiversideHouses) let map = MapGen.generateWithSize 512 384 (uint64 4242) - Assert.IsTrue(map.Buildings.Length >= 22, sprintf "P28 512 must place >=22 houses, saw %d" map.Buildings.Length) + Assert.IsTrue(map.Buildings.Length >= 28, sprintf "P30 512 must place >=28 houses, saw %d" map.Buildings.Length) let widths = map.Buildings |> List.map (fun b -> b.Width) |> Set.ofList Assert.IsTrue(widths.Contains 2, "outbuilding width 2 must appear") Assert.IsTrue(widths.Contains 3, "house width 3 must appear") Assert.IsTrue(widths.Contains 4, "hall width 4 must appear") - Assert.AreEqual<int>(24, map.Buildings.Length) + Assert.IsTrue(map.Buildings.Length + map.Farmhouses.Length >= 32, "total houses (clusters + farmsteads) must be >=32") [<TestMethod>] member _.DefaultSizeHouseCountUnchanged () = @@ -439,6 +458,89 @@ type MapGenTests () = let c = MapGen.generateWithSize 512 384 (uint64 7) Assert.AreNotEqual<string>(MapGen.serialize a, MapGen.serialize c) + // ---- P30:512 民居增密 + 组团规模 + 乡村空区填充 + 主路柔化 ---- + + [<TestMethod>] + member _.LargeMapGroupsAreTwoToFiveWithFewIsolated () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let groups = houseGroups map.Buildings + Assert.IsTrue(groups.Length > 0, "512 must form house groups") + let singletons = groups |> List.filter (fun n -> n < 2) |> List.length + Assert.IsTrue(singletons <= 2, sprintf "isolated single-house groups must be <=2, saw %d" singletons) + let inRange = groups |> List.filter (fun n -> n >= 2 && n <= 5) |> List.length + Assert.IsTrue(inRange * 2 >= groups.Length, "most groups must hold 2-5 houses") + // 另一 seed 同样不产孤立组,验证不是对 4242 的过拟合。 + let other = houseGroups (MapGen.generateWithSize 512 384 (uint64 7)).Buildings + Assert.IsTrue((other |> List.filter (fun n -> n < 2) |> List.length) <= 2) + + [<TestMethod>] + member _.LargeMapCountrysideFillsEmptyBandsWithMatureTiles () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let isField code = code = int MapGen.GroundTile.PaddyField || code = int MapGen.GroundTile.VegetablePlot + let paddy = map.Tiles |> Array.filter (fun c -> c = int MapGen.GroundTile.PaddyField) |> Array.length + let veg = map.Tiles |> Array.filter (fun c -> c = int MapGen.GroundTile.VegetablePlot) |> Array.length + Assert.IsTrue(paddy > 0, "paddy fields must appear on 512") + Assert.IsTrue(veg > 0, "vegetable plots must appear on 512") + Assert.IsTrue(paddy + veg > 4000, sprintf "fields must cover a meaningful share, saw %d" (paddy + veg)) + Assert.IsTrue(map.Groves.Length > 0, "tree groves must appear") + Assert.IsTrue(map.Farmhouses.Length > 0, "scattered farmsteads must appear") + // 上部/下部空带都要有田块,避免大面积纯草。 + let topBand = [ for y in 0 .. 90 do for x in 0 .. map.Width - 1 -> map.Tiles.[y * map.Width + x] ] |> List.filter isField |> List.length + let bottomBand = [ for y in 296 .. map.Height - 1 do for x in 0 .. map.Width - 1 -> map.Tiles.[y * map.Width + x] ] |> List.filter isField |> List.length + Assert.IsTrue(topBand > 100, sprintf "top band must contain fields, saw %d" topBand) + Assert.IsTrue(bottomBand > 100, sprintf "bottom band must contain fields, saw %d" bottomBand) + // 默认图完全不进入乡村分支。 + let small = MapGen.generateWithSize 256 192 (uint64 4242) + Assert.AreEqual<int>(0, small.Groves.Length) + Assert.AreEqual<int>(0, small.Farmhouses.Length) + Assert.AreEqual<int>(0, small.Tiles |> Array.filter isField |> Array.length) + + [<TestMethod>] + member _.FieldsStayOnLandAwayFromWaterPathsAndCore () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let pathSet = System.Collections.Generic.HashSet<int>(map.Paths |> List.map (fun (x, y) -> y * map.Width + x)) + let isField code = code = int MapGen.GroundTile.PaddyField || code = int MapGen.GroundTile.VegetablePlot + for y in 0 .. map.Height - 1 do + for x in 0 .. map.Width - 1 do + let i = y * map.Width + x + if isField map.Tiles.[i] then + Assert.AreNotEqual<int>(int MapGen.GroundTile.Water, map.Tiles.[i]) + Assert.IsFalse(pathSet.Contains i, sprintf "field (%d,%d) must not sit on a path" x y) + let inCore = x >= map.Core.MinX && x <= map.Core.MaxX && y >= map.Core.MinY && y <= map.Core.MaxY + Assert.IsFalse(inCore, sprintf "field (%d,%d) must stay outside the core" x y) + + [<TestMethod>] + member _.LargeMapFarmsteadDoorsAreReachableAndDeterministic () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + Assert.IsTrue(map.ReachabilityOk, "512 reachability must hold with farmsteads") + let visited = MapGen.floodFill map (fst map.Spawns.Head) (snd map.Spawns.Head) + for farmhouse in map.Farmhouses do + Assert.IsTrue(MapGen.isWalkable map farmhouse.DoorX farmhouse.DoorY, sprintf "farmstead door (%d,%d) must be walkable" farmhouse.DoorX farmhouse.DoorY) + Assert.IsTrue(visited.[farmhouse.DoorY * map.Width + farmhouse.DoorX], sprintf "farmstead door (%d,%d) unreachable" farmhouse.DoorX farmhouse.DoorY) + let other = MapGen.generateWithSize 512 384 (uint64 4242) + Assert.IsTrue(map.Farmhouses = other.Farmhouses) + Assert.IsTrue(map.Groves = other.Groves) + Assert.IsTrue(map.Tiles = other.Tiles) + Assert.AreEqual<string>(MapGen.serialize map, MapGen.serialize other) + + [<TestMethod>] + member _.LargeMapMainRoadIsDeterministicSerpentine () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let other = MapGen.generateWithSize 512 384 (uint64 4242) + // 同 seed 两次逐字节一致(含主路中心线)。 + Assert.IsTrue(map.MainRoadCenter = other.MainRoadCenter, "road centerline must be byte-identical across runs") + Assert.AreEqual<string>(MapGen.serialize map, MapGen.serialize other) + // 逐点微扰:相邻列步长 <=1,且相对中段基准确有 >=2 格弯曲。 + map.MainRoadCenter + |> Array.pairwise + |> Array.iter (fun (a, b) -> Assert.IsTrue(abs (b - a) <= 1, sprintf "road step must be <=1, saw %d -> %d" a b)) + let baseY = map.MainRoadCenter.[map.Width / 2] + Assert.IsTrue(map.MainRoadCenter |> Array.exists (fun y -> abs (y - baseY) >= 2), "main road must bend by >=2 tiles") + // 默认图主街仍笔直(不进入新分支)。 + let small = MapGen.generateWithSize 256 192 (uint64 4242) + let smallBase = small.MainRoadCenter.[small.Width / 2] + Assert.IsTrue(small.MainRoadCenter |> Array.forall (fun y -> y = smallBase), "default map road must stay straight") + [<TestMethod>] member _.VisibleRangeClampsAtLegacyMapEdges () = let x0, y0, x1, y1 = MapGen.visibleTileRange 64 48 0 0 2048 1536 |
