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
;