Day 10 was some mostly list/stream processing, but with some finicky rules about counting them. The core of the problem was to find the signals, a pair of the register value and the cycle during which it had that value.
Running the script
Parsing the input was standard attoparsec.
data Operation = Noop | Addx Int
deriving (Show, Eq)
operationsP = operationP `sepBy` endOfLine
operationP = noopP <|> addxP
noopP = Noop <$ "noop"
addxP = Addx <$> ("addx " *> signed decimal)
Applying the operations was a two-step process. First, I converted each operation into the one or two cycles it took and the change in register value after that cycle. Second, I combined those changes (with scanl
and zip
) into a trace of the register value and the cycle during which it held.
type Signal = (Int, Int) -- during cycle, register
apply :: [Operation] -> [Signal]
apply = zip [1..] . scanl' (+) 1 . concatMap applyOp
applyOp :: Operation -> [Int]
applyOp Noop = [0]
applyOp (Addx d) = [0, d]
That took some care, and comparison to the examples, to check there were no off-by-one errors.
Part 1
This was a bit of arithmetic to find the correct cycles of interest (in a filter
), then a list comprehension to do the multiplications.
part1 :: [Signal] -> Int
part1 = calculateSixSignals . extractSignals
extractSignals :: [Signal] -> [Signal]
extractSignals = filter (\(t, _n) -> (t + 20) `mod` 40 == 0)
calculateSixSignals :: [Signal] -> Int
calculateSixSignals signals = sum [ (t * n)
| (t, n) <- signals
, t <= 220
]
Part 2
There was another source of off-by-one errors here. While the time counts up from 1, the display column starts at 0. That gets handled in columnOf
, extracting the column of the CRT's ray during each cycle. Given that, isLit
returns if the pixel is within the sprite, and showPixel
converts a pixel value into the displayed representation.
All that's left is to chunk the pixels into rows and combine them.
part2 :: [Signal] -> String
part2 regVals = unlines display
where pixels = map (showPixel . isLit) regVals
display = chunksOf 40 pixels
isLit :: Signal -> Bool
isLit (n, x) = abs (x - c) <= 1
where c = colummOf n
colummOf :: Int -> Int
colummOf n = (n - 1) `mod` 40
showPixel :: Bool -> Char
-- showPixel True = '#'
-- showPixel False = '.'
showPixel True = '█'
showPixel False = ' '
Code
You can get the code from my locally-hosted Git repo, or from Gitlab.