-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstate.ml
382 lines (338 loc) · 13 KB
/
state.ml
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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
open Types
open Command
let object_size = (16., 13.)
let sprite_movement = 3.0
(* [get_sprite] returns the sprite with id int
* requires: id is a valid sprite_id and lst is all the sprites
* returns: desired sprite based on id *)
let rec get_sprite (id: int) lst =
match lst with
| [] -> failwith "invalid sprite id [GET]"
| h::t ->
if h.id = id then h
else get_sprite id t
(* returns player location in state *)
let get_player_location st =
(get_sprite 0 st.all_sprites).location
(* helper function for all_sprite_locations
requires: lst and ret are lists,
returns: assoc list of name to location for each sprite *)
let rec location_helper (lst: sprite list) ret =
match lst with
| [] -> ret
| sprite::t -> (sprite.name, sprite.location)::ret
(* returns location of all sprites in state *)
let all_sprite_locations st =
let all_sprites = st.all_sprites in
location_helper all_sprites []
(* [current_room_id st] is the current room that the player is in *)
let current_room_id st =
st.current_room_id
(* [get_health id st] returns the health of sprite with id=id *)
let get_health id st =
(get_sprite id st.all_sprites).health
(* [get_location id st] returns the location of the sprite with id =id *)
let get_location id st =
(get_sprite id st.all_sprites).location
(* helper function to get direction of sprite
requires: id is a sprite id, lst is a list of sprites *)
let get_sprite_direction id st =
(get_sprite id st.all_sprites).direction
(* helper function to check of sprite is on a square *
requires: all_sprites is the list of all_sprites, loc is a location *)
let rec sprite_on_square (all_sprites: sprite list) loc =
match all_sprites with
| [] -> true
| h::t ->
(if h.location.coordinate = loc then false
else sprite_on_square t loc)
(* helper function to get room via room id in state *)
let rec get_target_room all_rooms r_id =
match all_rooms with
| [] -> failwith "INVALID ROOM ID [get_target_room]"
| room::t ->
if room.room_id = r_id then room
else get_target_room t r_id
(* helper function to get all objects in a room *)
let objects_in_room st =
let room_id = st.current_room_id in
let target_room = get_target_room st.all_rooms room_id in
target_room.obj_lst
(* Checks to see if two coordinates w/ sizes are overlapping
* does not check to see if the coordinates are in the same room *)
let overlapping ((width1, height1), (x1,y1)) ((width2, height2), (x2,y2)) =
let x1_min = x1 in
let x1_max = x1 +. width1 in
let y1_min = y1 in
let y1_max = y1 +. height1 in
let x2_min = x2 in
let x2_max = x2 +. width2 in
let y2_min = y2 in
let y2_max = y2 +. height2 in
let xs_overlap =
if (x1_min >= x2_min && x1_min <= x2_max) ||
(x2_min >= x1_min && x2_min <= x1_max)
then true else false in
let ys_overlap =
if (y1_min >= y2_min && y1_min <= y2_max) ||
( y2_min >= y1_min && y2_min <= y1_max)
then true else false in
xs_overlap && ys_overlap
(* [extract_loc_from_ob ob] returns the location that obj ob has *)
let extract_loc_from_ob ob =
match ob with
| Portal portal -> portal.location
| Texture texture -> texture
| Obstacle obst -> obst
| End loc -> loc
(* helper to return object at location loc *)
let rec get_obj_by_loc sprite loc (all_objs: obj list) =
match all_objs with
| [] -> Texture loc
| h::t ->
let targ_loc = extract_loc_from_ob h in
let overlap = (overlapping (sprite.size, loc.coordinate) (object_size, targ_loc.coordinate)) in
if overlap then h else
get_obj_by_loc sprite loc t
(* returns true if sprite is attemtping to move outside of playable area *)
let is_outside loc height width =
fst loc < 0. || snd loc < 0.
|| fst loc > width *.25.5 || snd loc > height *. 25.5
(* returns true if p is a player, false if p is an enemy *)
let is_player p =
match p.name with
| Player -> true
| Enemy _-> false
(* [process_move command st sprite curr_room] returns an updated location,
* based on the command (keys pressed), and the sprite.
* If the location is a portal, update current_room_id in state and teleport
* the player to the new room.
* Enemies cannot teleport *)
let process_move command st (sprite: sprite) curr_room =
let target_sprite = sprite in
let current_loc = sprite.location.coordinate in
let x_off =
if command.d then sprite.speed
else if command.a then sprite.speed *. -1.
else 0. in
let y_off =
if command.s then sprite.speed
else if command.w then sprite.speed *. -1.
else 0. in
let target_loc= ((fst current_loc +. x_off), snd current_loc +. y_off) in
let targ_room = get_target_room st.all_rooms st.current_room_id in
if is_outside target_loc targ_room.height targ_room.width
then sprite.location
else
let new_loc = {target_sprite.location with coordinate = target_loc} in
if sprite.name = Enemy Boss || sprite.name = Enemy Coop then new_loc
else
let target_obj = get_obj_by_loc target_sprite new_loc curr_room.obj_lst in
match target_obj with
| Texture t -> new_loc
| Portal p when is_player sprite -> p.teleport_to
| Portal _ -> new_loc
| Obstacle _ -> sprite.location
| End _ -> st.has_won <- true; new_loc
(* a helper function that calls process_move *)
let move_helper dir st sprite_id =
process_move dir st sprite_id
(* returns: the location sprite is in *)
let sprite_room (sprite: sprite) =
sprite.location.room
(* [update_action command sprite st] updates the animation based on key press *)
let update_action command sprite st =
if sprite.counter > sprite.max_count then Step
else let () = sprite.counter <- sprite.counter + 1 in
if command.w then Step
else if command.a then Step
else if command.s then Step
else if command.d then Step
else if command.j then Attack
else if command.k then Attack
else if command.l then Attack
else sprite.action
(* Only the Boss enemy type changes its size depending on the move performed *)
let update_size command sprite st =
match sprite.name with
| Player -> sprite.size
| Enemy e ->
(match e with
| Boss ->
if command.k = true
then (fst sprite.size) *. 1.2, (snd sprite.size) *. 1.2
else sprite.size
| _ -> sprite.size)
(* Only the Boss enemy type changes its speed depending on the move performed *)
let update_speed command sprite st =
match sprite.name with
| Player -> sprite.speed
| Enemy e ->
(match e with
| Boss ->
if command.j = true
then sprite.speed *. 1.2
else sprite.speed
| _ -> sprite.speed)
(* determines direction based on last key press, or the current direction
* if no keys are being pressed *)
let determine_direction command sprite =
if command.w then North
else if command.a then West
else if command.s then South
else if command.d then East
else sprite.direction
(* helper function to determine if a direction key is currently pressed *)
let dir_key_pressed command =
command.w || command.a || command.s || command.d
(* updates the location according to command,
* calls process_move which is the bulk of functionality *)
let update_location command (sprite: sprite) st =
if dir_key_pressed command then
process_move command st sprite (get_target_room st.all_rooms st.current_room_id)
else sprite.location
(* [get_other_sprites st sprite_id] returns all of the sprites in
* state whose id is not sprite_id *)
let get_other_sprites st id =
let all_sprites = st.all_sprites in
List.filter (fun sprite -> sprite.id <> id) all_sprites
(* updates the health of a sprite based on an overlap with either an
* enemy sprite or an attack hitbox, depending on the type of sprite *)
let update_health command sprite st =
let current_room = sprite_room sprite in
let other_sprites = get_other_sprites st sprite.id in
match sprite.name with
| Player ->
let got_hit = List.fold_left
(fun acc other_sprite ->
(overlapping
((sprite.size), (sprite.location.coordinate))
((other_sprite.size), (other_sprite.location.coordinate))) || acc)
false other_sprites in
if got_hit then (fst sprite.health) -. 1.0, snd sprite.health else sprite.health
| Enemy _ ->
if (snd st.attack).room <> current_room then sprite.health
else
let got_hit = overlapping
((sprite.size), (sprite.location.coordinate))
((fst st.attack), (snd st.attack).coordinate) in
if got_hit then (fst sprite.health) -. 2.0, snd sprite.health else sprite.health
(* count # of sprites that are dead *)
let count_dead st =
List.fold_left
(fun acc sprite -> if fst sprite.health <= 0.0 then acc + 1 else acc)
0 st.all_sprites
(* updates kill count based on current kill count and # of dead sprites *)
let update_kill_count command sprite st =
sprite.kill_count + count_dead st
(* updates direction based on given command *)
let update_direction command sprite st =
determine_direction command sprite
(* returns true if sprite is moving, else false *)
let update_moving command (sprite: sprite) st =
let curr_loc = sprite.location in
if dir_key_pressed command then
let new_loc = process_move command st sprite (get_target_room st.all_rooms st.current_room_id) in
curr_loc <> new_loc
else false
(* returns true if player has won, else false *)
let update_has_won command sprite st =
if sprite.name = Player then
let targ_room = get_target_room st.all_rooms st.current_room_id in
let obj_type = get_obj_by_loc sprite sprite.location targ_room.obj_lst in
match obj_type with
| End _ -> true
| _ -> false
else false
(* sprite takes action based on command and then each field
* is updated individually with a helper function *)
let sprite_take_action st sprite =
if fst sprite.health <= 0.0 then
match sprite.name with
| Player -> [{sprite with health = -1., snd sprite.health}]
| Enemy _ -> []
else
let command =
match sprite.name with
| Enemy _ -> ai_command st sprite.id
| Player -> player_command in
let new_health = update_health command sprite st in
if fst new_health <= 0.0
then match sprite.name with
| Player -> [{sprite with health = -1., snd sprite.health}]
| Enemy _ -> [{sprite with health = -1., snd sprite.health}]
else
[{sprite with action = update_action command sprite st;
size = update_size command sprite st;
speed = update_speed command sprite st;
location = update_location command sprite st;
health = new_health;
kill_count = update_kill_count command sprite st;
direction = update_direction command sprite st;
moving = update_moving command sprite st;
has_won = update_has_won command sprite st}]
(* [getSprites sprites] parses a sprite list to
* (player_sprite, other_sprites) *)
let getSprites st =
let all_sprites = st.all_sprites in
let isEnemy (sprite: sprite) =
match sprite.name with
| Enemy _ -> true
| Player -> false in
let rec spriteList sprites player other =
match sprites with
| [] -> (player, other)
| h :: t ->
if isEnemy h
then spriteList t player (h :: other)
else spriteList t (h :: player) other in
spriteList all_sprites [] []
(* some constants *)
let blank_attack =
(0.0,0.0), {coordinate = (0., 0.); room = "NONE"}
let sword_length = 20.
let sword_width = 16.
(* gets attack of player
* requires: player is a sprite
* returns: the attack with correct size/direction *)
let get_attack player =
match player.action with
| Attack ->
let offsets_and_sizes = (match player.direction with
| North -> (0., -. (sword_length /. 2.)), (sword_width, sword_length)
| South -> (0., (sword_length /. 2.)), (sword_width, sword_length)
| East -> (sword_length /. 2., 0.), (sword_length, sword_width)
| West -> (-. (sword_length /. 2.), 0.), (sword_length, sword_width)) in
let coordinates = player.location.coordinate in
let offsets = fst offsets_and_sizes in
let sizes = snd offsets_and_sizes in
(sizes),
{coordinate
= ((fst coordinates) +. (fst offsets), (snd coordinates) +. (snd offsets));
room = player.location.room}
| _ -> blank_attack
let do' st =
let sprites = getSprites st in
let player = List.hd (fst sprites) in
let enemy_sprites = snd sprites in
let next_player_sprite = List.hd (sprite_take_action st player) in
let current_room = sprite_room next_player_sprite in
let next_enemy_sprites =
List.fold_left
(fun acc (sprite: sprite) ->
if sprite_room sprite = current_room
then (sprite_take_action st sprite) @ acc
else sprite :: acc)
[] enemy_sprites in
let won = next_player_sprite.has_won in
let attack = get_attack next_player_sprite in
{st with all_sprites = next_player_sprite :: next_enemy_sprites;
has_won = won;
current_room_id = current_room;
attack = attack}
(* true if player has won, else false *)
let get_has_won st =
st.has_won
(* [get_curr_room st returns the current room of state] *)
let get_curr_room st =
st.current_room_id