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

const PATH = "input/day24.txt";
const Str = []const u8;

const directions = [_]Coord{
    // east, west
    .{ 1, 0 },
    .{ -1, 0 },
    // north east, north west
    .{ 0, -1 },
    .{ 1, -1 },
    // south east, south west
    .{ 0, 1 },
    .{ -1, 1 },
};

pub fn first(allocator: ?std.mem.Allocator) anyerror!usize {
    var tf = TileFloor{
        .blacks = std.AutoArrayHashMap(Coord, void).init(allocator.?),
    };
    defer tf.blacks.deinit();

    var lines = std.mem.tokenize(u8, @embedFile(PATH), "\n");
    while (lines.next()) |line| {
        const target = parseInstruction(line);
        tf.blackFlip(target);
    }

    return tf.blacks.count();
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!usize {
    var tf = TileFloor{
        .blacks = std.AutoArrayHashMap(Coord, void).init(allocator.?),
        .neighbor = std.AutoArrayHashMap(Coord, usize).init(allocator.?),
    };
    defer {
        tf.blacks.deinit();
        tf.neighbor.deinit();
    }

    // Collect initial blacks
    var lines = std.mem.tokenize(u8, @embedFile(PATH), "\n");
    while (lines.next()) |line| {
        const target = parseInstruction(line);
        tf.blackFlip(target);
    }

    var round: usize = 0;
    while (round < 100) : (round += 1) {
        for (tf.blacks.keys()) |black| {
            for (directions) |dir| {
                const entry = try tf.neighbor.getOrPut(.{ black[0] + dir[0], black[1] + dir[1] });
                if (entry.found_existing) entry.value_ptr.* += 1 else entry.value_ptr.* = 1;
            }
        }

        var next = std.AutoArrayHashMap(Coord, void).init(allocator.?);

        while (tf.neighbor.popOrNull()) |nbor| {
            const is_black = tf.blacks.contains(nbor.key);
            if ((is_black and nbor.value <= 2) or (!is_black and nbor.value == 2)) {
                try next.put(nbor.key, {});
            }
        }

        tf.blacks.deinit();
        tf.blacks = next;
    }

    // std.debug.print("{any}\n", .{tf.blacks.keys()});

    return tf.blacks.count();
}

const HexType = i8;
// const Coord = @Vector(2, HexType);
const Coord = [2]HexType;
const TileFloor = struct {
    blacks: std.AutoArrayHashMap(Coord, void),
    neighbor: std.AutoArrayHashMap(Coord, usize) = undefined,

    inline fn blackFlip(self: *@This(), coord: Coord) void {
        if (!self.blacks.swapRemove(coord)) self.blacks.put(coord, {}) catch unreachable;
    }

    fn blackNeighbor(self: @This(), current: Coord) u3 {
        var count: u3 = 0;
        for (directions) |dir| {
            if (self.blacks.contains(current + dir)) count += 1;
        }
        return count;
    }
};

fn parseInstruction(instruction: Str) Coord {
    var ret: Coord = .{ 0, 0 };
    var south = false;
    var north = false;
    var step: Coord = undefined;
    for (instruction) |ch| {
        switch (ch) {
            's' => south = true,
            'n' => north = true,
            'e', 'w' => {
                if (south) {
                    step[1] = 1;
                } else if (north) {
                    step[1] = -1;
                } else {
                    step[1] = 0;
                }

                switch (ch) {
                    'e' => {
                        if (south) step[0] = 0 else step[0] = 1;
                    },
                    'w' => {
                        if (north) step[0] = 0 else step[0] = -1;
                    },
                    else => unreachable,
                }

                ret = .{ ret[0] + step[0], ret[1] + step[1] };

                south = false;
                north = false;
            },
            else => unreachable,
        }
    }
    return ret;
}

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

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

test "esew" {
    try std.testing.expectEqual(Coord{ 0, 1 }, parseInstruction("esew"));
}

test "nwwswee" {
    try std.testing.expectEqual(Coord{ 0, 0 }, parseInstruction("nwwswee"));
}

const test_input =
    \\sesenwnenenewseeswwswswwnenewsewsw
    \\neeenesenwnwwswnenewnwwsewnenwseswesw
    \\seswneswswsenwwnwse
    \\nwnwneseeswswnenewneswwnewseswneseene
    \\swweswneswnenwsewnwneneseenw
    \\eesenwseswswnenwswnwnwsewwnwsene
    \\sewnenenenesenwsewnenwwwse
    \\wenwwweseeeweswwwnwwe
    \\wsweesenenewnwwnwsenewsenwwsesesenwne
    \\neeswseenwwswnwswswnw
    \\nenwswwsewswnenenewsenwsenwnesesenew
    \\enewnwewneswsewnwswenweswnenwsenwsw
    \\sweneswneswneneenwnewenewwneswswnese
    \\swwesenesewenwneswnwwneseswwne
    \\enesenwswwswneneswsenwnewswseenwsese
    \\wnwnesenesenenwwnenwsewesewsesesew
    \\nenewswnwewswnenesenwnesewesw
    \\eneswnwswnwsenenwnwnwwseeswneewsenese
    \\neswnwewnwnwseenwseesewsenwsweewe
    \\wseweeenwnesenwwwswnew
;