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.

    Neil Smith

    Read more posts by this author.