🍯 Glaze

const std = @import("std");
const builtin = @import("builtin");

fn readInputFile(allocator: std.mem.Allocator, filename: []const u8) ![]u32 {
    var result = std.ArrayList(u32).init(allocator);

    const file = try std.fs.cwd().openFile(filename, .{ .read = true });
    defer file.close();

    const reader = file.reader();

    while (true) {
        var line: []u8 = undefined;
        if (builtin.os.tag == .windows) {
            // NOTE: Read another byte on windows due to two-byte eol.
            line = reader.readUntilDelimiterAlloc(allocator, '\r', 512) catch break;
            _ = try reader.readByte();
        } else {
            line = reader.readUntilDelimiterAlloc(allocator, '\n', 512) catch break;
        }
        defer allocator.free(line);

        for (line) |char| {
            try result.append(@as(u32, char - 48));
        }
    }

    return result.items;
}

pub fn task1(width: u32, height: u32, input: []u32) u32 {
    var result: u32 = 0;

    var y: u32 = 0;
    while (y < height) : (y += 1) {
        var i = y * width;
        var x: u32 = 0;
        while (x < width) : (x += 1) {
            var j = i + x;
            var depth = input[j];

            var is_low_point: bool = true;
            if (y > 0 and input[j - width] <= depth) {
                is_low_point = false;
            }
            if (y < height - 1 and input[j + width] <= depth) {
                is_low_point = false;
            }
            if (x > 0 and input[j - 1] <= depth) {
                is_low_point = false;
            }
            if (x < width - 1 and input[j + 1] <= depth) {
                is_low_point = false;
            }

            if (is_low_point) {
                // std.log.debug("low point->{}", .{depth});
                result += depth + 1;
            }
        }
    }

    return result;
}

fn drawGrid(width: u32, height: u32, grid: []u32) void {
    var y: u32 = 0;
    std.log.debug("grid", .{});
    while (y < height) : (y += 1) {
        std.log.debug("{any}", .{grid[(y * width)..((y + 1) * width)]});
    }
}

fn flood(x: u32, y: u32, width: u32, height: u32, grid: []u32) u32 {
    var result: u32 = 1;
    const j = (y * width) + x;
    grid[j] = 9;
    if (y > 0 and grid[j - width] != 9) {
        result += flood(x, y - 1, width, height, grid);
    }
    if (y < height - 1 and grid[j + width] != 9) {
        result += flood(x, y + 1, width, height, grid);
    }
    if (x > 0 and grid[j - 1] != 9) {
        result += flood(x - 1, y, width, height, grid);
    }
    if (x < width - 1 and grid[j + 1] != 9) {
        result += flood(x + 1, y, width, height, grid);
    }
    return result;
}

const desc_u32 = std.sort.desc(u32);

pub fn task2(allocator: std.mem.Allocator, width: u32, height: u32, input: []u32) !u32 {
    var result: u32 = 1;

    var basins = std.ArrayList(u32).init(allocator);
    defer basins.clearAndFree();

    var y: u32 = 0;
    while (y < height) : (y += 1) {
        var i = y * width;
        var x: u32 = 0;
        while (x < width) : (x += 1) {
            if (input[i + x] != 9) {
                const basin = flood(x, y, width, height, input);
                // drawGrid(width, height, input);
                try basins.append(basin);
            }
        }
    }

    std.sort.sort(u32, basins.items, {}, desc_u32);
    for (basins.items[0..3]) |i| {
        result *= i;
    }

    return result;
}

pub fn main() !void {
    var buffer: [2000000]u8 = undefined;
    var fixed_buffer = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fixed_buffer.allocator();

    const input = try readInputFile(allocator, "input.txt");

    const task_1_result = task1(100, 100, input);
    std.log.info("Task 1 result: {}", .{task_1_result});

    const task_2_result = task2(allocator, 100, 100, input);
    std.log.info("Task 2 result: {}", .{task_2_result});
}