Advent of Code 2022 day 10

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.