const std = @import("std"); const PATH = "input/day24.txt"; const Str = []const u8; const directions = [_]Coord{ // east, west .{ 1, 0 }, .{ -1, 0 }, // north east, north west .{ 0, -1 }, .{ 1, -1 }, // south east, south west .{ 0, 1 }, .{ -1, 1 }, }; pub fn first(allocator: ?std.mem.Allocator) anyerror!usize { var tf = TileFloor{ .blacks = std.AutoArrayHashMap(Coord, void).init(allocator.?), }; defer tf.blacks.deinit(); var lines = std.mem.tokenize(u8, @embedFile(PATH), "\n"); while (lines.next()) |line| { const target = parseInstruction(line); tf.blackFlip(target); } return tf.blacks.count(); } pub fn second(allocator: ?std.mem.Allocator) anyerror!usize { var tf = TileFloor{ .blacks = std.AutoArrayHashMap(Coord, void).init(allocator.?), .neighbor = std.AutoArrayHashMap(Coord, usize).init(allocator.?), }; defer { tf.blacks.deinit(); tf.neighbor.deinit(); } // Collect initial blacks var lines = std.mem.tokenize(u8, @embedFile(PATH), "\n"); while (lines.next()) |line| { const target = parseInstruction(line); tf.blackFlip(target); } var round: usize = 0; while (round < 100) : (round += 1) { for (tf.blacks.keys()) |black| { for (directions) |dir| { const entry = try tf.neighbor.getOrPut(.{ black[0] + dir[0], black[1] + dir[1] }); if (entry.found_existing) entry.value_ptr.* += 1 else entry.value_ptr.* = 1; } } var next = std.AutoArrayHashMap(Coord, void).init(allocator.?); while (tf.neighbor.popOrNull()) |nbor| { const is_black = tf.blacks.contains(nbor.key); if ((is_black and nbor.value <= 2) or (!is_black and nbor.value == 2)) { try next.put(nbor.key, {}); } } tf.blacks.deinit(); tf.blacks = next; } // std.debug.print("{any}\n", .{tf.blacks.keys()}); return tf.blacks.count(); } const HexType = i8; // const Coord = @Vector(2, HexType); const Coord = [2]HexType; const TileFloor = struct { blacks: std.AutoArrayHashMap(Coord, void), neighbor: std.AutoArrayHashMap(Coord, usize) = undefined, inline fn blackFlip(self: *@This(), coord: Coord) void { if (!self.blacks.swapRemove(coord)) self.blacks.put(coord, {}) catch unreachable; } fn blackNeighbor(self: @This(), current: Coord) u3 { var count: u3 = 0; for (directions) |dir| { if (self.blacks.contains(current + dir)) count += 1; } return count; } }; fn parseInstruction(instruction: Str) Coord { var ret: Coord = .{ 0, 0 }; var south = false; var north = false; var step: Coord = undefined; for (instruction) |ch| { switch (ch) { 's' => south = true, 'n' => north = true, 'e', 'w' => { if (south) { step[1] = 1; } else if (north) { step[1] = -1; } else { step[1] = 0; } switch (ch) { 'e' => { if (south) step[0] = 0 else step[0] = 1; }, 'w' => { if (north) step[0] = 0 else step[0] = -1; }, else => unreachable, } ret = .{ ret[0] + step[0], ret[1] + step[1] }; south = false; north = false; }, else => unreachable, } } return ret; } test "day24a" { try std.testing.expectEqual(@as(usize, 332), try first(std.testing.allocator)); } test "day24b" { try std.testing.expectEqual(@as(usize, 3900), try second(std.testing.allocator)); } test "esew" { try std.testing.expectEqual(Coord{ 0, 1 }, parseInstruction("esew")); } test "nwwswee" { try std.testing.expectEqual(Coord{ 0, 0 }, parseInstruction("nwwswee")); } const test_input = \\sesenwnenenewseeswwswswwnenewsewsw \\neeenesenwnwwswnenewnwwsewnenwseswesw \\seswneswswsenwwnwse \\nwnwneseeswswnenewneswwnewseswneseene \\swweswneswnenwsewnwneneseenw \\eesenwseswswnenwswnwnwsewwnwsene \\sewnenenenesenwsewnenwwwse \\wenwwweseeeweswwwnwwe \\wsweesenenewnwwnwsenewsenwwsesesenwne \\neeswseenwwswnwswswnw \\nenwswwsewswnenenewsenwsenwnesesenew \\enewnwewneswsewnwswenweswnenwsenwsw \\sweneswneswneneenwnewenewwneswswnese \\swwesenesewenwneswnwwneseswwne \\enesenwswwswneneswsenwnewswseenwsese \\wnwnesenesenenwwnenwsewesewsesesew \\nenewswnwewswnenesenwnesewesw \\eneswnwswnwsenenwnwnwwseeswneewsenese \\neswnwewnwnwseenwseesewsenwsweewe \\wseweeenwnesenwwwswnew ;