summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/VillagePresentation.fs
blob: 95dc178117a8cb7aebe03b4c0047331a6fae54f8 (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
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
namespace LivingVillage.Desktop

module VillagePresentation =

    (**
       中文江南水乡阶段一样板的纯展示快照。

       TilePosition 使用整数瓦片坐标;一个坐标单位代表一块场景瓦片,原点和
       屏幕像素换算留给后续 MonoGame 组合层。SimulationTick 使用 int64,表示
       Kernel 同一时间轴上的一个模拟 tick。PrototypeScene 以及其中的 record
       和 list 都是不可变快照,调用方只能读取,不能把它们当作可变模拟状态;
       本模块不保存可变全局状态,也不依赖 MonoGame。
    **)

    [<Struct>]
    type TilePosition =
        { X: int
          Y: int }

    [<Struct>]
    type SimulationTick =
        | SimulationTick of int64

    [<Struct>]
    type HomeId =
        | HomeId of int

    type VillageProp =
        | WhiteWallDarkTileHome
        | HomeDoor
        | RiverChannel
        | RiverBank
        | StoneBridge
        | StonePaving
        | Bamboo
        | VegetableGarden

    type VillageElement =
        { Prop: VillageProp
          Position: TilePosition }

    type Home =
        { Id: HomeId
          Door: TilePosition }

    type PrototypeScene =
        { Elements: VillageElement list
          Homes: Home list
          InitialPlayerPosition: TilePosition }

    type Direction =
        | North
        | South
        | West
        | East

    type DirectionInput =
        | UpInput
        | DownInput
        | LeftInput
        | RightInput
        | NoInput

    type CharacterAnimationFrame =
        | FrameA
        | FrameB

    type CharacterStyle =
        | StrawHatFarmer
        | BlueApronVillager
        | GreenScarfVillager

    type HomeMode =
        | Outside
        | Inside of HomeId

    type HomeTransitionError =
        | NotNearHomeDoor
        | AlreadyInsideHome of HomeId
        | NotInsideHome
        | HomeNotFound of HomeId

    type StageOneCopy =
        { Interact: string
          Needs: string
          Trade: string
          EnterHome: string }

    let private tile x y : TilePosition =
        { X = x; Y = y }

    let private element prop position : VillageElement =
        { Prop = prop
          Position = position }

    let createScene () : PrototypeScene =
        let home =
            { Id = HomeId 1
              Door = tile 6 5 }

        { Elements =
            [ element WhiteWallDarkTileHome (tile 6 4)
              element HomeDoor home.Door
              element StonePaving (tile 4 5)
              element StonePaving (tile 5 5)
              element StonePaving (tile 6 5)
              element StonePaving (tile 7 5)
              element RiverBank (tile 10 2)
              element RiverBank (tile 10 3)
              element RiverChannel (tile 10 4)
              element RiverChannel (tile 10 5)
              element StoneBridge (tile 10 4)
              element Bamboo (tile 3 2)
              element Bamboo (tile 3 3)
              element VegetableGarden (tile 8 7)
              element VegetableGarden (tile 9 7) ]
          Homes = [ home ]
          InitialPlayerPosition = tile 5 5 }

    let scene : PrototypeScene =
        createScene ()

    let private manhattanDistance first second : int =
        abs (first.X - second.X) + abs (first.Y - second.Y)

    let isNearHomeDoor (position: TilePosition) (home: Home) : bool =
        manhattanDistance position home.Door <= 1

    let enterHome (position: TilePosition) (mode: HomeMode) : Result<HomeMode, HomeTransitionError> =
        match mode with
        | Inside homeId -> Error(AlreadyInsideHome homeId)
        | Outside ->
            match scene.Homes |> List.tryFind (isNearHomeDoor position) with
            | Some home -> Ok(Inside home.Id)
            | None -> Error NotNearHomeDoor

    let exitHome (mode: HomeMode) : Result<HomeMode, HomeTransitionError> =
        match mode with
        | Outside -> Error NotInsideHome
        | Inside homeId ->
            if scene.Homes |> List.exists (fun home -> home.Id = homeId) then
                Ok Outside
            else
                Error(HomeNotFound homeId)

    let directionForInput (input: DirectionInput) : Direction option =
        match input with
        | UpInput -> Some North
        | DownInput -> Some South
        | LeftInput -> Some West
        | RightInput -> Some East
        | NoInput -> None

    let private positiveModulo divisor value =
        let remainder = value % divisor
        if remainder < 0L then remainder + divisor else remainder

    let animationFrame (SimulationTick tick) : CharacterAnimationFrame =
        if positiveModulo 8L tick < 4L then FrameA else FrameB

    let characterStyle (LivingVillage.Kernel.Sim.NpcId npcId) : CharacterStyle =
        match ((npcId % 3) + 3) % 3 with
        | 0 -> StrawHatFarmer
        | 1 -> BlueApronVillager
        | _ -> GreenScarfVillager

    let stageOneCopy : StageOneCopy =
        { Interact = "互动"
          Needs = "需求"
          Trade = "交易"
          EnterHome = "进入民居" }