summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/MapGen.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/MapGen.fs')
-rw-r--r--src/LivingVillage.Desktop/MapGen.fs186
1 files changed, 147 insertions, 39 deletions
diff --git a/src/LivingVillage.Desktop/MapGen.fs b/src/LivingVillage.Desktop/MapGen.fs
index 4f59f84..c59e6bd 100644
--- a/src/LivingVillage.Desktop/MapGen.fs
+++ b/src/LivingVillage.Desktop/MapGen.fs
@@ -32,6 +32,20 @@ module MapGen =
SpawnColumns: int
SpawnRows: int }
+ /// P20 第二步:一条贯穿全图的东西向河道(每列恰好一段,宽度下限 RiverWidth)。
+ type River =
+ { CenterY: int
+ Width: int }
+
+ /// 沿河民居色块:白墙黑瓦矩形屋顶,门开向石板路。
+ type Building =
+ { Left: int
+ Top: int
+ Width: int
+ Height: int
+ DoorX: int
+ DoorY: int }
+
type Result =
{ Width: int
Height: int
@@ -39,8 +53,13 @@ module MapGen =
Tiles: int array
Core: Bounds
Spawns: (int * int) list
+ Rivers: River list
+ Bridges: (int * int) list
+ Paths: (int * int) list
+ Buildings: Building list
ReachableTiles: int
- ReachabilityOk: bool }
+ ReachabilityOk: bool
+ BridgeCrossingsOk: bool }
/// 默认仍是现状游戏尺寸 64x48,核心 14 格、5x6=30 个 spawn。
let defaultParams (seed: uint64) : Params =
@@ -125,21 +144,22 @@ module MapGen =
if multiOctave widthScale x y p.Seed < 0.18f && not (inCore x y) then
tiles.[idx x y] <- int GroundTile.Peat
- // 2) 河道:RiverCount 条自西向东随机游走,宽度 RiverWidth。
- let mutable rng = p.Seed ^^^ 0xBEEFUL
- for _ in 1 .. max 0 p.RiverCount do
- let nextState, rawStartY = splitmix rng
- rng <- nextState
- let mutable y = 2 + int (rawStartY % uint64 (max 1 (h - 4)))
- for x in 2 .. w - 3 do
- let nextStep, rawStep = splitmix rng
- rng <- nextStep
- let drift = int (rawStep % 3UL) - 1
- y <- max 2 (min (h - 3) (y + drift))
- for dy in 0 .. p.RiverWidth - 1 do
- let yy = y + dy
- if yy <= h - 2 && not (inCore x yy) then
- tiles.[idx x yy] <- int GroundTile.Water
+ // 2) 河道:连续贯穿全图(每列恰好一段),置于核心区上下两侧,宽度恒 >= RiverWidth。
+ let riverWidth = max 3 p.RiverWidth
+ let riverBands =
+ [ for i in 0 .. max 0 p.RiverCount - 1 do
+ if i % 2 = 0 then
+ let centerY = max 1 (min (h - 1 - riverWidth) (min (core.MinY - riverWidth - 2) (h / 4)))
+ if centerY + riverWidth - 1 < core.MinY then
+ yield { CenterY = centerY; Width = riverWidth }
+ else
+ let centerY = max 1 (min (h - 1 - riverWidth) (max (core.MaxY + 2) (3 * h / 4)))
+ if centerY > core.MaxY then
+ yield { CenterY = centerY; Width = riverWidth } ]
+ for river in riverBands do
+ for x in 1 .. w - 2 do
+ for dy in 0 .. river.Width - 1 do
+ tiles.[idx x (river.CenterY + dy)] <- int GroundTile.Water
// 3) 核心活动区整平为草地(生成器保证可通行)。
for y in core.MinY .. core.MaxY do
@@ -151,7 +171,7 @@ module MapGen =
let minSq = minDist * minDist
let attempts = max 100 ((w * h) / 16)
let mutable placed: (int * int) list = []
- let mutable scatterRng = rng
+ let mutable scatterRng = p.Seed ^^^ 0xC0FFEEUL
for _ in 1 .. attempts do
let nextState, raw = splitmix scatterRng
scatterRng <- nextState
@@ -167,7 +187,70 @@ module MapGen =
tiles.[idx x y] <- int GroundTile.Stone
placed <- (x, y) :: placed
- // 5) 30 个 spawn:核心区内的规则网格。
+ // 5) 交通:桥 + 石板路网 + 沿河民居(白墙黑瓦矩形色块,门朝远岸路)。
+ let mutable structureRng = p.Seed ^^^ 0x51AB1EUL
+ let inMap x y = x >= 0 && x < w && y >= 0 && y < h
+ let mutable bridges: (int * int) list = []
+ let mutable paths: (int * int) list = []
+ let mutable buildings: Building list = []
+ let pathSet = System.Collections.Generic.HashSet<int>()
+ let addPath x y =
+ if inMap x y && tiles.[idx x y] <> int GroundTile.Water then
+ tiles.[idx x y] <- int GroundTile.Stone
+ if pathSet.Add(idx x y) then paths <- (x, y) :: paths
+ let nextBetween lo hi =
+ let nextState, raw = splitmix structureRng
+ structureRng <- nextState
+ lo + int (raw % uint64 (max 1 (hi - lo + 1)))
+ let roadY = (core.MinY + core.MaxY) / 2
+ for x in 1 .. w - 2 do
+ addPath x roadY
+ let bridgeColumns =
+ [ for river in riverBands do
+ let mutable picked: int list = []
+ for _ in 1 .. 2 do
+ let mutable bx = nextBetween 4 (max 4 (w - 5))
+ let mutable guard = 0
+ while List.contains bx picked && guard < 8 do
+ bx <- nextBetween 4 (max 4 (w - 5))
+ guard <- guard + 1
+ picked <- bx :: picked
+ yield (river, bx) ]
+ for river, bx in bridgeColumns do
+ for dx in 0 .. 1 do
+ if bx + dx <= w - 2 then
+ for dy in 0 .. river.Width - 1 do
+ let yy = river.CenterY + dy
+ if inMap (bx + dx) yy then
+ bridges <- (bx + dx, yy) :: bridges
+ let yLo = min (river.CenterY - 1) roadY
+ let yHi = max (river.CenterY + river.Width) roadY
+ for y in yLo .. yHi do
+ addPath bx y
+ let farY = if river.CenterY < core.MinY then river.CenterY - 2 else river.CenterY + river.Width + 1
+ for x in max 1 (bx - 7) .. min (w - 2) (bx + 7) do
+ addPath x farY
+ let buildingWidth = 3 + nextBetween 0 1
+ let doorX = min (w - 3) (max 2 (bx + 3))
+ let building =
+ if river.CenterY < core.MinY then
+ { Left = max 1 (doorX - buildingWidth / 2)
+ Top = max 1 (farY - 2)
+ Width = buildingWidth
+ Height = 2
+ DoorX = doorX
+ DoorY = max 1 (farY - 1) }
+ else
+ { Left = max 1 (doorX - buildingWidth / 2)
+ Top = farY + 1
+ Width = buildingWidth
+ Height = 2
+ DoorX = doorX
+ DoorY = farY + 1 }
+ buildings <- building :: buildings
+ addPath building.DoorX building.DoorY
+
+ // 6) 30 个 spawn:核心区内的规则网格。
let sc = max 2 p.SpawnColumns
let sr = max 2 p.SpawnRows
let spanX = max 1 (core.MaxX - core.MinX - 2)
@@ -179,8 +262,9 @@ module MapGen =
(core.MinX + 1 + (c * spanX) / (sc - 1),
core.MinY + 1 + (r * spanY) / (sr - 1)) ]
- // 6) BFS(4 邻接、非水可走):从核心中心洪泛,校验全部 spawn 可达。
- let walkable x y = tiles.[idx x y] <> int GroundTile.Water
+ // 7) BFS(4 邻接、桥面可走):校验 spawn / 门 / 桥与两岸全部连通。
+ let bridgeSet = System.Collections.Generic.HashSet<int>(bridges |> List.map (fun (x, y) -> idx x y))
+ let walkable x y = tiles.[idx x y] <> int GroundTile.Water || bridgeSet.Contains(idx x y)
let flood startX startY =
let visited = Array.create (w * h) false
let queue = System.Collections.Generic.Queue<int>()
@@ -202,25 +286,14 @@ module MapGen =
queue.Enqueue ni
visited
- let carve (sx: int) (sy: int) =
- let mutable x = sx
- while x <> cx do
- x <- x + sign (cx - x)
- if tiles.[idx x sy] = int GroundTile.Water then tiles.[idx x sy] <- int GroundTile.Grass
- let mutable y = sy
- while y <> cy do
- y <- y + sign (cy - y)
- if tiles.[idx x y] = int GroundTile.Water then tiles.[idx x y] <- int GroundTile.Grass
-
- let mutable visited = flood cx cy
- let unreachable = spawns |> List.filter (fun (x, y) -> not visited.[idx x y])
- for spawn in unreachable do
- carve (fst spawn) (snd spawn)
- if not unreachable.IsEmpty then
- visited <- flood cx cy
-
+ let visited = flood (fst spawns.Head) (snd spawns.Head)
+ let spawnsOk = spawns |> List.forall (fun (x, y) -> visited.[idx x y])
+ let doorsOk = buildings |> List.forall (fun b -> visited.[idx b.DoorX b.DoorY])
+ let bridgesOk = bridges |> List.forall (fun (x, y) -> visited.[idx x y])
+ let crossingsOk =
+ bridgeColumns
+ |> List.forall (fun (river, bx) -> visited.[idx bx (river.CenterY - 1)] && visited.[idx bx (river.CenterY + river.Width)])
let reachableTiles = visited |> Array.filter id |> Array.length
- let reachabilityOk = spawns |> List.forall (fun (x, y) -> visited.[idx x y])
{ Width = w
Height = h
@@ -228,12 +301,47 @@ module MapGen =
Tiles = tiles
Core = core
Spawns = spawns
+ Rivers = riverBands
+ Bridges = bridges
+ Paths = paths
+ Buildings = buildings
ReachableTiles = reachableTiles
- ReachabilityOk = reachabilityOk }
+ ReachabilityOk = spawnsOk && doorsOk && bridgesOk
+ BridgeCrossingsOk = crossingsOk }
let generateWithSize (width: int) (height: int) (seed: uint64) : Result =
generate { defaultParams seed with Width = width; Height = height }
+ /// 可行走判定:非水,或位于桥面上(桥面仍标记为水但可通行)。
+ let isWalkable (map: Result) (x: int) (y: int) : bool =
+ if x < 0 || x >= map.Width || y < 0 || y >= map.Height then
+ false
+ else
+ map.Tiles.[y * map.Width + x] <> int GroundTile.Water
+ || (map.Bridges |> List.exists (fun (bx, by) -> bx = x && by = y))
+
+ /// 从给定起点做 4 邻接洪泛(承认桥面),返回逐瓦片可达标记。
+ let floodFill (map: Result) (startX: int) (startY: int) : bool array =
+ let visited = Array.create (map.Width * map.Height) false
+ let queue = System.Collections.Generic.Queue<int>()
+ if isWalkable map startX startY then
+ let start = startY * map.Width + startX
+ visited.[start] <- true
+ queue.Enqueue start
+ while queue.Count > 0 do
+ let cur = queue.Dequeue()
+ let x = cur % map.Width
+ let y = cur / map.Width
+ for dx, dy in [ (1, 0); (-1, 0); (0, 1); (0, -1) ] do
+ let nx = x + dx
+ let ny = y + dy
+ if isWalkable map nx ny then
+ let ni = ny * map.Width + nx
+ if not visited.[ni] then
+ visited.[ni] <- true
+ queue.Enqueue ni
+ visited
+
// ---- 视口裁剪(纯函数):只绘制可见瓦片,与 drawWorld 现有窗口一致 ----
/// 返回含端点的可见瓦片范围 (x0, y0, x1, y1);空视口返回 (0,0,-1,-1)。