#!nix-shell --pure -i ocaml -p ocaml
*)
type direction = Up | Down | Left | Right
type input =
{ walls: (int * int) list
; boxes: (int * int * int * int) list
; robot: int * int
; movements: direction list }
let grid, directions =
let rec read_input (grid, directions) grid_part =
try
match (read_line (), grid_part) with
| "", _ ->
read_input (grid, directions) true
| x, false ->
read_input (grid ^ x ^ "\n", directions) false
| x, true ->
read_input (grid, directions ^ x) true
with End_of_file -> (grid, directions)
in
read_input ("", "") false
let parse_input grid =
let rec parse_grid output x y = function
| '\n' :: xs ->
parse_grid output 0 (y + 1) xs
| '#' :: xs ->
parse_grid {output with walls= (x, y) :: output.walls} (x + 1) y xs
| 'O' :: xs ->
parse_grid
{output with boxes= (x, y, x, y) :: output.boxes}
(x + 1) y xs
| '[' :: ']' :: xs ->
parse_grid
{output with boxes= (x, y, x + 1, y) :: output.boxes}
(x + 2) y xs
| '@' :: xs ->
parse_grid {output with robot= (x, y)} (x + 1) y xs
| '.' :: xs ->
parse_grid output (x + 1) y xs
| x :: xs ->
failwith ("bad char in grid: " ^ String.make 1 x)
| [] ->
output
in
let rec parse_directions directions = function
| '^' :: xs ->
parse_directions (directions @ [Up]) xs
| 'v' :: xs ->
parse_directions (directions @ [Down]) xs
| '<' :: xs ->
parse_directions (directions @ [Left]) xs
| '>' :: xs ->
parse_directions (directions @ [Right]) xs
| x :: xs ->
failwith ("bad char in directions: " ^ String.make 1 x)
| [] ->
directions
in
parse_grid
{ walls= []
; boxes= []
; robot= (0, 0)
; movements=
directions |> String.to_seq |> List.of_seq |> parse_directions [] }
0 0
(String.to_seq grid |> List.of_seq)
let calc_gps (x, y, _, _) = (y * 100) + x
let next_position (x, y) = function
| Up ->
(x, y - 1)
| Down ->
(x, y + 1)
| Left ->
(x - 1, y)
| Right ->
(x + 1, y)
let next_position' (x1, y1, x2, y2) direction =
let x1, y1 = next_position (x1, y1) direction in
let x2, y2 = next_position (x2, y2) direction in
(x1, y1, x2, y2)
let check_wall_intersection input =
List.mem input.robot input.walls
|| List.exists
(fun (x1, y1, x2, y2) ->
List.mem (x1, y1) input.walls || List.mem (x2, y2) input.walls )
input.boxes
let compare_direction direction (ax1, ay1, ax2, ay2) (bx1, by1, bx2, by2) =
match direction with
| Up ->
Int.max by1 by2 - Int.max ay1 ay2
| Down ->
Int.min ay1 ay2 - Int.min by1 by2
| Left ->
Int.max bx1 bx2 - Int.max ax1 ax2
| Right ->
Int.min ax1 ax2 - Int.min bx1 bx2
let has_intersection_with moved (cx1, cy1, cx2, cy2) =
List.exists
(fun (x1, y1, x2, y2) ->
(x1, y1) = (cx1, cy1)
|| (x1, y1) = (cx2, cy2)
|| (x2, y2) = (cx1, cy1)
|| (x2, y2) = (cx2, cy2) )
moved
let handle_movement input direction =
let rx, ry = next_position input.robot direction in
let sorted_boxes = List.sort (compare_direction direction) input.boxes in
let rec move_boxes acc moved = function
| x :: xs ->
if has_intersection_with moved x then
let next = next_position' x direction in
move_boxes (next :: acc) (next :: moved) xs
else move_boxes (x :: acc) moved xs
| [] ->
(acc, moved)
in
let boxes, moved = move_boxes [] [(rx, ry, rx, ry)] sorted_boxes in
let final = {input with robot= (rx, ry); boxes} in
if check_wall_intersection {final with boxes= moved} then input else final
let rec follow_directions input =
match input.movements with
| x :: xs ->
let out = handle_movement {input with movements= xs} x in
follow_directions out
| [] ->
input
let part1 =
let input = parse_input grid in
let final = follow_directions input in
List.map calc_gps final.boxes
|> List.fold_left ( + ) 0 |> print_int |> print_newline
let part2 =
let grid =
let rec aux acc = function
| '.' :: xs ->
aux (acc ^ "..") xs
| '#' :: xs ->
aux (acc ^ "##") xs
| '@' :: xs ->
aux (acc ^ "@.") xs
| 'O' :: xs ->
aux (acc ^ "[]") xs
| '\n' :: xs ->
aux (acc ^ "\n") xs
| x :: xs ->
failwith ("invalid character in input " ^ String.make 1 x)
| [] ->
acc
in
aux "" (grid |> String.to_seq |> List.of_seq)
in
let input = parse_input grid in
let final = follow_directions input in
List.map calc_gps final.boxes
|> List.fold_left ( + ) 0 |> print_int |> print_newline