2024-12-16 19:07:05 +00:00
|
|
|
{lib, pkgs, ...}: input: rec {
|
2024-12-16 10:00:40 +00:00
|
|
|
inherit lib;
|
2024-12-16 19:07:05 +00:00
|
|
|
inherit (lib) splitString mod range;
|
2024-12-16 10:00:40 +00:00
|
|
|
inherit (lib.lists) findFirstIndex imap0;
|
|
|
|
inherit (lib.strings) stringToCharacters;
|
2024-12-17 14:58:14 +00:00
|
|
|
inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor deepSeq listToAttrs attrValues attrNames mapAttrs hasAttr sort;
|
2024-12-16 10:00:40 +00:00
|
|
|
|
|
|
|
index = i: list: elemAt list i;
|
|
|
|
index2d = {x, y}: m: m |> index y |> index x;
|
|
|
|
|
|
|
|
init = let
|
|
|
|
chart = input |> splitString "\n" |> map stringToCharacters;
|
|
|
|
width = (elemAt chart 0 |> length) + 1;
|
|
|
|
height = length chart;
|
|
|
|
startIndex = input |> stringToCharacters |> findFirstIndex (char: char == "S") null;
|
|
|
|
endIndex = input |> stringToCharacters |> findFirstIndex (char: char == "E") null;
|
|
|
|
in {
|
|
|
|
inherit chart width height;
|
|
|
|
pos = {
|
|
|
|
x = mod startIndex width;
|
|
|
|
y = startIndex / width;
|
|
|
|
};
|
|
|
|
goal = {
|
|
|
|
x = mod endIndex width;
|
|
|
|
y = endIndex / width;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
chartToStr = chart: chart |> (map (concatStringsSep "")) |> concatStringsSep "\n";
|
|
|
|
|
|
|
|
rotate' = {x, y}: {y = -x; x = y;};
|
|
|
|
rotate = {x, y}: {y = x; x = -y;};
|
|
|
|
|
|
|
|
addVec = a: b: {x = a.x or 0 + b.x or 0; y = a.y or 0 + b.y or 0;};
|
|
|
|
multVec = a: m: {x = a.x or 0 * m; y = a.y or 0 * m;};
|
|
|
|
|
|
|
|
|
2024-12-16 19:07:05 +00:00
|
|
|
dirs = {
|
|
|
|
north = {x = 0; y = -1;};
|
|
|
|
east = {x = 1; y = 0;};
|
|
|
|
south = {x = 0; y = 1;};
|
|
|
|
west = {x = -1; y = 0;};
|
|
|
|
};
|
|
|
|
|
|
|
|
search = {
|
|
|
|
pos,
|
|
|
|
dir,
|
|
|
|
score ? 0,
|
|
|
|
}: let
|
|
|
|
fwd = addVec pos dir;
|
2024-12-16 10:00:40 +00:00
|
|
|
in
|
|
|
|
if
|
2024-12-16 19:07:05 +00:00
|
|
|
index2d fwd init.chart == "#"
|
2024-12-17 14:58:14 +00:00
|
|
|
then {pos = null; dist = null;}
|
2024-12-16 19:07:05 +00:00
|
|
|
else
|
2024-12-17 14:58:14 +00:00
|
|
|
if (isCorner fwd || fwd == init.goal || fwd == init.pos)
|
2024-12-16 19:07:05 +00:00
|
|
|
then {
|
2024-12-17 14:58:14 +00:00
|
|
|
pos = (key fwd);
|
|
|
|
dist = score + 1;
|
2024-12-16 19:07:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
search {pos = fwd; inherit dir; score = score + 1;}
|
|
|
|
;
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
isCorner = pos: index2d pos init.chart == "." &&
|
2024-12-16 19:07:05 +00:00
|
|
|
(dirs
|
|
|
|
|> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".")
|
|
|
|
|> ({north, east, south, west}:
|
|
|
|
north && east || east && south || south && west || west && north))
|
|
|
|
;
|
|
|
|
|
|
|
|
key = {x, y}: "${toString (y + 1)},${toString (x + 1)}";
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
corners = range 1 (init.width - 3) |> map (x:
|
2024-12-16 19:07:05 +00:00
|
|
|
range 1 (init.height - 3) |> map (y: {inherit x y;})
|
|
|
|
)
|
|
|
|
|> concatLists
|
2024-12-17 14:58:14 +00:00
|
|
|
|> filter isCorner
|
2024-12-16 19:07:05 +00:00
|
|
|
|> (nodes: nodes ++ [init.pos init.goal])
|
|
|
|
|> map (pos: {
|
|
|
|
name = key pos;
|
2024-12-17 14:58:14 +00:00
|
|
|
value = ({
|
2024-12-16 19:07:05 +00:00
|
|
|
north = search {inherit pos; dir = dirs.north;};
|
|
|
|
east = search {inherit pos; dir = dirs.east;};
|
|
|
|
south = search {inherit pos; dir = dirs.south;};
|
|
|
|
west = search {inherit pos; dir = dirs.west;};
|
2024-12-17 14:59:21 +00:00
|
|
|
} |> lib.filterAttrs (n: v: !isNull v.pos));
|
2024-12-16 19:07:05 +00:00
|
|
|
})
|
|
|
|
|> listToAttrs;
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
notNull = value: !isNull value;
|
|
|
|
|
|
|
|
nodeGraph = graph: pkgs.runCommand "graph" {} ''
|
2024-12-16 19:07:05 +00:00
|
|
|
mkdir -p $out
|
|
|
|
echo 'digraph {
|
2024-12-17 14:58:14 +00:00
|
|
|
${graph |> mapAttrs (from: edges:
|
|
|
|
edges |> mapAttrs (dir: edge: if isNull edge.pos or null then "" else ''
|
|
|
|
"${from}" -> "${edge.pos}" [label="${dir} ${toString edge.dist}"]
|
2024-12-16 19:07:05 +00:00
|
|
|
'') |> attrValues |> concatStringsSep "\n"
|
|
|
|
) |> attrValues |> concatStringsSep "\n"}
|
|
|
|
}' > $out/graph.dot
|
|
|
|
cat $out/graph.dot | ${pkgs.graphviz}/bin/dot -Tsvg > $out/graph.svg
|
|
|
|
'';
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
graphs = {
|
|
|
|
corners = nodeGraph corners;
|
|
|
|
};
|
2024-12-16 19:07:05 +00:00
|
|
|
|
|
|
|
initScores = {
|
2024-12-17 14:58:14 +00:00
|
|
|
${key init.pos} = {
|
|
|
|
dir = "east";
|
|
|
|
steps = 0;
|
|
|
|
turns = 0;
|
|
|
|
pos = key init.pos;
|
|
|
|
};
|
2024-12-16 19:07:05 +00:00
|
|
|
};
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
getLowest = state: let
|
2024-12-16 19:07:05 +00:00
|
|
|
unvisited = removeAttrs state.scores state.done;
|
|
|
|
in
|
2024-12-17 14:58:14 +00:00
|
|
|
attrValues unvisited |> foldl' (acc: node:
|
|
|
|
if isNull acc.turns then node else
|
|
|
|
if isNull node.turns then acc else
|
|
|
|
if acc.turns < node.turns then acc else node
|
|
|
|
) {turns = null; steps = null;}
|
|
|
|
;
|
|
|
|
|
|
|
|
getScores = {done ? [], scores ? initScores}: let
|
|
|
|
prev = getLowest {inherit done scores;};
|
|
|
|
in
|
|
|
|
corners.${prev.pos}
|
|
|
|
|> mapAttrs (dir: edge: let
|
|
|
|
turns = if prev.dir == dir then prev.turns else prev.turns + 1;
|
|
|
|
|
|
|
|
existing = scores.${edge.pos} or null;
|
|
|
|
steps = prev.steps + edge.dist;
|
|
|
|
sameSteps = notNull existing
|
|
|
|
&& existing.steps == steps
|
|
|
|
# && turns == existing.turns
|
|
|
|
;
|
|
|
|
isBetter = isNull existing ||
|
|
|
|
(existing.steps > steps);
|
|
|
|
newScore = {
|
|
|
|
inherit steps dir turns;
|
|
|
|
inherit (edge) pos;
|
|
|
|
prev = prev.pos;
|
2024-12-16 19:07:05 +00:00
|
|
|
};
|
2024-12-17 14:58:14 +00:00
|
|
|
in {
|
|
|
|
name = edge.pos;
|
|
|
|
value = if sameSteps then
|
|
|
|
existing // {alt = newScore;}
|
|
|
|
else if isBetter
|
|
|
|
then newScore
|
|
|
|
else existing
|
|
|
|
;
|
2024-12-16 19:07:05 +00:00
|
|
|
})
|
|
|
|
|> attrValues
|
|
|
|
|> listToAttrs
|
|
|
|
|> (newScores: {
|
2024-12-17 14:58:14 +00:00
|
|
|
scores = scores // newScores;
|
|
|
|
done = done ++ [prev.pos];
|
2024-12-16 19:07:05 +00:00
|
|
|
})
|
|
|
|
;
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
fastest = goal: s: let
|
|
|
|
next = getScores s;
|
|
|
|
in if elem (key goal) (next.done) then next else fastest goal next;
|
2024-12-16 10:00:40 +00:00
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
pathScore = steps: turns: steps + turns * 1000;
|
2024-12-16 19:07:05 +00:00
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
toGoal = (fastest init.goal {}).scores.${key init.goal};
|
|
|
|
|
|
|
|
part1result = pathScore toGoal.steps toGoal.turns;
|
|
|
|
|
|
|
|
# PART 2
|
|
|
|
|
|
|
|
# WIP:
|
|
|
|
# go through the path, add each pos to visited
|
|
|
|
# if point has alt, find points on that path not visited yet.
|
|
|
|
# add length of filtered alt, (-1 for the overlap at end?).
|
|
|
|
# add alt to visited.
|
|
|
|
pathToList = scores: pos:
|
|
|
|
let
|
|
|
|
res = scores.${pos};
|
|
|
|
prevPath = if res ? prev then pathToList scores res.prev else [];
|
|
|
|
fullaltPath = pathToList scores res.alt.prev;
|
|
|
|
joinIndex = findFirstIndex ({pos,...}: !elem pos (map ({pos, ...}: pos) prevPath)) null fullaltPath;
|
|
|
|
altPath = lib.sublist (joinIndex - 1) (length fullaltPath) fullaltPath;
|
|
|
|
|
|
|
|
thisSpot = {inherit (res) pos steps turns dir; };
|
|
|
|
|
|
|
|
in
|
|
|
|
prevPath
|
|
|
|
++
|
|
|
|
[(thisSpot // {
|
|
|
|
alt = if res ? alt then altPath ++ [thisSpot] else null;
|
|
|
|
})]
|
2024-12-16 19:07:05 +00:00
|
|
|
;
|
|
|
|
|
2024-12-17 14:58:14 +00:00
|
|
|
cornerScores = fastest init.goal {};
|
|
|
|
|
|
|
|
part1path = (pathToList cornerScores.scores (key init.goal));
|
2024-12-16 19:07:05 +00:00
|
|
|
|
2024-12-16 10:00:40 +00:00
|
|
|
|
|
|
|
}
|