summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/LivingVillage.Desktop.Tests/DesktopTests.fs53
-rw-r--r--src/LivingVillage.Desktop.Tests/SampleTests.fs2
-rw-r--r--src/LivingVillage.Desktop/Game.fs11
-rw-r--r--src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj1
-rw-r--r--src/LivingVillage.Desktop/M6Presentation.fs2
-rw-r--r--src/LivingVillage.Desktop/WorldBootstrap.fs36
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 }