1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
namespace LivingVillage.Desktop.Tests
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
open LivingVillage.Desktop
open LivingVillage.Desktop.VillagePresentation
module private Harness =
let atNpc (id: int) (world: World) : World =
let npc = world.Npcs.[id]
{ world with Avatar = { world.Avatar with Pos = npc.Pos } }
[<TestClass>]
type DesktopTests () =
[<TestMethod>]
member _.InteractOpensTheSixIntentMenu () =
let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0
let next, view = M5Interaction.apply Interact world M5Interaction.initial
Assert.AreEqual<World>(world, next)
Assert.AreEqual<M5Panel>(DialoguePanel, view.Panel)
Assert.AreEqual<int>(6, view.Menu.Value.Options.Length)
Assert.IsTrue(M5Interaction.titleText view |> fun text -> text.Contains("闲聊"))
[<TestMethod>]
member _.SelectingAnIntentUpdatesWorldAndChronicle () =
let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0
let openedWorld, opened = M5Interaction.apply Interact world M5Interaction.initial
let next, view = M5Interaction.apply Intent1 openedWorld opened
Assert.IsTrue(next.Events |> List.exists (fun event ->
match event.Kind with
| DialogueEvent _ -> true
| _ -> false))
Assert.IsTrue(next.Annals |> List.exists (fun entry ->
match entry.Kind with
| DialogueAnnal _ -> true
| _ -> false))
Assert.IsTrue(view.Chronicle.Contains("dialogue"))
Assert.IsTrue(view.Status.Contains("dialogue"))
[<TestMethod>]
member _.NeedsAndObservationPanelsUseKernelViews () =
let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0
let afterStep = Sim.step { Input = { MoveX = 0.0f; MoveY = 0.0f } } world
let _, needsView = M5Interaction.apply ToggleNeeds afterStep M5Interaction.initial
let _, observationView = M5Interaction.apply Observe world M5Interaction.initial
Assert.AreEqual<Needs>(afterStep.Avatar.Mind.Needs, needsView.Needs.Value.Needs)
Assert.IsTrue(observationView.Observations |> List.exists (fun observation -> observation.Id = NpcId 0))
[<TestMethod>]
member _.HeadlessSmokeProvesTheThreeHopCausalChain () =
let result = Program.M5Smoke.run ()
Assert.IsTrue(result.Passed, result.Failure)
Assert.AreEqual<int>(6, result.MenuOptions)
Assert.AreEqual<int>(4, result.RumorPathLength)
Assert.IsTrue(result.Chronicle.Contains("dialogue"))
Assert.IsTrue(result.RumorTrace.Contains("receiver=-1"))
Assert.IsTrue(result.RumorTrace.Contains("depth=3"))
[<TestMethod>]
member _.DayNightStateChangesTheRenderProfile () =
let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L)
let night = M6Presentation.profileAtTick (22L * Sim.ticksPerDay / 24L)
Assert.AreEqual<M6DayNight>(Day, day.Mode)
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)
|