Advent of Code 2020 solutions in Zig
const std = @import("std");

const PATH = "input/day11.txt";

const Str = []const u8;

const Seat = enum {
    floor,
    empty,
    occupied,
};

const GRID_ROWS = 93;
const GRID_COLS = 97;
const Grid = struct {
    grd: [GRID_ROWS][GRID_COLS]Seat,

    const directions = [_][2]i2{
        .{ -1, -1 },
        .{ -1, 0 },
        .{ -1, 1 },
        .{ 1, -1 },
        .{ 1, 0 },
        .{ 1, 1 },
        .{ 0, -1 },
        .{ 0, 1 },
    };

    fn countOccupied(self: *const @This()) usize {
        var sum: usize = 0;
        for (self.grd) |gr| {
            for (gr) |item| {
                if (item == .occupied) sum += 1;
            }
        }
        return sum;
    }

    fn adjacentOccupied(self: *const @This(), row: usize, col: usize) u4 {
        var ret: u4 = 0;
        for (directions) |d| {
            const diffrow = @intCast(isize, row) + d[0];
            if (diffrow < 0 or diffrow >= GRID_ROWS) continue;

            const diffcol = @intCast(isize, col) + d[1];
            if (diffcol < 0 or diffcol >= GRID_COLS) continue;

            if (self.grd[@intCast(usize, diffrow)][@intCast(usize, diffcol)] == .occupied) ret += 1;
        }
        return ret;
    }

    fn seenOccupied(self: *const @This(), row: usize, col: usize) u4 {
        var ret: u4 = 0;
        for (directions) |d| {
            var diffrow = @intCast(isize, row);
            var diffcol = @intCast(isize, col);
            while (true) {
                diffrow += d[0];
                if (diffrow < 0 or diffrow >= GRID_ROWS) break;

                diffcol += d[1];
                if (diffcol < 0 or diffcol >= GRID_COLS) break;

                switch (self.grd[@intCast(usize, diffrow)][@intCast(usize, diffcol)]) {
                    .occupied => {
                        ret += 1;
                        break;
                    },
                    .empty => break,
                    .floor => continue,
                }
            }
        }
        return ret;
    }
};

pub fn first(allocator: ?std.mem.Allocator) anyerror!usize {
    _ = allocator;

    var grid = parseInput(@embedFile(PATH));

    while (true) {
        var next_grid = grid.grd;
        var changed = false;
        for (grid.grd) |grid_row, row| {
            for (grid_row) |item, col| {
                switch (item) {
                    .empty => {
                        if (grid.adjacentOccupied(row, col) == 0) {
                            next_grid[row][col] = .occupied;
                            changed = true;
                        }
                    },
                    .occupied => {
                        if (grid.adjacentOccupied(row, col) >= 4) {
                            next_grid[row][col] = .empty;
                            changed = true;
                        }
                    },
                    .floor => {},
                }
            }
        }

        grid.grd = next_grid;

        if (!changed) break;
    }

    return grid.countOccupied();
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!usize {
    _ = allocator;

    var grid = parseInput(@embedFile(PATH));

    while (true) {
        var next_grid = grid.grd;
        var changed = false;
        for (grid.grd) |grid_row, row| {
            for (grid_row) |item, col| {
                switch (item) {
                    .empty => {
                        if (grid.seenOccupied(row, col) == 0) {
                            next_grid[row][col] = .occupied;
                            changed = true;
                        }
                    },
                    .occupied => {
                        if (grid.seenOccupied(row, col) >= 5) {
                            next_grid[row][col] = .empty;
                            changed = true;
                        }
                    },
                    .floor => {},
                }
            }
        }

        grid.grd = next_grid;

        if (!changed) break;
    }

    return grid.countOccupied();
}

fn parseInput(input: Str) Grid {
    var lines = std.mem.tokenize(u8, input, "\n");

    var grid: Grid = undefined;

    var row: usize = 0;
    while (lines.next()) |line| : (row += 1) {
        for (line) |ch, col| {
            grid.grd[row][col] = switch (ch) {
                '.' => .floor,
                'L' => .empty,
                '#' => .occupied,
                else => unreachable,
            };
        }
    }

    return grid;
}

test "day11a" {
    try std.testing.expectEqual(@as(usize, 2316), try first(std.testing.allocator));
}

test "day11b" {
    try std.testing.expectEqual(@as(usize, 2128), try second(std.testing.allocator));
}