diff --git a/sample/day15-sample3.txt b/sample/day15-sample3.txt new file mode 100644 index 0000000..6ee6098 --- /dev/null +++ b/sample/day15-sample3.txt @@ -0,0 +1,9 @@ +####### +#...#.# +#.....# +#..OO@# +#..O..# +#.....# +####### + + read_input + |> calculate + |> io.debug +} + +fn calculate(input: #(Dict(#(Int, Int), Item), List(Movement))) -> Int { + let #(textmap, movements) = input + let #(robot, restmap) = extract_robot(textmap) + + do_movements(restmap, robot, movements) + |> dict.filter(fn(_, v) { v == Box }) + |> dict.fold(0, fn(acc, k, _) { + let #(x, y) = k + acc + x + y * 100 + }) +} + +fn do_movements( + textmap: Dict(#(Int, Int), Item), + robot: #(Int, Int), + movements: List(Movement), +) -> Dict(#(Int, Int), Item) { + //debug_print_map(textmap, robot) + case movements { + [] -> textmap + [next, ..rest] -> { + let #(newmap, newrobot) = move_robot(textmap, robot, next) + do_movements(newmap, newrobot, rest) + } + } +} + +// Hey, here's a freebie debug function :D +fn debug_print_map(textmap: Dict(#(Int, Int), Item), robot: #(Int, Int)) -> Nil { + list.range(0, 7) + |> list.each(fn(y) { + list.range(0, 20) + |> list.each(fn(x) { + let at_pos = dict.get(textmap, #(x, y)) + let prev_pos = dict.get(textmap, #(x - 1, y)) + case at_pos, prev_pos, robot { + Ok(Box), _, _ -> io.print("[") + _, Ok(Box), _ -> io.print("]") + Ok(Wall), _, _ | _, Ok(Wall), _ -> io.print("#") + _, _, #(rx, ry) if rx == x && ry == y -> io.print("@") + _, _, _ -> io.print(".") + } + }) + io.print("\n") + }) +} + +fn move_robot( + textmap: Dict(#(Int, Int), Item), + robot: #(Int, Int), + movement: Movement, +) -> #(Dict(#(Int, Int), Item), #(Int, Int)) { + let next_pos = calculate_next_position(robot, movement) + let at_pos = + dict.get(textmap, next_pos) + |> result.or(dict.get(textmap, calculate_next_position(next_pos, Left))) + case at_pos { + Ok(Wall) -> #(textmap, robot) + Ok(Box) -> + case try_move_box(textmap, next_pos, movement) { + Ok(new_map) -> #(new_map, next_pos) + _ -> #(textmap, robot) + } + _ -> #(textmap, next_pos) + } +} + +fn try_move_box( + textmap: Dict(#(Int, Int), Item), + maybe_pos: #(Int, Int), + movement: Movement, +) -> Result(Dict(#(Int, Int), Item), Nil) { + let box_pos = case dict.has_key(textmap, maybe_pos) { + True -> maybe_pos + False -> calculate_next_position(maybe_pos, Left) + } + + case movement { + Left | Right -> { + let next_pos = calculate_next_position(box_pos, movement) + let check_pos = calculate_next_position(next_pos, movement) + case dict.get(textmap, check_pos) { + Ok(Wall) -> Error(Nil) + Ok(Box) -> + case try_move_box(textmap, check_pos, movement) { + Ok(new_map) -> + Ok(new_map |> dict.delete(box_pos) |> dict.insert(next_pos, Box)) + _ -> Error(Nil) + } + _ -> Ok(textmap |> dict.delete(box_pos) |> dict.insert(next_pos, Box)) + } + } + Up | Down -> { + let #(x, y) = calculate_next_position(box_pos, movement) + let moved_map = + [#(x - 1, y), #(x, y), #(x + 1, y)] + |> filter(fn(p) { dict.has_key(textmap, p) }) + |> list.fold_until(Ok(textmap), fn(acc, p) { + case dict.get(textmap, p) { + Ok(Wall) -> Stop(Error(Nil)) + Ok(Box) -> { + case + result.try(acc, fn(current) { + try_move_box(current, p, movement) + }) + { + Ok(new_map) -> Continue(Ok(new_map)) + _ -> Stop(Error(Nil)) + } + } + _ -> Continue(acc) + } + }) + + case moved_map { + Ok(new_map) -> + Ok(new_map |> dict.delete(box_pos) |> dict.insert(#(x, y), Box)) + _ -> Error(Nil) + } + } + } +} + +fn calculate_next_position( + current: #(Int, Int), + movement: Movement, +) -> #(Int, Int) { + let #(x, y) = current + case movement { + Up -> #(x, y - 1) + Down -> #(x, y + 1) + Left -> #(x - 1, y) + Right -> #(x + 1, y) + } +} + +fn extract_robot( + objects: Dict(#(Int, Int), Item), +) -> #(#(Int, Int), Dict(#(Int, Int), Item)) { + let assert [robot] = + objects |> dict.filter(fn(_, v) { v == Robot }) |> dict.keys + + #(robot, objects |> dict.delete(robot)) +} + +pub type Item { + Box + Wall + Robot +} + +pub type Movement { + Up + Left + Right + Down +} + +fn read_input( + it: iterator.Iterator(String), +) -> #(Dict(#(Int, Int), Item), List(Movement)) { + let #(map_lines, movement_lines) = + it + |> iterator.to_list + |> map(string.trim) + |> filter(not(string.is_empty)) + |> list.split_while(fn(s) { s |> string.starts_with("#") }) + + let map_objects = + map_lines + |> list.index_fold(dict.new(), parse_map_line) + + let movements = + movement_lines + |> string.concat + |> string.to_graphemes + |> list.map(parse_movement) + + #(map_objects, movements) +} + +fn parse_map_line( + acc: Dict(#(Int, Int), Item), + line: String, + y: Int, +) -> Dict(#(Int, Int), Item) { + line + |> string.to_graphemes + |> list.index_fold(acc, fn(a, char, x) { + case char { + "#" -> dict.insert(a, #(x * 2, y), Wall) + "O" -> dict.insert(a, #(x * 2, y), Box) + "@" -> dict.insert(a, #(x * 2, y), Robot) + _ -> a + } + }) +} + +fn parse_movement(char: String) -> Movement { + case char { + "^" -> Up + "<" -> Left + ">" -> Right + "v" -> Down + _ -> panic as "invalid char" + } +} + +fn not(a: fn(a) -> Bool) { + fn(param: a) -> Bool { !a(param) } +}