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); }