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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
namespace LivingVillage.Kernel.Tests
open System
open System.IO
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
module private DailyTaskHarness =
let taskOf (template: Occupation.TaskTemplateId) (state: Occupation.TaskState) : Occupation.DailyTask =
{ TemplateId = template
TargetNpc = None
TargetTile = None
OfferedTick = 0L
DueTick = Sim.ticksPerDay - 1L
State = state }
let accepted (template: Occupation.TaskTemplateId) : Occupation.DailyTask =
Occupation.acceptTask (taskOf template Occupation.Offered)
[<TestClass>]
type DailyTaskTests () =
// ---- §3 生成:hash(seed ^ occupationSeed, dayIndex) mod |pool|(splitmix 同源)----
[<TestMethod>]
member _.TaskHashFollowsSplitmixOverSeedXorOccupationSeed () =
Assert.AreEqual<uint64>(0xF7E9F3F88CC04AD6UL, Occupation.taskHash 42UL 7UL 0L)
Assert.AreEqual<uint64>(0xFB624BD98F2BE6C4UL, Occupation.taskHash 42UL 7UL 1L)
Assert.AreEqual<uint64>(0x87FC3F1DAC740225UL, Occupation.taskHash 42UL 7UL 2L)
Assert.AreEqual<uint64>(0xDA258F1151868B40UL, Occupation.taskHash 42UL 7UL 3L)
Assert.AreEqual<uint64>(0x02D114B7ABDD7A1FUL, Occupation.taskHash 4242UL 99UL 0L)
[<TestMethod>]
member _.FarmerAndFisherPoolsAreThreeTemplatesEach () =
Assert.AreEqual(
[ Occupation.DeliverGrain; Occupation.HelpWork; Occupation.TillSoil ],
Occupation.taskPoolOf Occupation.Farmer)
Assert.AreEqual(
[ Occupation.NightCatch; Occupation.SellFish; Occupation.MarketInquiry ],
Occupation.taskPoolOf Occupation.Fisher)
Assert.AreEqual<int>(0, Occupation.taskPoolOf Occupation.Peddler |> List.length)
Assert.AreEqual<int>(0, Occupation.taskPoolOf Occupation.Scholar |> List.length)
[<TestMethod>]
member _.GeneratedTasksStayInPoolAndRepeatDeterministically () =
for seed, occ in [ (42UL, 7UL); (7UL, 42UL); (123456789UL, 555UL) ] do
for kind in [ Occupation.Farmer; Occupation.Fisher ] do
let pool = Occupation.taskPoolOf kind
for day in 0L .. 299L do
let task = (Occupation.dailyTaskOf seed occ day kind).Value
Assert.IsTrue(pool |> List.contains task.TemplateId, $"day {day} escaped the pool")
let again = (Occupation.dailyTaskOf seed occ day kind).Value
Assert.AreEqual<Occupation.DailyTask>(task, again)
let distinct =
[ 0L .. 299L ]
|> List.map (fun day -> (Occupation.dailyTaskOf seed occ day kind).Value.TemplateId)
|> Set.ofList
Assert.IsTrue(distinct.Count >= 2, "300 days must not pin a single template")
[<TestMethod>]
member _.PeddlerAndScholarHaveNoDailyTaskYet () =
Assert.AreEqual<Occupation.DailyTask option>(None, Occupation.dailyTaskOf 42UL 7UL 0L Occupation.Peddler)
Assert.AreEqual<Occupation.DailyTask option>(None, Occupation.dailyTaskOf 42UL 7UL 0L Occupation.Scholar)
[<TestMethod>]
member _.TaskWindowCoversExactlyOneDayStartingOffered () =
let task = (Occupation.dailyTaskOf 42UL 7UL 3L Occupation.Farmer).Value
Assert.AreEqual<int64>(3L * Sim.ticksPerDay, task.OfferedTick)
Assert.AreEqual<int64>(4L * Sim.ticksPerDay - 1L, task.DueTick)
Assert.AreEqual<Occupation.TaskState>(Occupation.Offered, task.State)
Assert.AreEqual<int64>(3L, Occupation.dayIndexOf (3L * Sim.ticksPerDay))
Assert.AreEqual<int64>(3L, Occupation.dayIndexOf (4L * Sim.ticksPerDay - 1L))
[<TestMethod>]
member _.TaskTemplateChineseNamesMatchDesignTable () =
Assert.AreEqual<string>("送粮", Occupation.taskNameOf Occupation.DeliverGrain)
Assert.AreEqual<string>("帮工", Occupation.taskNameOf Occupation.HelpWork)
Assert.AreEqual<string>("翻土", Occupation.taskNameOf Occupation.TillSoil)
Assert.AreEqual<string>("夜捕", Occupation.taskNameOf Occupation.NightCatch)
Assert.AreEqual<string>("卖渔", Occupation.taskNameOf Occupation.SellFish)
Assert.AreEqual<string>("询价", Occupation.taskNameOf Occupation.MarketInquiry)
// ---- §3 状态机 Offered|Active|Done|Failed ----
[<TestMethod>]
member _.AcceptMovesOfferedToActiveOnly () =
let offered = DailyTaskHarness.taskOf Occupation.HelpWork Occupation.Offered
let active = Occupation.acceptTask offered
Assert.AreEqual<Occupation.TaskState>(Occupation.Active, active.State)
Assert.AreEqual<Occupation.TaskState>(Occupation.Active, (Occupation.acceptTask active).State)
let finished = { active with State = Occupation.Done }
Assert.AreEqual<Occupation.TaskState>(Occupation.Done, (Occupation.acceptTask finished).State)
let failed = { active with State = Occupation.Failed }
Assert.AreEqual<Occupation.TaskState>(Occupation.Failed, (Occupation.acceptTask failed).State)
[<TestMethod>]
member _.SellFishCompletesOnlyOnPlayerFishSales () =
let active = DailyTaskHarness.accepted Occupation.SellFish
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) active).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Traded(playerId, Spice, 1)) active).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Traded(NpcId 2, Fish, 4)) active).State)
let offered = DailyTaskHarness.taskOf Occupation.SellFish Occupation.Offered
Assert.AreEqual<Occupation.TaskState>(
Occupation.Offered,
(Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) offered).State)
[<TestMethod>]
member _.DeliverGrainCompletesOnlyOnPlayerFoodGiveaways () =
let active = DailyTaskHarness.accepted Occupation.DeliverGrain
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Traded(playerId, Food, 3)) active).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) active).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Traded(NpcId 5, Food, 1)) active).State)
[<TestMethod>]
member _.HelpWorkAndTillSoilRespectOptionalTargets () =
let wildcard = DailyTaskHarness.accepted Occupation.HelpWork
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Dialogued(NpcId 4)) wildcard).State)
let targeted =
{ DailyTaskHarness.accepted Occupation.HelpWork with TargetNpc = Some(NpcId 2) }
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Dialogued(NpcId 2)) targeted).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Dialogued(NpcId 4)) targeted).State)
let untilled = DailyTaskHarness.accepted Occupation.TillSoil
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.ArrivedAt(1, 1)) untilled).State)
let plotTilling =
{ DailyTaskHarness.accepted Occupation.TillSoil with TargetTile = Some(52, 38) }
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.ArrivedAt(52, 38)) plotTilling).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.ArrivedAt(10, 10)) plotTilling).State)
[<TestMethod>]
member _.NightCatchAndMarketInquiryCompleteOnTheirSignals () =
let active = DailyTaskHarness.accepted Occupation.NightCatch
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal Occupation.NightAtWater active).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal (Occupation.Dialogued(NpcId 0)) active).State)
let inquiring = DailyTaskHarness.accepted Occupation.MarketInquiry
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Dialogued(NpcId 3)) inquiring).State)
Assert.AreEqual<Occupation.TaskState>(
Occupation.Active,
(Occupation.applySignal Occupation.NightAtWater inquiring).State)
[<TestMethod>]
member _.DoneAndFailedAreTerminalStates () =
let finished = { DailyTaskHarness.accepted Occupation.SellFish with State = Occupation.Done }
Assert.AreEqual<Occupation.TaskState>(
Occupation.Done,
(Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) finished).State)
let failed = { DailyTaskHarness.accepted Occupation.SellFish with State = Occupation.Failed }
Assert.AreEqual<Occupation.TaskState>(
Occupation.Failed,
(Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) failed).State)
[<TestMethod>]
member _.OverdueTasksFailAtDayBoundaryButNotBefore () =
let active = DailyTaskHarness.accepted Occupation.SellFish
Assert.AreEqual<Occupation.TaskState>(Occupation.Active, (Occupation.expireAt active.DueTick active).State)
Assert.AreEqual<Occupation.TaskState>(Occupation.Failed, (Occupation.expireAt (active.DueTick + 1L) active).State)
let offered = DailyTaskHarness.taskOf Occupation.SellFish Occupation.Offered
Assert.AreEqual<Occupation.TaskState>(Occupation.Failed, (Occupation.expireAt (offered.DueTick + 1L) offered).State)
let finished = { active with State = Occupation.Done }
Assert.AreEqual<Occupation.TaskState>(Occupation.Done, (Occupation.expireAt (finished.DueTick + 1L) finished).State)
|