diff options
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/DesktopTests.fs | 53 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/SampleTests.fs | 2 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 11 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj | 1 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/M6Presentation.fs | 2 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/WorldBootstrap.fs | 36 |
6 files changed, 99 insertions, 6 deletions
diff --git a/src/LivingVillage.Desktop.Tests/DesktopTests.fs b/src/LivingVillage.Desktop.Tests/DesktopTests.fs index 1c175ce..20cbbe9 100644 --- a/src/LivingVillage.Desktop.Tests/DesktopTests.fs +++ b/src/LivingVillage.Desktop.Tests/DesktopTests.fs @@ -4,6 +4,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop +open LivingVillage.Desktop.VillagePresentation module private Harness = @@ -71,3 +72,55 @@ type DesktopTests () = Assert.AreEqual<M6DayNight>(Night, night.Mode) Assert.AreNotEqual(day.Background, night.Background) Assert.AreNotEqual(day.WorldTint, night.WorldTint) + + [<TestMethod>] + member _.DesktopBootstrapMovesAvatarOffTheHomeDoorAndSeparatesVillagers () = + let world = WorldBootstrap.initialWorld 42UL Sim.npcCount + let avatarPixel : PixelPosition = + { X = world.Avatar.Pos.X + Y = world.Avatar.Pos.Y } + let avatarTile = + sceneTileFromPixel avatarPixel + let home = scene.Homes |> List.head + let renderPlan = VillageArt.sampleRenderPlan () + let blockedTiles = + (renderPlan.Grounds |> List.map (fun (tile, _) -> VillageArt.worldTile renderPlan tile)) + @ (renderPlan.Props |> List.map (fun prop -> VillageArt.worldTile renderPlan prop.Position)) + |> List.map (fun tile -> tile.X, tile.Y) + |> Set.ofList + let sceneTileOf (position: Vec2) : TilePosition = + sceneTileFromPixel + { X = position.X + Y = position.Y } + let distanceSquared (first: Vec2) (second: Vec2) : float32 = + let dx = second.X - first.X + let dy = second.Y - first.Y + dx * dx + dy * dy + let npcTiles = world.Npcs |> Array.map (fun npc -> sceneTileOf npc.Pos) + + Assert.AreEqual<int>(6, avatarTile.X) + Assert.AreEqual<int>(7, avatarTile.Y) + Assert.IsFalse(isNearHomeDoor avatarTile home, "The avatar must start beyond the doorway interaction radius.") + Assert.IsTrue( + npcTiles |> Array.forall (fun tile -> not (Set.contains (tile.X, tile.Y) blockedTiles)), + "A villager must not spawn on the house, water, bridge, or garden footprint.") + Assert.IsTrue( + world.Npcs + |> Array.forall (fun npc -> distanceSquared world.Avatar.Pos npc.Pos >= 96.0f * 96.0f), + "Villagers must not overlap the avatar spawn.") + + for first in 0 .. world.Npcs.Length - 2 do + for second in first + 1 .. world.Npcs.Length - 1 do + Assert.IsTrue( + distanceSquared world.Npcs.[first].Pos world.Npcs.[second].Pos >= 96.0f * 96.0f, + sprintf "villagers %d and %d are too close" first second) + + let repeated = WorldBootstrap.initialWorld 42UL Sim.npcCount + Assert.AreEqual<World>(world, repeated) + + [<TestMethod>] + member _.DaylightUsesAWarmHighContrastWorldTint () = + let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L) + + Assert.IsTrue(day.WorldTint.R > day.WorldTint.G) + Assert.IsTrue(day.WorldTint.G > day.WorldTint.B) diff --git a/src/LivingVillage.Desktop.Tests/SampleTests.fs b/src/LivingVillage.Desktop.Tests/SampleTests.fs index 4c833b5..ae81b64 100644 --- a/src/LivingVillage.Desktop.Tests/SampleTests.fs +++ b/src/LivingVillage.Desktop.Tests/SampleTests.fs @@ -12,7 +12,7 @@ type SampleTests () = let areEqual (expected: 'T) (actual: 'T) = Assert.AreEqual<'T>(expected, actual) - let freshWorld () = Sim.initialWorldN 42UL Sim.npcCount + let freshWorld () = WorldBootstrap.initialWorld 42UL Sim.npcCount let freshView (world: World) = M5Interaction.refreshPrompt world M5Interaction.initial diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index 1245e02..27e44a7 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -124,7 +124,7 @@ type LivingVillageGame() as this = let mutable cjkAtlas = Unchecked.defaultof<Texture2D> let mutable pixel = Unchecked.defaultof<Texture2D> let renderPlan = VillageArt.sampleRenderPlan () - let mutable world = Sim.initialWorldN 42UL Sim.npcCount + let mutable world = WorldBootstrap.initialWorld 42UL Sim.npcCount let mutable camera = Vector2.Zero let mutable avatarFacing = VillageArt.SouthFacing let mutable fpsFrames = 0 @@ -168,7 +168,7 @@ type LivingVillageGame() as this = this.CenterCamera() member private this.StartNewGame() = - world <- Sim.initialWorldN 42UL Sim.npcCount + world <- WorldBootstrap.initialWorld 42UL Sim.npcCount this.ResetWorldView() menu <- menu |> MenuState.setCurrentWorld true |> MenuState.enterGame printfn $"new-game=ok seed=42 npcs={Sim.npcCount}" @@ -258,6 +258,9 @@ type LivingVillageGame() as this = this.CenterCamera() if nextState.Stage <> stageBefore then printfn $"sample stage={nextState.Stage} tick={nextWorld.Tick} pos=({nextWorld.Avatar.Pos.X:F0},{nextWorld.Avatar.Pos.Y:F0})" + if nextState.Stage = SampleScript.SampleComplete then + printfn (if nextState.TimedOut then "sample result=timeout" else "sample result=ok") + this.Exit() member private this.UpdatePlaying (gameTime: GameTime) (kb: KeyboardState) (pressed: Keys -> bool) (pressedAny: Keys list -> bool) = if sampleMode then @@ -552,9 +555,9 @@ type LivingVillageGame() as this = spriteBatch.Begin() match m5View.HomeMode with | Inside _ -> - VillageArt.drawInterior spriteBatch pixel artTextures viewport.Width viewport.Height renderPlan.Interior Color.White + VillageArt.drawInterior spriteBatch pixel artTextures viewport.Width viewport.Height renderPlan.Interior profile.WorldTint let interiorAvatarSpec = VillageArt.avatarSpriteSpec avatarFacing world.Tick - VillageArt.drawInteriorCharacter spriteBatch artTextures viewport.Width viewport.Height world.Avatar.Pos interiorAvatarSpec Color.White + VillageArt.drawInteriorCharacter spriteBatch artTextures viewport.Width viewport.Height world.Avatar.Pos interiorAvatarSpec profile.WorldTint | Outside -> VillageArt.drawWorld spriteBatch diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj index 5bd431e..fcb1883 100644 --- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj +++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj @@ -7,6 +7,7 @@ <ItemGroup> <Compile Include="VillagePresentation.fs" /> + <Compile Include="WorldBootstrap.fs" /> <Compile Include="VillageArt.fs" /> <Compile Include="ChineseText.fs" /> <Compile Include="CjkGlyphAtlas.fs" /> diff --git a/src/LivingVillage.Desktop/M6Presentation.fs b/src/LivingVillage.Desktop/M6Presentation.fs index b6c24fa..8e13183 100644 --- a/src/LivingVillage.Desktop/M6Presentation.fs +++ b/src/LivingVillage.Desktop/M6Presentation.fs @@ -28,7 +28,7 @@ module M6Presentation = else { Mode = Day Background = Color(100, 160, 210) - WorldTint = Color.White } + WorldTint = Color(255, 248, 224) } let clockLabel (control: SimulationControl) : string = if control.Paused then diff --git a/src/LivingVillage.Desktop/WorldBootstrap.fs b/src/LivingVillage.Desktop/WorldBootstrap.fs new file mode 100644 index 0000000..612931b --- /dev/null +++ b/src/LivingVillage.Desktop/WorldBootstrap.fs @@ -0,0 +1,36 @@ +namespace LivingVillage.Desktop + +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim + +module WorldBootstrap = + + let private tileCenter (x: int) (y: int) : Vec2 = + { X = float32 (x * Sim.tilePixels + Sim.tilePixels / 2) + Y = float32 (y * Sim.tilePixels + Sim.tilePixels / 2) } + + let private npcSpawnTiles : (int * int) array = + [| for y in [ 14; 18; 28; 32; 36; 40 ] do + for x in [ 24; 28; 32; 36; 40 ] do + yield x, y |] + + let private avatarSpawnPosition = tileCenter 31 25 + + let initialWorld (seed: uint64) (count: int) : World = + if count < 0 || count > npcSpawnTiles.Length then + invalidArg (nameof count) "Desktop bootstrap supports at most 30 NPCs." + + let baseWorld = Sim.initialWorldN seed count + let avatar = + { baseWorld.Avatar with + Pos = avatarSpawnPosition + Mind = { baseWorld.Avatar.Mind with Target = avatarSpawnPosition } } + let npcs = + baseWorld.Npcs + |> Array.mapi (fun index npc -> + let x, y = npcSpawnTiles.[index] + { npc with Pos = tileCenter x y }) + + { baseWorld with + Avatar = avatar + Npcs = npcs } |
