(* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let debug = false ;; let fatal_time = 1.0 ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) type pt = { x : int ; y : int ; } type bomb = { xy : pt ; size : int ; det_time : float ; } type player = { id : int ; xy : pt ; nspeed : int ; nbomb_atonce : int ; bomb_radius : int ; ndash : int ; ntraps : int ; } type boost = { xy : pt ; spec : int ; } let default_point = { x = 0 ; y = 0 ; } let default_bomb = { xy = default_point ; size = 0 ; det_time = 0. ; } and default_player = { id = 0 ; xy = default_point ; nspeed = 0 ; nbomb_atonce = 0 ; bomb_radius = 0 ; ndash = 0 ; ntraps = 0 ; } and default_boost = { xy = default_point ; spec = 0 ; } and useless = ref 0 ;; type game_data = { mutable dt : float ; mutable player_id : int ; mutable laby : int array array ; mutable nbombs : int ; mutable bombs : bomb array ; mutable nplayers : int ; mutable players : player array ; mutable nboosts : int ; mutable boosts : boost array ; } type danger = Safe | Danger | Fatal | Blocked ;; let int_of_danger = function | Safe -> 0 | Danger -> 1 | Fatal -> 2 | Blocked -> 3 ;; let danger_of_int = function | 0 -> Safe | 1 -> Danger | 2 -> Fatal | _ -> Blocked ;; type moveType = EscapeDeath | BlowUpCrates | KillPlayers | ClaimLand ;; exception ReturnInt of int ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let current_status = ref BlowUpCrates ;; let equal_pt (p1 : pt) (p2 : pt) = p1.x = p2.x && p1.y = p2.y ;; let swap arr i j = let temp = arr.(i) in arr.(i) <- arr.(j) ; arr.(j) <- temp ;; let is_valid i j len hei = i >= 0 && j >= 0 && i < len && j < hei ;; let print_direction = function | 0 -> Printf.printf "NORTH " | 1 -> Printf.printf "EAST " | 2 -> Printf.printf "SOUTH " | 3 -> Printf.printf "WEST " | 4 -> Printf.printf "STILL " | _-> failwith "ERROR : invalid direction" ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let print_game_data (gd : game_data) = Printf.printf "--------------------------------| Board data |--------------------------------\n" ; Printf.printf "Time : %f\n" gd.dt ; Printf.printf "ID : %d\n" gd.player_id ; Printf.printf "Laby [of size %d %d]:\n" (Array.length gd.laby) (Array.length gd.laby.(0)); for l = 0 to Array.length gd.laby -1 do Printf.printf " " ; for c = 0 to Array.length gd.laby.(l) -1 do Printf.printf "%d " gd.laby.(l).(c) ; done; Printf.printf "\n" done ; Printf.printf "Bombs (%d) : \n" gd.nbombs ; for b = 0 to gd.nbombs -1 do Printf.printf " [Bomb] (at %d %d) (of size %d) (blowing up at %f)\n" gd.bombs.(b).xy.x gd.bombs.(b).xy.y gd.bombs.(b).size gd.bombs.(b).det_time ; done; Printf.printf "Players (%d) : \n" gd.nplayers ; for b = 0 to gd.nplayers -1 do Printf.printf " [Player %d] (at %d %d) (holding %d %d %d %d %d)\n" gd.players.(b).id gd.players.(b).xy.x gd.players.(b).xy.y gd.players.(b).nspeed gd.players.(b).nbomb_atonce gd.players.(b).bomb_radius gd.players.(b).ndash gd.players.(b).ntraps ; done; Printf.printf "Boosts (%d) : \n" gd.nboosts ; for b = 0 to gd.nboosts -1 do Printf.printf " [Boost] (at %d %d) (of type %d)\n" gd.boosts.(b).xy.x gd.boosts.(b).xy.y gd.boosts.(b).spec ; done;; let print_danger_levels (map : danger array array) = Printf.printf "--------------------------------| Danger levels |--------------------------------\n" ; for l = 0 to (Array.length map -1) do for c = 0 to (Array.length map.(l) -1) do match map.(l).(c) with | Blocked -> Printf.printf "@ " | Safe -> Printf.printf ". " | Danger -> Printf.printf "! " | Fatal -> Printf.printf "X " done; Printf.printf "\n" done ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let int_of_string (str : string) = String.fold_right (fun ch acc -> let cd = Char.code ch in if cd >= 48 || cd <= 57 then 10*acc + cd - 48 else failwith "not an integer\n") str 0 ;; let int_n_of_string (str : string) (n : int) (nlast : int ref) = let res = Array.make n 0 in let rec aux idres idstr = match idstr with | k when k = String.length str || idres >= n -> nlast := k | k -> if str.[k] = ' ' then aux (idres+1) (k+1) else begin let cd = Char.code str.[k] in if cd >= 48 && cd <= 57 then begin res.(idres) <- 10 * res.(idres) + cd - 48 ; aux (idres) (k+1) end else failwith "not an integer (n/n)\n" end in aux 0 0 ; res ;; let parse_input (str : string) = let ptr = open_in str in let (res : game_data) = {dt = 0. ; player_id = 0 ; laby = [||] ; nbombs = 0 ; bombs = [||] ; nplayers = 0 ; players = [||] ; nboosts = 0 ; boosts = [||] ;} in try (* time *) if debug then Printf.printf "Time\n" ; res.dt <- Float.of_string (input_line ptr) ; (* player_id *) if debug then Printf.printf "PID\n" ; res.player_id <- int_of_string (input_line ptr) ; (* maze *) if debug then Printf.printf "Maze\n" ; let msize = int_n_of_string (input_line ptr) 2 useless in res.laby <- Array.make msize.(0) [||] ; for lane = 0 to msize.(0) -1 do let psd = input_line ptr in res.laby.(lane) <- int_n_of_string psd msize.(1) useless ; done; (* bombs *) if debug then Printf.printf "Boom\n" ; res.nbombs <- int_of_string (input_line ptr) ; res.bombs <- Array.make res.nbombs default_bomb ; for b = 0 to res.nbombs -1 do let psd = input_line ptr and last = ref 0 in let dat = int_n_of_string psd 3 last in let dtime = Float.of_string (String.init (String.length psd - !last) (fun i -> psd.[i + !last])) in res.bombs.(b) <- {xy = {x = dat.(0) ; y = dat.(1) ;} ; size = dat.(2) ; det_time = dtime ; } done; (* players *) if debug then Printf.printf "Players\n" ; res.nplayers <- int_of_string (input_line ptr) ; res.players <- Array.make res.nplayers default_player ; for p = 0 to res.nplayers -1 do let dat = int_n_of_string (input_line ptr) 8 useless in res.players.(p) <- {id = dat.(2) ; xy = {x = dat.(0) ; y = dat.(1) ;} ; nspeed = dat.(3) ; nbomb_atonce = dat.(4) ; bomb_radius = dat.(5) ; ndash = dat.(6) ; ntraps = dat.(7) ;} done; (* boosts *) if debug then Printf.printf "Boosts\n" ; res.nboosts <- int_of_string (input_line ptr) ; res.boosts <- Array.make res.nboosts default_boost ; for p = 0 to res.nboosts -1 do let dat = int_n_of_string (input_line ptr) 3 useless in res.boosts.(p) <- {xy = {x = dat.(0) ; y = dat.(1) ;} ; spec = dat.(2)} done; if debug then Printf.printf "Done!\n" ; close_in ptr ; res with | End_of_file -> close_in ptr ; failwith "cannot happen unless something is wrong" ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let warn_level (b : bomb) (ct : float) = match (b.det_time -. ct) with | k when k < fatal_time -> Fatal ; | _ -> Danger ;; let danger_priority (p1 : danger) (p2 : danger) = danger_of_int (max (int_of_danger p1) (int_of_danger p2)) ;; let order = [|(1, 0); (-1, 0); (0, 1); (0, -1)|] ;; let evaluate_dangers (gd : game_data) = let lines = Array.length gd.laby and cols = Array.length gd.laby.(0) in let res = Array.make_matrix lines cols Safe in (* add solid blocks *) for l = 0 to lines -1 do for c = 0 to cols -1 do if gd.laby.(l).(c) = 1 || gd.laby.(l).(c) = 2 then res.(l).(c) <- Blocked ; done done ; (* add players as warning (in case they bomb) *) for p = 0 to gd.nplayers -1 do if p <> gd.player_id then begin let halt = ref false in let bx = gd.players.(p).xy.x and by = gd.players.(p).xy.y in for dir = 0 to 3 do for w = 0 to gd.players.(p).bomb_radius do let cx = bx + w*(fst order.(dir)) and cy = by + w*(snd order.(dir)) in if not !halt && is_valid (cx) (cy) lines cols then begin if gd.laby.(cx).(cy) = 1 then (* bedrock *) halt := true ; if gd.laby.(cx).(cy) = 2 then begin (* crate *) halt := true ; res.(cx).(cy) <- danger_priority res.(cx).(cy) Danger ; end else begin res.(cx).(cy) <- danger_priority res.(cx).(cy) Danger ; end end done; halt := false ; done end done; (* sort bombs based on detonation time *) for b = 0 to gd.nbombs -1 do let m = ref gd.bombs.(b).det_time and im = ref b in for j = b+1 to gd.nbombs -1 do if gd.bombs.(j).det_time < !m then begin m := gd.bombs.(j).det_time ; im := j ; end done; swap gd.bombs b (!im) ; done; (* add bomb tiles *) let exploded = Hashtbl.create 12 in for b = 0 to gd.nbombs -1 do let dgr = warn_level gd.bombs.(b) gd.dt in let halt = ref false in let bx = gd.bombs.(b).xy.x and by = gd.bombs.(b).xy.y in for dir = 0 to 3 do for w = 0 to gd.bombs.(b).size do let cx = bx + w*(fst order.(dir)) and cy = by + w*(snd order.(dir)) in if not !halt && is_valid (cx) (cy) lines cols then begin if gd.laby.(cx).(cy) = 1 then (* bedrock *) halt := true ; if gd.laby.(cx).(cy) = 2 && Hashtbl.find_opt exploded (cx, cy) = None then begin (* unexploded crate *) halt := true ; res.(cx).(cy) <- danger_priority res.(cx).(cy) dgr ; Hashtbl.add exploded (cx, cy) 1 end else begin res.(cx).(cy) <- danger_priority res.(cx).(cy) dgr ; end end done; halt := false ; done done; res ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let is_safe = function | Safe | Danger -> true | Fatal | Blocked -> false ;; let move_safe (gd : game_data) (dgs : danger array array) = (* use this whenever you are standing on a non-safe tile *) (* Strat : find the closest safe tile (or warning tile if there are no reachable safe tile) *) let pid = gd.player_id in let lines = Array.length gd.laby and cols = Array.length gd.laby.(0) in (* BFS to find the nearest safe spot (if it exists) *) try (* 0. if you're standing on a safe tile, stay there *) let (cx, cy) = (gd.players.(pid).xy.x, gd.players.(pid).xy.y) in if dgs.(cx).(cy) = Safe then raise (ReturnInt 4) ; let q = Queue.create () in let visited = Hashtbl.create 100 in (* 1. try not to walk on fatal tiles *) if debug then Printf.printf "[escape] Attempt 1/3...\n"; if cx <> 0 && is_safe dgs.(cx-1).(cy) then (* North *) Queue.add (cx-1, cy, 0) q ; if cx <> lines -1 && is_safe dgs.(cx+1).(cy) then (* South *) Queue.add (cx+1, cy, 2) q ; if cy <> 0 && is_safe dgs.(cx).(cy-1) then (* West *) Queue.add (cx, cy-1, 3) q ; if cy <> cols -1 && is_safe dgs.(cx).(cy+1) then (* East *) Queue.add (cx, cy+1, 1) q ; while not (Queue.is_empty q) do let (cx, cy, dir) = Queue.pop q in Hashtbl.add visited (cx, cy, 1) 1 ; if dgs.(cx).(cy) = Safe then raise (ReturnInt dir) else begin (* add neighbors *) if cx <> 0 && Hashtbl.find_opt visited (cx-1, cy, 1) = None && is_safe dgs.(cx-1).(cy) then (* North *) Queue.add (cx-1, cy, dir) q ; if cx <> lines -1 && Hashtbl.find_opt visited (cx+1, cy, 1) = None && is_safe dgs.(cx+1).(cy) then (* South *) Queue.add (cx+1, cy, dir) q ; if cy <> 0 && Hashtbl.find_opt visited (cx, cy-1, 1) = None && is_safe dgs.(cx).(cy-1) then (* West *) Queue.add (cx, cy-1, dir) q ; if cy <> cols -1 && Hashtbl.find_opt visited (cx, cy+1, 1) = None && is_safe dgs.(cx).(cy+1) then (* East *) Queue.add (cx, cy+1, dir) q ; end done; (* 2. find the path that get you off fatal tiles ASAP *) if debug then Printf.printf "[escape] Attempt 2/3...\n"; if cx <> 0 && dgs.(cx-1).(cy) <> Blocked then (* North *) Queue.add (cx-1, cy, 0) q ; if cx <> lines -1 && dgs.(cx+1).(cy) <> Blocked then (* South *) Queue.add (cx+1, cy, 2) q ; if cy <> 0 && dgs.(cx).(cy-1) <> Blocked then (* West *) Queue.add (cx, cy-1, 3) q ; if cy <> cols -1 && dgs.(cx).(cy+1) <> Blocked then (* East *) Queue.add (cx, cy+1, 1) q ; while not (Queue.is_empty q) do let (cx, cy, dir) = Queue.pop q in Hashtbl.add visited (cx, cy, 2) 1 ; if dgs.(cx).(cy) = Safe then raise (ReturnInt dir) else begin (* add neighbors *) if cx <> 0 && Hashtbl.find_opt visited (cx-1, cy, 2) = None && dgs.(cx-1).(cy) <> Blocked then (* North *) Queue.add (cx-1, cy, dir) q ; if cx <> lines -1 && Hashtbl.find_opt visited (cx+1, cy, 2) = None && dgs.(cx+1).(cy) <> Blocked then (* South *) Queue.add (cx+1, cy, dir) q ; if cy <> 0 && Hashtbl.find_opt visited (cx, cy-1, 2) = None && dgs.(cx-1).(cy) <> Blocked then (* West *) Queue.add (cx, cy-1, dir) q ; if cy <> cols -1 && Hashtbl.find_opt visited (cx, cy+1, 2) = None && dgs.(cx).(cy+1) <> Blocked then (* East *) Queue.add (cx, cy+1, dir) q ; end done; (* 3. no safe tile within reach (very rare), look out for warning *) if debug then Printf.printf "[escape] Attempt 3/3...\n"; if cx <> 0 && dgs.(cx-1).(cy) <> Blocked then (* North *) Queue.add (cx-1, cy, 0) q ; if cx <> lines -1 && dgs.(cx+1).(cy) <> Blocked then (* South *) Queue.add (cx+1, cy, 2) q ; if cy <> 0 && dgs.(cx).(cy-1) <> Blocked then (* West *) Queue.add (cx, cy-1, 3) q ; if cy <> cols -1 && dgs.(cx).(cy+1) <> Blocked then (* East *) Queue.add (cx, cy+1, 1) q ; while not (Queue.is_empty q) do let (cx, cy, dir) = Queue.pop q in Hashtbl.add visited (cx, cy, 3) 1 ; if dgs.(cx).(cy) = Danger then raise (ReturnInt dir) else begin (* add neighbors *) if cx <> 0 && Hashtbl.find_opt visited (cx-1, cy, 3) = None && dgs.(cx-1).(cy) <> Blocked then (* North *) Queue.add (cx-1, cy, dir) q ; if cx <> lines -1 && Hashtbl.find_opt visited (cx+1, cy, 3) = None && dgs.(cx+1).(cy) <> Blocked then (* South *) Queue.add (cx+1, cy, dir) q ; if cy <> 0 && Hashtbl.find_opt visited (cx, cy-1, 3) = None && dgs.(cx-1).(cy) <> Blocked then (* West *) Queue.add (cx, cy-1, dir) q ; if cy <> cols -1 && Hashtbl.find_opt visited (cx, cy+1, 3) = None && dgs.(cx).(cy+1) <> Blocked then (* East *) Queue.add (cx, cy+1, dir) q ; end done; (* you're probably dead if the code reaches here *) if debug then Printf.printf "[escape] Attempt F...\n"; 4 with | ReturnInt k -> k ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let move_explore (gd: game_data) (dgs : danger array array) = (* destroy crates *) let pid = gd.player_id in let lines = Array.length gd.laby and cols = Array.length gd.laby.(0) in (* find nearest crate and blow it up *) try let q = Queue.create () in let visited = Hashtbl.create 100 in let (cx, cy) = (gd.players.(pid).xy.x, gd.players.(pid).xy.y) in (* O. if there's a crate right next to you, blow it up *) if cx <> 0 && gd.laby.(cx-1).(cy) = 2 then (* North *) begin current_status := EscapeDeath; raise (ReturnInt 4) end ; if cx <> lines -1 && gd.laby.(cx+1).(cy) = 2 then (* South *) begin current_status := EscapeDeath; raise (ReturnInt 4) end ; if cy <> 0 && gd.laby.(cx).(cy-1) = 2 then (* West *) begin current_status := EscapeDeath; raise (ReturnInt 4) end ; if cy <> cols -1 && gd.laby.(cx).(cy+1) = 2 then (* East *) begin current_status := EscapeDeath; raise (ReturnInt 4) end ; (* 1. search without walking on dangerous tiles *) if debug then Printf.printf "[explore] Attempt 1/2...\n"; if cx <> 0 && is_safe dgs.(cx-1).(cy) then (* North *) Queue.add (cx-1, cy, 0) q ; if cx <> lines -1 && is_safe dgs.(cx+1).(cy) then (* South *) Queue.add (cx+1, cy, 2) q ; if cy <> 0 && is_safe dgs.(cx).(cy-1) then (* West *) Queue.add (cx, cy-1, 3) q ; if cy <> cols -1 && is_safe dgs.(cx).(cy+1) then (* East *) Queue.add (cx, cy+1, 1) q ; while not (Queue.is_empty q) do let (cx, cy, dir) = Queue.pop q in Hashtbl.add visited (cx, cy, 1) 1 ; if gd.laby.(cx).(cy) = 2 then raise (ReturnInt dir) else begin (* add neighbors *) if cx <> 0 && Hashtbl.find_opt visited (cx-1, cy, 1) = None && is_safe dgs.(cx-1).(cy) then (* North *) Queue.add (cx-1, cy, dir) q ; if cx <> lines -1 && Hashtbl.find_opt visited (cx+1, cy, 1) = None && is_safe dgs.(cx+1).(cy) then (* South *) Queue.add (cx+1, cy, dir) q ; if cy <> 0 && Hashtbl.find_opt visited (cx, cy-1, 1) = None && is_safe dgs.(cx).(cy-1) then (* West *) Queue.add (cx, cy-1, dir) q ; if cy <> cols -1 && Hashtbl.find_opt visited (cx, cy+1, 1) = None && is_safe dgs.(cx).(cy+1) then (* East *) Queue.add (cx, cy+1, dir) q ; end done; (* 2. search one anyway *) if debug then Printf.printf "[explore] Attempt 2/2...\n"; let (cx, cy) = (gd.players.(pid).xy.x, gd.players.(pid).xy.y) in if cx <> 0 && is_safe dgs.(cx-1).(cy) then (* North *) Queue.add (cx-1, cy, 0) q ; if cx <> lines -1 && is_safe dgs.(cx+1).(cy) then (* South *) Queue.add (cx+1, cy, 2) q ; if cy <> 0 && is_safe dgs.(cx).(cy-1) then (* West *) Queue.add (cx, cy-1, 3) q ; if cy <> cols -1 && is_safe dgs.(cx).(cy+1) then (* East *) Queue.add (cx, cy+1, 1) q ; while not (Queue.is_empty q) do let (cx, cy, dir) = Queue.pop q in Hashtbl.add visited (cx, cy, 2) 1 ; if gd.laby.(cx).(cy) = 2 then raise (ReturnInt dir) else begin (* add neighbors *) if cx <> 0 && Hashtbl.find_opt visited (cx-1, cy, 2) = None && dgs.(cx-1).(cy) <> Blocked then (* North *) Queue.add (cx-1, cy, dir) q ; if cx <> lines -1 && Hashtbl.find_opt visited (cx+1, cy, 2) = None && dgs.(cx+1).(cy) <> Blocked then (* South *) Queue.add (cx+1, cy, dir) q ; if cy <> 0 && Hashtbl.find_opt visited (cx, cy-1, 2) = None && dgs.(cx).(cy-1) <> Blocked then (* West *) Queue.add (cx, cy-1, dir) q ; if cy <> cols -1 && Hashtbl.find_opt visited (cx, cy+1, 2) = None && dgs.(cx).(cy+1) <> Blocked then (* East *) Queue.add (cx, cy+1, dir) q ; end done; (* no crates ? Go for kills *) if debug then Printf.printf "[explore] Attempt F...\n"; 4 with | ReturnInt k -> k ;; (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) (* ---------------------------------------------------------------------------------------------------------------------------------------------------- *) let game_d = parse_input "input_test.txt" ;; let dangers = evaluate_dangers game_d ;; print_game_data game_d ;; print_danger_levels dangers ;; Printf.printf "move at ";; print_direction (move_safe game_d dangers) ;; Printf.printf "\n" ;;