const std = @import("std"); fn StackList(comptime T: type, comptime capacity_type: type, comptime capacity: capacity_type) type { return struct { const Self = @This(); mem: [capacity]T, length: capacity_type, fn add(self: *Self, value: T) void { self.mem[self.length] = value; self.length += 1; } fn has(self: *Self, needle: T) bool { for (0..self.length) |i| { if (self.mem[i] == needle) { return true; } } return false; } fn getMutableSlice(self: *Self) []T { return (&self.mem)[0..self.length]; } fn getSlice(self: *const Self) []const T { return self.mem[0..self.length]; } fn init() Self { return Self{ .mem = undefined, .length = 0, }; } }; } fn debugBoard(lines: []const []const u8) void { for (lines) |line| { std.debug.print("{s}\n", .{line}); } } fn getNextPoints(char: u8, point: [2]usize) [2][2]usize { return switch (char) { 'L' => .{ .{ point[0] - 1, point[1] }, .{ point[0], point[1] + 1 } }, 'F' => .{ .{ point[0] + 1, point[1] }, .{ point[0], point[1] + 1 } }, '7' => .{ .{ point[0] + 1, point[1] }, .{ point[0], point[1] - 1 } }, 'J' => .{ .{ point[0] - 1, point[1] }, .{ point[0], point[1] - 1 } }, '-' => .{ .{ point[0], point[1] - 1 }, .{ point[0], point[1] + 1 } }, '|' => .{ .{ point[0] - 1, point[1] }, .{ point[0] + 1, point[1] } }, else => unreachable, }; } fn getNextPoint(char: u8, point: [2]usize, previous_point: [2]usize) [2]usize { const next_points = getNextPoints(char, point); if (next_points[0][0] == previous_point[0] and next_points[0][1] == previous_point[1]) { return next_points[1]; } if (next_points[1][0] == previous_point[0] and next_points[1][1] == previous_point[1]) { return next_points[0]; } unreachable; } fn expandBoard(source: []const []const u8, target: [][]u8) [2][2]usize { var sources = StackList([2]usize, usize, 1).init(); for (source, 0..) |source_row, i| { for (source_row, 0..) |char, j| { switch (char) { '.' => {}, 'L' => { target[3 * i + 1][3 * j + 1] = 'L'; target[3 * i + 1][3 * j + 2] = '-'; target[3 * i + 0][3 * j + 1] = '|'; }, 'F' => { target[3 * i + 1][3 * j + 1] = 'F'; target[3 * i + 1][3 * j + 2] = '-'; target[3 * i + 2][3 * j + 1] = '|'; }, '7' => { target[3 * i + 1][3 * j + 1] = '7'; target[3 * i + 1][3 * j + 0] = '-'; target[3 * i + 2][3 * j + 1] = '|'; }, 'J' => { target[3 * i + 1][3 * j + 1] = 'J'; target[3 * i + 1][3 * j + 0] = '-'; target[3 * i + 0][3 * j + 1] = '|'; }, '|' => { target[3 * i + 0][3 * j + 1] = '|'; target[3 * i + 1][3 * j + 1] = '|'; target[3 * i + 2][3 * j + 1] = '|'; }, '-' => { target[3 * i + 1][3 * j + 0] = '-'; target[3 * i + 1][3 * j + 1] = '-'; target[3 * i + 1][3 * j + 2] = '-'; }, 'S' => { sources.add(.{ i, j }); }, else => unreachable, } } } const source_start_value = sources.getSlice()[0]; const source_i = source_start_value[0]; const source_j = source_start_value[1]; const target_i = 3 * source_i + 1; const target_j = 3 * source_j + 1; var adjacents = StackList([2]usize, usize, 2).init(); if (source_i > 0) { switch (source[source_i - 1][source_j]) { '7', '|', 'F' => { target[target_i - 1][target_j] = '|'; adjacents.add(.{ target_i - 1, target_j }); }, 'J', '-', 'L', '.' => {}, else => unreachable, } } if (source_i + 1 < source.len) { switch (source[source_i + 1][source_j]) { 'J', '|', 'L' => { target[target_i + 1][target_j] = '|'; adjacents.add(.{ target_i + 1, target_j }); }, '7', '-', 'F', '.' => {}, else => unreachable, } } if (source_j > 0) { switch (source[source_i][source_j - 1]) { 'L', '-', 'F' => { target[target_i][target_j - 1] = '-'; adjacents.add(.{ target_i, target_j - 1 }); }, 'J', '|', '7', '.' => {}, else => unreachable, } } if (source_j + 1 < source[source_i].len) { switch (source[source_i][source_j + 1]) { 'J', '-', '7' => { target[target_i][target_j + 1] = '-'; adjacents.add(.{ target_i, target_j + 1 }); }, 'L', '|', 'F', '.' => {}, else => unreachable, } } return .{ .{ target_i, target_j }, adjacents.getSlice()[1] }; } fn walkBoard(board: [][]u8, starting_info: [2][2]usize) void { const starting_point = starting_info[0]; board[starting_point[0]][starting_point[1]] = 'X'; var path_length: usize = 1; var previous_point = starting_info[0]; var current_point = starting_info[1]; while (current_point[0] != starting_point[0] or current_point[1] != starting_point[1]) { const next_point = getNextPoint(board[current_point[0]][current_point[1]], current_point, previous_point); previous_point = current_point; current_point = next_point; board[previous_point[0]][previous_point[1]] = 'X'; path_length += 1; } } fn cleanJunkPipe(board: [][]u8) void { for (board) |row| { for (row) |*cell| { switch (cell.*) { 'X', '.' => {}, '7', 'F', 'L', 'J', '|', '-' => { cell.* = '.'; }, else => unreachable, } } } } fn floodBoard(board: [][]u8) !void { var points = try std.ArrayList([2]usize).initCapacity(std.heap.page_allocator, 1048576); defer points.deinit(); try points.append(.{ 0, 0 }); var step: usize = 0; while (step < points.items.len) : (step += 1) { const point = points.items[step]; const i = point[0]; const j = point[1]; if (board[i][j] == 'O') { continue; } board[i][j] = 'O'; if (i > 0) { if (board[i - 1][j] == '.') { try points.append(.{ i - 1, j }); } } if (i + 1 < board.len) { if (board[i + 1][j] == '.') { try points.append(.{ i + 1, j }); } } if (j > 0) { if (board[i][j - 1] == '.') { try points.append(.{ i, j - 1 }); } } if (j + 1 < board[i].len) { if (board[i][j + 1] == '.') { try points.append(.{ i, j + 1 }); } } } } fn countEmptyCells(expanded_board: []const []const u8) usize { var result: usize = 0; var i: usize = 1; while (i < expanded_board.len) : (i += 3) { var j: usize = 1; while (j < expanded_board[i].len) : (j += 3) { if (expanded_board[i][j] == '.') { result += 1; } } } return result; } fn solveLines(source: [][]u8) !usize { //std.debug.print("original board\n", .{}); //debugBoard(source); var target_storage: [1048576]u8 = undefined; @memset(target_storage[0..], '.'); var target_raw: [1024][]u8 = undefined; for (0..source.len) |i| { for (0..3) |j| { const index = 3 * i + j; target_raw[index] = target_storage[1024 * index .. 1024 * index + 3 * source[i].len]; } } var target = target_raw[0..(3 * source.len)]; const starting_point_info = expandBoard(source, target); //std.debug.print("new board\n", .{}); //debugBoard(target); walkBoard(target, starting_point_info); //std.debug.print("after walking\n", .{}); //debugBoard(target); cleanJunkPipe(target); //std.debug.print("after cleaning\n", .{}); //debugBoard(target); try floodBoard(target); //std.debug.print("after flooding\n", .{}); //debugBoard(target); return countEmptyCells(target); } pub fn solveAll(reader: anytype) !usize { var result: usize = 0; while (true) { var allocator_buffer: [100000]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&allocator_buffer); var allocator = fba.allocator(); var lines = StackList([]u8, usize, 200).init(); var empty_line_reached = false; var line_buffer: [1000]u8 = undefined; while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| { if (line.len == 0) { empty_line_reached = true; break; } lines.add(try allocator.dupe(u8, line)); } result += try solveLines(lines.getMutableSlice()); if (!empty_line_reached) { return result; } } } pub fn main() !void { const stdout = std.io.getStdOut().writer(); const raw_in = std.io.getStdIn(); var buffered_reader = std.io.bufferedReader(raw_in.reader()); var reader = buffered_reader.reader(); const result = try solveAll(&reader); try stdout.print("{d}\n", .{result}); }