🏡 index : ~doyle/aoc.git

#!/usr/bin/env nix-shell
#!nix-shell --pure -i "runghc -- -i../" -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ ])"

import Aoc (readAndParseStdin)
import Data.Bifunctor (Bifunctor (first))
import Data.List (sortBy)
import Data.Ord (Down (Down), comparing)
import Text.Parsec (char, digit, endBy, many1, (<|>))
import Text.Parsec.String (Parser)

main = do
  (input, _) <- first sortBricks . dropBricks <$> readAndParseStdin parser
  print $ part1 input
  print $ part2 input

part1 :: [Brick] -> Int
part1 input = length $ findNonLoadBearing input
  where
    findNonLoadBearing input = filter (flip all input . isNonLoadBearing) input
    supportedCount y = length $ filter (`isSupporting` y) input
    isNonLoadBearing x y = not (x `isSupporting` y) || supportedCount y > 1

part2 :: [Brick] -> Int
part2 input = sum $ map (snd . dropBricks . dropIndex input) [0 .. length input - 1]
  where
    dropIndex input i = take i input ++ drop (i + 1) input

-- drops bricks until they're all settled
dropBricks :: [Brick] -> ([Brick], Int)
dropBricks = foldl processBrick ([], 0)
  where
    processBrick (acc, n) x =
      let (x', n') = dropUntilSupported acc x 0
       in (x' : acc, n + min n' 1)
    dropUntilSupported acc x n =
      if isSupported acc x
        then (x, n)
        else dropUntilSupported acc (decrementZ x) (n + 1)
    isSupported acc x = any (`isSupporting` x) acc || findBaseZ x == 1
    decrementZ (Brick a b) = Brick (a {z = z a - 1}) (b {z = z b - 1})
    findBaseZ (Brick a b) = min (z a) (z b)

-- checks if a is supporting b
isSupporting :: Brick -> Brick -> Bool
isSupporting a@(Brick a1 a2) b@(Brick b1 b2) =
  let (minX1, maxX1) = (min (x a1) (x a2), max (x a1) (x a2))
      (minY1, maxY1) = (min (y a1) (y a2), max (y a1) (y a2))
      (minZ1, maxZ1) = (min (z a1) (z a2), max (z a1) (z a2))
      (minX2, maxX2) = (min (x b1) (x b2), max (x b1) (x b2))
      (minY2, maxY2) = (min (y b1) (y b2), max (y b1) (y b2))
      (minZ2, maxZ2) = (min (z b1) (z b2), max (z b1) (z b2))
      zSupport = minZ2 == (maxZ1 + 1)
      xOverlap = not (maxX1 < minX2 || maxX2 < minX1)
      yOverlap = not (maxY1 < minY2 || maxY2 < minY1)
   in a /= b && zSupport && xOverlap && yOverlap

-- sorts bricks in ascending order of Z axis
sortBricks :: [Brick] -> [Brick]
sortBricks = sortBy (\(Brick a1 a2) (Brick b1 b2) -> compare (min (z a1) (z a2)) (min (z b1) (z b2)))

parser :: Parser [Brick]
parser = sortBricks <$> parseBrick `endBy` char '\n'
  where
    parseBrick = Brick <$> parseCoord <*> (char '~' *> parseCoord)
    parseCoord = do
      x <- read <$> many1 digit <* char ','
      y <- read <$> many1 digit <* char ','
      z <- read <$> many1 digit
      return $ Coord {x, y, z}

data Coord = Coord {x :: Int, y :: Int, z :: Int} deriving (Show, Eq)

data Brick = Brick Coord Coord deriving (Show, Eq)