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) [] type DailyTaskTests () = // ---- §3 生成:hash(seed ^ occupationSeed, dayIndex) mod |pool|(splitmix 同源)---- [] member _.TaskHashFollowsSplitmixOverSeedXorOccupationSeed () = Assert.AreEqual(0xF7E9F3F88CC04AD6UL, Occupation.taskHash 42UL 7UL 0L) Assert.AreEqual(0xFB624BD98F2BE6C4UL, Occupation.taskHash 42UL 7UL 1L) Assert.AreEqual(0x87FC3F1DAC740225UL, Occupation.taskHash 42UL 7UL 2L) Assert.AreEqual(0xDA258F1151868B40UL, Occupation.taskHash 42UL 7UL 3L) Assert.AreEqual(0x02D114B7ABDD7A1FUL, Occupation.taskHash 4242UL 99UL 0L) [] 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(0, Occupation.taskPoolOf Occupation.Peddler |> List.length) Assert.AreEqual(0, Occupation.taskPoolOf Occupation.Scholar |> List.length) [] 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(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") [] member _.PeddlerAndScholarHaveNoDailyTaskYet () = Assert.AreEqual(None, Occupation.dailyTaskOf 42UL 7UL 0L Occupation.Peddler) Assert.AreEqual(None, Occupation.dailyTaskOf 42UL 7UL 0L Occupation.Scholar) [] member _.TaskWindowCoversExactlyOneDayStartingOffered () = let task = (Occupation.dailyTaskOf 42UL 7UL 3L Occupation.Farmer).Value Assert.AreEqual(3L * Sim.ticksPerDay, task.OfferedTick) Assert.AreEqual(4L * Sim.ticksPerDay - 1L, task.DueTick) Assert.AreEqual(Occupation.Offered, task.State) Assert.AreEqual(3L, Occupation.dayIndexOf (3L * Sim.ticksPerDay)) Assert.AreEqual(3L, Occupation.dayIndexOf (4L * Sim.ticksPerDay - 1L)) [] member _.TaskTemplateChineseNamesMatchDesignTable () = Assert.AreEqual("送粮", Occupation.taskNameOf Occupation.DeliverGrain) Assert.AreEqual("帮工", Occupation.taskNameOf Occupation.HelpWork) Assert.AreEqual("翻土", Occupation.taskNameOf Occupation.TillSoil) Assert.AreEqual("夜捕", Occupation.taskNameOf Occupation.NightCatch) Assert.AreEqual("卖渔", Occupation.taskNameOf Occupation.SellFish) Assert.AreEqual("询价", Occupation.taskNameOf Occupation.MarketInquiry) // ---- §3 状态机 Offered|Active|Done|Failed ---- [] member _.AcceptMovesOfferedToActiveOnly () = let offered = DailyTaskHarness.taskOf Occupation.HelpWork Occupation.Offered let active = Occupation.acceptTask offered Assert.AreEqual(Occupation.Active, active.State) Assert.AreEqual(Occupation.Active, (Occupation.acceptTask active).State) let finished = { active with State = Occupation.Done } Assert.AreEqual(Occupation.Done, (Occupation.acceptTask finished).State) let failed = { active with State = Occupation.Failed } Assert.AreEqual(Occupation.Failed, (Occupation.acceptTask failed).State) [] member _.SellFishCompletesOnlyOnPlayerFishSales () = let active = DailyTaskHarness.accepted Occupation.SellFish Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) active).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Traded(playerId, Spice, 1)) active).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Traded(NpcId 2, Fish, 4)) active).State) let offered = DailyTaskHarness.taskOf Occupation.SellFish Occupation.Offered Assert.AreEqual( Occupation.Offered, (Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) offered).State) [] member _.DeliverGrainCompletesOnlyOnPlayerFoodGiveaways () = let active = DailyTaskHarness.accepted Occupation.DeliverGrain Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Traded(playerId, Food, 3)) active).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) active).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Traded(NpcId 5, Food, 1)) active).State) [] member _.HelpWorkAndTillSoilRespectOptionalTargets () = let wildcard = DailyTaskHarness.accepted Occupation.HelpWork Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Dialogued(NpcId 4)) wildcard).State) let targeted = { DailyTaskHarness.accepted Occupation.HelpWork with TargetNpc = Some(NpcId 2) } Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Dialogued(NpcId 2)) targeted).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Dialogued(NpcId 4)) targeted).State) let untilled = DailyTaskHarness.accepted Occupation.TillSoil Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.ArrivedAt(1, 1)) untilled).State) let plotTilling = { DailyTaskHarness.accepted Occupation.TillSoil with TargetTile = Some(52, 38) } Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.ArrivedAt(52, 38)) plotTilling).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.ArrivedAt(10, 10)) plotTilling).State) [] member _.NightCatchAndMarketInquiryCompleteOnTheirSignals () = let active = DailyTaskHarness.accepted Occupation.NightCatch Assert.AreEqual( Occupation.Done, (Occupation.applySignal Occupation.NightAtWater active).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal (Occupation.Dialogued(NpcId 0)) active).State) let inquiring = DailyTaskHarness.accepted Occupation.MarketInquiry Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Dialogued(NpcId 3)) inquiring).State) Assert.AreEqual( Occupation.Active, (Occupation.applySignal Occupation.NightAtWater inquiring).State) [] member _.DoneAndFailedAreTerminalStates () = let finished = { DailyTaskHarness.accepted Occupation.SellFish with State = Occupation.Done } Assert.AreEqual( Occupation.Done, (Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) finished).State) let failed = { DailyTaskHarness.accepted Occupation.SellFish with State = Occupation.Failed } Assert.AreEqual( Occupation.Failed, (Occupation.applySignal (Occupation.Traded(playerId, Fish, 1)) failed).State) [] member _.OverdueTasksFailAtDayBoundaryButNotBefore () = let active = DailyTaskHarness.accepted Occupation.SellFish Assert.AreEqual(Occupation.Active, (Occupation.expireAt active.DueTick active).State) Assert.AreEqual(Occupation.Failed, (Occupation.expireAt (active.DueTick + 1L) active).State) let offered = DailyTaskHarness.taskOf Occupation.SellFish Occupation.Offered Assert.AreEqual(Occupation.Failed, (Occupation.expireAt (offered.DueTick + 1L) offered).State) let finished = { active with State = Occupation.Done } Assert.AreEqual(Occupation.Done, (Occupation.expireAt (finished.DueTick + 1L) finished).State)