diff --git a/src/day18.zig b/src/day18.zig new file mode 100644 index 0000000..51b5ab2 --- /dev/null +++ b/src/day18.zig @@ -0,0 +1,262 @@ +const std = @import("std"); +const assert = std.debug.assert; +const aoc = @import("./aoc.zig"); +const Allocator = std.mem.Allocator; +const PointLib = @import("./point.zig"); +const PointI32 = PointLib.PointI32; +const PointU32 = PointLib.PointU32; + +const Direction = enum { + Up, + Down, + Left, + Right, + + fn from_str(str: []const u8) ?Direction { + if (std.mem.eql(u8, str, "U")) { + return .Up; + } else if (std.mem.eql(u8, str, "D")) { + return .Down; + } else if (std.mem.eql(u8, str, "L")) { + return .Left; + } else if (std.mem.eql(u8, str, "R")) { + return .Right; + } else { + return null; + } + } + + fn to_offset(self: Direction) PointI32 { + return switch (self) { + .Up => PointI32{ .x = 0, .y = -1 }, + .Down => PointI32{ .x = 0, .y = 1 }, + .Left => PointI32{ .x = -1, .y = 0 }, + .Right => PointI32{ .x = 1, .y = 0 }, + }; + } +}; + +const DigInstruction = struct { + dir: Direction, + steps: u32 +}; + +const Instructions = std.ArrayList(DigInstruction); + +fn parse_dig_instruction1(line: []const u8) !DigInstruction { + var iter = std.mem.splitScalar(u8, line, ' '); + + const dir_str = iter.next() orelse return error.MissingDirection; + const steps_str = iter.next() orelse return error.MissingSteps; + + return DigInstruction{ + .dir = Direction.from_str(dir_str) orelse return error.InvalidDirection, + .steps = try std.fmt.parseUnsigned(u32, steps_str, 10) + }; +} + +fn parse_input1(allocator: Allocator, lines: []const []const u8) !Instructions { + var result = Instructions.init(allocator); + errdefer result.deinit(); + + for (lines) |line| { + try result.append(try parse_dig_instruction1(line)); + } + + return result; +} + +fn get_bounds(insts: Instructions) std.meta.Tuple(&.{ PointI32, PointI32 }) { + var upper_left = PointI32.zero(); + var lower_right = PointI32.zero(); + + var current = PointI32.zero(); + for (insts.items) |inst| { + const step = inst.dir.to_offset().mul(@intCast(inst.steps)); + current = current.add(step); + + upper_left.x = @min(upper_left.x, current.x); + upper_left.y = @min(upper_left.y, current.y); + lower_right.x = @max(lower_right.x, current.x); + lower_right.y = @max(lower_right.y, current.y); + } + + return .{ upper_left, lower_right }; +} + +fn flood_fill(allocator: Allocator, map: []bool, map_size: PointU32, from: PointU32) !void { + var stack = std.ArrayList(PointU32).init(allocator); + defer stack.deinit(); + + try stack.append(from); + map[from.y * map_size.x + from.x] = true; + + while (stack.popOrNull()) |point| { + const directions = [_]Direction{ + .Up, + .Right, + .Down, + .Left + }; + for (directions) |dir| { + const offset = dir.to_offset(); + const neighbour_x = @as(i32, @intCast(point.x)) + offset.x; + const neighbour_y = @as(i32, @intCast(point.y)) + offset.y; + + if (!(0 <= neighbour_x and neighbour_x < map_size.x)) continue; + if (!(0 <= neighbour_y and neighbour_y < map_size.y)) continue; + + const neighbour = PointU32{ + .x = @intCast(neighbour_x), + .y = @intCast(neighbour_y) + }; + const neighbour_idx = neighbour.y * map_size.x + neighbour.x; + if (!map[neighbour_idx]) { + map[neighbour_idx] = true; + try stack.append(neighbour); + } + } + } +} + +fn find_flood_fill_start(map: []bool, width: u32, height: u32) ?PointU32 { + assert(height >= 2); + + var found_wall = false; + for (1..height) |y| { + for (0..width) |x| { + const idx = y * width + x; + if (!found_wall) { + if (map[idx]) { + found_wall = true; + } + } else { + if (!map[idx]) { + return PointU32{ .x = @intCast(x), .y = @intCast(y) }; + } + } + } + } + + return null; +} + +fn get_lagoon_size(allocator: Allocator, instructions: Instructions) !usize { + const bounds = get_bounds(instructions); + const upper_left: PointI32 = bounds[0]; + const lower_right: PointI32 = bounds[1]; + const size = PointU32{ + .x = @abs(lower_right.x - upper_left.x) + 1, + .y = @abs(lower_right.y - upper_left.y) + 1 + }; + + std.debug.print("size: {any}\n", .{size}); + + const map = try allocator.alloc(bool, size.x * size.y); + defer allocator.free(map); + @memset(map, false); + + var current = PointI32.zero(); + for (instructions.items) |inst| { + const dir_offset = inst.dir.to_offset(); + + for (0..inst.steps) |i| { + const pos = current.add(dir_offset.mul(@intCast(i))); + const idx = (pos.y - upper_left.y) * @as(i32, @intCast(size.x)) + (pos.x - upper_left.x); + map[@intCast(idx)] = true; + } + + const step = dir_offset.mul(@intCast(inst.steps)); + current = current.add(step); + } + + const flood_fill_start = find_flood_fill_start(map, size.x, size.y) orelse return error.NoFloodFillStart; + try flood_fill(allocator, map, size, flood_fill_start); + + // for (0..size.y) |y| { + // for (0..size.x) |x| { + // const idx = size.x * y + x; + // if (map[idx]) { + // std.debug.print("#", .{}); + // } else { + // std.debug.print(".", .{}); + // } + // } + // std.debug.print("\n", .{}); + // } + + return std.mem.count(bool, map, &.{ true }); +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + + var instructions = Instructions.init(allocator); + defer instructions.deinit(); + for (input.lines) |line| { + try instructions.append(try parse_dig_instruction1(line)); + } + + return .{ .uint = try get_lagoon_size(allocator, instructions) }; +} + +fn parse_dig_instruction2(line: []const u8) !DigInstruction { + var iter = std.mem.splitScalar(u8, line, ' '); + + _ = iter.next(); + _ = iter.next(); + + const color_str = iter.next() orelse return error.MissingColor; + const color_trimmed = std.mem.trim(u8, color_str, "()#"); + if (color_trimmed.len != 6) return error.ColorTooShort; + + const dir: Direction = switch (color_trimmed[5]) { + '0' => .Up, + '1' => .Down, + '2' => .Left, + '3' => .Up, + else => return error.InvalidDirection + }; + + return DigInstruction{ + .dir = dir, + .steps = try std.fmt.parseUnsigned(u32, color_trimmed[1..5], 16) + }; +} + +pub fn part2(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + + var instructions = Instructions.init(allocator); + defer instructions.deinit(); + for (input.lines) |line| { + try instructions.append(try parse_dig_instruction2(line)); + } + + return .{ .uint = try get_lagoon_size(allocator, instructions) }; +} + +const example_input = [_][]const u8{ + "R 6 (#70c710)", + "D 5 (#0dc571)", + "L 2 (#5713f0)", + "D 2 (#d2c081)", + "R 2 (#59c680)", + "D 2 (#411b91)", + "L 5 (#8ceee2)", + "U 2 (#caa173)", + "L 1 (#1b58a2)", + "U 2 (#caa171)", + "R 2 (#7807d2)", + "U 3 (#a77fa3)", + "L 2 (#015232)", + "U 2 (#7a21e3)", +}; + +test "part 1 example" { + try aoc.expectAnswerUInt(part1, 62, &example_input); +} + +test "part 2 example" { + try aoc.expectAnswerUInt(part2, 952408144115, &example_input); +} diff --git a/src/main.zig b/src/main.zig index eaf3f3d..36083ed 100644 --- a/src/main.zig +++ b/src/main.zig @@ -38,6 +38,7 @@ const Days = [_]aoc.Day{ create_day(@import("./day15.zig")), create_day(@import("./day16.zig")), create_day(@import("./day17.zig")), + create_day(@import("./day18.zig")), }; fn kilobytes(count: u32) u32 { @@ -198,4 +199,5 @@ test { _ = @import("./day15.zig"); _ = @import("./day16.zig"); _ = @import("./day17.zig"); + _ = @import("./day18.zig"); } diff --git a/src/point.zig b/src/point.zig index 3636c8f..c1b742c 100644 --- a/src/point.zig +++ b/src/point.zig @@ -34,6 +34,13 @@ pub fn Point(comptime T: type) type { }; } + pub fn mul(self: Self, scale: T) Self { + return Self{ + .x = self.x * scale, + .y = self.y * scale + }; + } + pub fn to_i32(self: Self) Point(i32) { return Point(i32){ .x = @intCast(self.x),