~ihabunek/aoc2021

9fd7af4cff24ba04a82a64b5b1a6d56087446ffe — Ivan Habunek 2 years ago 91bf56f
Day 17
1 files changed, 77 insertions(+), 0 deletions(-)

A src/aoc2021/day17.clj
A src/aoc2021/day17.clj => src/aoc2021/day17.clj +77 -0
@@ 0,0 1,77 @@
; --- Day 17: Trick Shot ---
; https://adventofcode.com/2021/day/17

(ns aoc2021.day17
  (:require [clojure.java.io :as io]
            [clojure.string :as str]
            [clojure.edn :as edn]
            [clojure.pprint :refer [pprint cl-format]]))

(defn ceil [x] (long (Math/ceil x)))

(defn sqrt [x] (Math/sqrt x))

(defn abs-dec [n]
  (cond
    (> n 0) (dec n)
    (< n 0) (inc n)
    true n))

(defn range' [^long a ^long b]
  (if (> a b)
    (reverse (range b (inc a)))
    (range a (inc b))))

(defn step [[x y] [dx dy]]
  [[(+ x dx) (+ y dy)]
   [(abs-dec dx) (dec dy)]])

(defn hit? [[x y] [[x1 x2] [y1 y2]]]
  (and (>= x x1) (<= x x2)
       (>= y y1) (<= y y2)))

(defn overshot? [[x y] [[x1 x2] [y1 y2]]]
  (or (> x x2) (< y y1)))

; returns: max height on hit, nil on miss
(defn shoot [start-velocity bounds]
  (loop [position [0 0]
         velocity start-velocity
         max-height 0]
    (cond
      (overshot? position bounds) nil
      (hit? position bounds) max-height
      true (let [[position' velocity'] (step position velocity)
                 max-height' (max max-height (second position'))]
               (recur position' velocity' max-height')))))

; all starting velocities which could possibly hit the target
; using the power of high school math!
(defn velocity-range [[[x1 x2] [y1 y2]]]
  (let [dx-min (ceil (/ (+ -1 (sqrt (+ 1 (* 8 x1)))) 2))
        dx-max (inc x2)
        dy-min (dec y1)
        dy-max (- y1)]
    ; order by max dy first since we're looking for the highest shot
    (for [dy (range' dy-max dy-min)
          dx (range' dx-min dx-max)]
      [dx dy])))

(defn find-highest-shot [bounds]
  (->> (velocity-range bounds)
       (map #(shoot % bounds))
       (remove nil?)
       (first)))

(defn count-hits [bounds]
  (->> (velocity-range bounds)
       (map #(shoot % bounds))
       (remove nil?)
       (count)))

; target area: x=277..318, y=-92..-53

(defn main []
  (let [bounds [[277 318] [-92 -53]]]
    (println (str "Part 1:" (time (find-highest-shot bounds))))
    (println (str "Part 2:" (time (count-hits bounds))))))