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

const PATH = "input/day12.txt";

const CardinalType = u2;
const Cardinal = enum(CardinalType) {
    north,
    east,
    south,
    west,
};

const Ship = struct {
    dir: Cardinal = .east,
    x: isize = 0,
    y: isize = 0,

    fn manhattan(self: @This()) usize {
        const absx = std.math.absInt(self.x) catch unreachable;
        const absy = std.math.absInt(self.y) catch unreachable;
        return @intCast(usize, absx + absy);
    }
};

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

    const file = @embedFile(PATH);
    var lines = std.mem.tokenize(u8, file, "\n");

    var ship = Ship{};

    while (lines.next()) |line| {
        var op = line[0];
        if (op == 'F') {
            switch (ship.dir) {
                .north => op = 'N',
                .east => op = 'E',
                .west => op = 'W',
                .south => op = 'S',
            }
        }
        switch (op) {
            'N' => ship.y -= try std.fmt.parseUnsigned(isize, line[1..], 0),
            'S' => ship.y += try std.fmt.parseUnsigned(isize, line[1..], 0),
            'W' => ship.x -= try std.fmt.parseUnsigned(isize, line[1..], 0),
            'E' => ship.x += try std.fmt.parseUnsigned(isize, line[1..], 0),
            'R' => {
                const deg = try std.fmt.parseUnsigned(u9, line[1..], 0);
                const pos: i4 = @enumToInt(ship.dir);
                ship.dir = @intToEnum(Cardinal, @mod(pos + @intCast(i4, deg / 90), std.math.maxInt(CardinalType) + 1));
            },
            'L' => {
                const deg = try std.fmt.parseUnsigned(u9, line[1..], 0);
                const pos: i4 = @enumToInt(ship.dir);
                ship.dir = @intToEnum(Cardinal, @mod(pos - @intCast(i4, deg / 90), std.math.maxInt(CardinalType) + 1));
            },
            else => unreachable,
        }
    }

    return ship.manhattan();
}

const Waypoint = struct {
    x: isize = 10,
    y: isize = -1,
};

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

    var file = @embedFile(PATH);
    var lines = std.mem.tokenize(u8, file, "\n");

    var ship = Ship{};
    var wp = Waypoint{};

    while (lines.next()) |line| {
        switch (line[0]) {
            'N' => wp.y -= try std.fmt.parseUnsigned(isize, line[1..], 0),
            'S' => wp.y += try std.fmt.parseUnsigned(isize, line[1..], 0),
            'W' => wp.x -= try std.fmt.parseUnsigned(isize, line[1..], 0),
            'E' => wp.x += try std.fmt.parseUnsigned(isize, line[1..], 0),
            'R', 'L' => {
                var deg = try std.fmt.parseUnsigned(u9, line[1..], 0);

                // R90 == L270 and R270 == L90
                if (deg == 90 and line[0] == 'L')
                    deg = 270
                else if (deg == 270 and line[0] == 'L')
                    deg = 90;

                // Rotate right with `deg` degrees
                switch (deg) {
                    0 => {},
                    90 => {
                        const tmp = wp.x;
                        wp.x = -wp.y;
                        wp.y = tmp;
                    },
                    180 => {
                        wp.x *= -1;
                        wp.y *= -1;
                    },
                    270 => {
                        const tmp = wp.x;
                        wp.x = wp.y;
                        wp.y = -tmp;
                    },
                    else => unreachable,
                }
            },
            'F' => {
                const times = try std.fmt.parseUnsigned(isize, line[1..], 0);
                ship.x += wp.x * times;
                ship.y += wp.y * times;
            },
            else => unreachable,
        }
    }
    return ship.manhattan();
}

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

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