diff --git a/day17/build.zig b/day17/build.zig new file mode 100644 index 0000000..54a298a --- /dev/null +++ b/day17/build.zig @@ -0,0 +1,27 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("day17", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/day17/input b/day17/input new file mode 100644 index 0000000..d0697d6 --- /dev/null +++ b/day17/input @@ -0,0 +1,8 @@ +####...# +......#. +#..#.##. +.#...#.# +..###.#. +##.###.. +.#...### +.##....# diff --git a/day17/src/main.zig b/day17/src/main.zig new file mode 100644 index 0000000..9fa1622 --- /dev/null +++ b/day17/src/main.zig @@ -0,0 +1,633 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + var gpa = &arena.allocator; + + var f = try std.fs.cwd().openFile("input", .{}); + var contents = try f.readToEndAlloc(gpa, std.math.maxInt(u64)); + defer gpa.free(contents); + + var sim = try Simulation.init(gpa); + defer sim.deinit(); + + var sim4d = try Simulation4D.init(gpa); + defer sim4d.deinit(); + + var it = std.mem.tokenize(contents, "\n"); + var y : i64 = 0; + while (it.next()) |line| { + for (line) |c, k| { + var state : CubeState = .inactive; + if (c == '#') { + state = .active; + } + try sim.add_cube([_]i64 {@intCast(i64, k), y, 0}, state); + try sim4d.add_cube([_]i64 {@intCast(i64, k), y, 0, 0}, state); + } + y += 1; + } + + // Run 6 rounds + try sim.do_round(); // 1 + try sim.do_round(); // 2 + try sim.do_round(); // 3 + try sim.do_round(); // 4 + try sim.do_round(); // 5 + try sim.do_round(); // 6 + + std.log.info("After 6 rounds, there are {} active cubes", + .{sim.count_active_cubes()}); + + // Part 2 + try sim4d.do_round(); // 1 + try sim4d.do_round(); // 2 + try sim4d.do_round(); // 3 + try sim4d.do_round(); // 4 + try sim4d.do_round(); // 5 + try sim4d.do_round(); // 6 + + std.log.info("After 6 rounds, there are {} active cubes", + .{sim4d.count_active_cubes()}); +} + +const CubeState = enum { + inactive, + active +}; + +const Cube = struct { + pos: [3]i64, + state: CubeState = .inactive, + next_state: ?CubeState = null, + neighbours: u64 = 0, + + pub fn get_neighbour_positions(self: *Cube) [26][3]i64 { + return [_][3]i64 { + // z-1 + [_]i64 {self.pos[0], self.pos[1], self.pos[2]-1}, + [_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]-1}, + [_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]-1}, + [_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]-1}, + [_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]-1}, + [_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]-1}, + [_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]-1}, + [_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]-1}, + [_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]-1}, + // z+0 + [_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]}, + [_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]}, + [_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]}, + [_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]}, + [_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]}, + [_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]}, + [_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]}, + [_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]}, + // z+1 + [_]i64 {self.pos[0], self.pos[1], self.pos[2]+1}, + [_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]+1}, + [_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]+1}, + [_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]+1}, + [_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]+1}, + [_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]+1}, + [_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]+1}, + [_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]+1}, + [_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]+1}, + }; + } + + pub fn print_state(self: *Cube) !void { + var stdout = std.io.getStdOut().writer(); + var c : u8 = '.'; + if (self.state == .active) { + c = '#'; + } + try stdout.print("({}x, {}y, {}z) {c}\n", .{self.pos[0], self.pos[1], self.pos[2], c}); + } +}; + +const Simulation = struct { + map: std.hash_map.AutoHashMap([3]i64, Cube), + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) !*Simulation { + var self = try allocator.create(Simulation); + errdefer allocator.destroy(self); + + self.* = Simulation { + .map = std.hash_map.AutoHashMap([3]i64, Cube).init(allocator), + .allocator = allocator, + }; + return self; + } + + pub fn deinit(self: *Simulation) void { + self.map.deinit(); + self.allocator.destroy(self); + } + + pub fn add_cube(self: *Simulation, pos: [3]i64, state: CubeState) !void { + try self.map.put(pos, Cube {.pos = pos, .state = state}); + } + + pub fn print_state(self: *Simulation) !void { + // @TODO Organize the output somehow + var stdout = std.io.getStdOut().writer(); + var z_min : i64 = std.math.maxInt(i64); + var z_max : i64 = std.math.minInt(i64); + var y_min : i64 = std.math.maxInt(i64); + var y_max : i64 = std.math.minInt(i64); + var x_min : i64 = std.math.maxInt(i64); + var x_max : i64 = std.math.minInt(i64); + + var it = self.map.iterator(); + while (it.next()) |entry| { + //try entry.value.print_state(); + x_max = std.math.max(x_max, entry.value.pos[0]); + x_min = std.math.min(x_min, entry.value.pos[0]); + y_max = std.math.max(y_max, entry.value.pos[1]); + y_min = std.math.min(y_min, entry.value.pos[1]); + z_max = std.math.max(z_max, entry.value.pos[2]); + z_min = std.math.min(z_min, entry.value.pos[2]); + } + + // For from lowest to highest then + var z_pos = z_min; + while (z_pos <= z_max) : (z_pos += 1) { + try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max}); + var y_pos = y_min; + while (y_pos <= y_max) : (y_pos += 1) { + var x_pos = x_min; + while (x_pos <= x_max) : (x_pos += 1) { + //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos}); + var c : u8 = '.'; + if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| { + if (cube.state == .active) { + c = '#'; + } + } + try stdout.print("{c}", .{c}); + } + _ = try stdout.write("\n"); + } + _ = try stdout.write("\n"); + } + _ = try stdout.write("\n"); + + } + + pub fn print_state_neighbour_count (self: *Simulation) !void { + // @TODO Organize the output somehow + var stdout = std.io.getStdOut().writer(); + var z_min : i64 = std.math.maxInt(i64); + var z_max : i64 = std.math.minInt(i64); + var y_min : i64 = std.math.maxInt(i64); + var y_max : i64 = std.math.minInt(i64); + var x_min : i64 = std.math.maxInt(i64); + var x_max : i64 = std.math.minInt(i64); + + var it = self.map.iterator(); + while (it.next()) |entry| { + //try entry.value.print_state(); + x_max = std.math.max(x_max, entry.value.pos[0]); + x_min = std.math.min(x_min, entry.value.pos[0]); + y_max = std.math.max(y_max, entry.value.pos[1]); + y_min = std.math.min(y_min, entry.value.pos[1]); + z_max = std.math.max(z_max, entry.value.pos[2]); + z_min = std.math.min(z_min, entry.value.pos[2]); + } + + // For from lowest to highest then + var z_pos = z_min; + while (z_pos <= z_max) : (z_pos += 1) { + try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max}); + var y_pos = y_min; + while (y_pos <= y_max) : (y_pos += 1) { + var x_pos = x_min; + while (x_pos <= x_max) : (x_pos += 1) { + //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos}); + var c : u64 = 0; + if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| { + c = cube.neighbours; + } + try stdout.print("{:02}", .{c}); + } + _ = try stdout.write("\n"); + } + _ = try stdout.write("\n"); + } + _ = try stdout.write("\n"); + + } + + pub fn do_round(self: *Simulation) !void { + // For each cube, check all of it's neighbours + var it = self.map.iterator(); + var neighbours_to_add = std.ArrayList([3]i64).init(self.allocator); + defer neighbours_to_add.deinit(); + + // Get the neighbours we need possibly create for this round + while (it.next()) |entry| { + var neighbours = entry.value.get_neighbour_positions(); + var n_active : u64 = 0; + for (neighbours) |n_pos| { + if (self.map.get(n_pos)) |n| { + // Noop, we already have a cube + } + else { + // The neighbour doesn't yet exist, we should create it + // but we don't want to do that while iterating over + // the current entries. + // We also want to make sure that we're not adding a + // value that's already in the list to be added. + // For the moment, we'll check that when iterating + // over neighbours_to_add. + try neighbours_to_add.append(n_pos); + } + } + } + // Add any new neighbours + for (neighbours_to_add.items) |new_pos| { + // Double-check we don't already have this neighbour + if (self.map.get(new_pos)) |v| { + continue; + } + else { + try self.map.putNoClobber(new_pos, Cube {.pos = new_pos, .state = .inactive}); + } + } + + self.calculate_neighbour_states(); + try self.print_state_neighbour_count(); + + // Now next state should be set, we go through again to swap + // @TODO Update state + it = self.map.iterator(); + while (it.next()) |entry| { + if (entry.value.state == .active) { + if (entry.value.neighbours >= 2 and entry.value.neighbours <= 3) { + entry.value.state = .active; + } + else { + entry.value.state = .inactive; + } + } + else { + if (entry.value.neighbours == 3) { + entry.value.state = .active; + } + } + entry.value.next_state = null; + } + } + +pub fn calculate_neighbour_states(self: *Simulation) void { + var it = self.map.iterator(); + while (it.next()) |entry| { + var neighbours = entry.value.get_neighbour_positions(); + var n_active : u64 = 0; + std.log.warn("Checking neighbours for ({}, {}, {})", + .{entry.value.pos[0], entry.value.pos[1], entry.value.pos[2]}); + for (neighbours) |n_pos| { + if (self.map.get(n_pos)) |n| { + if (n.state == .active) { + std.log.warn("Neighbour at ({}, {}, {}) is active", + .{n.pos[0], n.pos[1], n.pos[2]}); + n_active += 1; + } + } + else { + // Noop, another function creates our neighbours, and it should + // be run before this one. + } + } + entry.value.neighbours = n_active; + } +} + + pub fn count_active_cubes(self: *Simulation) u64 { + var it = self.map.iterator(); + var count : u64 = 0; + while (it.next()) |entry| { + if (entry.value.state == .active) { + count += 1; + } + } + return count; + } +}; + +// Part 2, is just more tedious +const Cube4D = struct { + pos: [4]i64, + state: CubeState = .inactive, + neighbours: u64 = 0, + + pub fn get_neighbour_positions(self: *Cube4D, a: *std.mem.Allocator) [80][4]i64 { + var neighbours : [80][4]i64 = undefined; + var x = self.pos[0]-1; + var idx : usize = 0; + while (x <= self.pos[0]+1) : (x += 1) { + var y = self.pos[1]-1; + while (y <= self.pos[1]+1) : (y +=1) { + var z = self.pos[2]-1; + while (z <= self.pos[2]+1) : (z +=1) { + var w = self.pos[3]-1; + while (w <= self.pos[3]+1) : (w+=1) { + if (x == self.pos[0] and y == self.pos[1] and z == self.pos[2] + and w == self.pos[3]) { + continue; + } + neighbours[idx] = [_]i64{x, y, z, w}; + idx += 1; + } + } + } + } + return neighbours; + } +}; + + +const Simulation4D = struct { + map: std.hash_map.AutoHashMap([4]i64, Cube4D), + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) !*Simulation4D { + var self = try allocator.create(Simulation4D); + errdefer allocator.destroy(self); + + self.* = Simulation4D { + .map = std.hash_map.AutoHashMap([4]i64, Cube4D).init(allocator), + .allocator = allocator, + }; + return self; + } + + pub fn deinit(self: *Simulation4D) void { + self.map.deinit(); + self.allocator.destroy(self); + } + + pub fn add_cube(self: *Simulation4D, pos: [4]i64, state: CubeState) !void { + try self.map.put(pos, Cube4D {.pos = pos, .state = state}); + } + + // pub fn print_state(self: *Simulation) !void { + // // @TODO Organize the output somehow + // var stdout = std.io.getStdOut().writer(); + // var z_min : i64 = std.math.maxInt(i64); + // var z_max : i64 = std.math.minInt(i64); + // var y_min : i64 = std.math.maxInt(i64); + // var y_max : i64 = std.math.minInt(i64); + // var x_min : i64 = std.math.maxInt(i64); + // var x_max : i64 = std.math.minInt(i64); + + // var it = self.map.iterator(); + // while (it.next()) |entry| { + // //try entry.value.print_state(); + // x_max = std.math.max(x_max, entry.value.pos[0]); + // x_min = std.math.min(x_min, entry.value.pos[0]); + // y_max = std.math.max(y_max, entry.value.pos[1]); + // y_min = std.math.min(y_min, entry.value.pos[1]); + // z_max = std.math.max(z_max, entry.value.pos[2]); + // z_min = std.math.min(z_min, entry.value.pos[2]); + // } + + // // For from lowest to highest then + // var z_pos = z_min; + // while (z_pos <= z_max) : (z_pos += 1) { + // try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max}); + // var y_pos = y_min; + // while (y_pos <= y_max) : (y_pos += 1) { + // var x_pos = x_min; + // while (x_pos <= x_max) : (x_pos += 1) { + // //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos}); + // var c : u8 = '.'; + // if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| { + // if (cube.state == .active) { + // c = '#'; + // } + // } + // try stdout.print("{c}", .{c}); + // } + // _ = try stdout.write("\n"); + // } + // _ = try stdout.write("\n"); + // } + // _ = try stdout.write("\n"); + + // } + + // pub fn print_state_neighbour_count (self: *Simulation) !void { + // // @TODO Organize the output somehow + // var stdout = std.io.getStdOut().writer(); + // var z_min : i64 = std.math.maxInt(i64); + // var z_max : i64 = std.math.minInt(i64); + // var y_min : i64 = std.math.maxInt(i64); + // var y_max : i64 = std.math.minInt(i64); + // var x_min : i64 = std.math.maxInt(i64); + // var x_max : i64 = std.math.minInt(i64); + + // var it = self.map.iterator(); + // while (it.next()) |entry| { + // //try entry.value.print_state(); + // x_max = std.math.max(x_max, entry.value.pos[0]); + // x_min = std.math.min(x_min, entry.value.pos[0]); + // y_max = std.math.max(y_max, entry.value.pos[1]); + // y_min = std.math.min(y_min, entry.value.pos[1]); + // z_max = std.math.max(z_max, entry.value.pos[2]); + // z_min = std.math.min(z_min, entry.value.pos[2]); + // } + + // // For from lowest to highest then + // var z_pos = z_min; + // while (z_pos <= z_max) : (z_pos += 1) { + // try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max}); + // var y_pos = y_min; + // while (y_pos <= y_max) : (y_pos += 1) { + // var x_pos = x_min; + // while (x_pos <= x_max) : (x_pos += 1) { + // //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos}); + // var c : u64 = 0; + // if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| { + // c = cube.neighbours; + // } + // try stdout.print("{:02}", .{c}); + // } + // _ = try stdout.write("\n"); + // } + // _ = try stdout.write("\n"); + // } + // _ = try stdout.write("\n"); + + // } + + pub fn do_round(self: *Simulation4D) !void { + // For each cube, check all of it's neighbours + var it = self.map.iterator(); + var neighbours_to_add = std.ArrayList([4]i64).init(self.allocator); + defer neighbours_to_add.deinit(); + + // Get the neighbours we need possibly create for this round + while (it.next()) |entry| { + var neighbours = entry.value.get_neighbour_positions(self.allocator); + //defer self.allocator.free(neighbours); + var n_active : u64 = 0; + for (neighbours) |n_pos| { + if (self.map.get(n_pos)) |n| { + // Noop, we already have a cube + } + else { + // The neighbour doesn't yet exist, we should create it + // but we don't want to do that while iterating over + // the current entries. + // We also want to make sure that we're not adding a + // value that's already in the list to be added. + // For the moment, we'll check that when iterating + // over neighbours_to_add. + try neighbours_to_add.append(n_pos); + } + } + } + // Add any new neighbours + for (neighbours_to_add.items) |new_pos| { + // Double-check we don't already have this neighbour + if (self.map.get(new_pos)) |v| { + continue; + } + else { + try self.map.putNoClobber(new_pos, Cube4D {.pos = new_pos, .state = .inactive}); + } + } + + self.calculate_neighbour_states(); + + // Now next state should be set, we go through again to swap + // @TODO Update state + it = self.map.iterator(); + while (it.next()) |entry| { + if (entry.value.state == .active) { + if (entry.value.neighbours >= 2 and entry.value.neighbours <= 3) { + entry.value.state = .active; + } + else { + entry.value.state = .inactive; + } + } + else { + if (entry.value.neighbours == 3) { + entry.value.state = .active; + } + } + } + } + + pub fn calculate_neighbour_states(self: *Simulation4D) void { + var it = self.map.iterator(); + while (it.next()) |entry| { + var neighbours = entry.value.get_neighbour_positions(self.allocator); + //defer self.allocator.free(neighbours); + var n_active : u64 = 0; + //std.log.warn("Checking neighbours for ({}, {}, {})", + // .{entry.value.pos[0], entry.value.pos[1], entry.value.pos[2]}); + for (neighbours) |n_pos| { + if (self.map.get(n_pos)) |n| { + if (n.state == .active) { + //std.log.warn("Neighbour at ({}, {}, {}) is active", + // .{n.pos[0], n.pos[1], n.pos[2]}); + n_active += 1; + } + } + else { + // Noop, another function creates our neighbours, and it should + // be run before this one. + } + } + entry.value.neighbours = n_active; + } + } + + pub fn count_active_cubes(self: *Simulation4D) u64 { + var it = self.map.iterator(); + var count : u64 = 0; + while (it.next()) |entry| { + if (entry.value.state == .active) { + count += 1; + } + } + return count; + } +}; + +test "get_neighbour_positions" { + var cube = Cube{ + .pos = [_]i64{0, 0, 0}, + }; + var neighbours = cube.get_neighbour_positions(); + for (neighbours) |n| { + std.log.warn("({}, {}, {})", .{n[0], n[1], n[2]}); + } +} + +test "small_cubeway" { + var sim = try Simulation.init(std.testing.allocator); + defer sim.deinit(); + try sim.add_cube([_]i64 {0, 0, 0}, .inactive); + try sim.add_cube([_]i64 {1, 0, 0}, .active); + try sim.add_cube([_]i64 {2, 0, 0}, .inactive); + + try sim.add_cube([_]i64 {0, 1, 0}, .inactive); + try sim.add_cube([_]i64 {1, 1, 0}, .inactive); + try sim.add_cube([_]i64 {2, 1, 0}, .active); + + try sim.add_cube([_]i64 {0, 2, 0}, .active); + try sim.add_cube([_]i64 {1, 2, 0}, .active); + try sim.add_cube([_]i64 {2, 2, 0}, .active); + + try sim.print_state(); + sim.calculate_neighbour_states(); + try sim.print_state_neighbour_count(); + std.testing.expectEqual(@as(u64, 5), sim.count_active_cubes()); + + std.log.warn("\n\n --- First round --- \n", .{}); + try sim.do_round(); // 1 + try sim.print_state(); + std.testing.expectEqual(@as(u64, 11), sim.count_active_cubes()); + + try sim.do_round(); // 2 + try sim.do_round(); // 3 + try sim.do_round(); // 4 + try sim.do_round(); // 5 + try sim.do_round(); // 6 + + var active = sim.count_active_cubes(); + std.testing.expectEqual(@as(u64, 112), active); +} + +test "4d_cubeway" { + var sim = try Simulation4D.init(std.testing.allocator); + defer sim.deinit(); + try sim.add_cube([_]i64 {0, 0, 0, 0}, .inactive); + try sim.add_cube([_]i64 {1, 0, 0, 0}, .active); + try sim.add_cube([_]i64 {2, 0, 0, 0}, .inactive); + + try sim.add_cube([_]i64 {0, 1, 0, 0}, .inactive); + try sim.add_cube([_]i64 {1, 1, 0, 0}, .inactive); + try sim.add_cube([_]i64 {2, 1, 0, 0}, .active); + + try sim.add_cube([_]i64 {0, 2, 0, 0}, .active); + try sim.add_cube([_]i64 {1, 2, 0, 0}, .active); + try sim.add_cube([_]i64 {2, 2, 0, 0}, .active); + + try sim.do_round(); // 1 + try sim.do_round(); // 2 + try sim.do_round(); // 3 + try sim.do_round(); // 4 + try sim.do_round(); // 5 + try sim.do_round(); // 6 + + var active = sim.count_active_cubes(); + std.testing.expectEqual(@as(u64, 848), active); +}