summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P37ProfessionTests.fs
blob: e5307d3bb39a93c445d49f508caf9577376dba1e (plain)
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
namespace LivingVillage.Desktop.Tests

open System
open System.IO
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
open LivingVillage.Desktop
open LivingVillage.Desktop.ChineseText

/// P37 职业系统首切片验收回归(docs/design/profession-system.md §4):
/// 渔夫首项钓鱼相关、职业任务生成确定性、开局属性差异、旧档兼容、中文 UI 选择。
[<TestClass>]
type P37ProfessionTests () =

    let withTempSave (action: string -> unit) =
        let id = Guid.NewGuid().ToString("N")
        let path = Path.Combine(Path.GetTempPath(), $"living-village-p37-{id}.save")
        try
            action path
        finally
            if File.Exists path then File.Delete path

    let fisherPool : Occupation.TaskTemplateId list =
        [ Occupation.NightCatch; Occupation.SellFish; Occupation.MarketInquiry ]

    let sequence (seed: uint64) (kind: Occupation.Kind) (days: int64 list) : Occupation.TaskTemplateId list =
        days
        |> List.map (fun day ->
            (Occupation.dailyTaskOf seed (Occupation.occupationSeedOf kind) day kind).Value.TemplateId)

    [<TestMethod>]
    member _.FisherFirstTaskIsFishingRelated () =
        let state = WorldBootstrap.occupationStateFor 42UL Occupation.Fisher 0L
        match state.Today with
        | Some task ->
            Assert.IsTrue(fisherPool |> List.contains task.TemplateId, sprintf "渔夫当日任务越池:%A" task.TemplateId)
            Assert.IsTrue(
                [ "夜捕"; "卖渔"; "询价" ] |> List.contains (Occupation.taskNameOf task.TemplateId),
                "渔夫首项必须是钓鱼相关任务")
        | None -> Assert.Fail("渔夫开局必须生成当日任务")

    [<TestMethod>]
    member _.SameProfessionAndSeedProduceIdenticalTaskSequences () =
        let days = [ 0L .. 29L ]
        for kind in Occupation.all do
            let once = sequence 42UL kind days
            let twice = sequence 42UL kind days
            Assert.AreEqual<Occupation.TaskTemplateId list>(once, twice)
            for day in days do
                let task = (Occupation.dailyTaskOf 42UL (Occupation.occupationSeedOf kind) day kind).Value
                Assert.IsTrue(Occupation.taskPoolOf kind |> List.contains task.TemplateId, sprintf "day %d 越池" day)

    [<TestMethod>]
    member _.DifferentProfessionsProduceDifferentTaskSequences () =
        let days = [ 0L .. 29L ]
        let fisher = sequence 42UL Occupation.Fisher days
        for kind in [ Occupation.Farmer; Occupation.Peddler; Occupation.Scholar ] do
            Assert.IsTrue(fisher <> sequence 42UL kind days, sprintf "渔夫与 %A 的任务序列不应相同" kind)

    [<TestMethod>]
    member _.OpeningMoneyAndEnergyDifferPerProfession () =
        let needsOf (kind: Occupation.Kind) =
            let world = WorldBootstrap.initialWorldWithOccupation false 42UL 4 (Some(Occupation.stateOf kind))
            world.Avatar.Mind.Needs
        Assert.AreEqual<float32>(60.0f, (needsOf Occupation.Farmer).Money)
        Assert.AreEqual<float32>(45.0f, (needsOf Occupation.Fisher).Money)
        Assert.AreEqual<float32>(90.0f, (needsOf Occupation.Peddler).Money)
        Assert.AreEqual<float32>(70.0f, (needsOf Occupation.Scholar).Money)
        Assert.AreEqual<float32>(95.0f, (needsOf Occupation.Fisher).Energy)
        for kind in [ Occupation.Farmer; Occupation.Peddler; Occupation.Scholar ] do
            Assert.AreEqual<float32>(100.0f, (needsOf kind).Energy)

    [<TestMethod>]
    member _.OpeningInventoryBundleIsDefinedPerProfession () =
        Assert.AreEqual<(Sim.ItemKind * int) list>([ Sim.Food, 12 ], (Occupation.profileOf Occupation.Farmer).InitialInventory)
        Assert.AreEqual<(Sim.ItemKind * int) list>(
            [ Sim.Food, 8; Sim.Fish, 3 ],
            (Occupation.profileOf Occupation.Fisher).InitialInventory)
        Assert.AreEqual<(Sim.ItemKind * int) list>(
            [ Sim.Food, 6; Sim.Spice, 4 ],
            (Occupation.profileOf Occupation.Peddler).InitialInventory)
        Assert.AreEqual<(Sim.ItemKind * int) list>(
            [ Sim.Food, 10; Sim.Scroll, 2 ],
            (Occupation.profileOf Occupation.Scholar).InitialInventory)

    [<TestMethod>]
    member _.LegacyAndNoProfessionSavesLoadAsNoOccupation () =
        let world = Sim.initialWorldN 42UL 4
        withTempSave (fun path ->
            File.WriteAllText(path, WorldSave.save world)
            let v2Text = File.ReadAllText path
            Assert.IsTrue(v2Text.StartsWith("LV_WORLD_SAVE_V2"), "无职业存档应保持 v2")
            match WorldSave.loadFromFileWith path with
            | Ok (_, occupation) -> Assert.AreEqual<Occupation.State option>(None, occupation)
            | Error message -> Assert.Fail(sprintf "v2 读取失败:%s" message)

            let firstPipe = v2Text.IndexOf('|')
            let secondPipe = v2Text.IndexOf('|', firstPipe + 1)
            let thirdPipe = v2Text.IndexOf('|', secondPipe + 1)
            File.WriteAllText(path, "LV_WORLD_SAVE_V1" + v2Text.Substring(thirdPipe))
            match WorldSave.loadFromFileWith path with
            | Ok (_, occupation) -> Assert.AreEqual<Occupation.State option>(None, occupation)
            | Error message -> Assert.Fail(sprintf "v1 读取失败:%s" message))

    [<TestMethod>]
    member _.OccupationSelectPageNavigatesAndEmitsChosenKind () =
        // 默认选中「暂不选择」(index 4);两次 Down 到「渔夫」(index 1),Enter 确认。
        let page = MenuState.openOccupationSelect (MenuState.create SimulationControl.initial false)
        Assert.AreEqual<MenuPage>(OccupationSelect, page.Page)
        Assert.AreEqual<Occupation.Kind option>(None, MenuState.selectedOccupation page)
        let fisher = MenuState.move Down (MenuState.move Down page)
        Assert.AreEqual<Occupation.Kind option>(Some Occupation.Fisher, MenuState.selectedOccupation fisher)
        let _, command = MenuState.update Confirm fisher
        Assert.AreEqual<MenuCommand>(StartNewGameWith(Some Occupation.Fisher), command)

    [<TestMethod>]
    member _.OccupationUiLabelsAreAtlasBacked () =
        let labels =
            [ "选择职业"; "暂不选择"; Occupation.nameOf Occupation.Farmer; Occupation.nameOf Occupation.Fisher
              Occupation.nameOf Occupation.Peddler; Occupation.nameOf Occupation.Scholar ]
        for label in labels do
            Assert.IsTrue(requiredUiLabels |> List.contains label, sprintf "缺少必备用字登记:%s" label)
            Assert.IsTrue(PixelText.isRenderable label, sprintf "文案不可渲染(缺字):%s" label)