2024-12-27 18:05:58 -08:00

287 lines
10 KiB
Zig

const std = @import("std");
const print = std.debug.print;
const util = @import("util.zig");
pub fn main() !void {
const output = try part_two();
print("{}\n", .{output});
}
const Location = struct { x: u32, y: u32 };
const NavigationError = error{OutOfBounds};
const Direction = enum {
north,
north_east,
east,
south_east,
south,
south_west,
west,
north_west,
// Errors if asked to go below zero
fn progress(self: Direction, from: Location) NavigationError!Location {
return switch (self) {
Direction.north => {
if (from.y > 0) {
return Location{ .x = from.x, .y = from.y - 1 };
} else {
return error.OutOfBounds;
}
},
Direction.north_east => {
if (from.y > 0) {
return Location{ .x = from.x + 1, .y = from.y - 1 };
} else {
return error.OutOfBounds;
}
},
Direction.east => Location{ .x = from.x + 1, .y = from.y },
Direction.south_east => Location{ .x = from.x + 1, .y = from.y + 1 },
Direction.south => Location{ .x = from.x, .y = from.y + 1 },
Direction.south_west => {
if (from.x > 0) {
return Location{ .x = from.x - 1, .y = from.y + 1 };
} else {
return error.OutOfBounds;
}
},
Direction.west => {
if (from.x > 0) {
return Location{ .x = from.x - 1, .y = from.y };
} else {
return error.OutOfBounds;
}
},
Direction.north_west => {
if (from.x > 0 and from.y > 0) {
return Location{ .x = from.x - 1, .y = from.y - 1 };
} else {
return error.OutOfBounds;
}
},
};
}
pub fn getIndicesFromStartingPoint(self: Direction, starting_point: Location) ![4]Location {
var response: [4]Location = undefined;
response[0] = starting_point;
var idx: u8 = 1;
while (idx < 4) {
response[idx] = try self.progress(response[idx - 1]);
idx += 1;
}
return response;
}
};
// !?
const ALL_DIRECTIONS: [8]Direction = .{ Direction.north, Direction.north_east, Direction.east, Direction.south_east, Direction.south, Direction.south_west, Direction.west, Direction.north_west };
pub fn part_one() !u32 {
// Logic:
// * Iterate over the letters
// * When X is found:
// * Iterate over all 8 directions
// * If an XMAS is found in that direction, increment counter
const is_test_case: bool = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const data = try util.readAllInput(try util.getInputFile("04", is_test_case));
// https://stackoverflow.com/a/79199470/1040915
var it = std.mem.splitScalar(u8, data, '\n');
var lines_list = std.ArrayList([]const u8).init(allocator);
defer lines_list.deinit();
while (it.next()) |line| {
if (line.len > 1) {
try lines_list.append(line);
}
}
const lines = lines_list.items;
// Makes the assumption that all lines are the same length.
// In real production code, we'd assert that.
const line_length = lines[0].len;
const number_of_lines = lines.len;
print("DEBUG - line_length is {} and number_of_lines is {}\n", .{ line_length, number_of_lines });
var x: u32 = 0;
var number_of_xmasses: u32 = 0;
while (x < line_length) : (x += 1) {
var y: u32 = 0;
while (y < number_of_lines) : (y += 1) {
const starting_point = Location{ .x = x, .y = y };
print("SUPER-DEBUG - checking in neighbourhood of {}\n", .{starting_point});
// Short-circuit - only do checking if the starting_point is `X`
if (lines[starting_point.y][starting_point.x] != 'X') {
continue;
}
print("DEBUG - found an X at {}\n", .{starting_point});
for (ALL_DIRECTIONS) |dir| {
if (is_valid_xmas(lines, starting_point, dir)) {
number_of_xmasses += 1;
}
}
}
}
return number_of_xmasses;
}
fn is_valid_xmas(data: [][]const u8, starting_point: Location, dir: Direction) bool {
const indices = dir.getIndicesFromStartingPoint(starting_point) catch {
// If we fail to get the indices (because they strayed out-of-bounds), then of _course_ that's not a valid XMAS.
return false;
};
// Check for validity to avoid bounds issues
for (indices) |index| {
if (index.x >= data[0].len or index.y >= data.len) {
return false;
}
}
print("DEBUG - checking indices {any}\n", .{indices});
const return_value = (data[indices[0].y][indices[0].x] == 'X' and
data[indices[1].y][indices[1].x] == 'M' and
data[indices[2].y][indices[2].x] == 'A' and
data[indices[3].y][indices[3].x] == 'S');
if (return_value) {
print("DEBUG - found an XMAS at {}, going {}\n", .{ starting_point, dir });
}
return return_value;
}
const expect = std.testing.expect;
test "part_one" {
try expect(try part_one() == 18);
}
// ==========
pub fn part_two() !u32 {
// Logic:
// * Iterate over the letters
// * When M is found:
// * Look for a X going forwards-down and backwards-down
// (No need to look for Xs going upwards because all upward-crosses will have already been found as part of an
// earlier pass)
const is_test_case: bool = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const data = try util.readAllInput(try util.getInputFile("04", is_test_case));
// https://stackoverflow.com/a/79199470/1040915
var it = std.mem.splitScalar(u8, data, '\n');
var lines_list = std.ArrayList([]const u8).init(allocator);
defer lines_list.deinit();
while (it.next()) |line| {
if (line.len > 1) {
try lines_list.append(line);
}
}
const lines = lines_list.items;
// Makes the assumption that all lines are the same length.
// In real production code, we'd assert that.
const line_length = lines[0].len;
const number_of_lines = lines.len;
print("DEBUG - line_length is {} and number_of_lines is {}\n", .{ line_length, number_of_lines });
var x: u32 = 0;
var number_of_xmasses: u32 = 0;
while (x < line_length) : (x += 1) {
var y: u32 = 0;
while (y < number_of_lines) : (y += 1) {
const starting_point = Location{ .x = x, .y = y };
print("SUPER-DEBUG - checking in neighbourhood of {}\n", .{starting_point});
// Short-circuit - only do checking if the starting_point is `M`
if (lines[starting_point.y][starting_point.x] != 'M') {
continue;
}
print("DEBUG - found a M at {}\n", .{starting_point});
// I could probably have implemented `is_valid_cross` as a single function taking a 4-value enum, but, ehh.
for ([2]bool{ true, false }) |is_forwards| {
if (is_valid_horizontal_cross(lines, starting_point, is_forwards)) {
number_of_xmasses += 1;
}
}
for ([2]bool{ true, false }) |is_upward| {
if (is_valid_vertical_cross(lines, starting_point, is_upward)) {
number_of_xmasses += 1;
}
}
}
}
return number_of_xmasses;
}
fn is_valid_horizontal_cross(data: [][]const u8, starting_point: Location, is_forward: bool) bool {
if (is_forward) {
if (starting_point.x >= data[0].len - 2 or starting_point.y >= data.len - 2) {
return false;
}
const found_forward_cross = (data[starting_point.y + 2][starting_point.x] == 'M' and
data[starting_point.y + 1][starting_point.x + 1] == 'A' and
data[starting_point.y][starting_point.x + 2] == 'S' and
data[starting_point.y + 2][starting_point.x + 2] == 'S');
if (found_forward_cross) {
print("Found forward cross\n", .{});
}
return found_forward_cross;
} else {
if (starting_point.x < 2 or starting_point.y >= data.len - 2) {
return false;
}
const found_backward_cross = (data[starting_point.y + 2][starting_point.x] == 'M' and
data[starting_point.y + 1][starting_point.x - 1] == 'A' and
data[starting_point.y][starting_point.x - 2] == 'S' and
data[starting_point.y + 2][starting_point.x - 2] == 'S');
if (found_backward_cross) {
print("Found backward cross\n", .{});
}
return found_backward_cross;
}
}
fn is_valid_vertical_cross(data: [][]const u8, starting_point: Location, is_upward: bool) bool {
if (is_upward) {
if (starting_point.y < 2 or starting_point.x >= data[0].len - 2) {
return false;
}
const found_upward_cross = (data[starting_point.y][starting_point.x + 2] == 'M' and
data[starting_point.y - 1][starting_point.x + 1] == 'A' and
data[starting_point.y - 2][starting_point.x] == 'S' and
data[starting_point.y - 2][starting_point.x + 2] == 'S');
if (found_upward_cross) {
print("Found upward cross\n", .{});
}
return found_upward_cross;
} else {
if (starting_point.y >= data.len - 2 or starting_point.x >= data[0].len - 2) {
return false;
}
const found_downward_cross = (data[starting_point.y][starting_point.x + 2] == 'M' and
data[starting_point.y + 1][starting_point.x + 1] == 'A' and
data[starting_point.y + 2][starting_point.x] == 'S' and
data[starting_point.y + 2][starting_point.x + 2] == 'S');
if (found_downward_cross) {
print("Found downward cross\n", .{});
}
return found_downward_cross;
}
}
test "part_two" {
const answer_of_part_two = try part_two();
print("DEBUG - answer is {}\n", .{answer_of_part_two});
try expect(answer_of_part_two == 9);
}