never executed always true always false
    1 {-|
    2 Module:      Y2021.D11
    3 Description: Advent of Code 2021 Day 11 Solutions.
    4 License:     MIT
    5 Maintainer:  @tylerjl
    6 
    7 Solutions to the 2021 day 11 set of problems for <adventofcode.com>.
    8 -}
    9 module Y2021.D11
   10   ( parse11
   11   , part11A
   12   , part11B
   13   , solve11A
   14   , solve11B
   15   ) where
   16 
   17 import Data.Attoparsec.Text
   18 import Data.Either.Utils            (fromRight)
   19 import Data.Monoid
   20 import Data.Text                    (Text)
   21 import Math.Geometry.Grid
   22 import Math.Geometry.Grid.Octagonal (rectOctGrid, RectOctGrid)
   23 import Math.Geometry.GridMap
   24 import Math.Geometry.GridMap.Lazy
   25 
   26 import qualified Math.Geometry.GridMap as G
   27 
   28 -- |A GADT is useful here; although we could wrap an `Int` it's nice to have
   29 -- something with a bit more state
   30 data Octopus a
   31   = Flashed
   32   | Flashing
   33   | Unflashed a
   34   deriving (Eq, Ord, Show)
   35 -- |Type alias for better readability.
   36 type Octopi = LGridMap RectOctGrid (Octopus Int)
   37 
   38 -- |Solution to part A
   39 part11A :: Text -> Int
   40 part11A = solve11A . parse11
   41 
   42 -- |Solve part A
   43 solve11A :: Octopi -> Int
   44 solve11A = getSum . fst . flip (!!) 100 . iterate octoStep . (,) (Sum 0)
   45 
   46 -- |Solution to part B
   47 part11B :: Text -> Int
   48 part11B = solve11B . parse11
   49 
   50 -- |Solve part B
   51 solve11B :: Octopi -> Int
   52 solve11B = go 1 . step
   53   where
   54     go n octopi
   55       | all isFlashed octopi = n
   56       | otherwise
   57         = go (succ n) (step $ G.map reset octopi)
   58     step = octoStep' . G.map succ'
   59 
   60 -- |This is primarily for part A, which asks for iterative steps through the
   61 -- grid but also a running sum of flashes. We could probably make it more
   62 -- efficient with `State` to track a running sum, but this is pretty simple and
   63 -- I know it works.
   64 octoStep :: (Sum Int, Octopi) -> (Sum Int, Octopi)
   65 octoStep (flashes, octopi)
   66   = ( flashes + foldMap flashed octopi'
   67     , G.map reset octopi'
   68     )
   69   where
   70     octopi' = octoStep' $ G.map succ' octopi
   71     flashed Flashed = Sum 1
   72     flashed _ = 0
   73 
   74 -- |Step function to increment an `Octopus`, with the ceiling being `Flashing`
   75 -- (we don't downgrade to `Flashed` outside of `octoStep'`)
   76 succ' :: Octopus Int -> Octopus Int
   77 succ' (Unflashed (succ -> n))
   78   | n > 9     = Flashing
   79   | otherwise = Unflashed n
   80 succ' other = other
   81 
   82 -- |After steps, we call this function on `Octopus` grid in order to turn it
   83 -- into a clean state for the next iteration.
   84 reset :: Octopus Int -> Octopus Int
   85 reset Flashed  = Unflashed 0
   86 reset Flashing = error "something went wrong!"
   87 reset other    = other
   88 
   89 -- |Recursive function to iterate an `Octopi` grid until the state has settled
   90 -- and no flashing `Octopus` change the state.
   91 octoStep' :: Octopi -> Octopi
   92 octoStep' gOrig@(G.mapWithKey (observeFlash gOrig) -> gStepped)
   93   | gOrig == gStepped = gOrig
   94   | otherwise         = octoStep' gStepped
   95 
   96 -- |On a given iteration, we can map the grid and test the flashes of adjacent
   97 -- neighbors to increment the level of this `Octopus`. It's important that: we
   98 -- increment the right number of times, and that each `Octopus` _stops_ flashing
   99 -- after one step, which we can do with a simple pattern match.
  100 observeFlash :: Octopi -> (Int, Int) -> Octopus Int -> Octopus Int
  101 observeFlash _ _ Flashed  = Flashed
  102 observeFlash _ _ Flashing = Flashed
  103 observeFlash g point oct@(Unflashed _)
  104   = iterate succ' oct !! length adjacentFlashers
  105   where
  106     adjacentFlashers = [x | x <- neighbours g point, isFlashing (g ! x)]
  107 
  108 -- |Simple predicate to check for a flashing octopus.
  109 isFlashing :: Octopus a -> Bool
  110 isFlashing Flashing = True
  111 isFlashing _ = False
  112 
  113 -- |Check whether this octopus has flashed.
  114 isFlashed :: Octopus a -> Bool
  115 isFlashed Flashed = True
  116 isFlashed _ = False
  117 
  118 -- |Parse puzzle input into a octagon rectangular grid. Need octagons in order
  119 -- to correctly ask for diagonal neighbors.
  120 parse11 :: Text -> Octopi
  121 parse11 = fromRight . parseOnly (grid <$> parser)
  122   where
  123     grid [] = error "empty input"
  124     grid rows@(row:_) =
  125       lazyGridMap (rectOctGrid (length row) (length rows)) (concat rows)
  126     parser = line `sepBy1` endOfLine <* atEnd
  127     line = many1 (Unflashed . read . (: []) <$> digit)