blob: eee7faceeb884c47a9e24a4ad8b31fe4151f016a (
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
namespace LivingVillage.Desktop
open System
open LivingVillage.Kernel.Sim
module VillagePresentation =
(**
中文江南水乡阶段一样板的纯展示快照。
TilePosition 使用整数瓦片坐标;一个坐标单位代表一块场景瓦片,原点和
屏幕像素换算留给后续 MonoGame 组合层。SimulationTick 使用 int64,表示
Kernel 同一时间轴上的一个模拟 tick。PrototypeScene 以及其中的 record
和 list 都是不可变快照,调用方只能读取,不能把它们当作可变模拟状态;
本模块不保存可变全局状态,也不依赖 MonoGame。
**)
[<Struct>]
type TilePosition =
{ X: int
Y: int }
[<Struct>]
type PixelPosition =
{ X: float32
Y: float32 }
[<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 HomeInputAction =
| EnterHomeAction of HomeId
| ExitHomeAction of HomeId
| DialogueAction
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 sceneOrigin : TilePosition =
tile 25 18
let sceneTileFromPixel (pixel: PixelPosition) : TilePosition =
{ X = int (Math.Floor(float pixel.X / float tilePixels)) - sceneOrigin.X
Y = int (Math.Floor(float pixel.Y / float tilePixels)) - sceneOrigin.Y }
let worldTileForSceneTile (sceneTile: TilePosition) : TilePosition =
{ X = sceneOrigin.X + sceneTile.X
Y = sceneOrigin.Y + sceneTile.Y }
let private manhattanDistance (first: TilePosition) (second: TilePosition) : 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 homeInputAction (pixel: PixelPosition) (mode: HomeMode) : HomeInputAction =
let scenePosition = sceneTileFromPixel pixel
match mode with
| Outside ->
match enterHome scenePosition Outside with
| Ok(Inside homeId) -> EnterHomeAction homeId
| _ -> DialogueAction
| Inside homeId ->
match scene.Homes |> List.tryFind (fun home -> home.Id = homeId) with
| Some home when isNearHomeDoor scenePosition home ->
match exitHome mode with
| Ok Outside -> ExitHomeAction homeId
| _ -> DialogueAction
| _ -> DialogueAction
let resetHomeMode () : HomeMode =
Outside
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 = "进入民居" }
|