diff --git a/src/aoc.zig b/src/aoc.zig new file mode 100644 index 0000000..a887f0f --- /dev/null +++ b/src/aoc.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub const Input = struct { + allocator: Allocator, + lines: [][]const u8 +}; + +pub const Result = union(enum) { + float: f32, + uint: u32, + int: i32, + text: [64:0]u8 +}; + +pub const Solver = *const fn(input: *Input) anyerror!Result; +pub const Day = struct { + part1: ?Solver, + part2: ?Solver +}; diff --git a/src/cli.zig b/src/cli.zig new file mode 100644 index 0000000..34fc05c --- /dev/null +++ b/src/cli.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Self = @This(); + +allocator: Allocator, +day: u32, +part: u32, +input_file: []u8, + +fn print_usage(program: []const u8) void { + std.debug.print("Usage: {s} [input.txt]\n", .{program}); +} + +pub fn init(allocator: Allocator) !Self { + var args = std.process.args(); + + const program = try allocator.dupe(u8, args.next().?); + defer allocator.free(program); + + var day: u32 = 0; + var part: u32 = 0; + var input_file = try allocator.dupe(u8, "input.txt"); + var i: u32 = 0; + while (args.next()) |arg| { + if (i == 0) { + day = std.fmt.parseInt(u32, arg, 10) catch { + std.debug.print("ERROR: Invalid day '{s}'\n", .{arg}); + return error.cli; + }; + } else if (i == 1) { + part = std.fmt.parseInt(u32, arg, 10) catch { + std.debug.print("ERROR: Invalid part '{s}'\n", .{arg}); + return error.cli; + }; + + if (!(part == 1 or part == 2)) { + std.debug.print("ERROR: Part can only be 1 or 2\n", .{}); + return error.cli; + } + } else if (i == 2) { + allocator.free(input_file); + input_file = try allocator.dupe(u8, arg); + } + + i += 1; + } + + if (i < 2) { + print_usage(program); + return error.cli; + } + + return Self{ + .allocator = allocator, + .day = day, + .part = part, + .input_file = input_file + }; +} + +pub fn deinit(self: *Self) void { + self.allocator.free(self.input_file); +} diff --git a/src/day1.zig b/src/day1.zig index f56895e..746f85e 100644 --- a/src/day1.zig +++ b/src/day1.zig @@ -1,8 +1,12 @@ const std = @import("std"); +const aoc = @import("./aoc.zig"); +const Allocator = std.mem.Allocator; const digits = [_][]const u8{ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; -pub fn part1(lines: [][]const u8) u32 { +pub fn part1(input: *aoc.Input) !aoc.Result { + const lines = input.lines; + var sum: u32 = 0; for (lines) |line| { var first_digit: u8 = 0; @@ -23,10 +27,12 @@ pub fn part1(lines: [][]const u8) u32 { sum += number; } - return sum; + return .{ .uint = sum }; } -pub fn part2(lines: [][]const u8) u32 { +pub fn part2(input: *aoc.Input) !aoc.Result { + const lines = input.lines; + var sum: u32 = 0; for (lines) |line| { var first_digit: u8 = 0; @@ -84,23 +90,24 @@ pub fn part2(lines: [][]const u8) u32 { sum += number; } - return sum; + return .{ .uint = sum }; } test "part 1 example" { - var input = [_][]const u8{ + var lines = [_][]const u8{ "1abc2", "pqr3stu8vwx", "a1b2c3d4e5f", "treb7uchet", }; - var actual = part1(&input); + var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator }; + var actual = try part1(&input); var expected: u32 = 142; - try std.testing.expectEqual(expected, actual); + try std.testing.expectEqual(expected, actual.uint); } test "part 2 example" { - var input = [_][]const u8{ + var lines = [_][]const u8{ "two1nine", "eightwothree", "abcone2threexyz", @@ -109,7 +116,8 @@ test "part 2 example" { "zoneight234", "7pqrstsixteen", }; - var actual = part2(&input); + var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator }; + var actual = try part2(&input); var expected: u32 = 281; - try std.testing.expectEqual(expected, actual); + try std.testing.expectEqual(expected, actual.uint); } diff --git a/src/day2.zig b/src/day2.zig index dee3697..3604cfb 100644 --- a/src/day2.zig +++ b/src/day2.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const aoc = @import("./aoc.zig"); const Allocator = std.mem.Allocator; const Cube = enum { Red, Green, Blue }; @@ -49,7 +50,10 @@ fn parse_games(allocator: Allocator, lines: [][]const u8) ![]Game { return games; } -pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 { +pub fn part1(input: *aoc.Input) !aoc.Result { + const lines = input.lines; + const allocator = input.allocator; + var games = try parse_games(allocator, lines); defer allocator.free(games); @@ -58,7 +62,6 @@ pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 { const max_blue = 14; var sum: u32 = 0; for (1.., games) |idx, game| { - var possible = true; for (game.sets[0..game.set_count]) |cube_set| { if ((cube_set.get(.Red) > max_red) or (cube_set.get(.Blue) > max_blue) or (cube_set.get(.Green) > max_green)) { @@ -72,10 +75,12 @@ pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 { } } - return sum; + return .{ .uint = sum }; } -pub fn part2(allocator: Allocator, lines: [][]const u8) !u32 { +pub fn part2(input: *aoc.Input) !aoc.Result { + const lines = input.lines; + const allocator = input.allocator; var games = try parse_games(allocator, lines); defer allocator.free(games); @@ -94,31 +99,33 @@ pub fn part2(allocator: Allocator, lines: [][]const u8) !u32 { sum += max_red*max_green*max_blue; } - return sum; + return .{ .uint = sum }; } test "part 1 example" { - var input = [_][]const u8{ + var lines = [_][]const u8{ "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green" }; - var actual = try part1(std.testing.allocator, &input); + var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator }; + var actual = try part1(&input); var expected: u32 = 8; - try std.testing.expectEqual(expected, actual); + try std.testing.expectEqual(expected, actual.uint); } test "part 2 example" { - var input = [_][]const u8{ + var lines = [_][]const u8{ "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green" }; - var actual = try part2(std.testing.allocator, &input); + var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator }; + var actual = try part2(&input); var expected: u32 = 2286; - try std.testing.expectEqual(expected, actual); + try std.testing.expectEqual(expected, actual.uint); } diff --git a/src/main.zig b/src/main.zig index b0aadb7..ea9260c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,17 +1,60 @@ const std = @import("std"); +const aoc = @import("./aoc.zig"); +const cli = @import("./cli.zig"); + const StringArray = std.ArrayList([]const u8); +const DayHashMap = std.StringHashMap(aoc.Day); -const Day1 = @import("./day1.zig"); -const Day2 = @import("./day2.zig"); +fn create_day(comptime module: anytype) aoc.Day { + var part1: ?aoc.Solver = null; + if (@hasDecl(module, "part1")) { + part1 = @field(module, "part1"); + } -pub fn main() !void { + var part2: ?aoc.Solver = null; + if (@hasDecl(module, "part2")) { + part2 = @field(module, "part2"); + } + + return .{ .part1 = part1, .part2 = part2 }; +} + +const Days = [_]aoc.Day{ + create_day(@import("day1.zig")), + create_day(@import("day2.zig")) +}; + +pub fn main() !u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); defer { if (gpa.deinit() == .leak) @panic("Leaked memory"); } - var file = try std.fs.cwd().openFile("input.txt", .{}); + var args = cli.init(allocator) catch |err| switch (err) { + error.cli => return 255, + else => return err + } ; + defer args.deinit(); + + if (args.day > Days.len) { + std.debug.print("ERROR: Day {} does not exist\n", .{args.day}); + return 255; + } + + var day = Days[args.day-1]; + if ((args.part == 1 and day.part1 == null) or (args.part == 2 and day.part2 == null)) { + std.debug.print("ERROR: Part {} for day {} does not exist\n", .{args.part, args.day}); + return 255; + } + + var file = std.fs.cwd().openFile(args.input_file, .{}) catch |err| switch (err) { + error.FileNotFound => { + std.debug.print("ERROR: Input file '{s}' does not exist\n", .{args.input_file}); + return 255; + }, + else => return err + }; defer file.close(); var buf_reader = std.io.bufferedReader(file.reader()); @@ -31,9 +74,28 @@ pub fn main() !void { try lines.append(try allocator.dupe(u8, trimmed_line)); } - // std.debug.print("day1: {d}\n", .{Day1.part1(lines.items)}); - // std.debug.print("day2: {d}\n", .{Day1.part2(lines.items)}); + var input = aoc.Input{ .allocator = allocator, .lines = lines.items }; - std.debug.print("day1: {d}\n", .{try Day2.part1(allocator, lines.items)}); - std.debug.print("day2: {d}\n", .{try Day2.part2(allocator, lines.items)}); + var result: aoc.Result = undefined; + if (args.part == 1) { + result = try day.part1.?(&input); + } else if (args.part == 2) { + result = try day.part2.?(&input); + } else { + unreachable; + } + + switch (result) { + .uint => std.debug.print("{}\n", .{result.uint}), + .float => std.debug.print("{}\n", .{result.float}), + .int => std.debug.print("{}\n", .{result.int}), + .text => std.debug.print("{s}\n", .{result.text}), + } + + return 0; +} + +test { + _ = @import("day1.zig"); + _ = @import("day2.zig"); }