forked from ChrisPritchard/MiniKnight
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KnightController.fs
292 lines (264 loc) · 10.4 KB
/
KnightController.fs
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
module KnightController
open GameCore
open Model
open View
open Microsoft.Xna.Framework.Input
let walkSpeed = 0.15
let jumpSpeed = -0.6
let gravityStrength = 0.05
let terminalVelocity = 0.9
let coinScore = 2
let killScore = 10
let walkLeftKeys = [Keys.A;Keys.Left]
let walkRightKeys = [Keys.D;Keys.Right]
let jumpKeys = [Keys.W;Keys.Space]
let strikeKeys = [Keys.LeftControl;Keys.RightControl]
let blockKeys = [Keys.LeftAlt;Keys.RightAlt]
let private checkForVerticalBlocker (x, ny) isFalling =
let (floory, ceily) = floor ny, ceil ny
let isInVertical bx =
bx = x ||
(bx < x && (bx + 1.) > x) ||
(bx > x && (bx - 1.) < x)
List.tryFind (fun (bx, by) ->
isInVertical bx &&
match isFalling with
| false -> by = ceily - 1.
| true -> by = floory + 1.)
let tryApplyVelocity verticalSpeed (x, y) worldState =
let ny = y + verticalSpeed
let blockers =
[
worldState.blocks |> List.map (fun (bx, by, _) -> (float bx, float by));
worldState.orcs |> List.filter (fun o -> o.state <> Slain) |> List.map (fun o -> o.position)
] |> List.concat
if verticalSpeed < 0. then
let ceiling = checkForVerticalBlocker (x, ny) false blockers
match ceiling with
| Some (_, by) -> (x, float by + 1.), Some 0.
| None -> (x, ny), Some verticalSpeed
else
let floor = checkForVerticalBlocker (x, ny) true blockers
match floor with
| Some (_, by) -> (x, float by - 1.), None
| None -> (x, ny), Some verticalSpeed
let checkForHorizontalBlocker (nx, y) direction =
let isInHorizontal by =
by = y ||
(by < y && (by + 1.) > y) ||
(by > y && (by - 1.) < y)
List.tryFind (fun (bx, by) ->
isInHorizontal by &&
match direction with
| Left -> bx >= nx - 1. && bx < nx
| Right -> nx + 1. >= bx && bx > nx)
let tryWalk direction (x, y) worldState =
let nx = if direction = Left then x - walkSpeed else x + walkSpeed
let blockers =
[
worldState.blocks |> List.map (fun (bx, by, _) -> (float bx, float by));
worldState.orcs |> List.filter (fun o -> o.state <> Slain) |> List.map (fun o -> o.position)
] |> List.concat
match checkForHorizontalBlocker (nx, y) direction blockers with
| Some (bx, _) when direction = Left -> (float bx + 1., y)
| Some (bx, _) when direction = Right -> (float bx - 1., y)
| _ -> (nx, y)
let getWalkCommand (runState: RunState) =
let left = if runState.IsAnyPressed walkLeftKeys then Some Left else None
let right = if runState.IsAnyPressed walkRightKeys then Some Right else None
match [left;right] |> List.choose id with
| [dir] -> Some dir
| _ -> None
let checkForHorizontalTouch (x, y) direction =
List.map (fun (x, y) -> float x, float y)
>> checkForHorizontalBlocker (x, y) direction
>> Option.bind (fun (fx, fy) -> Some (int fx, int fy))
let checkForVerticalTouch (x, y) =
List.map (fun (x, y) -> float x, float y)
>> fun list -> [checkForVerticalBlocker (x, y) true list; checkForVerticalBlocker (x, y) false list]
>> List.tryPick id
>> Option.bind (fun (fx, fy) -> Some (int fx, int fy))
let bubbleFromCoin (x, y) =
{
text = sprintf "+ %i pts" coinScore
startY = float y
position = float x, float y
}
let bubbleFromKill (x, y) =
{
text = sprintf "+ %i pts" killScore
startY = y
position = x, y
}
let processInAir velocity runState worldState =
let knight = worldState.knight
let walkCommand = getWalkCommand runState
let direction = match walkCommand with Some dir -> dir | None -> knight.direction
let nv = min (velocity + gravityStrength) terminalVelocity
let (positionAfterVertical, verticalSpeed) = tryApplyVelocity nv knight.position worldState
let finalPosition =
match walkCommand with
| Some dir -> tryWalk dir positionAfterVertical worldState
| None -> positionAfterVertical
let hasHitSpikes = checkForVerticalTouch finalPosition worldState.spikes
let hasHitCoin = checkForVerticalTouch finalPosition worldState.coins
let newKnight =
{ knight with
position = finalPosition
direction = direction
verticalSpeed = verticalSpeed
score =
match hasHitCoin with
| Some _ -> knight.score + coinScore
| _ -> knight.score
state =
match hasHitSpikes with
| Some _ -> Dying runState.elapsed
| _ -> Walking }
let coins, bubbles =
match hasHitCoin with
| Some c ->
let newBubble = bubbleFromCoin c
List.except [c] worldState.coins, newBubble::worldState.bubbles
| _ -> worldState.coins, worldState.bubbles
{ worldState with
knight = newKnight
coins = coins
bubbles = bubbles
events =
match hasHitCoin, hasHitSpikes with
| _, Some _ -> HitSpikes::KnightDying::worldState.events
| Some _, _ -> CoinCollect::worldState.events
| _ -> worldState.events }
let testForStrickenOrc (kx, ky) direction elapsed (orcs : Orc list) =
let orc = orcs |> List.tryFind (fun orc ->
let ox, oy = orc.position
oy = ky &&
(match direction with
| Left -> ox > kx - 2. && ox < kx
| Right -> kx < ox && kx + 2. > ox))
match orc with
| Some o ->
match o.state with
| Guarding _ -> orcs, Some OrcBlocked
| _ ->
orcs |> List.map (fun oi ->
match oi with
| _ when oi = o ->
{ o with
health = o.health - 1
state = if o.health = 1 then Falling elapsed else o.state }
| _ -> oi), if o.health = 1 then Some OrcFalling else Some OrcHit
| _ -> orcs, None
let processStrike (runState: RunState) worldState =
let knight = worldState.knight
let (newOrcs, event) =
testForStrickenOrc
knight.position
knight.direction
runState.elapsed
worldState.orcs
let newKnight =
{ knight with
state = Striking runState.elapsed
score = match event with | Some OrcFalling -> knight.score + killScore | _ -> knight.score }
let bubbles =
match event with
| Some OrcFalling ->
(bubbleFromKill knight.position)::worldState.bubbles
| _ -> worldState.bubbles
{ worldState with
knight = newKnight
orcs = newOrcs
bubbles = bubbles
events =
match event with
| Some e -> e::KnightSwing::worldState.events
| _ -> KnightSwing::worldState.events }
let processWalk walkCommand runState worldState =
let knight = worldState.knight
let (position, state, direction) =
match walkCommand with
| Some dir -> tryWalk dir knight.position worldState, Walking, dir
| None -> knight.position, Standing, knight.direction
let hasHitCoin = checkForHorizontalTouch knight.position direction worldState.coins
let roughlyEqual (fx, fy) (ix, iy) =
abs (fx - float ix) < 0.2 && abs (fy - float iy) < 0.2
let hasHitExit = roughlyEqual knight.position worldState.exitPortal
let newKnight =
{ knight with
position = position
direction = direction
state =
match hasHitExit with
| true -> WarpingOut runState.elapsed
| false -> state
score =
match hasHitCoin with
| Some _ -> knight.score + coinScore
| _ -> knight.score }
let coins, bubbles =
match hasHitCoin with
| Some c ->
let newBubble = bubbleFromCoin c
List.except [c] worldState.coins, newBubble::worldState.bubbles
| _ -> worldState.coins, worldState.bubbles
{ worldState with
knight = newKnight
coins = coins
bubbles = bubbles
events =
match hasHitCoin, hasHitExit with
| _, true -> Warping::worldState.events
| Some _, _ -> CoinCollect::worldState.events
| _ -> worldState.events }
let processOnGround (runState: RunState) worldState =
let knight = worldState.knight
if strikeKeys |> runState.IsAnyPressed then
processStrike runState worldState
else
let walkCommand = getWalkCommand runState
let direction = match walkCommand with Some dir -> dir | None -> knight.direction
if blockKeys |> runState.IsAnyPressed then
let newKnight =
{ knight with
direction = direction
state = Blocking }
{ worldState with knight = newKnight }
else if jumpKeys |> runState.WasAnyJustPressed then
let newKnight =
{ knight with
direction = direction
verticalSpeed = Some jumpSpeed
state = Walking }
{ worldState with
knight = newKnight
events = Jump::worldState.events }
else
processWalk walkCommand runState worldState
let processKnight runState worldState =
let knight = worldState.knight
match knight.state with
| WarpingIn t when runState.elapsed - t < warpTime ->
worldState
| Dying t when runState.elapsed - t < (animSpeed * float dyingFrames) ->
worldState
| Dying _ ->
{ worldState with knight = { worldState.knight with state = Dead } }
| Striking t when runState.elapsed - t > (animSpeed * float strikeFrames) ->
let newKnight = { knight with state = Standing }
{ worldState with knight = newKnight }
| Dead | WarpingOut _ | Striking _ ->
worldState
| _ ->
match knight.verticalSpeed with
| Some velocity ->
processInAir velocity runState worldState
| None ->
let (_,gravityEffect) = tryApplyVelocity gravityStrength knight.position worldState
match gravityEffect with
| Some v ->
let newKnight = { knight with verticalSpeed = Some v }
{ worldState with knight = newKnight }
| None ->
processOnGround runState worldState