|
| 1 | +(ns day16 |
| 2 | + (:require [clojure.set :as set] |
| 3 | + [clojure.string :as str] |
| 4 | + [clojure.java.io :as io])) |
| 5 | + |
| 6 | +(defrecord Cell [north south east west]) |
| 7 | + |
| 8 | +(defrecord Config [path path-score]) |
| 9 | + |
| 10 | +(defn read-maze [filename] |
| 11 | + (defn process-line [y line] |
| 12 | + (map-indexed (fn [x e] [[x y] e]) (str/split line #""))) |
| 13 | + |
| 14 | + (defn create-cell [x y mapping] |
| 15 | + (defn helper [p] (if (contains? mapping p) p)) |
| 16 | + (Cell. (helper [x (- y 1)]) |
| 17 | + (helper [x (+ y 1)]) |
| 18 | + (helper [(+ x 1) y]) |
| 19 | + (helper [(- x 1) y]))) |
| 20 | + |
| 21 | + (defn find-loc [sym pos-to-sym] |
| 22 | + (first (first (filter #(= sym (last %)) pos-to-sym)))) |
| 23 | + (let [pos-to-sym (with-open [file (io/reader filename)] |
| 24 | + (->> |
| 25 | + file |
| 26 | + (line-seq) |
| 27 | + (map-indexed process-line) |
| 28 | + (apply concat) |
| 29 | + (filter #(#{"." "S" "E"} (last %))) |
| 30 | + (into {}))) |
| 31 | + start-loc (find-loc "S" pos-to-sym) |
| 32 | + end-loc (find-loc "E" pos-to-sym) |
| 33 | + maze (->> |
| 34 | + pos-to-sym |
| 35 | + (keys) |
| 36 | + (map (fn [[x y]] [[x y] (create-cell x y pos-to-sym)])) |
| 37 | + (into {}))] |
| 38 | + [maze start-loc end-loc])) |
| 39 | + |
| 40 | +(def directions #{:north :south :east :west}) |
| 41 | + |
| 42 | +(defn opposite-of [d] |
| 43 | + (case d |
| 44 | + :north :south |
| 45 | + :south :north |
| 46 | + :east :west |
| 47 | + :west :east)) |
| 48 | + |
| 49 | +(defn solve-part [maze start-loc end-loc] |
| 50 | + (defn get-next-positions [[loc dir]] |
| 51 | + (->> |
| 52 | + (disj directions (opposite-of dir)) |
| 53 | + (map (fn [d] [(d (maze loc)) d])) |
| 54 | + (filter #(some? (first %))))) |
| 55 | + |
| 56 | + (defn make-new-config [pos config pos-to-score] |
| 57 | + (let [curr-path (:path config) |
| 58 | + curr-dir (second (first curr-path)) |
| 59 | + new-dir (second pos)] |
| 60 | + (Config. (cons pos curr-path) |
| 61 | + (+ (:path-score config) (if (= curr-dir new-dir) 1 1001))))) |
| 62 | + |
| 63 | + (defn update-pos-to-score [pos-to-score config] |
| 64 | + (let [pos (first (:path config)) |
| 65 | + pos-score (get pos-to-score pos Integer/MAX_VALUE) |
| 66 | + new-score (:path-score config)] |
| 67 | + (if (< new-score pos-score) |
| 68 | + (assoc pos-to-score pos new-score) |
| 69 | + pos-to-score))) |
| 70 | + |
| 71 | + (defn explore-paths [configs pos-to-score end-score best-positions] |
| 72 | + (let [config (first configs) |
| 73 | + rest-configs (rest configs) |
| 74 | + curr-pos (first (:path config)) |
| 75 | + path-score (:path-score config)] |
| 76 | + (if (and (some? end-score) (> path-score end-score)) |
| 77 | + [end-score best-positions] |
| 78 | + (if (= (first curr-pos) end-loc) |
| 79 | + (recur rest-configs pos-to-score path-score |
| 80 | + (concat best-positions (:path config))) |
| 81 | + (if (<= path-score (get pos-to-score curr-pos Integer/MAX_VALUE)) |
| 82 | + (let [new-configs (->> |
| 83 | + (get-next-positions curr-pos) |
| 84 | + (map #(make-new-config % config pos-to-score)))] |
| 85 | + (recur (sort-by #(:path-score %) |
| 86 | + (concat new-configs rest-configs)) |
| 87 | + (reduce update-pos-to-score pos-to-score new-configs) |
| 88 | + end-score best-positions)) |
| 89 | + (recur rest-configs pos-to-score end-score best-positions)))))) |
| 90 | + |
| 91 | + (let [start-pos [start-loc :east] |
| 92 | + start-configs [(Config. [start-pos] 1)] |
| 93 | + [score best-tiles] (explore-paths start-configs {start-pos 1} nil #{})] |
| 94 | + [(- score 1) |
| 95 | + (count (distinct (map first best-tiles)))])) |
| 96 | + |
| 97 | +(defn -main [filename] |
| 98 | + (let [[maze start-loc end-loc] (read-maze filename)] |
| 99 | + (println (solve-part maze start-loc end-loc)))) |
0 commit comments