You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
6.7 KiB
Zig

const std = @import("std");
const Coord = struct { row: usize, col: usize };
pub fn main() !void {
const grid = @embedFile("data.txt");
const width = std.mem.indexOf(u8, grid, "\n").?; // can't do this at comptime. damn
const height = grid.len / width - 1;
const start = std.mem.indexOf(u8, grid, "S").?;
const startCoord = indexToCoord(start, width);
const allStartingNeighbors = neighbors(startCoord, width, height);
// find the 2 path starting points
var paths: [2]Coord = undefined;
var pathIndex: u2 = 0;
for (allStartingNeighbors, 0..) |neighbor, dir| {
if (neighbor) |c| {
const value = grid[coordToIndex(c, width)];
var validPath = false;
if (dir == 0 and (value == '|' or value == 'F' or value == '7')) {
validPath = true;
}
if (dir == 1 and (value == '-' or value == 'J' or value == '7')) {
validPath = true;
}
if (dir == 2 and (value == '|' or value == 'J' or value == 'L')) {
validPath = true;
}
if (dir == 3 and (value == '-' or value == 'F' or value == 'L')) {
validPath = true;
}
if (validPath) {
paths[pathIndex] = c;
pathIndex += 1;
}
}
}
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// capture the loop's coordinates in a map
var loop = std.ArrayList(Coord).init(allocator);
defer loop.deinit();
try loop.append(startCoord);
try loop.append(paths[0]);
// count the steps
var steps: u16 = 1;
var totalSteps: u16 = 0;
var prevs = [2]Coord{ startCoord, startCoord };
while (true) {
// for each of the 2 paths
for (0..2) |i| {
const temp = visit(paths[i], prevs[i], grid, width).?;
if (i == 0) {
// try loop.put(temp, {});
try loop.append(temp);
}
prevs[i] = paths[i];
paths[i] = temp;
}
steps += 1;
// if the two paths converge (and not at the starting point again)
if (paths[0].row == paths[1].row and paths[0].col == paths[1].col and (paths[0].row != startCoord.row or paths[0].col != startCoord.col)) {
totalSteps = steps;
}
// for part 2, continue the first path until it gets back to the beginning
if (paths[0].row == startCoord.row and paths[0].col == startCoord.col) {
break;
}
}
std.debug.print("Part 1: {d}\n", .{totalSteps});
// Part 2
var visited = std.AutoHashMap(Coord, void).init(allocator);
defer visited.deinit();
// scarn left to right, keeping track of if we're "in" or "out" of the loop
var numVisited: u16 = 0;
for (0..height) |r| {
var in: bool = false;
var lastAngle: ?u8 = null;
for (0..width) |c| {
const coord = Coord{ .row = r, .col = c };
const value = grid[coordToIndex(coord, width)];
const onLoop = contains(&loop, coord);
if (onLoop) {
if (value == 'S') {
lastAngle = 'L'; // '7' for test data.. TODO don't hard code this
} else if (value == '|') { // hit a part of the loop. toggle "in"
in = !in;
} else if (value == 'L' or value == 'F') { // we're starting to go "along" the loop
lastAngle = value;
} else if (value == '7' or value == 'J') { // we're coming out of the edge of the loop
if ((value == '7' and lastAngle == 'L') or (value == 'J' and lastAngle == 'F')) {
in = !in;
}
}
}
if (!onLoop and in) { // if we aren't on a loop, and inside, add it to the list!
numVisited += 1;
try visited.put(coord, {});
}
}
}
printGrid(grid, width, visited, &loop);
std.debug.print("Part 2: {any}\n", .{numVisited});
}
fn printGrid(grid: []const u8, width: usize, visited: anytype, loop: anytype) void {
for (grid, 0..) |value, idx| {
const c = indexToCoord(idx, width);
if (contains(loop, c)) {
const code: u16 = switch (value) {
'-' => 0x2501,
'|' => 0x2503,
'F' => 0x250F,
'7' => 0x2513,
'J' => 0x251B,
else => 0x2517,
};
std.debug.print("{u}", .{code});
} else if (visited.get(c) != null) {
std.debug.print("X", .{});
} else if (value == '\n') {
std.debug.print("{c}", .{value});
} else {
std.debug.print(".", .{});
}
}
}
fn contains(ar: *std.ArrayList(Coord), item: Coord) bool {
for (ar.items) |i| {
if (i.row == item.row and i.col == item.col) {
return true;
}
}
return false;
}
// pass it a coordinate and the previous coordinate, and it finds the next one
fn visit(c: Coord, prev: Coord, grid: []const u8, width: usize) ?Coord {
const value = grid[coordToIndex(c, width)];
return switch (value) {
'-' => Coord{ .row = c.row, .col = if (prev.col < c.col) c.col + 1 else c.col - 1 },
'|' => Coord{ .row = if (prev.row < c.row) c.row + 1 else c.row - 1, .col = c.col },
'F' => if (prev.row > c.row) Coord{ .row = c.row, .col = c.col + 1 } else Coord{ .row = c.row + 1, .col = c.col },
'7' => if (prev.row > c.row) Coord{ .row = c.row, .col = c.col - 1 } else Coord{ .row = c.row + 1, .col = c.col },
'J' => if (prev.row < c.row) Coord{ .row = c.row, .col = c.col - 1 } else Coord{ .row = c.row - 1, .col = c.col },
'L' => if (prev.row < c.row) Coord{ .row = c.row, .col = c.col + 1 } else Coord{ .row = c.row - 1, .col = c.col },
else => null,
};
}
// returns all neighboring coordinates, or null if it would be off the grid
fn neighbors(c: Coord, width: usize, height: usize) [4]?Coord {
const up = if (c.row > 0) Coord{ .row = c.row - 1, .col = c.col } else null;
const right = if (c.col < width - 1) Coord{ .row = c.row, .col = c.col + 1 } else null;
const down = if (c.row < height - 1) Coord{ .row = c.row + 1, .col = c.col } else null;
const left = if (c.col > 0) Coord{ .row = c.row, .col = c.col - 1 } else null;
return [_]?Coord{
up,
right,
down,
left,
};
}
fn coordToIndex(c: Coord, width: usize) usize {
return c.row * (width + 1) + c.col;
}
fn indexToCoord(index: usize, width: usize) Coord {
return Coord{ .row = index / (width + 1), .col = index % (width + 1) };
}