{-# Language ImportQualifiedPost, BangPatterns #-}
{-|
Module      : Main
Description : Day 22 solution
Copyright   : (c) Eric Mertens, 2017
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2017/day/22>

-}
module Main where

import Advent.Coord (Coord(..), turnLeft, turnRight, turnAround, north)
import Advent.Input (getInputMap)
import Data.Map (Map)
import Data.Map qualified as Map

-- | >>> :main
-- 5399
-- 2511776
main :: IO ()
IO ()
main =
  do Map Coord Char
input <- Int -> Int -> IO (Map Coord Char)
getInputMap Int
2017 Int
22
     let grid :: Map Coord Status
grid = (Char -> Maybe Status) -> Map Coord Char -> Map Coord Status
forall a b k. (a -> Maybe b) -> Map k a -> Map k b
Map.mapMaybe (\Char
x -> if Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'#' then Status -> Maybe Status
forall a. a -> Maybe a
Just Status
Infected else Maybe Status
forall a. Maybe a
Nothing) Map Coord Char
input
     let C Int
my Int
mx = [Coord] -> Coord
forall a. HasCallStack => [a] -> a
last (Map Coord Char -> [Coord]
forall k a. Map k a -> [k]
Map.keys Map Coord Char
input)
     let start :: Coord
start = Int -> Int -> Coord
C (Int
my Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2) (Int
mx Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2)
     Int -> IO ()
forall a. Show a => a -> IO ()
print ((Status -> Status)
-> Int -> Int -> Coord -> Coord -> Map Coord Status -> Int
go Status -> Status
rule1    Int
10000 Int
0 Coord
north Coord
start Map Coord Status
grid)
     Int -> IO ()
forall a. Show a => a -> IO ()
print ((Status -> Status)
-> Int -> Int -> Coord -> Coord -> Map Coord Status -> Int
go Status -> Status
rule2 Int
10000000 Int
0 Coord
north Coord
start Map Coord Status
grid)

data Status = Clean | Weakened | Infected | Flagged deriving Status -> Status -> Bool
(Status -> Status -> Bool)
-> (Status -> Status -> Bool) -> Eq Status
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Status -> Status -> Bool
== :: Status -> Status -> Bool
$c/= :: Status -> Status -> Bool
/= :: Status -> Status -> Bool
Eq

-- | Transition rule used in part 1
rule1 :: Status -> Status
rule1 :: Status -> Status
rule1 Status
Clean = Status
Infected
rule1 Status
_     = Status
Clean

-- | Transition rule used in part 2
rule2 :: Status -> Status
rule2 :: Status -> Status
rule2 Status
Clean    = Status
Weakened
rule2 Status
Weakened = Status
Infected
rule2 Status
Infected = Status
Flagged
rule2 Status
Flagged  = Status
Clean

-- | Turn rule used by the virus carrier.
turnRule :: Status -> Coord -> Coord
turnRule :: Status -> Coord -> Coord
turnRule Status
Clean    = Coord -> Coord
turnLeft
turnRule Status
Weakened = Coord -> Coord
forall a. a -> a
id
turnRule Status
Infected = Coord -> Coord
turnRight
turnRule Status
Flagged  = Coord -> Coord
turnAround

-- | Run the world simulation for a specified number of iterations.
-- Returns the number of infections caused by the virus carrier.
go ::
  (Status -> Status) {- ^ update rule                              -} ->
  Int                {- ^ iterations remaining                     -} ->
  Int                {- ^ infection counter                        -} ->
  Coord              {- ^ facing direction                         -} ->
  Coord              {- ^ current location                         -} ->
  Map Coord Status   {- ^ world map                                -} ->
  Int                {- ^ infections caused after given iterations -}
go :: (Status -> Status)
-> Int -> Int -> Coord -> Coord -> Map Coord Status -> Int
go Status -> Status
rule !Int
n !Int
acc !Coord
dir !Coord
c !Map Coord Status
world
  | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = Int
acc
  | Bool
otherwise = (Status -> Status)
-> Int -> Int -> Coord -> Coord -> Map Coord Status -> Int
go Status -> Status
rule (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) Int
acc' Coord
dir' Coord
c' Map Coord Status
world'
  where
    cell :: Status
cell   = Status -> Coord -> Map Coord Status -> Status
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Status
Clean Coord
c Map Coord Status
world
    cell' :: Status
cell'  = Status -> Status
rule Status
cell
    world' :: Map Coord Status
world' = Coord -> Status -> Map Coord Status -> Map Coord Status
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Coord
c Status
cell' Map Coord Status
world

    acc' :: Int
acc' | Status
cell' Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
Infected = Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
acc
         | Bool
otherwise         = Int
acc

    dir' :: Coord
dir' = Status -> Coord -> Coord
turnRule Status
cell Coord
dir  -- new facing direction
    c' :: Coord
c'   = Coord
c Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+ Coord
dir'           -- new world coordinate