287 lines
10 KiB
Zig
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);
|
|
}
|