summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj2
-rw-r--r--src/LivingVillage.Desktop/MapGen.fs24
-rw-r--r--src/LivingVillage.Desktop/WorldBootstrap.fs35
3 files changed, 54 insertions, 7 deletions
diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
index 739b750..6f388f2 100644
--- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
+++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj
@@ -7,9 +7,9 @@
<ItemGroup>
<Compile Include="VillagePresentation.fs" />
- <Compile Include="WorldBootstrap.fs" />
<Compile Include="ProceduralMap.fs" />
<Compile Include="MapGen.fs" />
+ <Compile Include="WorldBootstrap.fs" />
<Compile Include="VillageArt.fs" />
<Compile Include="ChineseText.fs" />
<Compile Include="CjkGlyphAtlas.fs" />
diff --git a/src/LivingVillage.Desktop/MapGen.fs b/src/LivingVillage.Desktop/MapGen.fs
index c59e6bd..7c7059a 100644
--- a/src/LivingVillage.Desktop/MapGen.fs
+++ b/src/LivingVillage.Desktop/MapGen.fs
@@ -312,6 +312,30 @@ module MapGen =
let generateWithSize (width: int) (height: int) (seed: uint64) : Result =
generate { defaultParams seed with Width = width; Height = height }
+ /// 生成结果的确定性序列化(同 seed 逐字节一致),供回归测试与跨机核对。
+ let serialize (map: Result) : string =
+ let builder = System.Text.StringBuilder()
+ builder.Append(map.Width).Append('x').Append(map.Height).Append('|').Append(map.Seed).Append('|') |> ignore
+ for code in map.Tiles do
+ builder.Append(code).Append(',') |> ignore
+ builder.Append('|') |> ignore
+ for river in map.Rivers do
+ builder.Append(river.CenterY).Append('*').Append(river.Width).Append(';') |> ignore
+ builder.Append('|') |> ignore
+ for (x, y) in map.Bridges do
+ builder.Append(x).Append(':').Append(y).Append(';') |> ignore
+ builder.Append('|') |> ignore
+ for (x, y) in map.Paths do
+ builder.Append(x).Append(':').Append(y).Append(';') |> ignore
+ builder.Append('|') |> ignore
+ for building in map.Buildings do
+ builder
+ .Append(building.Left).Append(':').Append(building.Top).Append(':')
+ .Append(building.Width).Append(':').Append(building.Height).Append(':')
+ .Append(building.DoorX).Append(':').Append(building.DoorY).Append(';')
+ |> ignore
+ builder.ToString()
+
/// 可行走判定:非水,或位于桥面上(桥面仍标记为水但可通行)。
let isWalkable (map: Result) (x: int) (y: int) : bool =
if x < 0 || x >= map.Width || y < 0 || y >= map.Height then
diff --git a/src/LivingVillage.Desktop/WorldBootstrap.fs b/src/LivingVillage.Desktop/WorldBootstrap.fs
index f8ba775..bf0ef65 100644
--- a/src/LivingVillage.Desktop/WorldBootstrap.fs
+++ b/src/LivingVillage.Desktop/WorldBootstrap.fs
@@ -35,17 +35,26 @@ module WorldBootstrap =
nowTick
state.Today }
- let initialWorldWithOccupation (startAtDaylight: bool) (seed: uint64) (count: int) (occupation: Occupation.State option) : World =
- if count < 0 || count > npcSpawnTiles.Length then
+ /// 以显式站位建世界:avatarTile 为玩家出生瓦片,npcTiles 为 NPC 出生瓦片序列。
+ /// 64x48 旧档与世界走 `avatarSpawnPosition` + `npcSpawnTiles` 的等价调用,字节口径不变。
+ let initialWorldWithPlacement
+ (startAtDaylight: bool)
+ (seed: uint64)
+ (count: int)
+ (avatarTile: int * int)
+ (npcTiles: (int * int) list)
+ (occupation: Occupation.State option) : World =
+ if count < 0 || count > npcTiles.Length then
invalidArg (nameof count) "Desktop bootstrap supports at most 30 NPCs."
+ let avatarPosition = tileCenter (fst avatarTile) (snd avatarTile)
let startTick = if startAtDaylight then daylightStartTick else 0L
let baseWorld = Sim.initialWorldN seed count
let avatar =
let baseAvatar =
{ baseWorld.Avatar with
- Pos = avatarSpawnPosition
- Mind = { baseWorld.Avatar.Mind with Target = avatarSpawnPosition } }
+ Pos = avatarPosition
+ Mind = { baseWorld.Avatar.Mind with Target = avatarPosition } }
match occupation with
| None -> baseAvatar
| Some state ->
@@ -56,11 +65,11 @@ module WorldBootstrap =
Needs =
{ needs with
Money = float32 state.Profile.InitialMoney
- Energy = state.Profile.InitialEnergy } } }
+ Energy = float32 state.Profile.InitialEnergy } } }
let npcs =
baseWorld.Npcs
|> Array.mapi (fun index npc ->
- let x, y = npcSpawnTiles.[index]
+ let x, y = List.item index npcTiles
{ npc with Pos = tileCenter x y })
{ baseWorld with
@@ -69,6 +78,20 @@ module WorldBootstrap =
Avatar = avatar
Npcs = npcs }
+ let initialWorldWithOccupation (startAtDaylight: bool) (seed: uint64) (count: int) (occupation: Occupation.State option) : World =
+ initialWorldWithPlacement
+ startAtDaylight
+ seed
+ count
+ (31, 25)
+ (List.ofArray npcSpawnTiles)
+ occupation
+
+ /// P20 第三步:默认可玩世界。生成器的核心可达区作为站位,30 个 spawn 全部落核心可达域。
+ let initialWorldInMap (startAtDaylight: bool) (seed: uint64) (count: int) (map: MapGen.Result) : World =
+ let avatarTile = ((map.Core.MinX + map.Core.MaxX) / 2, (map.Core.MinY + map.Core.MaxY) / 2)
+ initialWorldWithPlacement startAtDaylight seed count avatarTile map.Spawns None
+
let initialWorldForDaylight (startAtDaylight: bool) (seed: uint64) (count: int) : World =
initialWorldWithOccupation startAtDaylight seed count None