summaryrefslogtreecommitdiff
path: root/Day10/app/Main.hs
blob: d43370945af6ead47b6d0dcad84b1bba6be0a00d (plain) (blame)
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
module Main (main) where

import System.Environment ( getArgs )
import Text.Read (readMaybe)
import Data.Bifunctor (bimap)

main :: IO ()
main = getArgs >>= readFile . head >>= putStr . solveDay10

solveDay10 :: String -> String
solveDay10 = formatDay10Result . fmap solveDay10Parts . parseDay10Input

formatDay10Result :: Either String (Int, String) -> String
formatDay10Result (Left s) = s
formatDay10Result (Right (p1, p2)) = "Part 1: " ++ show p1 ++ "\n\nPart 2:\n" ++ p2

data Command = NoOp | AddX Int

parseDay10Input :: String -> Either String [Command]
parseDay10Input = mapM parseCommand . lines

parseCommand :: String -> Either String Command
parseCommand "noop" = Right NoOp
parseCommand ('a':'d':'d':'x':' ':ss) =  maybe (Left "Invalid parameter for addx instruction") (Right . AddX) $ readMaybe ss 
parseCommand x = Left $ "Unknown instruction: " ++ x

solveDay10Parts :: [Command] -> (Int, String)
solveDay10Parts = bimap solveDay9Part1 solveDay9Part2 . dup
    where dup x = (x,x)

newtype CycleCount = CycleCount Int deriving (Eq, Ord)
newtype RegisterValue = RegisterValue Int
newtype SignalStrength = SignalStrength Int
newtype CpuState = CpuState (CycleCount, RegisterValue)

newtype DeltaCpuStates = DeltaCpuStates [CpuState]
unDeltaCpuStates :: DeltaCpuStates -> [CpuState]
unDeltaCpuStates (DeltaCpuStates d) = d

initialCpuState :: CpuState
initialCpuState = CpuState (CycleCount 1, RegisterValue 1)

getCycleCount :: CpuState -> CycleCount
getCycleCount (CpuState (c,_)) = c

nextCycle :: CycleCount -> CycleCount
nextCycle (CycleCount c) = CycleCount (c+1)

unSignalStrength :: SignalStrength -> Int
unSignalStrength (SignalStrength a) = a

signalStrength :: CycleCount -> RegisterValue -> SignalStrength
signalStrength (CycleCount c) (RegisterValue r) = SignalStrength (c*r)

getRegisterValue :: CpuState -> RegisterValue
getRegisterValue (CpuState (_,r)) = r

solveDay9Part1 :: [Command] -> Int 
solveDay9Part1 = getSumOfRelevantSignalStrengths . cpuStates

cpuStates :: [Command] -> DeltaCpuStates -- this could be solved using scanl, but the problem is easier to solve using a dedicated worker function.
cpuStates = cpuStatesWorker initialCpuState

cpuStatesWorker :: CpuState -> [Command] -> DeltaCpuStates
cpuStatesWorker c [] = DeltaCpuStates $ return c
cpuStatesWorker (CpuState (CycleCount c, r)) (NoOp:cs) = cpuStatesWorker (CpuState (CycleCount (c+1), r)) cs
cpuStatesWorker (CpuState (CycleCount c, RegisterValue r)) ((AddX x):cs) = DeltaCpuStates $ newState:unDeltaCpuStates (cpuStatesWorker newState cs)
    where newState = CpuState (CycleCount (c+2), RegisterValue (r+x))

getSumOfRelevantSignalStrengths :: DeltaCpuStates -> Int
getSumOfRelevantSignalStrengths s = sum $ map (unSignalStrength . getSignalStrengthAtCycleCount s . CycleCount) $ take 6 $ iterate (+40) 20

getSignalStrengthAtCycleCount :: DeltaCpuStates -> CycleCount -> SignalStrength
getSignalStrengthAtCycleCount s c = signalStrength c $ getRegisterAtCycleCount s c

getRegisterAtCycleCount :: DeltaCpuStates -> CycleCount -> RegisterValue
getRegisterAtCycleCount s c = getRegisterValue $ last $ takeWhile (\(CpuState (cc,_)) -> cc <= c) $ unDeltaCpuStates s

-- Part 2 is basically: for each cycle, check if RegisterValue is CycleCount-1, CycleCount, or CycleCount+1, and if yes, print '#', otherwise print '.'.

newtype ScreenWidth = ScreenWidth Int

solveDay9Part2 :: [Command] -> String
solveDay9Part2 = addNewlines (ScreenWidth 40) . take 240 . map (toPixel (ScreenWidth 40)) . expandCycles . cpuStates

expandCycles :: DeltaCpuStates -> [CpuState]
expandCycles (DeltaCpuStates c) = expandCyclesWorker c $ CycleCount 1
    where expandCyclesWorker (c1:cs) i | getCycleCount c1 > i = CpuState (i,RegisterValue 1):expandCyclesWorker (c1:cs) (nextCycle i) -- first few steps
          -- If we have still at least two states, we have to check if the current is still good, if not we have to advance.
          expandCyclesWorker (c1:c2:cs) i | getCycleCount c2 > i = CpuState (i,getRegisterValue c1):expandCyclesWorker (c1:c2:cs) (nextCycle i)
          expandCyclesWorker (c1:c2:cs) i = expandCyclesWorker (c2:cs) i
          -- If we are at the end, repeat last value infinitely.
          expandCyclesWorker (c:_) i = CpuState (i, getRegisterValue c):expandCyclesWorker [c] (nextCycle i) 


toPixel :: ScreenWidth -> CpuState -> Char
toPixel (ScreenWidth w) (CpuState (CycleCount c, RegisterValue r)) = if isBrightPixel then '#' else '.'
    where isBrightPixel = abs (((c-1) `mod` w)-r) <=1 -- c-1 because pixels start at 0, cycles start at 1... Because of course they do.

addNewlines :: ScreenWidth -> String -> String
addNewlines (ScreenWidth i) = addNewLinesWorker i 0
    where addNewLinesWorker every current (c:cs) | current /= 0 && current `mod` every == 0 = '\n':c:addNewLinesWorker every (current + 1) cs
          addNewLinesWorker every current (c:cs) = c:addNewLinesWorker every (current + 1) cs
          addNewLinesWorker every current [] = "\n"