-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay06.hs
133 lines (116 loc) · 3.78 KB
/
Day06.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
module Main where
import Advent.Grid (Grid, Position)
import Advent.Grid qualified as Grid
import Advent.Utils (run)
import Data.List (nub, unfoldr)
import Data.Maybe (fromJust)
import Data.Set (Set)
import Data.Set qualified as Set
import Debug.Trace (traceShow)
type Input = Grid Char
data SimStatus = Exit | Loop | Running
deriving (Eq, Show)
data Facing = North | South | East | West
deriving (Eq, Ord, Show)
data Guard = Guard Position Facing
deriving (Eq, Ord, Show)
data State = State
{ current :: Guard
, visited :: Set Guard
, status :: SimStatus
, steps :: Int
}
deriving (Eq, Show)
testInput :: Input
testInput =
prepare
[ "....#....."
, ".........#"
, ".........."
, "..#......."
, ".......#.."
, ".........."
, ".#..^....."
, "........#."
, "#........."
, "......#..."
]
nextPos :: Guard -> Position
nextPos (Guard (x, y) North) = (x, y - 1)
nextPos (Guard (x, y) South) = (x, y + 1)
nextPos (Guard (x, y) East) = (x + 1, y)
nextPos (Guard (x, y) West) = (x - 1, y)
turn :: Guard -> Guard
turn (Guard pos North) = Guard pos East
turn (Guard pos South) = Guard pos West
turn (Guard pos East) = Guard pos South
turn (Guard pos West) = Guard pos North
move :: Guard -> Guard
move g@(Guard _ facing) = Guard (nextPos g) facing
peak :: Input -> Guard -> Maybe Char
peak grid guard = Grid.get grid $ nextPos guard
initialState :: Input -> State
initialState grid =
let
start = Grid.findPosition (== '^') grid
in
State (Guard (fromJust start) North) Set.empty Running 0
countVisited :: State -> Int
countVisited state = length . nub $ (\(Guard pos _) -> pos) <$> Set.elems (visited state)
-- | Gives the next state after one update
runStep :: Input -> State -> State
runStep grid state = go (peak grid (current state))
where
go :: Maybe Char -> State
go Nothing = state{status = Exit, steps = steps state + 1}
go (Just '#') = state{current = turn (current state), steps = steps state + 1}
go (Just _) =
let
nextGuard = move (current state)
nextVisited = Set.insert (current state) (visited state)
in
-- TODO: I'm over classifying Loops
if Set.member nextGuard (visited state)
then state{status = Loop}
else State nextGuard nextVisited Running (steps state + 1)
-- | Runs steps until either Loop or Exit
runSimulation :: Input -> State -> [State]
runSimulation grid =
unfoldr
( \s@(State _ _ status' _) ->
if status' == Running
then Just (s, runStep grid s)
else Nothing
)
{- | Similar to run simulation, but doesn't accumulate state list
sets point in front of guard to '#'.
-}
findLoop :: Input -> State -> Bool
findLoop grid state =
let
nextPoint = nextPos (current state)
nextGrid = Grid.set grid nextPoint '#'
result = go nextGrid state
in
traceShow (current state, steps state, result) result
where
go :: Input -> State -> Bool
go _ (State _ _ Loop _) = True
go _ (State _ _ Exit _) = False
go g s =
-- NOTE: Sim was getting stuck at a certain test case, this will
-- kick it out if it seems to be going too long
(steps s <= uncurry (*) (Grid.dimensions g)) && go g (runStep g s)
part1 :: Input -> Int
part1 grid = (1 +) . countVisited . last $ runSimulation grid (initialState grid)
-- | Uses results from part1 to inform search space
part2 :: Input -> Int
part2 grid =
let
searchSpace = runSimulation grid (initialState grid)
in
traceShow (length searchSpace) length $ filter (findLoop grid) searchSpace
prepare :: [String] -> Input
prepare = Grid.fromList
main :: IO ()
main = run part1 part2 prepare