From 2c3d2da7f57ea3c992a08f16b32f5275497f9995 Mon Sep 17 00:00:00 2001 From: drakeerv Date: Mon, 16 Dec 2024 22:32:16 -0500 Subject: [PATCH] drakeerv day 15 & 16 --- 2024/15/drakeerv.v | 271 +++++++++++++++++++++++++++++++++++++ 2024/15/movements.input | 21 +++ 2024/16/drakeerv.v | 232 +++++++++++++++++++++++++++++++ 2024/16/map.input | 15 ++ known/2024/15/drakeerv.out | 2 + known/2024/16/drakeerv.out | 2 + 6 files changed, 543 insertions(+) create mode 100644 2024/15/drakeerv.v create mode 100644 2024/15/movements.input create mode 100644 2024/16/drakeerv.v create mode 100644 2024/16/map.input create mode 100644 known/2024/15/drakeerv.out create mode 100644 known/2024/16/drakeerv.out diff --git a/2024/15/drakeerv.v b/2024/15/drakeerv.v new file mode 100644 index 0000000..d8a5501 --- /dev/null +++ b/2024/15/drakeerv.v @@ -0,0 +1,271 @@ +import os +import arrays + +enum Tile { + wall + box1 + box2 + empty +} + +struct Vector { +pub: + // Change to immutable + x int + y int +} + +fn (v Vector) update(other Vector) Vector { + return Vector{other.x, other.y} +} + +fn (v Vector) add(other Vector) Vector { + return Vector{v.x + other.x, v.y + other.y} +} + +fn (v Vector) sub(other Vector) Vector { + return Vector{v.x - other.x, v.y - other.y} +} + +fn (v Vector) mul(other Vector) Vector { + return Vector{v.x * other.x, v.y * other.y} +} + +fn (v Vector) eq(other Vector) bool { + return v.x == other.x && v.y == other.y +} + +fn (v Vector) clone() Vector { + return Vector{v.x, v.y} +} + +fn get_tile(grid [][]Tile, position Vector) Tile { + return grid[position.y][position.x] +} + +fn set_tile(mut grid [][]Tile, position Vector, tile Tile) { + grid[position.y][position.x] = tile +} + +fn parse_map_tile(c string) Tile { + return match c { + '#' { Tile.wall } + 'O' { Tile.box1 } + '.' { Tile.empty } + '@' { Tile.empty } + else { panic('Invalid tile character: ${c}') } + } +} + +fn parse_movement(c string) Vector { + return match c { + 'v' { Vector{0, 1} } + '^' { Vector{0, -1} } + '<' { Vector{-1, 0} } + '>' { Vector{1, 0} } + else { panic('Invalid movement character: ${c}') } + } +} + +fn find_both_boxes(grid [][]Tile, position Vector) []Vector { + tile := get_tile(grid, position) + if tile == .box1 { + mut box2_pos := position.clone() + box2_pos = box2_pos.add(Vector{1, 0}) + return [position, box2_pos] + } else if tile == .box2 { + mut box1_pos := position.clone() + box1_pos = box1_pos.add(Vector{-1, 0}) + return [box1_pos, position] + } + panic('Invalid box position') +} + +fn is_movable_y(grid [][]Tile, position Vector, movement Vector) bool { + tile := get_tile(grid, position) + if tile == .wall { + return false + } else if tile == .empty { + return true + } + + // Find both boxes and check if they can move + boxes := find_both_boxes(grid, position) + mut next_box1_pos := boxes[0].clone() + mut next_box2_pos := boxes[1].clone() + next_box1_pos = next_box1_pos.add(movement) + next_box2_pos = next_box2_pos.add(movement) + + return is_movable_y(grid, next_box1_pos, movement) + && is_movable_y(grid, next_box2_pos, movement) +} + +fn move_box_y(mut grid [][]Tile, box1_pos Vector, movement Vector) { + next_left_pos := box1_pos.add(movement) + next_right_pos := next_left_pos.add(Vector{1, 0}) + next_left_tile := get_tile(grid, next_left_pos) + next_right_tile := get_tile(grid, next_right_pos) + + if next_left_tile == .box1 { + move_box_y(mut grid, next_left_pos, movement) + } else if next_left_tile == .box2 { + move_box_y(mut grid, next_left_pos.add(Vector{-1, 0}), movement) + } + + if next_right_tile == .box1 { + move_box_y(mut grid, next_right_pos, movement) + } + + set_tile(mut grid, next_left_pos, .box1) + set_tile(mut grid, next_right_pos, .box2) + set_tile(mut grid, box1_pos, .empty) + set_tile(mut grid, box1_pos.add(Vector{1, 0}), .empty) +} + +fn is_movable_x(grid [][]Tile, position Vector, movement Vector) ?Vector { + tile := get_tile(grid, position) + if tile == .wall { + return none + } else if tile == .empty { + return position + } + + if tile == .box1 { + return is_movable_x(grid, position.add(Vector{2, 0}), movement) + } else if tile == .box2 { + return is_movable_x(grid, position.add(Vector{-2, 0}), movement) + } + return none +} + +fn main() { + data := os.read_file('movements.input')!.replace('\r', '').split('\n') + separation := data.index('') + + // Parse the grid + mut grid := [][]Tile{} + for row in data[..separation] { + grid << row.split('').map(parse_map_tile) + } + + // Find robot position + mut robot := Vector{0, 0} + for y, row in data[..separation] { + x := row.index('@') or { continue } + robot = Vector{int(x), y} + break + } + + // Parse movements + movements := data[separation + 1..].filter(it != '').join('').split('').map(parse_movement) + + // PART 1 + mut robot1 := robot.clone() + mut grid1 := [][]Tile{len: grid.len, init: grid[index].clone()} + + // Execute movements + for movement in movements { + mut next_robot_position := robot1.clone() + next_robot_position = next_robot_position.add(movement) + next_robot_tile := get_tile(grid1, next_robot_position) + + if next_robot_tile == .wall { + continue + } + + if next_robot_tile == .box1 { + mut next_box_position := next_robot_position.clone() + next_box_position = next_box_position.add(movement) + mut next_box_tile := get_tile(grid1, next_box_position) + + for next_box_tile == .box1 { + next_box_position = next_box_position.add(movement) + next_box_tile = get_tile(grid1, next_box_position) + } + + if next_box_tile == .wall { + continue + } + + set_tile(mut grid1, next_box_position, .box1) + set_tile(mut grid1, next_robot_position, .empty) + } + + robot1 = robot1.update(next_robot_position) + } + + // Calculate part 1 + mut part1 := 0 + for y, row in grid1 { + for x, tile in row { + if tile == .box1 { + part1 += (y * 100) + x + } + } + } + println('part1: ${part1}') + + // PART 2 + mut robot2 := robot.clone() + robot2 = robot2.mul(Vector{2, 1}) + + mut grid2 := [][]Tile{} + for row in grid { + mut row_tiles := [][]Tile{} + for tile in row { + if tile == .box1 { + row_tiles << [Tile.box1, Tile.box2] + } else { + row_tiles << [tile, tile] + } + } + grid2 << arrays.flatten[Tile](row_tiles) + } + + // Execute movements for part 2 + for movement in movements { + next_robot_position := robot2.add(movement) + next_robot_tile := get_tile(grid2, next_robot_position) + + if next_robot_tile == .wall { + continue + } + + if next_robot_tile in [.box1, .box2] { + if movement.x == 0 { + // Vertical movement logic + if !is_movable_y(grid2, next_robot_position, movement) { + continue + } + boxes := find_both_boxes(grid2, next_robot_position) + move_box_y(mut grid2, boxes[0], movement) + } else { + // Horizontal movement logic + next_box_pos := is_movable_x(grid2, next_robot_position, movement) or { continue } + + mut next_box_move_pos := next_box_pos + mut next_box_tile := get_tile(grid2, next_box_move_pos.sub(movement)) + set_tile(mut grid2, next_box_pos, next_box_tile) + + for next_box_tile != .empty { + next_box_move_pos = next_box_move_pos.sub(movement) + next_box_tile = get_tile(grid2, next_box_move_pos.sub(movement)) + set_tile(mut grid2, next_box_move_pos, next_box_tile) + } + } + } + + robot2 = robot2.update(next_robot_position) + } + + // Calculate part 2 + mut part2 := 0 + for y, row in grid2 { + for x, tile in row { + if tile == .box1 { + part2 += (y * 100) + x + } + } + } + println('part2: ${part2}') +} diff --git a/2024/15/movements.input b/2024/15/movements.input new file mode 100644 index 0000000..84cf1fb --- /dev/null +++ b/2024/15/movements.input @@ -0,0 +1,21 @@ +########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ diff --git a/2024/16/drakeerv.v b/2024/16/drakeerv.v new file mode 100644 index 0000000..c8552d4 --- /dev/null +++ b/2024/16/drakeerv.v @@ -0,0 +1,232 @@ +import os + +enum Direction { + north = 0 + east = 1 + south = 2 + west = 3 +} + +enum NodeType { + available + stuck + end_ +} + +enum Tile { + empty + wall +} + +struct Position { +pub: + x int + y int +} + +struct Node { +pub mut: + dir Direction + pos Position + score int + path []Position + // Add path tracking back to Node +} + +struct PriorityQueue { +mut: + items []Node +} + +fn (mut pq PriorityQueue) sift_up(index int) { + mut current := index + for current > 0 { + parent := (current - 1) / 2 + if pq.items[parent].score <= pq.items[current].score { + break + } + pq.items[parent], pq.items[current] = pq.items[current], pq.items[parent] + current = parent + } +} + +fn (mut pq PriorityQueue) sift_down(index int) { + size := pq.items.len + mut smallest := index + + for { + left := 2 * index + 1 + right := 2 * index + 2 + + if left < size && pq.items[left].score < pq.items[smallest].score { + smallest = left + } + if right < size && pq.items[right].score < pq.items[smallest].score { + smallest = right + } + if smallest == index { + break + } + + pq.items[index], pq.items[smallest] = pq.items[smallest], pq.items[index] + smallest = index + } +} + +fn (mut pq PriorityQueue) push(item Node) { + pq.items << item + pq.sift_up(pq.items.len - 1) +} + +fn (mut pq PriorityQueue) pop() ?Node { + if pq.items.len == 0 { + return none + } + + result := pq.items[0] + last := pq.items.pop() + + if pq.items.len > 0 { + pq.items[0] = last + pq.sift_down(0) + } + + return result +} + +fn apply_direction(pos Position, dir Direction) Position { + match dir { + .north { return Position{pos.x, pos.y - 1} } + .east { return Position{pos.x + 1, pos.y} } + .south { return Position{pos.x, pos.y + 1} } + .west { return Position{pos.x - 1, pos.y} } + } +} + +fn load_map(filename string) !([][]Tile, Position, Position) { + content := os.read_file(filename)!.split_into_lines() + mut grid := [][]Tile{} + mut start := Position{0, 0} + mut finish := Position{0, 0} + + for y, line in content { + mut row := []Tile{} + for x, c in line { + match c.ascii_str() { + 'S' { + start = Position{x, y} + row << .empty + } + 'E' { + finish = Position{x, y} + row << .empty + } + '#' { + row << .wall + } + else { + row << .empty + } + } + } + grid << row + } + + return grid, start, finish +} + +fn encode_state(pos Position, dir Direction) u32 { + return (u32(pos.y) << 16) | (u32(pos.x) << 8) | u32(dir) +} + +fn find_path(grid [][]Tile, start Position, finish Position) (int, int) { + mut queue := PriorityQueue{} + mut state_cache := map[u32]int{} + mut best_score := 9999999 + mut visited_tiles := map[string]bool{} + mut all_best_paths := [][]Position{} // Track all paths with best score + height := grid.len + width := grid[0].len + + queue.push(Node{ + dir: .east + pos: start + score: 0 + path: [start] + }) + + for { + current := queue.pop() or { break } + + if current.pos == finish { + if current.score <= best_score { + if current.score < best_score { + // New best score found, clear previous paths + best_score = current.score + all_best_paths.clear() + } + + // Add this path to our collection + all_best_paths << current.path.clone() + continue + } + } + + if current.score >= best_score { + continue + } + + state_key := encode_state(current.pos, current.dir) + if state_key in state_cache && state_cache[state_key] < current.score { + continue + } + state_cache[state_key] = current.score + + next_dir_left := unsafe { Direction((int(current.dir) - 1 + 4) % 4) } + next_dir_right := unsafe { Direction((int(current.dir) + 1) % 4) } + + // Helper to check and add new moves + moves := [ + DirectionMove{current.dir, 1}, + DirectionMove{next_dir_left, 1001}, + DirectionMove{next_dir_right, 1001}, + ] + + for move in moves { + next_pos := apply_direction(current.pos, move.dir) + if next_pos.y >= 0 && next_pos.y < height && next_pos.x >= 0 && next_pos.x < width + && grid[next_pos.y][next_pos.x] == .empty { + mut new_path := current.path.clone() + new_path << next_pos + queue.push(Node{ + dir: move.dir + pos: next_pos + score: current.score + move.cost + path: new_path + }) + } + } + } + + // Mark all positions from all best paths as visited + for path in all_best_paths { + for pos in path { + key := '${pos.x},${pos.y}' + visited_tiles[key] = true + } + } + + return best_score, visited_tiles.len +} + +struct DirectionMove { + dir Direction + cost int +} + +fn main() { + grid, start, finish := load_map('map.input') or { panic(err) } + score, tile_count := find_path(grid, start, finish) + println('part1: ${score}') + println('part2: ${tile_count}') +} diff --git a/2024/16/map.input b/2024/16/map.input new file mode 100644 index 0000000..2c21676 --- /dev/null +++ b/2024/16/map.input @@ -0,0 +1,15 @@ +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +############### diff --git a/known/2024/15/drakeerv.out b/known/2024/15/drakeerv.out new file mode 100644 index 0000000..c856ad2 --- /dev/null +++ b/known/2024/15/drakeerv.out @@ -0,0 +1,2 @@ +part1: 10092 +part2: 9021 diff --git a/known/2024/16/drakeerv.out b/known/2024/16/drakeerv.out new file mode 100644 index 0000000..347f772 --- /dev/null +++ b/known/2024/16/drakeerv.out @@ -0,0 +1,2 @@ +part1: 7036 +part2: 45