🍯 Glaze

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

const Input = struct {
    draw_numbers: []u32 = undefined,
    boards: [][25]u32 = undefined,
};

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

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

    const reader = file.reader();

    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 unreachable;
        _ = try reader.readByte();
    } else {
        line = reader.readUntilDelimiterAlloc(allocator, '\n', 512) catch unreachable;
    }

    {
        var token = std.mem.split(u8, line, ",");
        while (token.next()) |slice| {
            const number = try std.fmt.parseUnsigned(u32, slice, 10);
            try draw_numbers.append(number);
        }
        allocator.free(line);
    }
    result.draw_numbers = draw_numbers.items;

    while (true) {
        // NOTE: Skip line.
        _ = reader.readByte() catch break;
        if (builtin.os.tag == .windows) {
            // NOTE: Read another byte on windows due to two-byte eol.
            _ = reader.readByte() catch break;
        }

        var i: u32 = 0;
        var board = std.mem.zeroes([25]u32);
        var board_index: u32 = 0;
        while (i < 5) : (i += 1) {
            if (builtin.os.tag == .windows) {
                // NOTE: Read another byte on windows due to two-byte eol.
                line = reader.readUntilDelimiterAlloc(allocator, '\r', 512) catch unreachable;
                _ = try reader.readByte();
            } else {
                line = reader.readUntilDelimiterAlloc(allocator, '\n', 512) catch unreachable;
            }
            var token = std.mem.tokenize(u8, line, " ");
            while (token.next()) |slice| {
                board[board_index] = try std.fmt.parseUnsigned(u32, slice, 10);
                board_index += 1;
            }
            allocator.free(line);
        }
        try boards.append(board);
    }
    result.boards = boards.items;

    return result;
}

const Marker: u32 = 999;

inline fn boardTickNumber(number: u32, board: []u32) void {
    for (board) |value, i| {
        if (value == number) {
            board[i] = Marker;
        }
    }
}

inline fn checkBoard(board_w: u32, board_h: u32, board: []u32) bool {
    var x: u32 = 0;
    var y: u32 = 0;

    while (y < board_h) : (y += 1) {
        var foundNumber = false;
        x = 0;
        while (x < board_w) : (x += 1) {
            if (board[(y * board_w) + x] != Marker) {
                foundNumber = true;
            }
        }
        if (!foundNumber) {
            return true;
        }
    }

    x = 0;
    while (x < board_w) : (x += 1) {
        var foundNumber = false;
        y = 0;
        while (y < board_h) : (y += 1) {
            if (board[(y * board_w) + x] != Marker) {
                foundNumber = true;
            }
        }
        if (!foundNumber) {
            return true;
        }
    }

    return false;
}

inline fn getBoardValue(board: []u32) u32 {
    var result: u32 = 0;
    for (board) |value| {
        if (value != Marker) {
            result += value;
        }
    }
    return result;
}

pub fn task1(draw_numbers: []const u32, board_w: u32, board_h: u32, boards: [][25]u32) u32 {
    var result: u32 = 0;

    done: for (draw_numbers) |number| {
        std.log.debug("drew {}", .{number});

        for (boards) |*b, i| {
            var board = b[0..];

            boardTickNumber(number, board);
            if (checkBoard(board_w, board_h, board)) {
                result = getBoardValue(board) * number;
                break :done;
            }

            std.log.debug("board {}:{any}", .{ i, board.* });
        }
    }

    return result;
}

pub fn task2(draw_numbers: []const u32, board_w: u32, board_h: u32, boards: [][25]u32) u32 {
    var result: u32 = 0;

    for (draw_numbers) |number| {
        std.log.debug("drew {}", .{number});

        for (boards) |*b, i| {
            var board = b[0..];
            if (checkBoard(board_w, board_h, board)) {
                continue;
            }

            boardTickNumber(number, board);
            if (checkBoard(board_w, board_h, board)) {
                result = getBoardValue(board) * number;
                std.log.debug("result {}:{}->{}", .{ i, number, result });
            }

            std.log.debug("board {}:{any}", .{ i, board.* });
        }
    }

    return result;
}

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

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

    var draw_numbers = try allocator.dupe(u32, input.draw_numbers);
    var boards = try allocator.dupe([25]u32, input.boards);
    const task_1_result = task1(draw_numbers, 5, 5, boards);
    std.log.info("Task 1 result: {}", .{task_1_result});
    allocator.free(draw_numbers);
    allocator.free(boards);

    draw_numbers = try allocator.dupe(u32, input.draw_numbers);
    boards = try allocator.dupe([25]u32, input.boards);
    const task_2_result = task2(input.draw_numbers, 5, 5, input.boards);
    std.log.info("Task 2 result: {}", .{task_2_result});
    allocator.free(draw_numbers);
    allocator.free(boards);
}