🏡 index : ~doyle/aoc.git

#!/usr/bin/env nix-shell
#!nix-shell -i runghc -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ ])"

import Control.Applicative ((<*))
import Text.Parsec
import Text.Parsec.Char
import Text.Parsec.Combinator
import Text.Parsec.String (Parser)

{- https://adventofcode.com/2023/day/6 -}

main = do
  game <- readAndParseStdin
  print $ part1 game
  print $ part2 game

-- returns the product of how many winning times there are per game
part1 :: [(Int, Int)] -> Int
part1 = product . map (length . getWinningTimes)
  where
    getWinningTimes (time, winCond) = filter (> winCond) $ map (`distance` time) [1 .. time - 1]

-- concatenates all games into one big game and returns how many winning times
-- there are in the big game
part2 :: [(Int, Int)] -> Int
part2 game = part1 [foldl concatenate (0, 0) game]
  where
    concatenate (tAcc, dAcc) (t, d) = (tAcc * scale t + t, dAcc * scale d + d)

-- calculates the "scale" of a number + 1 and returns the magnitude ie. 8 -> 10, 23 -> 100, 694 -> 1000
scale :: Int -> Int
scale n
  | n < 10 = 10
  | otherwise = 10 * scale (n `div` 10)

-- calculates distance travelled in a game based on velocity * time minus "button pressing time"
distance :: Int -> Int -> Int
distance v t = v * (t - v)

-- reads stdin and parses game
readAndParseStdin :: IO [(Int, Int)]
readAndParseStdin = do
  content <- getContents
  case parse gameParser "" content of
    Left parseError -> error $ show parseError
    Right game -> return game

-- parses `Time: [n1] [n2] [n3]\nDistance:[n4] [n5] [n6]` and returns `[(n1, n4), (n2, n5), (n3, n6)]`
gameParser :: Parser [(Int, Int)]
gameParser = do
  time <- map read <$> (string "Time:" *> spaces *> many1 digit `endBy` spaces)
  distance <- map read <$> (string "Distance:" *> spaces *> many1 digit `endBy` spaces)
  return $ zip time distance