const std = @import("std"); const mem = std.mem; const fs = std.fs; const print = std.debug.print; const ParseError = error{ RanOutOfWords, }; const Parser = struct { it: mem.SplitIterator(u8, .scalar), fn fromString(str: []const u8) Parser { return Parser{ .it = mem.splitScalar(u8, str, ' '), }; } fn take(self: *Parser) ![]const u8 { const wordo = self.it.next(); if (wordo) |word| { return word; } else { return ParseError.RanOutOfWords; } } fn skip(self: *Parser, cnt: usize) void { for (0..cnt) |_| { _ = self.it.next(); } } fn takeAsNumber(self: *Parser, Type: type) !Type { const wordo = self.it.next(); if (wordo) |word| { return std.fmt.parseInt(Type, word, 10); } else { return ParseError.RanOutOfWords; } } }; const Raindeer = struct { speed: usize, flight: usize, rest: usize, fn distanceAfter(self: Raindeer, sec: usize) usize { const chunk_time = self.flight + self.rest; const chunk_dist = self.speed * self.flight; const chunked = chunk_dist * (sec / chunk_time); const left = @mod(sec, chunk_time); const rest = if (left > self.flight) self.speed * self.flight else self.speed * left; return chunked + rest; } }; fn getLines(path: []const u8, alloc: mem.Allocator) !std.ArrayList(Raindeer) { const file = fs.cwd().openFile(path, .{}) catch |err| { std.log.err("Failed to open file {s}", .{@errorName(err)}); return err; }; defer file.close(); var raindeer = std.ArrayList(Raindeer).init(alloc); while (file.reader().readUntilDelimiterOrEofAlloc(alloc, '\n', std.math.maxInt(usize)) catch |err| { std.log.err("Failed to read line: {s}", .{@errorName(err)}); return err; }) |line| { defer alloc.free(line); var words = Parser.fromString(line); words.skip(3); const speed = try words.takeAsNumber(usize); words.skip(2); const flight = try words.takeAsNumber(usize); words.skip(6); const rest = try words.takeAsNumber(usize); try raindeer.append(.{ .speed = speed, .flight = flight, .rest = rest, }); } return raindeer; } fn part1(raindeer: std.ArrayList(Raindeer)) !void { var score: usize = 0; for (raindeer.items) |rd| { score = @max(score, rd.distanceAfter(2503)); } print("Part1: {d}\n", .{score}); } const RaindeerState = struct { raindeer: Raindeer, running: bool, time: usize, distance: usize, points: usize, fn fromRaindeer(rnd: Raindeer) RaindeerState { return .{ .raindeer = rnd, .running = true, .time = 0, .distance = 0, .points = 0, }; } fn tick(self: *RaindeerState) void { if (self.running) { self.distance += self.raindeer.speed; self.time += 1; if (self.time >= self.raindeer.flight) { self.time = 0; self.running = false; } } else { self.time += 1; if (self.time >= self.raindeer.rest) { self.time = 0; self.running = true; } } } }; fn run(raindeer: std.ArrayList(Raindeer), alloc: mem.Allocator, sec: usize) !usize { var states = std.ArrayList(*RaindeerState).init(alloc); defer { for (states.items) |state| { alloc.destroy(state); } states.deinit(); } for (raindeer.items) |rnd| { const state = try alloc.create(RaindeerState); state.* = RaindeerState.fromRaindeer(rnd); try states.append(state); } for (0..sec) |_| { var max_dist: usize = 0; for (states.items) |state| { state.tick(); max_dist = @max(max_dist, state.distance); } for (states.items) |state| { if (state.distance == max_dist) state.points += 1; } } var max_points: usize = 0; for (states.items) |state| { max_points = @max(max_points, state.points); } return max_points; } fn part2(raindeer: std.ArrayList(Raindeer), alloc: mem.Allocator) !void { const score = try run(raindeer, alloc, 2503); print("Part2: {d}\n", .{score}); } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const alloc = gpa.allocator(); const filename = "day14.txt"; var raindeer = try getLines(filename, alloc); defer raindeer.deinit(); try part1(raindeer); try part2(raindeer, alloc); }