diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d5ab199..6557ea9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,7 @@ jobs: - name: Compile all solutions for current year run: zig build + working-directory: 2025 env: AOC_COOKIE: ${{ secrets.AOC_COOKIE }} diff --git a/.zigversion b/.zigversion deleted file mode 100644 index 4312e0d..0000000 --- a/.zigversion +++ /dev/null @@ -1 +0,0 @@ -0.15.2 diff --git a/2023/SUMMARY.md b/2023/README.md similarity index 100% rename from 2023/SUMMARY.md rename to 2023/README.md diff --git a/2023/day1.zig b/2023/src/day1.zig similarity index 100% rename from 2023/day1.zig rename to 2023/src/day1.zig diff --git a/2023/day10.zig b/2023/src/day10.zig similarity index 100% rename from 2023/day10.zig rename to 2023/src/day10.zig diff --git a/2023/day11.zig b/2023/src/day11.zig similarity index 100% rename from 2023/day11.zig rename to 2023/src/day11.zig diff --git a/2023/day12.zig b/2023/src/day12.zig similarity index 100% rename from 2023/day12.zig rename to 2023/src/day12.zig diff --git a/2023/day12_old.zig b/2023/src/day12_old.zig similarity index 100% rename from 2023/day12_old.zig rename to 2023/src/day12_old.zig diff --git a/2023/day13.zig b/2023/src/day13.zig similarity index 100% rename from 2023/day13.zig rename to 2023/src/day13.zig diff --git a/2023/day14.zig b/2023/src/day14.zig similarity index 100% rename from 2023/day14.zig rename to 2023/src/day14.zig diff --git a/2023/day15.zig b/2023/src/day15.zig similarity index 100% rename from 2023/day15.zig rename to 2023/src/day15.zig diff --git a/2023/day16.zig b/2023/src/day16.zig similarity index 100% rename from 2023/day16.zig rename to 2023/src/day16.zig diff --git a/2023/day17.zig b/2023/src/day17.zig similarity index 100% rename from 2023/day17.zig rename to 2023/src/day17.zig diff --git a/2023/day18.zig b/2023/src/day18.zig similarity index 100% rename from 2023/day18.zig rename to 2023/src/day18.zig diff --git a/2023/day19.zig b/2023/src/day19.zig similarity index 100% rename from 2023/day19.zig rename to 2023/src/day19.zig diff --git a/2023/day2.zig b/2023/src/day2.zig similarity index 100% rename from 2023/day2.zig rename to 2023/src/day2.zig diff --git a/2023/day20.zig b/2023/src/day20.zig similarity index 100% rename from 2023/day20.zig rename to 2023/src/day20.zig diff --git a/2023/day21.zig b/2023/src/day21.zig similarity index 100% rename from 2023/day21.zig rename to 2023/src/day21.zig diff --git a/2023/day22.zig b/2023/src/day22.zig similarity index 100% rename from 2023/day22.zig rename to 2023/src/day22.zig diff --git a/2023/day23.zig b/2023/src/day23.zig similarity index 100% rename from 2023/day23.zig rename to 2023/src/day23.zig diff --git a/2023/day24.zig b/2023/src/day24.zig similarity index 100% rename from 2023/day24.zig rename to 2023/src/day24.zig diff --git a/2023/day25.zig b/2023/src/day25.zig similarity index 100% rename from 2023/day25.zig rename to 2023/src/day25.zig diff --git a/2023/day3.zig b/2023/src/day3.zig similarity index 100% rename from 2023/day3.zig rename to 2023/src/day3.zig diff --git a/2023/day4.zig b/2023/src/day4.zig similarity index 100% rename from 2023/day4.zig rename to 2023/src/day4.zig diff --git a/2023/day5.zig b/2023/src/day5.zig similarity index 100% rename from 2023/day5.zig rename to 2023/src/day5.zig diff --git a/2023/day6.zig b/2023/src/day6.zig similarity index 100% rename from 2023/day6.zig rename to 2023/src/day6.zig diff --git a/2023/day7.zig b/2023/src/day7.zig similarity index 100% rename from 2023/day7.zig rename to 2023/src/day7.zig diff --git a/2023/day8.zig b/2023/src/day8.zig similarity index 100% rename from 2023/day8.zig rename to 2023/src/day8.zig diff --git a/2023/day9.zig b/2023/src/day9.zig similarity index 100% rename from 2023/day9.zig rename to 2023/src/day9.zig diff --git a/2024/SUMMARY.md b/2024/README.md similarity index 100% rename from 2024/SUMMARY.md rename to 2024/README.md diff --git a/2024/Graph.zig b/2024/src/Graph.zig similarity index 100% rename from 2024/Graph.zig rename to 2024/src/Graph.zig diff --git a/2024/day1.zig b/2024/src/day1.zig similarity index 100% rename from 2024/day1.zig rename to 2024/src/day1.zig diff --git a/2024/day10.zig b/2024/src/day10.zig similarity index 100% rename from 2024/day10.zig rename to 2024/src/day10.zig diff --git a/2024/day11.py b/2024/src/day11.py similarity index 100% rename from 2024/day11.py rename to 2024/src/day11.py diff --git a/2024/day11.zig b/2024/src/day11.zig similarity index 100% rename from 2024/day11.zig rename to 2024/src/day11.zig diff --git a/2024/day12-ext.py b/2024/src/day12-ext.py similarity index 100% rename from 2024/day12-ext.py rename to 2024/src/day12-ext.py diff --git a/2024/day12.py b/2024/src/day12.py similarity index 100% rename from 2024/day12.py rename to 2024/src/day12.py diff --git a/2024/day12.zig b/2024/src/day12.zig similarity index 100% rename from 2024/day12.zig rename to 2024/src/day12.zig diff --git a/2024/day13.py b/2024/src/day13.py similarity index 100% rename from 2024/day13.py rename to 2024/src/day13.py diff --git a/2024/day13.zig b/2024/src/day13.zig similarity index 100% rename from 2024/day13.zig rename to 2024/src/day13.zig diff --git a/2024/day14.zig b/2024/src/day14.zig similarity index 100% rename from 2024/day14.zig rename to 2024/src/day14.zig diff --git a/2024/day15.zig b/2024/src/day15.zig similarity index 100% rename from 2024/day15.zig rename to 2024/src/day15.zig diff --git a/2024/day16.zig b/2024/src/day16.zig similarity index 100% rename from 2024/day16.zig rename to 2024/src/day16.zig diff --git a/2024/day17.zig b/2024/src/day17.zig similarity index 100% rename from 2024/day17.zig rename to 2024/src/day17.zig diff --git a/2024/day18.zig b/2024/src/day18.zig similarity index 100% rename from 2024/day18.zig rename to 2024/src/day18.zig diff --git a/2024/day19.zig b/2024/src/day19.zig similarity index 100% rename from 2024/day19.zig rename to 2024/src/day19.zig diff --git a/2024/day2.zig b/2024/src/day2.zig similarity index 100% rename from 2024/day2.zig rename to 2024/src/day2.zig diff --git a/2024/day20.zig b/2024/src/day20.zig similarity index 100% rename from 2024/day20.zig rename to 2024/src/day20.zig diff --git a/2024/day21.old.zig b/2024/src/day21.old.zig similarity index 100% rename from 2024/day21.old.zig rename to 2024/src/day21.old.zig diff --git a/2024/day21.zig b/2024/src/day21.zig similarity index 100% rename from 2024/day21.zig rename to 2024/src/day21.zig diff --git a/2024/day22.zig b/2024/src/day22.zig similarity index 100% rename from 2024/day22.zig rename to 2024/src/day22.zig diff --git a/2024/day23.py b/2024/src/day23.py similarity index 100% rename from 2024/day23.py rename to 2024/src/day23.py diff --git a/2024/day23.zig b/2024/src/day23.zig similarity index 100% rename from 2024/day23.zig rename to 2024/src/day23.zig diff --git a/2024/day24.zig b/2024/src/day24.zig similarity index 100% rename from 2024/day24.zig rename to 2024/src/day24.zig diff --git a/2024/day25.zig b/2024/src/day25.zig similarity index 100% rename from 2024/day25.zig rename to 2024/src/day25.zig diff --git a/2024/day3.zig b/2024/src/day3.zig similarity index 100% rename from 2024/day3.zig rename to 2024/src/day3.zig diff --git a/2024/day4.zig b/2024/src/day4.zig similarity index 100% rename from 2024/day4.zig rename to 2024/src/day4.zig diff --git a/2024/day5.zig b/2024/src/day5.zig similarity index 100% rename from 2024/day5.zig rename to 2024/src/day5.zig diff --git a/2024/day6.zig b/2024/src/day6.zig similarity index 100% rename from 2024/day6.zig rename to 2024/src/day6.zig diff --git a/2024/day7.zig b/2024/src/day7.zig similarity index 100% rename from 2024/day7.zig rename to 2024/src/day7.zig diff --git a/2024/day8.zig b/2024/src/day8.zig similarity index 100% rename from 2024/day8.zig rename to 2024/src/day8.zig diff --git a/2024/day9.py b/2024/src/day9.py similarity index 100% rename from 2024/day9.py rename to 2024/src/day9.py diff --git a/2024/day9.zig b/2024/src/day9.zig similarity index 100% rename from 2024/day9.zig rename to 2024/src/day9.zig diff --git a/2025/.gitignore b/2025/.gitignore new file mode 100644 index 0000000..49ac128 --- /dev/null +++ b/2025/.gitignore @@ -0,0 +1,2 @@ +# Avoid commiting puzzle inputs +input/**/* diff --git a/2025/README.md b/2025/README.md new file mode 100644 index 0000000..cf83b4f --- /dev/null +++ b/2025/README.md @@ -0,0 +1,29 @@ +# Advent of Code 2025 +### Summary + + +# Day 01 +### Part 1 +Easy start. Some tipps: +- Parse `L` and `R` into `-1` and `1` +- Use `@mod` for the rotational arithmetic of the dial +- Don't forget to initialise the dial to `50`! + +### Part 2 +Somehow tried to be smart and calculate the amount of rotations first with: +```zig +const rotation_count = @abs(@divTrunc(dial, change)); +``` + + + +# Day 02 + + +# Day 03 +- Order is important + + +# Day 04 +- Find `rows` and `columns` size upfront. Ensure to trim! +- Index the one-dimensional buffer with index `(x * rows) + y` diff --git a/2025/SUMMARY.md b/2025/SUMMARY.md deleted file mode 100644 index 00a8d0b..0000000 --- a/2025/SUMMARY.md +++ /dev/null @@ -1,4 +0,0 @@ -# AoC 2025 -### Notes & Comments - -## Day 1 diff --git a/2025/build.zig b/2025/build.zig new file mode 100644 index 0000000..89b2ba3 --- /dev/null +++ b/2025/build.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const YEAR = 2025; +var DAY: u5 = 1; + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + if (b.args) |args| { + DAY = std.fmt.parseInt(u5, args[0], 10) catch 1; + std.debug.print("🎅🏞 Running day {d:0>2}\n", .{DAY}); + const exe = try aoc.setupDay(b, target, optimize, YEAR, DAY); + aoc.runDay(b, exe); + } else { + const src_dir = try std.fs.cwd().openDir("src/", .{ + .no_follow = true, + .iterate = true, + .access_sub_paths = false, + }); + var it = src_dir.iterate(); + while (try it.next()) |f| { + if (f.name[0] == '.') continue; + const name = std.fs.path.stem(f.name); + const day = try std.fmt.parseInt(aoc.types.Day, name[3..], 10); + std.debug.print("🎅🏞 Building day {d:0>2}\n", .{day}); + _ = try aoc.setupDay(b, target, optimize, YEAR, day); + } + } +} diff --git a/2025/build.zig.zon b/2025/build.zig.zon new file mode 100644 index 0000000..12ac06a --- /dev/null +++ b/2025/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .advent_of_code2025, + .fingerprint = 0x775c1acd7871bd1a, + .version = "0.0.1", + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .aoc = .{ .path = "../modules/aoc" }, + .libs = .{ .path = "../modules/libs" }, + }, + .paths = .{ + "README.md", + "build.zig", + "build.zig.zon", + "src", + "modules", + }, +} diff --git a/2025/input/example/.gitkeep b/2025/input/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/2025/input/puzzle/.gitkeep b/2025/input/puzzle/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/2025/src/day01.zig b/2025/src/day01.zig new file mode 100644 index 0000000..f1733a8 --- /dev/null +++ b/2025/src/day01.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const YEAR: u12 = 2025; +const DAY: u5 = 1; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn unifiedSolution() anyerror!void {} + +fn part1(allocator: Allocator) anyerror!void { + _ = allocator; + var row_it = std.mem.tokenizeSequence(u8, @embedFile("puzzle-01"), "\n"); + const max = 100; + var i: u32 = 0; + var dial: i32 = 50; + var result: u32 = 0; + while (row_it.next()) |row| : (i += 1) { + // std.debug.print("{d}\n", .{dial}); + const sign: i32 = if (row[0] == 'L') -1 else 1; + const number = try std.fmt.parseInt(i32, row[1..], 10); + dial += sign * number; + dial = @mod(dial, max); + if (dial == 0) result += 1; + } + std.debug.print("\nResult: {d}\n", .{result}); +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + var row_it = std.mem.tokenizeSequence(u8, @embedFile("puzzle-01"), "\n"); + const max = 100; + var dial: i32 = 50; + var result: u32 = 0; + while (row_it.next()) |row| { + const sign: i32 = if (row[0] == 'L') -1 else 1; + const number = try std.fmt.parseInt(i32, row[1..], 10); + var change = sign * number; + while (change != 0) : (change += -1 * sign) { + dial += sign; + dial = @mod(dial, max); + if (dial == 0) result += 1; + } + } + std.debug.print("\nResult: {d}", .{result}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day02.zig b/2025/src/day02.zig new file mode 100644 index 0000000..80ca329 --- /dev/null +++ b/2025/src/day02.zig @@ -0,0 +1,95 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const YEAR: u12 = 2025; +const DAY: u5 = 2; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn countDigits(number: usize) usize { + var r: usize = 0; + var n = number; + while (n > 0) : (r += 1) { + n /= 10; + } + return r; +} + +fn part1(allocator: Allocator) anyerror!void { + const input_embed = @embedFile("puzzle-02"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input_embed}); + + var result: usize = 0; + + var reader: std.Io.Reader = .fixed(std.mem.trimEnd(u8, input_embed, "\n")); + while (try reader.takeDelimiter(',')) |line| { + var ranges = std.mem.splitSequence(u8, line, "-"); + const lhs = ranges.next().?; + const rhs = ranges.next().?; + const lower = try std.fmt.parseInt(u64, lhs, 10); + const upper = try std.fmt.parseInt(u64, rhs, 10); + for (lower..upper + 1) |id| { + const id_str = try std.fmt.allocPrint(allocator, "{d}", .{id}); + if (try std.math.rem(usize, countDigits(id), 2) == 0) { + const left = id_str[0 .. id_str.len / 2]; + const right = id_str[id_str.len / 2 ..]; + defer allocator.free(id_str); + if (std.mem.eql(u8, left, right)) result += id; + } + } + } + + std.debug.print("Result: {d}\n", .{result}); +} + +fn part2(allocator: Allocator) anyerror!void { + const input_embed = @embedFile("puzzle-02"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input_embed}); + + var result: usize = 0; + + var reader: std.Io.Reader = .fixed(std.mem.trimEnd(u8, input_embed, "\n")); + while (try reader.takeDelimiter(',')) |line| { + var ranges = std.mem.splitSequence(u8, line, "-"); + const lhs = ranges.next().?; + const rhs = ranges.next().?; + const lower = try std.fmt.parseInt(u64, lhs, 10); + const upper = try std.fmt.parseInt(u64, rhs, 10); + for (lower..upper + 1) |id| { + const id_str = try std.fmt.allocPrint(allocator, "{d}", .{id}); + defer allocator.free(id_str); + + var invalid = false; + + const digit_count = countDigits(id); + check: for (1..digit_count) |x| { + const w = digit_count - x; + const remainder = try std.math.rem(usize, digit_count, w); + if (remainder > 0) continue; + + var window_it = std.mem.window(u8, id_str, w, w); + const search = window_it.next().?; + while (window_it.next()) |seq| { + if (!std.mem.eql(u8, search, seq)) { + continue :check; + } + } + invalid = true; + break :check; + } + if (invalid) result += id; + } + } + + std.debug.print("Result: {d}\n", .{result}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day03.zig b/2025/src/day03.zig new file mode 100644 index 0000000..8118a4f --- /dev/null +++ b/2025/src/day03.zig @@ -0,0 +1,119 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const expect = std.testing.expect; + +const YEAR: u12 = 2025; +const DAY: u5 = 3; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn part1(allocator: Allocator) anyerror!void { + _ = allocator; + const input_embed = @embedFile("puzzle-03"); + var sum: u32 = 0; + + var reader: std.Io.Reader = .fixed(input_embed); + while (try reader.takeDelimiter('\n')) |line| { + var largest: u8 = 0; + + for (0..line.len) |i| { + const start_digit = try std.fmt.charToDigit(line[i], 10); + for (i + 1..line.len) |j| { + const end_digit = try std.fmt.charToDigit(line[j], 10); + const combination = start_digit * 10 + end_digit; + if (combination > largest) largest = combination; + } + } + sum += largest; + } + std.debug.print("Result: {d}\n", .{sum}); +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + var total_joltage: u128 = 0; + + const input = @embedFile("puzzle-03"); + var reader: std.Io.Reader = .fixed(input); + + const battery_count = 12; + while (try reader.takeDelimiter('\n')) |line| { + const contained_number = try findLargestContainedNumber( + @TypeOf(total_joltage), + line, + battery_count, + ); + total_joltage += contained_number; + } + + std.debug.print("Result: {d}\n", .{total_joltage}); +} + +fn findLargestContainedNumber(comptime T: type, line: []const u8, battery_count: usize) !T { + var largest_contained_number: T = 0; + var start_idx: usize = 0; + var search_space = line[start_idx .. line.len - (battery_count - 1)]; + var found_digit_count: usize = 0; + for (0..battery_count) |e| { + var max: u8 = try std.fmt.charToDigit(search_space[0], 10); + var max_idx: usize = 0; + for (search_space[0..], 0..) |c, i| { + if (try std.fmt.charToDigit(c, 10) > max) { + max = c - '0'; + max_idx = i; + } + } + + largest_contained_number += max * std.math.pow(T, 10, (battery_count - 1) - e); + + found_digit_count += 1; + start_idx += max_idx + 1; + const upper_bound = @min( + line.len, + @max( + (line.len - (battery_count -| 1 -| found_digit_count)), + start_idx + 1, + ), + ); + search_space = line[start_idx..upper_bound]; + // std.debug.print(" > max: {d} [{d}]\n", .{ max, max_idx }); + } + return largest_contained_number; +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} + +test "example-part2" { + try expect(987654321111 == try findLargestContainedNumber(u32, "987654321111111", 12)); + try expect(811111111119 == try findLargestContainedNumber(u32, "811111111111119", 12)); + try expect(434234234278 == try findLargestContainedNumber(u32, "234234234234278", 12)); + try expect(888911112111 == try findLargestContainedNumber(u32, "818181911112111", 12)); + + try expect(919 == try findLargestContainedNumber(u32, "9119", 3)); + try expect(123 == try findLargestContainedNumber(u32, "111123", 3)); + try expect(923 == try findLargestContainedNumber(u32, "234789123", 3)); + try expect(11145 == try findLargestContainedNumber(u32, "1111145", 5)); + try expect(99919 == try findLargestContainedNumber(u32, "19191919", 5)); + + try expect(99919 == try findLargestContainedNumber(u32, "19191919", 5)); + + const input_example = @embedFile("example-03"); + var result: u128 = 0; + var reader: std.Io.Reader = .fixed(input_example); + while (try reader.takeDelimiter('\n')) |line| { + const number = try findLargestContainedNumber(u32, line, 12); + result += number; + } + try expect(result == 3121910778619); + + _ = try findLargestContainedNumber(u128, "2753445676625843555534776876555247667428557664243735457776754553427876646616644267454232337424744677", 12); +} diff --git a/2025/src/day04.zig b/2025/src/day04.zig new file mode 100644 index 0000000..7754e70 --- /dev/null +++ b/2025/src/day04.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const aoc = @import("aoc"); +const term = @import("libs").term; + +const YEAR: u12 = 2025; +const DAY: u5 = 4; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn findAccessibleRolls(map: []u8, rows: usize, cols: usize) !usize { + var result: usize = 0; + + for (0..cols) |i| { + for (0..rows) |j| { + const x: isize = @intCast(j); + const y: isize = @intCast(i); + var rolls_count: usize = 0; + + for (0..3) |yy| { + inner: for (0..3) |xx| { + const xo = x + @as(isize, @intCast(xx)) - 1; + const yo = y + @as(isize, @intCast(yy)) - 1; + if (xo < 0 or + yo < 0 or + xo > rows - 1 or + yo > cols - 1 or + (xo == x and yo == y)) continue :inner; + const map_value = map[@intCast((xo * @as(isize, @intCast(rows))) + yo)]; + if (map_value == '@' or map_value == 'x') { + rolls_count += 1; + } + } + } + + if (map[@intCast((x * @as(isize, @intCast(rows))) + y)] != '.' and rolls_count < 4) { + map[@intCast((x * @as(isize, @intCast(rows))) + y)] = '.'; + result += 1; + } + } + } + + // --- Print the map to visualise what's going on + for (0..cols) |_x| { + for (0..rows) |_y| { + const m = map[(_x * rows) + _y]; + std.debug.print("{s}{c}{s} ", .{ + if (m == '@') term.red else term.clear, + m, + term.clear, + }); + } + std.debug.print("\n", .{}); + } + + return result; +} + +fn part1(allocator: Allocator) anyerror!void { + const input_embed = std.mem.trimEnd(u8, @embedFile("puzzle-04"), "\n"); + + var it = std.mem.tokenizeSequence(u8, input_embed, "\n"); + const cols = it.peek().?.len; + const rows = input_embed.len / cols; + std.debug.print("Map size: {d} x {d}\n", .{ cols, rows }); + + const map = try std.mem.replaceOwned(u8, allocator, input_embed, "\n", ""); + defer allocator.free(map); + + const result = try findAccessibleRolls(map, rows, cols); + std.debug.print("Result: {d}\n", .{result}); +} + +fn part2(allocator: Allocator) anyerror!void { + const input_embed = std.mem.trimEnd(u8, @embedFile("puzzle-04"), "\n"); + + var it = std.mem.tokenizeSequence(u8, input_embed, "\n"); + const cols = it.peek().?.len; + const rows = input_embed.len / cols; + std.debug.print("Map size: {d} x {d}\n", .{ cols, rows }); + + const map = try std.mem.replaceOwned(u8, allocator, input_embed, "\n", ""); + defer allocator.free(map); + + var step_result = try findAccessibleRolls(map, rows, cols); + var summed_result: usize = step_result; + while (step_result > 0) { + step_result = try findAccessibleRolls(map, rows, cols); + summed_result += step_result; + } + std.debug.print("Result: {d}\n", .{summed_result}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day05.zig b/2025/src/day05.zig new file mode 100644 index 0000000..fd6d909 --- /dev/null +++ b/2025/src/day05.zig @@ -0,0 +1,105 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const YEAR: u12 = 2025; +const DAY: u5 = 5; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn part1(allocator: Allocator) anyerror!void { + var fresh_ingredients: usize = 0; + + const input_embed = @embedFile("puzzle-05"); + const split_at = std.mem.indexOf(u8, input_embed, "\n\n").?; + const ranges = std.mem.trim(u8, input_embed[0..split_at], "\n"); + const ingredients_ids = std.mem.trim(u8, input_embed[split_at..], "\n"); + + var ranges_list: std.array_list.Managed(@Vector(2, usize)) = .init(allocator); + defer ranges_list.deinit(); + var ranges_reader: std.Io.Reader = .fixed(ranges); + while (try ranges_reader.takeDelimiter('\n')) |range| { + const sep = std.mem.indexOf(u8, range, "-").?; + try ranges_list.append(.{ + try std.fmt.parseInt(usize, range[0..sep], 10), + try std.fmt.parseInt(usize, range[sep + 1 ..], 10), + }); + } + + var instructions_reader: std.Io.Reader = .fixed(ingredients_ids); + while (try instructions_reader.takeDelimiter('\n')) |line| { + const num = try std.fmt.parseInt(usize, line, 10); + var is_fresh = false; + for (ranges_list.items) |range| { + is_fresh = (num >= range[0] and num <= range[1]); + if (is_fresh) break; + } + if (is_fresh) fresh_ingredients += 1; + } + + std.debug.print("Result: {d}\n", .{fresh_ingredients}); +} + +fn part2(allocator: Allocator) anyerror!void { + const input_embed = @embedFile("puzzle-05"); + const split_at = std.mem.indexOf(u8, input_embed, "\n\n").?; + const ranges = std.mem.trim(u8, input_embed[0..split_at], "\n"); + + var ranges_list: std.array_list.Managed(@Vector(2, usize)) = .init(allocator); + defer ranges_list.deinit(); + + var ranges_reader: std.Io.Reader = .fixed(ranges); + while (try ranges_reader.takeDelimiter('\n')) |range| { + const sep = std.mem.indexOf(u8, range, "-").?; + const lower = try std.fmt.parseInt(usize, range[0..sep], 10); + const upper = try std.fmt.parseInt(usize, range[sep + 1 ..], 10); + + try ranges_list.append(.{ lower, upper }); + } + + std.mem.sort(@Vector(2, usize), ranges_list.items, {}, comptime struct { + pub fn f(_: void, a: @Vector(2, usize), b: @Vector(2, usize)) bool { + return a[0] < b[0]; + } + }.f); + + for (0..ranges_list.items.len - 1) |r| { + const curr = ranges_list.items[r]; + const next = ranges_list.items[r + 1]; + + // Current range overlaps next range on lower bound of next + if (curr[1] >= next[0]) { + + // Extend next range on lower end + ranges_list.items[r + 1][0] = curr[0]; + + // Current range overlaps next range on upper bound of next + if (curr[1] > next[1]) { + // Extend next range on upper end + ranges_list.items[r + 1][1] = curr[1]; + } + + // Dismiss current range + // (as the next range has been expanded and ids shouldn't be + // counted twice) + ranges_list.items[r] = .{ 0, 0 }; + } + } + + var sum: usize = 0; + for (ranges_list.items) |r| { + if (r[0] == 0 or r[1] == 0) continue; + sum += r[1] - r[0] + 1; + } + + std.debug.print("Result: {d}\n", .{sum}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day06.zig b/2025/src/day06.zig new file mode 100644 index 0000000..59877b8 --- /dev/null +++ b/2025/src/day06.zig @@ -0,0 +1,206 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const DAY: u5 = 6; + +const Allocator = std.mem.Allocator; +const log = std.log; + +const Cell = struct { + value: []const u8 = " ", + + pub fn format(self: Cell, writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("{s}", .{self.value}); + } +}; + +fn part1(allocator: Allocator) anyerror!void { + const input = std.mem.trimEnd(u8, @embedFile("puzzle-06"), "\n"); + + const eol = std.mem.indexOf(u8, input, "\n").?; + var first_line_reader: std.Io.Reader = .fixed(input[0..eol]); + var column_count: usize = 1; + var c = try first_line_reader.peekByte(); + while (true) { + const next = first_line_reader.takeByte() catch break; + if (c != ' ' and next == ' ') { + column_count += 1; + } + c = next; + } + std.debug.print("Column count: {d}\n", .{column_count}); + + var line_it = std.mem.splitSequence(u8, input, "\n"); + var row_count: usize = 0; + while (line_it.next()) |_| { + row_count += 1; + } + std.debug.print("{d} x {d}\n", .{ row_count, column_count }); + + var ops: std.array_list.Managed(u8) = .init(allocator); + defer ops.deinit(); + + var problems = try allocator.alloc(std.array_list.Managed(u32), column_count); + for (0..problems.len) |i| { + problems[i] = .init(allocator); + } + defer allocator.free(problems); + defer for (problems) |p| p.deinit(); + + var lines_it: std.Io.Reader = .fixed(input); + while (try lines_it.takeDelimiter('\n')) |line| { + if (line.len == 0) continue; + const line_trimmed = std.mem.trim(u8, line, "\n"); + var reader: std.Io.Reader = .fixed(line_trimmed); + + if (std.mem.containsAtLeast(u8, line_trimmed, 1, "+")) { + var o: u8 = ' '; + while (true) { + o = reader.takeByte() catch break; + switch (o) { + '*', '+' => try ops.append(o), + ' ' => continue, + else => unreachable, + } + } + break; + } + + var it = std.mem.splitScalar(u8, line, ' '); + var i: usize = 0; + while (it.next()) |next| { + if (next.len == 0) continue; + const v = try std.fmt.parseInt(u32, next, 10); + try problems[i].append(v); + i += 1; + } + } + + var sum: u64 = 0; + for (0..problems.len) |p| { + std.debug.print("({c} ", .{ops.items[p]}); + for (problems[p].items) |v| std.debug.print("{d} ", .{v}); + var result: usize = 0; + switch (ops.items[p]) { + '+' => { + for (problems[p].items) |v| result += v; + }, + '*' => { + result = 1; + for (problems[p].items) |v| result *= v; + }, + ' ' => break, + else => unreachable, + } + std.debug.print(" = {d}", .{result}); + std.debug.print(")\n", .{}); + sum += result; + } + std.debug.print("\nResult: {d}\n", .{sum}); +} + +fn part2(allocator: Allocator) anyerror!void { + const input = @embedFile("puzzle-06"); + var rows: usize = 0; + var cols: usize = 0; + var ops: std.array_list.Managed(u8) = .init(allocator); + var splits: std.array_list.Managed([2]usize) = .init(allocator); + + // Find number of rows + var line_it = std.mem.splitScalar(u8, input, '\n'); + var line: []const u8 = ""; + while (line_it.next()) |l| : (rows += 1) { + if (l.len == 0) break; + line = l; + } + + // Find number of columns + var ops_reader: std.Io.Reader = .fixed(line); + var previous: u8 = try ops_reader.takeByte(); + var s: usize = 0; + var i: usize = 0; + var last_seen_op: u8 = previous; + while (true) : (i += 1) { + const c = ops_reader.takeByte() catch { + // last split, intentionally create a larger end index + cols += 1; + try ops.append(last_seen_op); + try splits.append(.{ s, s + 5 }); + break; + }; + if (previous == ' ' and c != ' ') { + try ops.append(last_seen_op); + // store the last seen op to be available + // when iterator has no more data + last_seen_op = c; + try splits.append(.{ s, i }); + s = i; + } + if (c == ' ') { + previous = c; + continue; + } + cols += 1; + } + + std.debug.print("{d} x {d}\n", .{ rows, cols }); + + // Process input + var sum: usize = 0; + for (ops.items, 0..cols) |op, x| { + var sum_column: usize = 0; + var line_idx: usize = 0; + + for (0..rows) |r| { + var number: usize = 0; + + var digit_count: usize = 0; + line_it.reset(); + while (line_it.next()) |l| { + if (l.len == 0 or std.mem.containsAtLeast(u8, l, 1, "*")) break; + const lower = splits.items[x][0]; + const upper = @min(splits.items[x][1], l.len); + const slice = l[lower..upper]; + if (r >= slice.len) break; + if (slice[r] != ' ') digit_count += 1; + } + + line_it.reset(); + var pow: usize = std.math.pow(usize, 10, digit_count -| 1); + while (line_it.next()) |l| : (line_idx += 1) { + if (l.len == 0 or std.mem.containsAtLeast(u8, l, 1, "*")) break; + const lower = splits.items[x][0]; + const upper = @min(splits.items[x][1], l.len); + const slice = l[lower..upper]; + if (r >= slice.len) break; + + switch (slice[r]) { + '0'...'9' => |c| { + const digit = c - '0'; + number += digit * pow; + pow /= 10; + }, + else => continue, + } + } + + switch (op) { + '*' => sum_column = @max(1, sum_column) * @max(1, number), + '+' => sum_column += number, + else => unreachable, + } + } + + sum += sum_column; + } + std.debug.print("Result: {d}\n", .{sum}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day07.zig b/2025/src/day07.zig new file mode 100644 index 0000000..9d9a1a0 --- /dev/null +++ b/2025/src/day07.zig @@ -0,0 +1,77 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const DAY: u5 = 7; + +const Allocator = std.mem.Allocator; +const log = std.log; + +const Map = struct { + rows: usize, + cols: usize, + buffer: []u8, + + pub fn init(allocator: Allocator, buffer: []const u8) !Map { + if (std.mem.indexOf(u8, buffer, "\n")) |line_end| { + return .{ + .buffer = try std.mem.replaceOwned(u8, allocator, buffer, "\n", ""), + .cols = line_end, + .rows = (buffer.len - 1) / line_end, + }; + } + @panic("Failed to split input buffer!"); + } + + pub fn get(self: *const Map, x: usize, y: usize) u8 { + return self.buffer[(y * self.cols) + x]; + } + + pub fn format(self: Map, writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("Map {d} x {d}\n", .{ self.rows, self.cols }); + for (0..self.rows) |y| { + for (0..self.cols) |x| { + try writer.print("{c}", .{self.get(x, y)}); + } + try writer.print("\n", .{}); + } + } + + pub fn deinit(self: Map, allocator: Allocator) void { + allocator.free(self.buffer); + } +}; + +fn part1(allocator: Allocator) anyerror!void { + const input = @embedFile("example-07"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); + + const map: Map = try .init(allocator, input); + + var start: @Vector(2, usize) = @splat(0); + var splitters: std.array_list.Managed(@Vector(2, usize)) = .init(allocator); + + for (0..map.rows) |y| { + for (0..map.cols) |x| { + switch (map.get(x, y)) { + 'S' => start = .{ x, y }, + '^' => try splitters.append(.{ x, y }), + else => continue, + } + } + } +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + const input = @embedFile("puzzle-07"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + // try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day08.zig b/2025/src/day08.zig new file mode 100644 index 0000000..962c050 --- /dev/null +++ b/2025/src/day08.zig @@ -0,0 +1,222 @@ +const std = @import("std"); +const aoc = @import("aoc"); +const VectorSet = @import("libs").VectorSet; + +const DAY: u5 = 8; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn Box(comptime T: type) type { + return struct { + id: usize, + pos: @Vector(3, T), + + pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("[{d: >4}] {d: >6}", .{ self.id, self.pos }); + } + }; +} + +fn Connection(comptime T: type) type { + return struct { + a: Box(T), + b: Box(T), + d: T, + + pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("{f}---{f} [distance: {d: >10.3}]", .{ self.a, self.b, self.d }); + } + }; +} + +const Circuit = struct { + ids: std.bit_set.DynamicBitSet, + + pub fn initEmpty(allocator: Allocator) !Circuit { + return Circuit{ + .ids = try std.bit_set.DynamicBitSet.initEmpty(allocator, 4096), + }; + } + + pub fn init(allocator: Allocator, start: Box(f32)) !Circuit { + var ids = try std.bit_set.DynamicBitSet.initEmpty(allocator, 4096); + ids.set(start.id); + return Circuit{ + .ids = ids, + }; + } + + pub fn contains(self: *const Circuit, b: Box(f32)) bool { + return self.ids.isSet(b.id); + } + + pub fn merge(self: *Circuit, c: Circuit) void { + var it = c.ids.iterator(.{}); + while (it.next()) |id| { + self.ids.set(id); + } + } + + pub fn format(self: Circuit, writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("Circuit [{d}]\n", .{self.ids.count()}); + var it = self.ids.iterator(.{}); + while (it.next()) |c| { + std.debug.print(" + {d}\n", .{c}); + } + } + + pub fn deinit(self: *Circuit) void { + self.ids.deinit(); + } +}; + +fn distance(comptime T: type, a: @Vector(3, T), b: @Vector(3, T)) T { + const v = @abs(b - a); + return @sqrt(@reduce(.Add, v * v)); +} + +fn part1(allocator: Allocator) anyerror!void { + const input = @embedFile("puzzle-08"); + + var reader: std.Io.Reader = .fixed(input); + + var junction_boxes: std.array_list.Managed(Box(f32)) = .init(allocator); + defer junction_boxes.deinit(); + + var idx: usize = 0; + while (try reader.takeDelimiter('\n')) |line| { + var coords_it = std.mem.splitScalar(u8, line, ','); + var coord: @Vector(3, f32) = @splat(0); + var i: usize = 0; + while (coords_it.next()) |str| : (i += 1) { + const v = try std.fmt.parseFloat(f32, str); + std.debug.assert(v != 0); + coord[i] = v; + } + std.debug.assert(i <= 3); + try junction_boxes.append(.{ + .id = idx, + .pos = coord, + }); + idx += 1; + } + + var connections: std.array_list.Managed(Connection(f32)) = .init(allocator); + defer connections.deinit(); + + var used_boxes: std.bit_set.DynamicBitSet = try .initEmpty(allocator, 4096); + defer used_boxes.deinit(); + var circuits: std.array_list.Managed(Circuit) = .init(allocator); + defer circuits.deinit(); + defer for (circuits.items) |*c| c.deinit(); + + for (0..junction_boxes.items.len) |i| { + for (i..junction_boxes.items.len) |j| { + const a = junction_boxes.items[i]; + const b = junction_boxes.items[j]; + const d = distance(f32, a.pos, b.pos); + if (!used_boxes.isSet(a.id)) { + try circuits.append(try .init(allocator, a)); + used_boxes.set(a.id); + } + if (!used_boxes.isSet(b.id)) { + try circuits.append(try .init(allocator, b)); + used_boxes.set(b.id); + } + if (d == 0) continue; + try connections.append(.{ + .a = a, + .b = b, + .d = d, + }); + } + } + + std.mem.sort(Connection(f32), connections.items, {}, comptime struct { + pub fn f(_: void, a: Connection(f32), b: Connection(f32)) bool { + return a.d > b.d; + } + }.f); + + const limit: usize = 1000; + var i: usize = 0; + while (connections.pop()) |next| : (i += 1) { + if (i >= limit) break; + std.debug.print("[{d: >5}] {f}\n", .{ i, next }); + + var circuit_a: ?Circuit = null; + var count: usize = circuits.items.len; + for (0..count) |c| { + const circuit = circuits.items[c]; + if (circuit.contains(next.a) and circuit.contains(next.b)) continue; + if (circuit.contains(next.a)) { + circuit_a = circuits.swapRemove(c); + count = circuits.items.len; + break; + } + } + std.debug.print("Circuit A: {any}\n", .{circuit_a}); + + var circuit_b: ?Circuit = null; + for (0..circuits.items.len) |c| { + const circuit = circuits.items[c]; + if (circuit.contains(next.a) and circuit.contains(next.b)) continue; + if (circuit.contains(next.b)) { + circuit_b = circuits.swapRemove(c); + break; + } + } + std.debug.print("Circuit B: {any}\n", .{circuit_b}); + + if (circuit_a != null and circuit_b != null) { + std.debug.print(">>>>> Merge!\n", .{}); + defer circuit_a.?.deinit(); + defer circuit_b.?.deinit(); + var merged: Circuit = try .initEmpty(allocator); + merged.merge(circuit_a.?); + merged.merge(circuit_b.?); + try circuits.append(merged); + } + + // std.debug.print("\n------\n# Circuits: {d}\n", .{circuits.items.len}); + // for (0..circuits.items.len) |c| { + // std.debug.print("@{d} {f}\n", .{ c, circuits.items[c] }); + // } + // aoc.blockAskForNext(); + } + + std.mem.sort(Circuit, circuits.items, {}, comptime struct { + pub fn f(_: void, a: Circuit, b: Circuit) bool { + return a.ids.count() > b.ids.count(); + } + }.f); + + var result: usize = 1; + var total: usize = 0; + std.debug.print("\n------\n# Circuits: {d}\n", .{circuits.items.len}); + for (0..circuits.items.len) |c| { + if (c >= 3) break; + total += circuits.items[c].ids.count(); + result *= circuits.items[c].ids.count(); + std.debug.print("[{d: >3}]\n{f}\n", .{ c, circuits.items[c] }); + } + + std.debug.print("Total connections: {d}\n", .{total}); + std.debug.print("Result: {d}\n", .{result}); +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + // const input = @embedFile("example-08"); + // std.debug.print("--- INPUT---\n{s}\n------------\n", .{@embedFile()}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + // try aoc.runPart(allocator, part2); +} diff --git a/2025/src/day09.zig b/2025/src/day09.zig new file mode 100644 index 0000000..b3e8e43 --- /dev/null +++ b/2025/src/day09.zig @@ -0,0 +1,307 @@ +const std = @import("std"); +const aoc = @import("aoc"); +const t = @import("libs").term; +const svg = @import("libs").svg; +const Map = @import("libs").Map; + +const DAY: u5 = 9; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn calcArea(x0: u64, x1: u64, y0: u64, y1: u64) u64 { + const vec_a: u64 = @abs(@as(i64, @intCast(x0)) - @as(i64, @intCast(x1))) + 1; + const vec_b: u64 = @abs(@as(i64, @intCast(y0)) - @as(i64, @intCast(y1))) + 1; + return vec_a * vec_b; +} + +fn distance(a: @Vector(2, u64), b: @Vector(2, u64)) f64 { + const vec: @Vector(2, f64) = @as(@Vector(2, f64), @floatFromInt(a)) - @as(@Vector(2, f64), @floatFromInt(b)); + return @sqrt(@exp2(vec))[0]; +} + +fn findLeftMostLowest(list: *std.array_list.Managed(@Vector(2, u64))) @Vector(2, u64) { + std.debug.assert(list.items.len > 2); + var result = list.items[0]; + for (list.items) |p| { + if (p[0] < result[0]) { + result = p; + if (p[1] < result[1]) { + result = p; + } + } + } + return result; +} + +/// Cast a ray from point p to the right +/// Count how many times it intersects the path +/// count is odd => inside, count is even => outside +fn insidePath(path: *std.array_list.Managed(@Vector(2, u64)), p: @Vector(2, u64)) bool { + var inside = false; + for (0..path.items.len) |i| { + const a = path.items[i]; + const b = path.items[(i + 1) % path.items.len]; + + // Convert to signed integers for comparison + const ax = @as(i64, @intCast(a[0])); + const ay = @as(i64, @intCast(a[1])); + const bx = @as(i64, @intCast(b[0])); + const by = @as(i64, @intCast(b[1])); + const px = @as(i64, @intCast(p[0])); + const py = @as(i64, @intCast(p[1])); + + // Check if edge crosses the horizontal ray from point p to the right + // Edge must straddle the horizontal line at py + if ((ay >= py) != (by >= py)) { + // Calculate x-coordinate of intersection point + // Using: x = ax + (py - ay) * (bx - ax) / (by - ay) + const slope_num = (py - ay) * (bx - ax); + const slope_den = by - ay; + const intersect_x = ax + @divTrunc(slope_num, slope_den); + + // If intersection is to the right of point p, toggle inside + if (px + 1 < intersect_x) { + inside = !inside; + } + } + } + + return inside; +} + +fn part1(allocator: Allocator) anyerror!void { + const input = @embedFile("puzzle-09"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); + + var it: std.Io.Reader = .fixed(input); + var tiles: std.array_list.Managed(@Vector(2, u64)) = .init(allocator); + defer tiles.deinit(); + + var last_area: u64 = 0; + while (try it.takeDelimiter('\n')) |line| { + if (std.mem.indexOf(u8, line, ",")) |sep| { + const x = try std.fmt.parseInt(u64, line[0..sep], 10); + const y = try std.fmt.parseInt(u64, line[sep + 1 ..], 10); + for (tiles.items) |_| { + const area = calcArea(x, t[0], y, t[1]); + last_area = @max(area, last_area); + } + try tiles.append(.{ x, y }); + } + } + std.debug.print("Largest area: {d}\n", .{last_area}); +} + +fn part2(allocator: Allocator) anyerror!void { + const input = @embedFile("puzzle-09"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); + + var it: std.Io.Reader = .fixed(input); + var tiles: std.array_list.Managed(@Vector(2, u64)) = .init(allocator); + defer tiles.deinit(); + + var largest: @Vector(2, usize) = .{ 0, 0 }; + while (try it.takeDelimiter('\n')) |line| { + if (std.mem.indexOf(u8, line, ",")) |sep| { + const x = try std.fmt.parseInt(u64, line[0..sep], 10); + const y = try std.fmt.parseInt(u64, line[sep + 1 ..], 10); + // map.set(x, y, '#'); + if (x > largest[0]) { + largest[0] = x; + } + if (y > largest[1]) { + largest[1] = y; + } + try tiles.append(.{ x, y }); + } + } + + std.debug.print("{d} x {d}\n", .{ largest[0], largest[1] }); + + try svg.init( + "2025-day09-part2.svg", + largest[0], + largest[1], + 0, + largest[0], + 0, + largest[1], + ); + + try svg.startPolygon(); + for (tiles.items) |tile| { + try svg.addPolygonPoint(tile[0], tile[1]); + } + try svg.endPolygon("green"); + + var path: std.array_list.Managed(@Vector(2, u64)) = .init(allocator); + defer path.deinit(); + + const start = findLeftMostLowest(&tiles); + try path.append(start); + + // const map: Map = try .initEmpty(allocator, 14, 8, '.'); + // defer map.deinit(); + + std.debug.print("Start: {d}\n", .{start}); + // map.set(start[0], start[1], 'S'); + + const tile_count = tiles.items.len; + var current = start; + var idx: u64 = 1; + while (true) { + if (idx >= tile_count) break; + var closest_idx: usize = 0; + var closest = tiles.items[closest_idx]; + var d: f64 = 100.0; + for (1..tiles.items.len) |i| { + const tile = tiles.items[i]; + if (tile[0] == current[0] and tile[1] == current[1]) continue; + if (current[0] != tile[0] and current[1] != tile[1]) continue; + const closest_d = distance(current, tile); + if (closest_d < d) { + d = closest_d; + closest = tile; + closest_idx = i; + } + } + // map.set(closest[0], closest[1], idx + '0'); + idx += 1; + current = closest; + try path.append(tiles.swapRemove(closest_idx)); + } + + // std.debug.print("Path:\n{any}\n", .{path.items}); + // for (path.items) |p| { + // std.debug.print("{any}\n", .{p}); + // } + + // for (0..path.items.len) |p| { + // const s = path.items[@mod(p, path.items.len)]; + // const n = path.items[@mod(p + 1, path.items.len)]; + // const x_from = @min(s[0], n[0]); + // const x_to = @max(s[0], n[0]); + // const y_from = @min(s[1], n[1]); + // const y_to = @max(s[1], n[1]); + // if (x_from == x_to) { + // for (y_from + 1..y_to) |y| { + // // map.set(x_from, y, 'x'); + // } + // } + // if (y_from == y_to) { + // std.debug.print("{d} -- {d}\n", .{ x_from, x_to }); + // for (x_from + 1..x_to) |x| { + // // map.set(x, y_from, 'x'); + // } + // } + // } + + // const test_points = [_]@Vector(2, u64){ + // .{ 4, 4 }, + // .{ 1, 1 }, + // .{ 5, 7 }, + // .{ 2, 5 }, + // .{ 2, 1 }, + // }; + // for (test_points) |p| { + // const orig = map.get(p[0], p[1]); + // map.set(p[0], p[1], '?'); + // const is_inside = insidePath(&path, p); + // std.debug.print("Is Inside? --> {any}\n", .{is_inside}); + // std.debug.print("{f}\n", .{map}); + // map.set(p[0], p[1], orig); + // } + + var largest_area: u64 = 0; + var a: @Vector(2, u64) = .{ 0, 0 }; + var b: @Vector(2, u64) = .{ 0, 0 }; + for (0..path.items.len) |i| { + const p = path.items[i]; + outer: for (0..path.items.len) |j| { + const q = path.items[j]; + if (p[0] == q[0] and p[1] == q[1]) continue; + const area = calcArea(p[0], q[0], p[1], q[1]); + const opposite_a_inside = insidePath(&path, .{ p[0], q[1] }); + const opposite_b_inside = insidePath(&path, .{ q[0], p[1] }); + // const p_orig = map.get(p[0], p[1]); + // const q_orig = map.get(q[0], q[1]); + // const u_orig = map.get(p[0], q[1]); + // const v_orig = map.get(q[0], p[1]); + // map.set(p[0], p[1], 'O'); + // map.set(q[0], q[1], 'O'); + // map.set(p[0], q[1], 'O'); + // map.set(q[0], p[1], 'O'); + // std.debug.print("Area: {d} [{d} -- {d}] - Outside: {any}, {any}?\n{f}\n", .{ + // area, + // i, + // j, + // opposite_a_inside, + // opposite_b_inside, + // map, + // }); + // map.set(p[0], p[1], p_orig); + // map.set(q[0], q[1], q_orig); + // map.set(p[0], q[1], u_orig); + // map.set(q[0], p[1], v_orig); + if (opposite_a_inside and opposite_b_inside and area > largest_area) { + var all_inside = true; + if (p[1] < q[1]) { + for (p[1]..q[1]) |y| { + const inside = insidePath(&path, .{ p[0], y }); + all_inside = all_inside and inside; + if (!all_inside) continue :outer; + } + } + if (q[1] < p[1]) { + for (q[1]..p[1]) |y| { + const inside = insidePath(&path, .{ p[0], y }); + all_inside = all_inside and inside; + if (!all_inside) continue :outer; + } + } + if (all_inside) { + largest_area = area; + a = p; + b = q; + } + } + } + } + try svg.startPolygon(); + try svg.addPolygonPoint(a[0], a[1]); + try svg.addPolygonPoint(a[0], b[1]); + try svg.addPolygonPoint(b[0], b[1]); + try svg.addPolygonPoint(b[0], a[1]); + try svg.endPolygon("red"); + + try svg.close(); + + std.debug.print("Area: {d}\n", .{largest_area}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} + +test "simple test" { + var a: @Vector(2, u32) = .{ 7, 1 }; + var b: @Vector(2, u32) = .{ 11, 7 }; + try std.testing.expect(calcArea(a[0], b[0], a[1], b[1]) == 35); + try std.testing.expect(calcArea(b[0], a[0], b[1], a[1]) == 35); + + a = .{ 7, 3 }; + b = .{ 2, 3 }; + try std.testing.expect(calcArea(a[0], b[0], a[1], b[1]) == 6); + try std.testing.expect(calcArea(b[0], a[0], b[1], a[1]) == 6); + + a = .{ 2, 5 }; + b = .{ 11, 1 }; + try std.testing.expect(calcArea(a[0], b[0], a[1], b[1]) == 50); + try std.testing.expect(calcArea(b[0], a[0], b[1], a[1]) == 50); +} diff --git a/2025/src/day11.zig b/2025/src/day11.zig new file mode 100644 index 0000000..4b641d5 --- /dev/null +++ b/2025/src/day11.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const DAY: u5 = 11; + +const Allocator = std.mem.Allocator; +const log = std.log; + +const Node = struct { + name: []const u8, + connections: std.array_list.Managed([]const u8), + + pub fn format(self: Node, writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("{s}\n", .{self.name}); + for (self.connections.items) |c| { + try writer.print(" -- {s}\n", .{c}); + } + } +}; + +fn part1(allocator: Allocator) anyerror!void { + const input = @embedFile("puzzle-11"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); + + var device_map: std.hash_map.StringHashMap(Node) = .init(allocator); + defer device_map.deinit(); + + var reader: std.Io.Reader = .fixed(input); + while (try reader.takeDelimiter('\n')) |line| { + const colon = std.mem.indexOf(u8, line, ":") orelse @panic(": not found"); + const device_name = line[0..colon]; + const connections = line[colon + 1 ..]; + var connections_it = std.mem.splitSequence(u8, connections, " "); + var node: Node = .{ + .name = device_name, + .connections = .init(allocator), + }; + while (connections_it.next()) |connection| { + if (connection.len == 0) continue; + try node.connections.append(connection); + } + try device_map.put(device_name[0..3], node); + } + + var stack: std.array_list.Managed(*Node) = .init(allocator); + defer stack.deinit(); + try stack.append(device_map.getPtr("you").?); + + var result: usize = 0; + while (stack.pop()) |next| { + std.debug.print("[{d}] Next: {f}\n", .{ stack.items.len, next }); + for (next.connections.items) |connection| { + if (std.mem.eql(u8, connection, "out")) { + result += 1; + continue; + } + if (device_map.getPtr(connection)) |ptr| { + try stack.append(ptr); + } + } + } + std.debug.print("Result: {d}\n", .{result}); +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + const input = @embedFile("example-11"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + // try aoc.runPart(allocator, part2); +} diff --git a/aoc/common.zig b/aoc/common.zig deleted file mode 100644 index fc628ee..0000000 --- a/aoc/common.zig +++ /dev/null @@ -1,64 +0,0 @@ -const std = @import("std"); -const puzzle_input = @import("./input.zig"); -const stopwatch = @import("./stopwatch.zig"); - -const Allocator = std.mem.Allocator; - -pub fn printPart1() void { - std.debug.print("\n########## Part 1 ##########", .{}); -} - -pub fn printPart2() void { - std.debug.print("\n########## Part 2 ##########", .{}); -} - -pub fn printTime(time: u64) void { - const ns = time; - const us: f64 = @floatFromInt(time / std.time.ns_per_us); - const ms: f64 = @floatFromInt(time / std.time.ns_per_ms); - std.debug.print("\n— âē Running time: {d:3} ms / {d:3} Ξs / {d} ns\n", .{ ms, us, ns }); -} - -pub const PuzzleInput = enum { EXAMPLE, PUZZLE }; - -pub fn runPart( - allocator: std.mem.Allocator, - comptime year: u16, - comptime day: u8, - input_type: PuzzleInput, - comptime part_fn: fn (allocator: Allocator, input: []const u8) anyerror!void, -) !void { - const input = switch (input_type) { - .PUZZLE => try puzzle_input.getPuzzleInput(allocator, day, year), - .EXAMPLE => try puzzle_input.getExampleInput(allocator, day, year), - }; - stopwatch.start(); - try part_fn(allocator, input); - const time = stopwatch.stop(); - printTime(time); -} - -pub fn runDay( - allocator: std.mem.Allocator, - comptime year: u16, - comptime day: u8, - input_type: PuzzleInput, - comptime part1: fn (allocator: Allocator, input: []const u8) anyerror!void, - comptime part2: fn (allocator: Allocator, input: []const u8) anyerror!void, -) !void { - try runPart(allocator, year, day, input_type, part1); - try runPart(allocator, year, day, input_type, part2); -} - -pub fn blockAskForNext() void { - step: { - var reader_buffer: [10]u8 = undefined; - var reader = std.fs.File.stdin().readerStreaming(&reader_buffer); - std.debug.print("\n\n→ Step: [Enter]", .{}); - var writter_buffer: [10]u8 = undefined; - var writer = std.fs.File.stdout().writerStreaming(&writter_buffer); - _ = reader.interface.streamDelimiter(&writer.interface, '\n') catch return; - _ = reader.interface.takeByte() catch return; - break :step; - } -} diff --git a/aoc/input.zig b/aoc/input.zig deleted file mode 100644 index 1eaa57f..0000000 --- a/aoc/input.zig +++ /dev/null @@ -1,109 +0,0 @@ -const std = @import("std"); -const fs = std.fs; -const http = std.http; - -fn getAdventOfCodeCookieFromEnv(allocator: std.mem.Allocator) !?[]const u8 { - const env_map = try allocator.create(std.process.EnvMap); - env_map.* = try std.process.getEnvMap(allocator); - return env_map.get("AOC_COOKIE"); -} - -pub fn getPuzzleInputFromServer( - allocator: std.mem.Allocator, - year: u16, - day: usize, - file_path: []const u8, -) !void { - if (std.fs.path.dirname(file_path)) |basepath| { - fs.cwd().makeDir(basepath) catch { - std.debug.print("\n{s} already exists. Continue...", .{basepath}); - }; - } - - // Get AOC_COOKIE from environment - const cookie_from_env = try getAdventOfCodeCookieFromEnv(allocator); - if (cookie_from_env == null) { - std.log.err("\nPlease set AOC_COOKIE env variable", .{}); - } - std.debug.print("\nAOC_COOKIE: {s}", .{cookie_from_env.?}); - - var buf: [128]u8 = undefined; - const url = try std.fmt.bufPrint(&buf, "https://adventofcode.com/{d}/day/{d}/input", .{ - year, - day, - }); - - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); - - const output_file = try fs.cwd().createFile(file_path, .{}); - defer output_file.close(); - var buffer: [1]u8 = undefined; - var body_writer = output_file.writer(&buffer); - - var body = std.Io.Writer.Allocating.init(allocator); - defer body.deinit(); - try body.ensureUnusedCapacity(256); - - const req = try client.fetch(.{ - .location = .{ - .url = url, - }, - .extra_headers = &.{ - .{ - .name = "cookie", - .value = cookie_from_env.?, - }, - }, - .response_writer = &body_writer.interface, - }); - - if (req.status == .ok) std.debug.print("Success", .{}); - std.debug.print("\n{s}", .{file_path}); - std.debug.print("\nInput: {s}\n\n", .{try body.toOwnedSlice()}); -} - -pub fn getPuzzleInput(allocator: std.mem.Allocator, day: u8, year: u16) ![]const u8 { - var year_buf: [16]u8 = undefined; - var day_buf: [16]u8 = undefined; - const file_path = try std.fs.path.join(allocator, &.{ - "aoc", - "input", - try std.fmt.bufPrint(&year_buf, "{d}", .{year}), - try std.fmt.bufPrint(&day_buf, "day{d}.txt", .{day}), - }); - - const file = fs.cwd().openFile(file_path, .{}) catch { - try getPuzzleInputFromServer(allocator, year, day, file_path); - const f = try fs.cwd().openFile(file_path, .{}); - const s = try f.stat(); - var buffer: [1]u8 = undefined; - var reader = f.reader(&buffer); - return reader.interface.readAlloc(allocator, s.size); - }; - const stat = try file.stat(); - if (stat.size == 0) { - try getPuzzleInputFromServer(allocator, year, day, file_path); - } - - var buffer: [1]u8 = undefined; - var reader = file.reader(&buffer); - return reader.interface.readAlloc(allocator, stat.size); -} - -pub fn getExampleInput(allocator: std.mem.Allocator, day: u8, year: u16) ![]const u8 { - var day_buf: [16]u8 = undefined; - var year_buf: [16]u8 = undefined; - const file_path = try std.fs.path.join(allocator, &.{ - "aoc", - "input", - try std.fmt.bufPrint(&year_buf, "{d}", .{year}), - "examples", - try std.fmt.bufPrint(&day_buf, "day{d}.txt", .{day}), - }); - const file = try fs.cwd().openFile(file_path, .{}); - const stat = try file.stat(); - var buffer: [1]u8 = undefined; - var reader = file.reader(&buffer); - return reader.interface.readAlloc(allocator, stat.size); -} diff --git a/aoc/root.zig b/aoc/root.zig deleted file mode 100644 index 0faa777..0000000 --- a/aoc/root.zig +++ /dev/null @@ -1,5 +0,0 @@ -const aoc = @import("./common.zig"); - -pub const blockAskForNext = aoc.blockAskForNext; -pub const runPart = aoc.runPart; -pub const runDay = aoc.runDay; diff --git a/aoc/template.zig b/aoc/template.zig deleted file mode 100644 index f7538da..0000000 --- a/aoc/template.zig +++ /dev/null @@ -1,25 +0,0 @@ -const std = @import("std"); -const aoc = @import("aoc"); - -const DAY: u8 = -1; // @TODO -const Allocator = std.mem.Allocator; -const log = std.log; - -fn part1(allocator: Allocator, input: []const u8) anyerror!void { - _ = allocator; - _ = input; -} - -fn part2(allocator: Allocator, input: []const u8) anyerror!void { - _ = allocator; - _ = input; -} - -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - try aoc.runPart(allocator, 2024, DAY, .EXAMPLE, part1); - try aoc.runPart(allocator, 2024, DAY, .EXAMPLE, part2); -} diff --git a/build.zig b/build.zig deleted file mode 100644 index 2a868e1..0000000 --- a/build.zig +++ /dev/null @@ -1,128 +0,0 @@ -const std = @import("std"); -const fs = std.fs; -const aoc_input = @import("aoc/input.zig"); - -const YEAR: usize = 2024; - -const BuildTargetResults = struct { - exe: *std.Build.Step.Compile, - tests: *std.Build.Step.Compile, -}; - -fn createBuildTarget( - b: *std.Build, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, - aoc_module: *std.Build.Module, - libs: *std.StringHashMap(*std.Build.Module), - year: u16, - day: usize, -) !BuildTargetResults { - var src_buf: [32]u8 = undefined; - const source_file = try std.fmt.bufPrint(&src_buf, "{d}/day{d}.zig", .{ year, day }); - - // Check if source file actually exists - _ = std.fs.cwd().openFile(source_file, .{}) catch |err| { - std.debug.print("\n{s} does not exist!", .{source_file}); - return err; - }; - - var name_buf: [32]u8 = undefined; - const name = try std.fmt.bufPrint(&name_buf, "{d}-{d}", .{ year, day }); - const exe = b.addExecutable( - .{ - .name = name, - .root_module = b.createModule( - .{ - .root_source_file = b.path(source_file), - .target = target, - .optimize = optimize, - }, - ), - }, - ); - exe.root_module.addImport("aoc", aoc_module); - - var libs_it = libs.iterator(); - while (libs_it.next()) |lib| { - exe.root_module.addImport(lib.key_ptr.*, lib.value_ptr.*); - } - - const unit_tests = b.addTest(.{ - .root_module = b.createModule( - .{ - .root_source_file = b.path(source_file), - .target = target, - .optimize = optimize, - }, - ), - }); - - b.installArtifact(exe); - - return .{ - .exe = exe, - .tests = unit_tests, - }; -} - -pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const aoc_module = b.addModule("aoc", .{ .root_source_file = b.path("./aoc/root.zig"), .optimize = optimize, .target = target }); - - var libs = std.StringHashMap(*std.Build.Module).init(b.allocator); - - const lib_names = .{ "ppm", "math", "svg", "term", "datastructures" }; - inline for (0..lib_names.len) |i| { - const lib_name = lib_names[i]; - const module = b.addModule(lib_name, .{ - .root_source_file = b.path("./libs/" ++ lib_name ++ ".zig"), - .optimize = optimize, - .target = target, - }); - try libs.put(lib_name, module); - } - - // This allows the user to pass arguments to the application in the build - // command itself, like this: `zig build run -- arg1 arg2 etc` - if (b.args) |args| { - const day = std.fmt.parseInt(u8, args[0], 10) catch 1; - const res = createBuildTarget(b, target, optimize, aoc_module, &libs, YEAR, day) catch return; - - const run_cmd = b.addRunArtifact(res.exe); - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step(try std.fmt.allocPrint(b.allocator, "run", .{}), try std.fmt.allocPrint(b.allocator, "Run {d} of {d}", .{ day, YEAR })); - run_step.dependOn(&run_cmd.step); - - // Testing - const run_unit_tests = b.addRunArtifact(res.tests); - const test_step = b.step(try std.fmt.allocPrint(b.allocator, "test", .{}), try std.fmt.allocPrint(b.allocator, "Run test cases for {d} of {d}", .{ day, YEAR })); - test_step.dependOn(&run_unit_tests.step); - return; - } - - for (1..25) |day| { - _ = createBuildTarget(b, target, optimize, aoc_module, &libs, YEAR, day) catch continue; - } - - // Interactive prompt - if (false) { - // // If no argument was given, ask the user which day should be build/run - // std.debug.print("\nWhich day should be build/run [1 - 24]? ", .{}); - // // Ideally we would want to issue more than one read - // // otherwise there is no point in buffering. - // var msg_buf: [4096]u8 = undefined; - // const input = r.readUntilDelimiterOrEof(&msg_buf, '\n') catch ""; - // if (input) |input_txt| { - // const day = std.fmt.parseInt(u8, input_txt, 10) catch { - // std.debug.print("\nPlease give a number between 1 and 24\n\n", .{}); - // return; - // }; - // std.debug.print("Selected day {d}\n~ Compiling...\n", .{day}); - // try createBuildTarget(b, target, optimize, aoc_module, &libs, YEAR, day); - // } - } -} diff --git a/build.zig.zon b/build.zig.zon deleted file mode 100644 index 54b8a26..0000000 --- a/build.zig.zon +++ /dev/null @@ -1,7 +0,0 @@ -.{ - .name = .aoc, - .fingerprint = 0x808a3f8f28be9351, - .version = "0.0.1", - .dependencies = .{}, - .paths = .{"./aoc"}, -} diff --git a/libs/datastructures.zig b/libs/datastructures.zig deleted file mode 100644 index 457e115..0000000 --- a/libs/datastructures.zig +++ /dev/null @@ -1 +0,0 @@ -pub const VectorSet = @import("./datastructures/VectorSet.zig").VectorSet; diff --git a/modules/aoc/build.zig b/modules/aoc/build.zig new file mode 100644 index 0000000..fca51b9 --- /dev/null +++ b/modules/aoc/build.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const input = @import("src/input.zig"); +pub const types = @import("src/types.zig"); +const utils = @import("src/utils.zig"); + +pub fn setupDay( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + YEAR: types.Year, + DAY: types.Day, +) !*std.Build.Step.Compile { + const dep_aoc = b.dependency("aoc", .{}); + const aoc_puzzleinput = dep_aoc.artifact("puzzle-input-helper"); + + const arg_year = try std.fmt.allocPrint(b.allocator, "{d}", .{YEAR}); + defer b.allocator.free(arg_year); + + const arg_day = try std.fmt.allocPrint(b.allocator, "{d}", .{DAY}); + defer b.allocator.free(arg_day); + + const arg_input_type_puzzle = try std.fmt.allocPrint(b.allocator, "{d}", .{ + @intFromEnum(types.PuzzleInput.PUZZLE), + }); + defer b.allocator.free(arg_input_type_puzzle); + + const arg_input_type_example = try std.fmt.allocPrint(b.allocator, "{d}", .{ + @intFromEnum(types.PuzzleInput.EXAMPLE), + }); + defer b.allocator.free(arg_input_type_puzzle); + + const cmd_puzzle_input = b.addRunArtifact(aoc_puzzleinput); + cmd_puzzle_input.addArgs(&.{ + arg_input_type_puzzle, + arg_year, + arg_day, + "input", + }); + const captured_output_puzzle = cmd_puzzle_input.captureStdOut(); // use this as anonymous import + + const cmd_example_input = b.addRunArtifact(aoc_puzzleinput); + cmd_example_input.addArgs(&.{ + arg_input_type_example, + arg_year, + arg_day, + "input", + }); + const captured_output_example = cmd_example_input.captureStdOut(); // use this as anonymous import + + const src_name = try std.fmt.allocPrint(b.allocator, "day{d:0>2}.zig", .{DAY}); + defer b.allocator.free(src_name); + const src_path = try std.fs.path.join(b.allocator, &.{ "src", src_name }); + defer b.allocator.free(src_path); + const exe_name = try std.fmt.allocPrint(b.allocator, "aoc-{d}-day{d:0>2}", .{ YEAR, DAY }); + defer b.allocator.free(exe_name); + + _ = std.fs.cwd().openFile(src_path, .{}) catch { + std.debug.print("{s} not found. Creating from template...\n", .{src_path}); + const contents = try createTemplate(b.allocator, DAY); + defer b.allocator.free(contents); + std.debug.print("{s}\n", .{contents}); + const src_file = try std.fs.cwd().createFile(src_path, .{}); + defer src_file.close(); + try src_file.writeAll(contents); + }; + + const exe = b.addExecutable(.{ + .name = exe_name, + .root_module = b.createModule(.{ + .root_source_file = b.path(src_path), + .optimize = optimize, + .target = target, + }), + }); + b.installArtifact(exe); + + const puzzle_input_name = try std.fmt.allocPrint(b.allocator, "puzzle-{d:0>2}", .{DAY}); + defer b.allocator.free(puzzle_input_name); + exe.root_module.addAnonymousImport(puzzle_input_name, .{ + .root_source_file = captured_output_puzzle, + }); + + const example_input_name = try std.fmt.allocPrint(b.allocator, "example-{d:0>2}", .{DAY}); + defer b.allocator.free(puzzle_input_name); + exe.root_module.addAnonymousImport(example_input_name, .{ + .root_source_file = captured_output_example, + }); + + exe.root_module.addImport("aoc", dep_aoc.module("aoc")); + + const dep_libs = b.dependency("libs", .{}); + exe.root_module.addImport("libs", dep_libs.module("libs")); + + return exe; +} + +pub fn runDay(b: *std.Build, exe: *std.Build.Step.Compile) void { + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Start the program"); + run_step.dependOn(&run_cmd.step); + + // Tests + const tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = exe.root_module.root_source_file, + .target = exe.root_module.resolved_target, + .optimize = exe.root_module.optimize, + }), + }); + const run_tests = b.addRunArtifact(tests); + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_tests.step); +} + +pub fn createTemplate(allocator: std.mem.Allocator, day: types.Day) ![]const u8 { + var template_str = utils.getTemplate(); + std.debug.print("{any}\n", .{@TypeOf(template_str)}); + const search_DAY = "\"$DAY\""; + const day_str = try std.fmt.allocPrint(allocator, "{d}", .{day}); + defer allocator.free(day_str); + template_str = try std.mem.replaceOwned(u8, allocator, template_str, search_DAY, day_str); + const day_input_num = try std.fmt.allocPrint(allocator, "{d:0>2}", .{day}); + defer allocator.free(day_str); + const search_DAY_embedFile = "$DAY"; + for (0..2) |_| { + template_str = try std.mem.replaceOwned( + u8, + allocator, + template_str, + search_DAY_embedFile, + day_input_num, + ); + } + return template_str; +} + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("aoc", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const exe_puzzlehelper = b.addExecutable(.{ + .name = "puzzle-input-helper", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/fetch-puzzle-input.zig"), + .target = target, + .optimize = optimize, + }), + }); + b.installArtifact(exe_puzzlehelper); +} diff --git a/modules/aoc/build.zig.zon b/modules/aoc/build.zig.zon new file mode 100644 index 0000000..32f9a48 --- /dev/null +++ b/modules/aoc/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .aoc, + .fingerprint = 0x808a3f8f28be9351, + .version = "0.0.2", + .minimum_zig_version = "0.15.2", + .dependencies = .{}, + .paths = .{ + "README.md", + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/modules/aoc/src/common.zig b/modules/aoc/src/common.zig new file mode 100644 index 0000000..64bda7a --- /dev/null +++ b/modules/aoc/src/common.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const types = @import("types.zig"); +const puzzle_input = @import("input.zig"); +const stopwatch = @import("stopwatch.zig"); + +const Allocator = std.mem.Allocator; + +pub fn printTime(time: u64) void { + const ns = time; + const us: f64 = @floatFromInt(time / std.time.ns_per_us); + const ms: f64 = @floatFromInt(time / std.time.ns_per_ms); + const s: f64 = @floatFromInt(time / std.time.ns_per_s); + const min: f64 = @floatFromInt(time / std.time.ns_per_min); + std.debug.print("\n— ⌛ïļ Running time: {d:2}:{d:3} min / {d:3} ms / {d:3} Ξs / {d} ns\n", .{ + min, + s, + ms, + us, + ns, + }); +} + +pub fn runPart( + allocator: std.mem.Allocator, + comptime part_fn: fn (allocator: Allocator) anyerror!void, +) !void { + stopwatch.start(); + try part_fn(allocator); + const time = stopwatch.stop(); + printTime(time); +} + +pub fn runDay( + allocator: std.mem.Allocator, + comptime part1: fn (allocator: Allocator) anyerror!void, + comptime part2: fn (allocator: Allocator) anyerror!void, +) !void { + try runPart(allocator, part1); + try runPart(allocator, part2); +} diff --git a/modules/aoc/src/fetch-puzzle-input.zig b/modules/aoc/src/fetch-puzzle-input.zig new file mode 100644 index 0000000..3456c1e --- /dev/null +++ b/modules/aoc/src/fetch-puzzle-input.zig @@ -0,0 +1,73 @@ +/// Small helper executable that allows to fetch the puzzle input from +/// https://adventofcode.com/$YEAR/day/$DAY/input +/// CLI arguments are +/// - year +/// - day +/// - Optional file path to store the puzzle input to disk. This is sometimes useful, +/// e.g. if you want to implement a solution in another language or just need to +/// peek into the data +const std = @import("std"); +const types = @import("types.zig"); +const input = @import("input.zig"); + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + var args = try std.process.argsWithAllocator(allocator); + defer args.deinit(); + _ = args.next(); // binary name + + const INPUT_TYPE: types.PuzzleInput = @enumFromInt(try std.fmt.parseInt( + @typeInfo(types.PuzzleInput).@"enum".tag_type, + args.next().?, + 10, + )); + const YEAR = try std.fmt.parseInt(types.Year, args.next().?, 10); + const DAY = try std.fmt.parseInt(types.Day, args.next().?, 10); + const puzzle_file_path = args.next(); + + std.debug.print("{any}: {d}-{d}\n", .{ YEAR, DAY, INPUT_TYPE }); + + const sub_folder = switch (INPUT_TYPE) { + .EXAMPLE => "example", + .PUZZLE => "puzzle", + }; + const file_name = try std.fmt.allocPrint(allocator, "day{d:0>2.}", .{DAY}); + const file_path = try std.fs.path.join(allocator, &.{ ".", puzzle_file_path.?, sub_folder, file_name }); + const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { + switch (err) { + error.FileNotFound => { + _ = switch (INPUT_TYPE) { + .PUZZLE => { + const response = try input.getPuzzleInputFromServer( + allocator, + YEAR, + DAY, + file_path, + ); + try std.fs.File.stdout().writeAll(response); + return; + }, + .EXAMPLE => { + _ = try std.fs.cwd().createFile(file_path, .{}); + std.debug.print("🎁 Created empty {s}. Make sure to feed in data.\n", .{ + file_path, + }); + return; + }, + }; + }, + else => { + @panic("Error not handled"); + }, + } + }; + + // Read existing file + const end = try file.getEndPos(); + const result = try file.readToEndAlloc(allocator, end); + + try std.fs.File.stdout().writeAll(result); +} diff --git a/modules/aoc/src/input.zig b/modules/aoc/src/input.zig new file mode 100644 index 0000000..fa38c3d --- /dev/null +++ b/modules/aoc/src/input.zig @@ -0,0 +1,128 @@ +const std = @import("std"); +const types = @import("types.zig"); + +const fs = std.fs; +const http = std.http; + +fn getAdventOfCodeCookieFromEnv(allocator: std.mem.Allocator) !?[]const u8 { + const env_map = try allocator.create(std.process.EnvMap); + env_map.* = try std.process.getEnvMap(allocator); + return env_map.get("AOC_COOKIE"); +} + +pub fn getPuzzleInputFromServer( + allocator: std.mem.Allocator, + year: types.Year, + day: types.Day, + puzzle_file_path: ?[]const u8, +) ![]const u8 { + std.debug.print("🎄 Fetching puzzle input ...\n", .{}); + + // Get AOC_COOKIE from environment + const cookie = try getAdventOfCodeCookieFromEnv(allocator); + if (cookie == null) { + const msg = "⚠ Please set AOC_COOKIE env variable1"; + std.log.err(msg, .{}); + return msg; + } + std.debug.print("🍊 AOC_COOKIE:\n{s}\n", .{cookie.?}); + + // Build URL string + var buf: [128]u8 = undefined; + const url = try std.fmt.bufPrint(&buf, "https://adventofcode.com/{d}/day/{d}/input", .{ + year, + day, + }); + + // Init HTT client + var client = std.http.Client{ + .allocator = allocator, + }; + defer client.deinit(); + + // Body writer + var response: std.Io.Writer.Allocating = .init(allocator); + defer response.deinit(); + + // Perform HTTP call + _ = try client.fetch(.{ + .location = .{ .url = url }, + .extra_headers = &.{.{ + .name = "cookie", + .value = cookie.?, + }}, + .method = .GET, + .response_writer = &response.writer, + }); + + const puzzle_input = try response.toOwnedSlice(); + + // Write to file + if (puzzle_file_path) |filepath| { + var puzzle_file = try std.fs.cwd().createFile(filepath, .{}); + defer puzzle_file.close(); + try puzzle_file.writeAll(puzzle_input); + } + + return puzzle_input; +} + +pub fn getPuzzleInput( + allocator: std.mem.Allocator, + cwd: []const u8, + day: types.Day, +) ![]const u8 { + var day_buf: [16]u8 = undefined; + + const file_path = try std.fs.path.join(allocator, &.{ + cwd, + "src", + "input", + "puzzle", + try std.fmt.bufPrint(&day_buf, "day{d:0>2}.txt", .{day}), + }); + std.debug.print("{s}\n", .{file_path}); + + const file = try fs.cwd().openFile(file_path, .{}); + // catch { + // // try getPuzzleInputFromServer(allocator, year, day, file_path); + // // const f = try fs.cwd().openFile(file_path, .{}); + // // const s = try f.stat(); + // // var buffer: [1]u8 = undefined; + // // var reader = f.reader(&buffer); + // // return reader.interface.readAlloc(allocator, s.size); + // }; + const stat = try file.stat(); + std.debug.print("File size: {d}", .{stat.size}); + if (stat.size == 0) { + // try getPuzzleInputFromServer(allocator, year, day, file_path); + } + + var buffer: [1]u8 = undefined; + var reader = file.reader(&buffer); + return reader.interface.readAlloc(allocator, stat.size); +} + +pub fn getExampleInput( + allocator: std.mem.Allocator, + cwd: []const u8, + day: types.Day, +) ![]const u8 { + var day_buf: [16]u8 = undefined; + const file_path = try std.fs.path.join(allocator, &.{ + cwd, + "src", + "input", + "example", + // try std.fmt.bufPrint(&year_buf, "{d}", .{year}), + // "examples", + try std.fmt.bufPrint(&day_buf, "day{d:0>2}.txt", .{day}), + }); + errdefer allocator.free(file_path); + const file = try fs.cwd().openFile(file_path, .{}); + defer file.close(); + const stat = try file.stat(); + var buffer: [1]u8 = undefined; + var reader = file.reader(&buffer); + return reader.interface.readAlloc(allocator, stat.size); +} diff --git a/modules/aoc/src/root.zig b/modules/aoc/src/root.zig new file mode 100644 index 0000000..eb5a651 --- /dev/null +++ b/modules/aoc/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const common = @import("common.zig"); + +pub const types = @import("types.zig"); +pub const utils = @import("utils.zig"); + +pub const runPart = common.runPart; +pub const runDay = common.runDay; + +pub const blockAskForNext = utils.blockAskForNext; diff --git a/aoc/stopwatch.zig b/modules/aoc/src/stopwatch.zig similarity index 100% rename from aoc/stopwatch.zig rename to modules/aoc/src/stopwatch.zig diff --git a/modules/aoc/src/template.zig b/modules/aoc/src/template.zig new file mode 100644 index 0000000..6840ee7 --- /dev/null +++ b/modules/aoc/src/template.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const DAY: u5 = "$DAY"; + +const Allocator = std.mem.Allocator; +const log = std.log; + +fn part1(allocator: Allocator) anyerror!void { + _ = allocator; + const input = @embedFile("example-$DAY"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); +} + +fn part2(allocator: Allocator) anyerror!void { + _ = allocator; + const input = @embedFile("example-$DAY"); + std.debug.print("--- INPUT---\n{s}\n------------\n", .{input}); +} + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + try aoc.runPart(allocator, part1); + try aoc.runPart(allocator, part2); +} diff --git a/modules/aoc/src/types.zig b/modules/aoc/src/types.zig new file mode 100644 index 0000000..b36a889 --- /dev/null +++ b/modules/aoc/src/types.zig @@ -0,0 +1,6 @@ +pub const Year = u12; +pub const Day = u5; +pub const PuzzleInput = enum(u1) { + EXAMPLE = 0, + PUZZLE = 1, +}; diff --git a/modules/aoc/src/utils.zig b/modules/aoc/src/utils.zig new file mode 100644 index 0000000..936db5b --- /dev/null +++ b/modules/aoc/src/utils.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const types = @import("types.zig"); + +pub fn blockAskForNext() void { + step: { + var reader_buffer: [10]u8 = undefined; + var reader = std.fs.File.stdin().readerStreaming(&reader_buffer); + std.debug.print("\n\n→ Step: [Enter]", .{}); + var writter_buffer: [10]u8 = undefined; + var writer = std.fs.File.stdout().writerStreaming(&writter_buffer); + _ = reader.interface.streamDelimiter(&writer.interface, '\n') catch return; + _ = reader.interface.takeByte() catch return; + break :step; + } +} + +pub fn getTemplate() []const u8 { + return @embedFile("template.zig"); +} diff --git a/modules/libs/build.zig b/modules/libs/build.zig new file mode 100644 index 0000000..132030e --- /dev/null +++ b/modules/libs/build.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("libs", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); +} diff --git a/modules/libs/build.zig.zon b/modules/libs/build.zig.zon new file mode 100644 index 0000000..75f0117 --- /dev/null +++ b/modules/libs/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .aoc_libs, + .fingerprint = 0x988ff719da89c27e, + .version = "0.0.2", + .minimum_zig_version = "0.15.2", + .dependencies = .{}, + .paths = .{ + "README.md", + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/modules/libs/src/datastructures/Map.zig b/modules/libs/src/datastructures/Map.zig new file mode 100644 index 0000000..a3ab1e4 --- /dev/null +++ b/modules/libs/src/datastructures/Map.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const t = @import("../term.zig"); + +const Allocator = std.mem.Allocator; + +pub const Map = struct { + rows: usize, + cols: usize, + buffer: []u8, + allocator: Allocator = undefined, + + pub fn init(allocator: Allocator, buffer: []const u8) !Map { + if (std.mem.indexOf(u8, buffer, "\n")) |line_end| { + const clean = std.mem.trimEnd(u8, buffer, "\n"); + return .{ + .allocator = allocator, + .buffer = try std.mem.replaceOwned(u8, allocator, clean, "\n", ""), + .cols = line_end, + .rows = (clean.len - 1) / line_end, + }; + } + @panic("Failed to split input buffer!"); + } + + pub fn initEmpty(allocator: Allocator, cols: usize, rows: usize, fill: u8) !Map { + const buffer = try allocator.alloc(u8, rows * cols); + for (0..(rows * cols)) |i| buffer[i] = fill; + return .{ + .allocator = allocator, + .buffer = buffer, + .cols = cols, + .rows = rows, + }; + } + + pub fn get(self: *const Map, x: usize, y: usize) u8 { + return self.buffer[(y * self.cols) + x]; + } + + pub fn set(self: *const Map, x: usize, y: usize, v: u8) void { + self.buffer[(y * self.cols) + x] = v; + } + + pub fn format(self: Map, writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("Map {d} x {d}\n", .{ self.rows, self.cols }); + for (0..self.rows) |y| { + for (0..self.cols) |x| { + try writer.print("{c}", .{self.get(x, y)}); + } + try writer.print("\n", .{}); + } + } + + pub fn deinit(self: Map) void { + self.allocator.free(self.buffer); + } + + pub fn animate(self: Map) void { + std.debug.print(t.hide_cursor, .{}); + for (0..self.cols) |x| { + for (0..self.rows - 1) |y| { + std.debug.print(t.yx, .{ y, x }); + const v = self.get(x, y); + switch (v) { + '|' => std.debug.print("{s}{c}{s}", .{ t.yellow, v, t.clear }), + '^' => std.debug.print("{s}{c}{s}", .{ t.red, v, t.clear }), + '.' => std.debug.print("{s}{c}{s}", .{ t.dark_gray, v, t.clear }), + else => std.debug.print("{s}{c}{s}", .{ t.blue, v, t.clear }), + } + } + } + // std.Thread.sleep(std.time.ns_per_ms * 60); + } +}; diff --git a/libs/datastructures/VectorSet.zig b/modules/libs/src/datastructures/VectorSet.zig similarity index 100% rename from libs/datastructures/VectorSet.zig rename to modules/libs/src/datastructures/VectorSet.zig diff --git a/libs/math.zig b/modules/libs/src/math.zig similarity index 100% rename from libs/math.zig rename to modules/libs/src/math.zig diff --git a/libs/ppm.zig b/modules/libs/src/ppm.zig similarity index 100% rename from libs/ppm.zig rename to modules/libs/src/ppm.zig diff --git a/modules/libs/src/root.zig b/modules/libs/src/root.zig new file mode 100644 index 0000000..a8bc577 --- /dev/null +++ b/modules/libs/src/root.zig @@ -0,0 +1,6 @@ +pub const VectorSet = @import("datastructures/VectorSet.zig").VectorSet; +pub const Map = @import("datastructures/Map.zig").Map; +pub const math = @import("math.zig"); +pub const ppm = @import("ppm.zig"); +pub const svg = @import("svg.zig"); +pub const term = @import("term.zig"); diff --git a/libs/svg.zig b/modules/libs/src/svg.zig similarity index 57% rename from libs/svg.zig rename to modules/libs/src/svg.zig index 7b323a3..4233dec 100644 --- a/libs/svg.zig +++ b/modules/libs/src/svg.zig @@ -5,19 +5,17 @@ var file: fs.File = undefined; var buf: [128]u8 = undefined; pub fn init(file_path: []const u8, width: i128, height: i128, min_x: i128, max_x: i128, min_y: i128, max_y: i128) !void { - _ = max_y; - _ = min_y; - _ = max_x; - _ = min_x; file = try fs.cwd().createFile(file_path, .{}); - const margin_x: i128 = @intFromFloat(@as(f32, @floatFromInt(width)) * 0.1); - _ = margin_x; - const margin_y: i128 = @intFromFloat(@as(f32, @floatFromInt(height)) * 0.1); - _ = margin_y; - // const svg_el = try std.fmt.bufPrint(&buf, "", - // .{ -margin_x, -margin_y, width + 2 * margin_x, height + 2 * margin_y, width, height }); - const svg_el = try std.fmt.bufPrint(&buf, "", - .{ width, height }); + const svg_el = try std.fmt.bufPrint(&buf, "", .{ + min_x, + min_y, + max_x, + max_y, + width, + height, + }); + // const svg_el = try std.fmt.bufPrint(&buf, "", + // .{ width, height }); _ = try file.write(svg_el); } @@ -40,8 +38,9 @@ pub fn addPolygonPoint(x: i128, y: i128) !void { _ = try file.write(point); } -pub fn endPolygon() !void { - _ = try file.write("\" fill=\"black\" />"); +pub fn endPolygon(fill: []const u8) !void { + const end = try std.fmt.bufPrint(&buf, "\" fill=\"{s}\" />", .{fill}); + _ = try file.write(end); } pub fn close() !void { diff --git a/libs/term.zig b/modules/libs/src/term.zig similarity index 100% rename from libs/term.zig rename to modules/libs/src/term.zig