1
0
Advent-of-Code/day16/part1.zig
2024-12-16 23:10:47 +01:00

176 lines
7.7 KiB
Zig

const std = @import("std");
const print = std.debug.print;
const input = @embedFile("input");
const MAP_SIZE = 141;
// So so so, how do I do that ?
// I am thinking bruteforce but stop early if the score is already > min
// Otherwise I can do it from the rnf, I dont need to do from start to end. I can do from end to start
// I cant really brute force it either though. As their is an infinite number of path since I can go around for ever
// So start from the start and making a random next step is a bit stupide it seem
// Specially because I think the next part will be a freaking giant map of something like that
// So first of all, rotation is 1000 cost lol. So Basically I need to find the lower number of turn until the end.
// If there is 2 identical, I check how many case it is. And maybe they are the same. You can do left right and right left, same price.
// But what matter is the number of turn
// Ok so what I can do it for each intersection, I do do all possible turn. But I need to do the step one by one
// I cant use recursion. Because I stop at the first found solution
// Damn that a good solution tho I think, as it will always take the minimum amount of calculation for 100% sure it's the minimum.
// Because Iam sure I can find a pathfinding algo somewhere that find the best at 95% but using 1% of the time.
//
// Anyway, so how do I do that ?
// I think I have a list of position + direction and a step function
// At each step, I go at the end of strait line and append to the next list if I find a possible route
// Like that it count like one turn and the next step it fo strait line
// When I found something the touch the exit I STILL FINISH THE CURRENT LIST as another one can finish at the same time in less cost
//
// If I need to turn 2 time, that mean I am in a cue de sac. So I can just forget it and don't add it to the next list
const Direction = enum { Top, Bottom, Left, Right };
const Cell = struct {
is: enum { Wall, Empty, End },
visited_by: struct {
top: bool = false,
bottom: bool = false,
left: bool = false,
right: bool = false,
} = .{},
fn visit(self: *Cell, direction: Direction) void {
if (direction == .Top) self.visited_by.top = true;
if (direction == .Left) self.visited_by.left = true;
if (direction == .Right) self.visited_by.right = true;
if (direction == .Bottom) self.visited_by.bottom = true;
}
fn visited(self: *Cell, direction: Direction) bool {
return switch (direction) {
.Top => self.visited_by.top,
.Bottom => self.visited_by.bottom,
.Right => self.visited_by.right,
.Left => self.visited_by.left,
};
}
};
const Position = struct {
x: usize,
y: usize,
};
const Path = struct {
position: Position,
direction: Direction,
number_of_turn_done: usize, // I could just save the cost total, I dont really need those 2 value
number_of_tile_walked: usize,
};
const Map = struct {
map: [MAP_SIZE][MAP_SIZE]Cell,
paths: *std.ArrayList(Path),
minimum: ?usize = null,
fn init(allocator: std.mem.Allocator) !Map {
const list = try allocator.create(std.ArrayList(Path));
list.* = std.ArrayList(Path).init(allocator);
var map: [MAP_SIZE][MAP_SIZE]Cell = undefined;
for (input[0 .. MAP_SIZE * (MAP_SIZE + 1)], 0..) |c, i| {
if (c == '\n') continue;
map[@divFloor(i, MAP_SIZE + 1)][i % (MAP_SIZE + 1)] = switch (c) {
'.', 'S' => Cell{ .is = .Empty },
'#' => Cell{ .is = .Wall },
'E' => Cell{ .is = .End },
else => unreachable,
};
if (c == 'S') try list.append(Path{
.direction = .Right,
.position = Position{ .x = @divFloor(i, MAP_SIZE + 1), .y = i % (MAP_SIZE + 1) - 1 },
.number_of_turn_done = 0,
.number_of_tile_walked = 0,
});
}
return Map{ .paths = list, .map = map };
}
// Each step is a turn of a path. So if a path need to turn to continue, it add itself to self.paths to be continue in the next step
// If it can continue walking it just continue walking as it cost nothing
fn solve(self: *Map, allocator: std.mem.Allocator) !?usize {
var minimum: ?usize = null;
while (minimum == null and self.paths.items.len != 0) {
const paths = try allocator.alloc(Path, self.paths.items.len);
defer allocator.free(paths);
for (self.paths.items, 0..) |path, i| paths[i] = path;
self.paths.clearRetainingCapacity();
for (paths) |*path| blk: while (true) {
var x: usize = path.position.x;
if (path.direction == .Top) x -= 1 else if (path.direction == .Bottom) x += 1;
var y: usize = path.position.y;
if (path.direction == .Left) y -= 1 else if (path.direction == .Right) y += 1;
if (self.map[x][y].is == .Wall) break :blk;
if (self.map[x][y].is == .End) {
if (minimum == null or path.number_of_turn_done * 1000 + path.number_of_tile_walked < minimum.?) {
minimum = path.number_of_turn_done * 1000 + path.number_of_tile_walked;
}
break :blk;
}
if (self.map[x][y].visited(path.direction)) break :blk;
self.map[x][y].visit(path.direction);
path.number_of_tile_walked += 1;
path.position.x = x;
path.position.y = y;
try self.checkSurroundingAndCreatePath(path.*);
};
}
return minimum;
}
fn checkSurroundingAndCreatePath(self: *Map, path: Path) !void {
const x = path.position.x;
const y = path.position.y;
switch (path.direction) {
.Right, .Left => {
if (self.map[x - 1][y].is == .Empty and !self.map[x - 1][y].visited(path.direction)) try self.paths.append(Path{
.position = path.position,
.direction = .Top,
.number_of_tile_walked = path.number_of_tile_walked,
.number_of_turn_done = path.number_of_turn_done + 1,
});
if (self.map[x + 1][y].is == .Empty and !self.map[x + 1][y].visited(path.direction)) try self.paths.append(Path{
.position = path.position,
.direction = .Bottom,
.number_of_tile_walked = path.number_of_tile_walked,
.number_of_turn_done = path.number_of_turn_done + 1,
});
}, //
.Top, .Bottom => {
if (self.map[x][y - 1].is == .Empty and !self.map[x][y - 1].visited(path.direction)) try self.paths.append(Path{
.position = path.position,
.direction = .Left,
.number_of_tile_walked = path.number_of_tile_walked,
.number_of_turn_done = path.number_of_turn_done + 1,
});
if (self.map[x][y + 1].is == .Empty and !self.map[x][y + 1].visited(path.direction)) try self.paths.append(Path{
.position = path.position,
.direction = .Right,
.number_of_tile_walked = path.number_of_tile_walked,
.number_of_turn_done = path.number_of_turn_done + 1,
});
},
}
}
};
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var map = try Map.init(allocator);
const minimum = try map.solve(allocator);
try std.testing.expectEqual(127520, minimum.?);
}