diff --git a/src/day23.zig b/src/day23.zig index 6ad3afd..952697d 100644 --- a/src/day23.zig +++ b/src/day23.zig @@ -275,7 +275,7 @@ fn compareQueueItem(_: void, a: QueueItem, b: QueueItem) std.math.Order { return std.math.order(b.distance, a.distance); } -fn find_longest_path(allocator: Allocator, node_map: NodeMap, start: PointU32, end: PointU32, max_best_distances: ?u32) !u64 { +fn findLongestPath(allocator: Allocator, node_map: NodeMap, start: PointU32, end: PointU32, max_best_distances: ?u32) !u64 { const start_region = node_map.region_map[node_map.width * start.y + start.x].?; const end_region = node_map.region_map[node_map.width * end.y + end.x].?; @@ -361,7 +361,7 @@ pub fn part1(input: *aoc.Input) !aoc.Result { .y = map.height-1 }; - const answer = try find_longest_path(allocator, node_map, start, end, null); + const answer = try findLongestPath(allocator, node_map, start, end, null); return .{ .uint = answer }; } @@ -392,7 +392,7 @@ pub fn part2(input: *aoc.Input) !aoc.Result { .y = map.height-1 }; - const answer = try find_longest_path(allocator, node_map, end, start, 1000); + const answer = try findLongestPath(allocator, node_map, end, start, 1000); return .{ .uint = answer }; } diff --git a/src/day24.zig b/src/day24.zig new file mode 100644 index 0000000..27a0a2b --- /dev/null +++ b/src/day24.zig @@ -0,0 +1,246 @@ +const aoc = @import("./aoc.zig"); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const Point = @import("./point.zig").Point; + +const Axis = enum { X, Y, Z }; + +fn Vec3(comptime T: type) type { + return struct { + x: T, + y: T, + z: T, + + fn project(self: @This(), axis: Axis) Point(T) { + return switch (axis) { + .X => Point(T){ .x = self.y, .y = self.z }, + .Y => Point(T){ .x = self.x, .y = self.z }, + .Z => Point(T){ .x = self.x, .y = self.y }, + }; + } + + fn sub(self: @This(), other: @This()) @This() { + return @This(){ + .x = self.x - other.x, + .y = self.y - other.y, + .z = self.z - other.z, + }; + } + + fn add(self: @This(), other: @This()) @This() { + return @This(){ + .x = self.x + other.x, + .y = self.y + other.y, + .z = self.z + other.z, + }; + } + + fn mul(self: @This(), multiplier: T) @This() { + return @This(){ + .x = self.x * multiplier, + .y = self.y * multiplier, + .z = self.z * multiplier, + }; + } + + fn cross(self: @This(), other: @This()) @This() { + return @This(){ + .x = self.y * other.z - self.z * other.y, + .y = self.z * other.x - self.x * other.z, + .z = self.x * other.y - self.y * other.x, + }; + } + + fn dot(self: @This(), other: @This()) T { + return self.x * other.x + self.y * other.y + self.z * other.z; + } + + fn cast(self: @This(), comptime to: type) Vec3(to) { + return Vec3(to){ + .x = self.x, + .y = self.y, + .z = self.z, + }; + } + }; +} + +const Hailstone = struct { + position: Vec3(i64), + velocity: Vec3(i64), +}; + +fn parseVec3(str: []const u8) !Vec3(i64) { + var components: [3]i64 = undefined; + var count: u32 = 0; + + var iter = std.mem.splitScalar(u8, str, ','); + while (iter.next()) |part| { + if (count == 3) return error.TooManyComponents; + components[count] = try std.fmt.parseInt(i64, std.mem.trim(u8, part, " "), 10); + count += 1; + } + + if (count < 3) { + return error.MissingComponents; + } + + return Vec3(i64){ + .x = components[0], + .y = components[1], + .z = components[2], + }; +} + +fn parseInput(allocator: Allocator, lines: []const []const u8) !std.ArrayList(Hailstone) { + var parsed = std.ArrayList(Hailstone).init(allocator); + errdefer parsed.deinit(); + + for (lines) |line| { + const separator = std.mem.indexOfScalar(u8, line, '@') orelse return error.NoSeparator; + try parsed.append(Hailstone{ + .position = try parseVec3(line[0..separator]), + .velocity = try parseVec3(line[(separator+1)..]) + }); + } + + return parsed; +} + +fn getIntersect(p1: Point(f64), v1: Point(f64), p2: Point(f64), v2: Point(f64)) ?Point(f64) { + const t = -( + ((p1.x - p2.x) * v2.y - (p1.y - p2.y) * v2.x) / + (v1.x * v2.y - v1.y * v2.x) + ); + + const u = ( + (v1.x * (p1.y - p2.y) - v1.y * (p1.x - p2.x)) / + (v1.x * v2.y - v1.y * v2.x) + ); + + const px = p1.x + t * v1.x; + const py = p1.y + t * v1.y; + + if (t >= 0 and u >= 0) { + return Point(f64){ .x = px, .y = py }; + } else { + return null; + } +} + +fn vec3FloatFromInt(p: Vec3(i64)) Vec3(f64) { + return Vec3(f64){ + .x = @floatFromInt(p.x), + .y = @floatFromInt(p.y), + .z = @floatFromInt(p.z), + }; +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const hailstones = try parseInput(input.allocator, input.lines); + defer hailstones.deinit(); + + const min_test_area = 200000000000000; + const max_test_area = 400000000000000; + + var answer: u32 = 0; + for (0..(hailstones.items.len-1)) |i| { + const hailstone1 = hailstones.items[i]; + for ((i+1)..hailstones.items.len) |j| { + const hailstone2 = hailstones.items[j]; + + const p1 = vec3FloatFromInt(hailstone1.position); + const v1 = vec3FloatFromInt(hailstone1.velocity); + + const p2 = vec3FloatFromInt(hailstone2.position); + const v2 = vec3FloatFromInt(hailstone2.velocity); + + const p = getIntersect( + .{ .x = p1.x, .y = p1.y }, + .{ .x = v1.x, .y = v1.y }, + .{ .x = p2.x, .y = p2.y }, + .{ .x = v2.x, .y = v2.y } + ) orelse continue; + + if (min_test_area <= p.x and p.x <= max_test_area and min_test_area <= p.y and p.y <= max_test_area) { + answer += 1; + } + } + } + + return .{ .uint = answer }; +} + +fn getIntersectTime(comptime T: type, p1: Vec3(T), v1: Vec3(T), p2: Vec3(T), v2: Vec3(T)) !T { + const place_normal = p1.cross(p1.add(v1)); + + const denominator = v2.dot(place_normal); + if (denominator == 0) { + return error.Parallel; + } + + const numerator = -p2.dot(place_normal); + if (@mod(numerator, denominator) != 0) { + return error.NonInteger; + } + + return @divExact(numerator, denominator); +} + +// This helped a LOT, really nice visualization and explanation of the math +// https://aidiakapi.com/blog/2024-01-20-advent-of-code-2023-day-24/ +pub fn part2(input: *aoc.Input) !aoc.Result { + const hailstones = try parseInput(input.allocator, input.lines); + defer hailstones.deinit(); + + var answer: u64 = 0; + var triplet_iter = std.mem.window(Hailstone, hailstones.items, 3, 1); + while (triplet_iter.next()) |triplet| { + const r = triplet[0]; // Reference point + const a = triplet[1]; // A point + const b = triplet[2]; // B point + + const r_p = r.position.cast(i128); + const r_v = r.velocity.cast(i128); + + const a_p = a.position.cast(i128); + const a_v = a.velocity.cast(i128); + + const b_p = b.position.cast(i128); + const b_v = b.velocity.cast(i128); + + const ra_p = a_p.sub(r_p); // A position relative to reference + const rb_p = b_p.sub(r_p); // A velocity relative to reference + + const ra_v = a_v.sub(r_v); // B position relative to reference + const rb_v = b_v.sub(r_v); // B velocity relative to reference + + // Check "Plane vs. Line" intersection for A and B + const t1 = getIntersectTime(i128, rb_p, rb_v, ra_p, ra_v) catch continue; + const t2 = getIntersectTime(i128, ra_p, ra_v, rb_p, rb_v) catch continue; + + // Convert the intersection time into a global position + // From this point on reference point is not needed + const c1 = a_p.add(a_v.mul(t1)); + const c2 = b_p.add(b_v.mul(t2)); + + const delta_time = t2 - t1; + const delta_intersect = c2.sub(c1); + if (@mod(delta_intersect.x, delta_time) != 0) continue; + if (@mod(delta_intersect.y, delta_time) != 0) continue; + if (@mod(delta_intersect.z, delta_time) != 0) continue; + const v = Vec3(i128){ + .x = @divExact(delta_intersect.x, delta_time), + .y = @divExact(delta_intersect.y, delta_time), + .z = @divExact(delta_intersect.z, delta_time), + }; + + const p = c1.sub(v.mul(t1)); + + answer = @intCast(p.x + p.y + p.z); + break; + } + + return .{ .uint = answer }; +} diff --git a/src/main.zig b/src/main.zig index 9781451..248f968 100644 --- a/src/main.zig +++ b/src/main.zig @@ -44,6 +44,7 @@ const Days = [_]aoc.Day{ create_day(@import("./day21.zig")), create_day(@import("./day22.zig")), create_day(@import("./day23.zig")), + create_day(@import("./day24.zig")), }; fn kilobytes(count: u32) u32 { @@ -210,4 +211,5 @@ test { _ = @import("./day21.zig"); _ = @import("./day22.zig"); _ = @import("./day23.zig"); + _ = @import("./day24.zig"); }