From 513641d092864ad19418df36d90c0aada4125a38 Mon Sep 17 00:00:00 2001 From: Hamothy <58985301+sgoudham@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:54:10 -0800 Subject: [PATCH] Initial commit --- .env.example | 2 + .gitignore | 4 ++ LICENSE | 29 +++++++++++ advent-of-haskell.cabal | 95 ++++++++++++++++++++++++++++++++++ aoc/AOC.hs | 7 +++ aoc/AOC/API.hs | 101 ++++++++++++++++++++++++++++++++++++ aoc/AOC/Types.hs | 19 +++++++ cabal.project | 3 ++ runner/Main.hs | 112 ++++++++++++++++++++++++++++++++++++++++ runner/Tests.hs | 77 +++++++++++++++++++++++++++ scripts/mkdays.sh | 29 +++++++++++ solutions/Days/Day01.hs | 16 ++++++ solutions/Days/Day02.hs | 16 ++++++ solutions/Days/Day03.hs | 16 ++++++ solutions/Days/Day04.hs | 16 ++++++ solutions/Days/Day05.hs | 16 ++++++ solutions/Days/Day06.hs | 16 ++++++ solutions/Days/Day07.hs | 16 ++++++ solutions/Days/Day08.hs | 16 ++++++ solutions/Days/Day09.hs | 16 ++++++ solutions/Days/Day10.hs | 16 ++++++ solutions/Days/Day11.hs | 16 ++++++ solutions/Days/Day12.hs | 16 ++++++ solutions/Days/Day13.hs | 16 ++++++ solutions/Days/Day14.hs | 16 ++++++ solutions/Days/Day15.hs | 16 ++++++ solutions/Days/Day16.hs | 16 ++++++ solutions/Days/Day17.hs | 16 ++++++ solutions/Days/Day18.hs | 16 ++++++ solutions/Days/Day19.hs | 16 ++++++ solutions/Days/Day20.hs | 16 ++++++ solutions/Days/Day21.hs | 16 ++++++ solutions/Days/Day22.hs | 16 ++++++ solutions/Days/Day23.hs | 16 ++++++ solutions/Days/Day24.hs | 16 ++++++ solutions/Solutions.hs | 56 ++++++++++++++++++++ 36 files changed, 918 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 advent-of-haskell.cabal create mode 100644 aoc/AOC.hs create mode 100644 aoc/AOC/API.hs create mode 100644 aoc/AOC/Types.hs create mode 100644 cabal.project create mode 100644 runner/Main.hs create mode 100644 runner/Tests.hs create mode 100755 scripts/mkdays.sh create mode 100644 solutions/Days/Day01.hs create mode 100644 solutions/Days/Day02.hs create mode 100644 solutions/Days/Day03.hs create mode 100644 solutions/Days/Day04.hs create mode 100644 solutions/Days/Day05.hs create mode 100644 solutions/Days/Day06.hs create mode 100644 solutions/Days/Day07.hs create mode 100644 solutions/Days/Day08.hs create mode 100644 solutions/Days/Day09.hs create mode 100644 solutions/Days/Day10.hs create mode 100644 solutions/Days/Day11.hs create mode 100644 solutions/Days/Day12.hs create mode 100644 solutions/Days/Day13.hs create mode 100644 solutions/Days/Day14.hs create mode 100644 solutions/Days/Day15.hs create mode 100644 solutions/Days/Day16.hs create mode 100644 solutions/Days/Day17.hs create mode 100644 solutions/Days/Day18.hs create mode 100644 solutions/Days/Day19.hs create mode 100644 solutions/Days/Day20.hs create mode 100644 solutions/Days/Day21.hs create mode 100644 solutions/Days/Day22.hs create mode 100644 solutions/Days/Day23.hs create mode 100644 solutions/Days/Day24.hs create mode 100644 solutions/Solutions.hs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..87e3f5e --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +SESSION=YOUR_SESSION_COOKIE +YEAR=2022 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06c01f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist* +.env +.inputs/ +data/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e4393e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, IndecisionTree +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/advent-of-haskell.cabal b/advent-of-haskell.cabal new file mode 100644 index 0000000..79c63c4 --- /dev/null +++ b/advent-of-haskell.cabal @@ -0,0 +1,95 @@ +cabal-version: 3.0 +name: advent-of-haskell +version: 0.1.0.0 +synopsis: Framework for AoC +description: Run, submit, test, and benchmark Advent of Code solutions. +license: BSD-3-Clause +license-file: LICENSE +author: IndecisionTree +build-type: Simple + +common common + default-language: GHC2021 + ghc-options: -Wall + default-extensions: RecordWildCards + +executable runner + import: common + main-is: Main.hs + build-depends: + , aoc + , base ^>=4.16.3.0 + , containers ^>=0.6.5 + , directory ^>=1.3.6 + , dotenv ^>=0.9.0 + , filepath ^>=1.4.2 + , megaparsec ^>=9.3.0 + , optparse-applicative ^>=0.17.0 + , solutions + , tasty ^>=1.4.2 + , tasty-bench ^>=0.3.2 + , tasty-hunit ^>=0.10.0 + , text ^>=2.0 + + other-modules: Tests + hs-source-dirs: runner + +library aoc + import: common + exposed-modules: AOC + other-modules: + AOC.API + AOC.Types + + build-depends: + , base ^>=4.16.3.0 + , bytestring ^>=0.11.3 + , http-api-data ^>=0.5 + , http-client ^>=0.7.13 + , http-client-tls ^>=0.3.6 + , http-media ^>=0.8.0 + , servant ^>=0.19.1 + , servant-client ^>=0.19 + , text ^>=2.0 + , time ^>=1.11.1 + + hs-source-dirs: aoc + +library solutions + import: common + + -- cabal-fmt: expand solutions + exposed-modules: + Days.Day01 + Days.Day02 + Days.Day03 + Days.Day04 + Days.Day05 + Days.Day06 + Days.Day07 + Days.Day08 + Days.Day09 + Days.Day10 + Days.Day11 + Days.Day12 + Days.Day13 + Days.Day14 + Days.Day15 + Days.Day16 + Days.Day17 + Days.Day18 + Days.Day19 + Days.Day20 + Days.Day21 + Days.Day22 + Days.Day23 + Days.Day24 + Solutions + + build-depends: + , aoc + , base ^>=4.16.3.0 + , containers ^>=0.6.5 + , text ^>=2.0 + + hs-source-dirs: solutions diff --git a/aoc/AOC.hs b/aoc/AOC.hs new file mode 100644 index 0000000..f606666 --- /dev/null +++ b/aoc/AOC.hs @@ -0,0 +1,7 @@ +module AOC ( + module AOC.Types, + mkAocClient +) where + +import AOC.Types +import AOC.API diff --git a/aoc/AOC/API.hs b/aoc/AOC/API.hs new file mode 100644 index 0000000..03e96f8 --- /dev/null +++ b/aoc/AOC/API.hs @@ -0,0 +1,101 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeOperators #-} + +module AOC.API ( + mkAocClient, +) where + +import AOC.Types +import Control.Monad ((>=>)) +import Data.ByteString qualified as BS +import Data.Proxy (Proxy (..)) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Data.Time.Clock (addUTCTime, getCurrentTime) +import GHC.Conc (atomically, newTVar) +import Network.HTTP.Client (Cookie (..), createCookieJar) +import Network.HTTP.Client.TLS (newTlsManager) +import Network.HTTP.Media ((//), (/:)) +import Servant.API +import Servant.Client hiding ((//), (/:)) + +mkAocClient :: String -> Int -> Int -> IO (IO T.Text, Submission -> IO Answer) +mkAocClient session year day = do + env <- mkAocEnv session + let aocInput :<|> aocSubmit = aocClient year day + f = flip runClientM env >=> pure . either (error . show) id + aocInput' = f aocInput + aocSubmit' = f . aocSubmit + return (aocInput', aocSubmit') + +mkAocEnv :: String -> IO ClientEnv +mkAocEnv session = do + current <- getCurrentTime + manager <- newTlsManager + let + base = BaseUrl Https "adventofcode.com" 443 "" + env = mkClientEnv manager base + year = 60 * 60 * 24 * 365 + expiry = addUTCTime year current + cookie = + Cookie + { cookie_name = "session", + cookie_value = T.encodeUtf8 . T.pack $ session, + cookie_expiry_time = expiry, + cookie_domain = T.encodeUtf8 . T.pack $ baseUrlHost base, + cookie_path = "/", + cookie_creation_time = current, + cookie_last_access_time = current, + cookie_persistent = True, + cookie_host_only = True, + cookie_secure_only = True, + cookie_http_only = True + } + cookies <- atomically $ newTVar (createCookieJar [cookie]) + return $ env {cookieJar = Just cookies} + +aocClient :: Int -> Int -> (ClientM T.Text :<|> (Submission -> ClientM Answer)) +aocClient = client (Proxy @API) + +type API = + Capture "year" Int :> + ("day" :> Capture "day" Int :> + ( + -- GET /:year/day/:day/input + ("input" :> Get '[RawPlainText] T.Text) :<|> + -- POST /:year/day/:day/answer level=<1|2>&answer=_ + ("answer" :> ReqBody '[FormUrlEncoded] Submission :> Post '[HTML] Answer) + ) + ) + +-- This is silly: https://github.com/haskell-servant/servant/issues/1002 +data RawPlainText + +instance Accept RawPlainText where + contentType _ = "text" // "plain" + +instance MimeUnrender RawPlainText T.Text where + mimeUnrender _ = Right . T.decodeUtf8 . BS.toStrict + +-- TODO implement marshalling from HTML to 'Day', 'Year', and 'Answer' +-- types which extracts yearly calendars, day prompts, and +-- pre-existing correct answer submissions via scraping the received +-- HTML. + +data HTML + +instance Accept HTML where + contentType _ = "text" // "html" /: ("charset", "utf-8") + +data Answer = Correct | Low | High | Empty + +instance MimeUnrender HTML Answer where + mimeUnrender _ bs + | "correct" `BS.isInfixOf` bs' = Right Correct + | "low" `BS.isInfixOf` bs' = Right Low + | "high" `BS.isInfixOf` bs' = Right High + | "provide an answer" `BS.isInfixOf` bs' = Right Empty + | otherwise = Left "Unknown answer response" + where + bs' = BS.toStrict bs diff --git a/aoc/AOC/Types.hs b/aoc/AOC/Types.hs new file mode 100644 index 0000000..1ea2ba0 --- /dev/null +++ b/aoc/AOC/Types.hs @@ -0,0 +1,19 @@ +{-# language GADTs #-} +module AOC.Types ( + Solution (..), + Submission (..) +) where + +import Data.Text (Text) +import GHC.Generics (Generic) +import Web.Internal.FormUrlEncoded (ToForm) + +data Solution where + Solution :: Show b => (Text -> a) -> (a -> b) -> (a -> b) -> Solution + +data Submission = Submission { + part :: Int, + answer :: String +} deriving (Generic) + +instance ToForm Submission diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..9d1ef45 --- /dev/null +++ b/cabal.project @@ -0,0 +1,3 @@ +with-compiler: ghc-9.2.4 + +packages: . diff --git a/runner/Main.hs b/runner/Main.hs new file mode 100644 index 0000000..0d28de7 --- /dev/null +++ b/runner/Main.hs @@ -0,0 +1,112 @@ +module Main where + +import AOC (Solution (..), mkAocClient) +import Configuration.Dotenv (defaultConfig, loadFile) +import Control.Exception (IOException, catch) +import Control.Monad (when) +import Data.Functor (void) +import Data.IntMap.Strict qualified as M +import Data.Text qualified as T +import Data.Text.IO qualified as T +import Options.Applicative +import Solutions (solutions) +import System.Directory (createDirectoryIfMissing) +import System.Environment (getEnv) +import Tests (test) +import Text.Printf (printf) + +-- | CLI Options +data Options = Option + { _command :: Action, + _day :: Int, + _part :: Maybe Int + } + +-- | CLI Commands +data Action = Run | Test | Bench | Submit + deriving (Show) + +main :: IO () +main = do + void $ loadFile defaultConfig + + session <- getEnv "SESSION" + year <- read <$> getEnv "YEAR" + + Option {..} <- customExecParser (prefs showHelpOnEmpty) opts + + when (_day < 1 || _day > 24) $ do + fail $ printf "Day '%d' is out of range (1-24)" _day + + (aocInput, aocSubmit) <- mkAocClient session year _day + + input <- getPuzzleInput _day aocInput + let day = solutions M.! _day + + case _command of + Run -> putStr $ run day _part input + Test -> test day _day _part + a -> fail $ "Unimplemented: " ++ show a + +-- | Retrieve puzzle input for a given day from a file. If no file is +-- found, hit the api. +getPuzzleInput :: Int -> IO T.Text -> IO T.Text +getPuzzleInput day aocInput = + T.readFile fp `catch` \(_ :: IOException) -> fetchInput + where + fp = printf ".inputs/%d.txt" day + + fetchInput = do + input <- aocInput + createDirectoryIfMissing True ".inputs" + T.writeFile fp input + return input + +-- | Run solution (optionally just part) on input and return the +-- result as a string ready for submission +run :: Solution -> Maybe Int -> T.Text -> String +run (Solution pInput part1 part2) part input = + case part of + Nothing -> printf "Part 1: %s\nPart 2: %s\n" part1' part2' + Just n -> printf "Part %d: %s\n" n $ if n == 1 then part1' else part2' + where + parsed = pInput input + part1' = show $ part1 parsed + part2' = show $ part2 parsed + +-- | CLI parser +opts :: ParserInfo Options +opts = + info + (commands <**> helper) + ( fullDesc + <> progDesc "Run, benchmark, test, or submit an AOC day" + <> header "runner - an AOC solution runner" + ) + where + commands = subparser $ runCmd <> testCmd <> benchCmd <> submitCmd + runCmd = + mkCmd + "run" + (Option Run <$> day <*> optional part) + "Run a given day, optionally specifying which part" + testCmd = + mkCmd + "test" + (Option Test <$> day <*> optional part) + "Test a given day, optionally specifying which part" + benchCmd = + mkCmd + "bench" + (Option Bench <$> day <*> optional part) + "Benchmark a given day, optionally specifying which part" + submitCmd = + mkCmd + "submit" + (Option Submit <$> day <*> (Just <$> part)) + "Run and submit the answer to a given day and part" + + day = argument auto (metavar "DAY") + part = argument auto (metavar "PART") + + mkCmd cmd f desc = command cmd (info f (progDesc desc)) diff --git a/runner/Tests.hs b/runner/Tests.hs new file mode 100644 index 0000000..fc269d6 --- /dev/null +++ b/runner/Tests.hs @@ -0,0 +1,77 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Tests (test) where + +import AOC (Solution (..)) +import Data.Text qualified as T +import Data.Text.IO qualified as T +import Data.Void (Void) +import System.FilePath ((<.>), ()) +import Test.Tasty (TestTree, testGroup) +import Test.Tasty.HUnit (testCase, (@=?)) +import Test.Tasty.Ingredients (tryIngredients) +import Test.Tasty.Ingredients.Basic (consoleTestReporter) +import Text.Megaparsec +import Text.Megaparsec.Char (newline, space) +import Text.Megaparsec.Char.Lexer qualified as L + +data TestInput = TestInput + { _testName :: T.Text, + _testInput :: T.Text, + _testExpected :: T.Text + } + deriving (Show) + +type Parser = Parsec Void T.Text + +-- | Test solution via tests in 'data/examples/(Day #)/part{1,2}.txt' +test :: Solution -> Int -> Maybe Int -> IO () +test (Solution pInput part1 part2) day part = do + p1 <- T.readFile $ "data" "examples" show day "part1" <.> "txt" + p2 <- T.readFile $ "data" "examples" show day "part2" <.> "txt" + + let p1Tests = + testGroup "Part 1" + . fmap (mkTest pInput part1) + . either (error . errorBundlePretty) id + . parse pTests "part1.txt" + $ p1 + p2Tests = + testGroup "Part 2" + . fmap (mkTest pInput part2) + . either (error . errorBundlePretty) id + . parse pTests "part2.txt" + $ p2 + tests = testGroup ("Day " ++ show day) $ case part of + Nothing -> [p1Tests, p2Tests] + Just 1 -> [p1Tests] + _ -> [p2Tests] + + case tryIngredients [consoleTestReporter] mempty tests of + Nothing -> error $ "Error running tests for day " ++ show day + Just act -> do + res <- act + putStrLn $ if res then "Success" else "Failure" + +-- | Given an input parser, part1 or part2 function, and a test input, +-- generate an HUnit test. +mkTest :: Show b => (T.Text -> a) -> (a -> b) -> TestInput -> TestTree +mkTest pInput part TestInput {..} = + testCase (T.unpack _testName) $ T.unpack _testExpected @=? result + where + result = show . part $ pInput _testInput + +-- | Parse test files into TestInput's +pTests :: Parser [TestInput] +pTests = many pTest <* eof + where + pTest = TestInput <$> pName <*> pInput <*> pExpected "Test" + pName = T.pack <$> (symbol "-" *> lexeme (some (anySingleBut '\n'))) "Test Name" + pInput = T.pack <$> someTill anySingle (symbol "==") "Input Lines" + pExpected = T.pack <$> many (anySingleBut '\n') <* newline "Expected Output" + +lexeme :: Parser a -> Parser a +lexeme = L.lexeme space + +symbol :: T.Text -> Parser T.Text +symbol = L.symbol space diff --git a/scripts/mkdays.sh b/scripts/mkdays.sh new file mode 100755 index 0000000..5da9195 --- /dev/null +++ b/scripts/mkdays.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +solutions="solutions/Days" + +cd "$(git rev-parse --show-toplevel)" + +mkdir "$solutions" + +for i in $(printf "%02i " {1..24}) +do + cat << EOF > "$solutions/Day$i.hs" +module Days.Day$i (day$i) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day$i :: Solution +day$i = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day $i" + +part1 :: a -> Int +part1 = error "part1 not defined for day $i" + +part2 :: a -> Int +part2 = error "part2 not defined for day $i" +EOF +done diff --git a/solutions/Days/Day01.hs b/solutions/Days/Day01.hs new file mode 100644 index 0000000..a2ef1ab --- /dev/null +++ b/solutions/Days/Day01.hs @@ -0,0 +1,16 @@ +module Days.Day01 (day01) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day01 :: Solution +day01 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 01" + +part1 :: a -> Int +part1 = error "part1 not defined for day 01" + +part2 :: a -> Int +part2 = error "part2 not defined for day 01" diff --git a/solutions/Days/Day02.hs b/solutions/Days/Day02.hs new file mode 100644 index 0000000..77fe1aa --- /dev/null +++ b/solutions/Days/Day02.hs @@ -0,0 +1,16 @@ +module Days.Day02 (day02) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day02 :: Solution +day02 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 02" + +part1 :: a -> Int +part1 = error "part1 not defined for day 02" + +part2 :: a -> Int +part2 = error "part2 not defined for day 02" diff --git a/solutions/Days/Day03.hs b/solutions/Days/Day03.hs new file mode 100644 index 0000000..1447cc5 --- /dev/null +++ b/solutions/Days/Day03.hs @@ -0,0 +1,16 @@ +module Days.Day03 (day03) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day03 :: Solution +day03 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 03" + +part1 :: a -> Int +part1 = error "part1 not defined for day 03" + +part2 :: a -> Int +part2 = error "part2 not defined for day 03" diff --git a/solutions/Days/Day04.hs b/solutions/Days/Day04.hs new file mode 100644 index 0000000..fcb50a7 --- /dev/null +++ b/solutions/Days/Day04.hs @@ -0,0 +1,16 @@ +module Days.Day04 (day04) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day04 :: Solution +day04 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 04" + +part1 :: a -> Int +part1 = error "part1 not defined for day 04" + +part2 :: a -> Int +part2 = error "part2 not defined for day 04" diff --git a/solutions/Days/Day05.hs b/solutions/Days/Day05.hs new file mode 100644 index 0000000..85af2ec --- /dev/null +++ b/solutions/Days/Day05.hs @@ -0,0 +1,16 @@ +module Days.Day05 (day05) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day05 :: Solution +day05 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 05" + +part1 :: a -> Int +part1 = error "part1 not defined for day 05" + +part2 :: a -> Int +part2 = error "part2 not defined for day 05" diff --git a/solutions/Days/Day06.hs b/solutions/Days/Day06.hs new file mode 100644 index 0000000..60418bc --- /dev/null +++ b/solutions/Days/Day06.hs @@ -0,0 +1,16 @@ +module Days.Day06 (day06) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day06 :: Solution +day06 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 06" + +part1 :: a -> Int +part1 = error "part1 not defined for day 06" + +part2 :: a -> Int +part2 = error "part2 not defined for day 06" diff --git a/solutions/Days/Day07.hs b/solutions/Days/Day07.hs new file mode 100644 index 0000000..8f314bf --- /dev/null +++ b/solutions/Days/Day07.hs @@ -0,0 +1,16 @@ +module Days.Day07 (day07) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day07 :: Solution +day07 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 07" + +part1 :: a -> Int +part1 = error "part1 not defined for day 07" + +part2 :: a -> Int +part2 = error "part2 not defined for day 07" diff --git a/solutions/Days/Day08.hs b/solutions/Days/Day08.hs new file mode 100644 index 0000000..79dbcfe --- /dev/null +++ b/solutions/Days/Day08.hs @@ -0,0 +1,16 @@ +module Days.Day08 (day08) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day08 :: Solution +day08 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 08" + +part1 :: a -> Int +part1 = error "part1 not defined for day 08" + +part2 :: a -> Int +part2 = error "part2 not defined for day 08" diff --git a/solutions/Days/Day09.hs b/solutions/Days/Day09.hs new file mode 100644 index 0000000..72f40c1 --- /dev/null +++ b/solutions/Days/Day09.hs @@ -0,0 +1,16 @@ +module Days.Day09 (day09) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day09 :: Solution +day09 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 09" + +part1 :: a -> Int +part1 = error "part1 not defined for day 09" + +part2 :: a -> Int +part2 = error "part2 not defined for day 09" diff --git a/solutions/Days/Day10.hs b/solutions/Days/Day10.hs new file mode 100644 index 0000000..296a502 --- /dev/null +++ b/solutions/Days/Day10.hs @@ -0,0 +1,16 @@ +module Days.Day10 (day10) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day10 :: Solution +day10 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 10" + +part1 :: a -> Int +part1 = error "part1 not defined for day 10" + +part2 :: a -> Int +part2 = error "part2 not defined for day 10" diff --git a/solutions/Days/Day11.hs b/solutions/Days/Day11.hs new file mode 100644 index 0000000..a324534 --- /dev/null +++ b/solutions/Days/Day11.hs @@ -0,0 +1,16 @@ +module Days.Day11 (day11) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day11 :: Solution +day11 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 11" + +part1 :: a -> Int +part1 = error "part1 not defined for day 11" + +part2 :: a -> Int +part2 = error "part2 not defined for day 11" diff --git a/solutions/Days/Day12.hs b/solutions/Days/Day12.hs new file mode 100644 index 0000000..b7ec370 --- /dev/null +++ b/solutions/Days/Day12.hs @@ -0,0 +1,16 @@ +module Days.Day12 (day12) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day12 :: Solution +day12 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 12" + +part1 :: a -> Int +part1 = error "part1 not defined for day 12" + +part2 :: a -> Int +part2 = error "part2 not defined for day 12" diff --git a/solutions/Days/Day13.hs b/solutions/Days/Day13.hs new file mode 100644 index 0000000..c19e12e --- /dev/null +++ b/solutions/Days/Day13.hs @@ -0,0 +1,16 @@ +module Days.Day13 (day13) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day13 :: Solution +day13 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 13" + +part1 :: a -> Int +part1 = error "part1 not defined for day 13" + +part2 :: a -> Int +part2 = error "part2 not defined for day 13" diff --git a/solutions/Days/Day14.hs b/solutions/Days/Day14.hs new file mode 100644 index 0000000..28ca0bc --- /dev/null +++ b/solutions/Days/Day14.hs @@ -0,0 +1,16 @@ +module Days.Day14 (day14) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day14 :: Solution +day14 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 14" + +part1 :: a -> Int +part1 = error "part1 not defined for day 14" + +part2 :: a -> Int +part2 = error "part2 not defined for day 14" diff --git a/solutions/Days/Day15.hs b/solutions/Days/Day15.hs new file mode 100644 index 0000000..444e826 --- /dev/null +++ b/solutions/Days/Day15.hs @@ -0,0 +1,16 @@ +module Days.Day15 (day15) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day15 :: Solution +day15 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 15" + +part1 :: a -> Int +part1 = error "part1 not defined for day 15" + +part2 :: a -> Int +part2 = error "part2 not defined for day 15" diff --git a/solutions/Days/Day16.hs b/solutions/Days/Day16.hs new file mode 100644 index 0000000..f4fca56 --- /dev/null +++ b/solutions/Days/Day16.hs @@ -0,0 +1,16 @@ +module Days.Day16 (day16) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day16 :: Solution +day16 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 16" + +part1 :: a -> Int +part1 = error "part1 not defined for day 16" + +part2 :: a -> Int +part2 = error "part2 not defined for day 16" diff --git a/solutions/Days/Day17.hs b/solutions/Days/Day17.hs new file mode 100644 index 0000000..ccc1e52 --- /dev/null +++ b/solutions/Days/Day17.hs @@ -0,0 +1,16 @@ +module Days.Day17 (day17) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day17 :: Solution +day17 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 17" + +part1 :: a -> Int +part1 = error "part1 not defined for day 17" + +part2 :: a -> Int +part2 = error "part2 not defined for day 17" diff --git a/solutions/Days/Day18.hs b/solutions/Days/Day18.hs new file mode 100644 index 0000000..2656f53 --- /dev/null +++ b/solutions/Days/Day18.hs @@ -0,0 +1,16 @@ +module Days.Day18 (day18) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day18 :: Solution +day18 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 18" + +part1 :: a -> Int +part1 = error "part1 not defined for day 18" + +part2 :: a -> Int +part2 = error "part2 not defined for day 18" diff --git a/solutions/Days/Day19.hs b/solutions/Days/Day19.hs new file mode 100644 index 0000000..f1b08c4 --- /dev/null +++ b/solutions/Days/Day19.hs @@ -0,0 +1,16 @@ +module Days.Day19 (day19) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day19 :: Solution +day19 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 19" + +part1 :: a -> Int +part1 = error "part1 not defined for day 19" + +part2 :: a -> Int +part2 = error "part2 not defined for day 19" diff --git a/solutions/Days/Day20.hs b/solutions/Days/Day20.hs new file mode 100644 index 0000000..9dc0ace --- /dev/null +++ b/solutions/Days/Day20.hs @@ -0,0 +1,16 @@ +module Days.Day20 (day20) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day20 :: Solution +day20 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 20" + +part1 :: a -> Int +part1 = error "part1 not defined for day 20" + +part2 :: a -> Int +part2 = error "part2 not defined for day 20" diff --git a/solutions/Days/Day21.hs b/solutions/Days/Day21.hs new file mode 100644 index 0000000..be883aa --- /dev/null +++ b/solutions/Days/Day21.hs @@ -0,0 +1,16 @@ +module Days.Day21 (day21) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day21 :: Solution +day21 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 21" + +part1 :: a -> Int +part1 = error "part1 not defined for day 21" + +part2 :: a -> Int +part2 = error "part2 not defined for day 21" diff --git a/solutions/Days/Day22.hs b/solutions/Days/Day22.hs new file mode 100644 index 0000000..428bcd1 --- /dev/null +++ b/solutions/Days/Day22.hs @@ -0,0 +1,16 @@ +module Days.Day22 (day22) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day22 :: Solution +day22 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 22" + +part1 :: a -> Int +part1 = error "part1 not defined for day 22" + +part2 :: a -> Int +part2 = error "part2 not defined for day 22" diff --git a/solutions/Days/Day23.hs b/solutions/Days/Day23.hs new file mode 100644 index 0000000..61b2151 --- /dev/null +++ b/solutions/Days/Day23.hs @@ -0,0 +1,16 @@ +module Days.Day23 (day23) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day23 :: Solution +day23 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 23" + +part1 :: a -> Int +part1 = error "part1 not defined for day 23" + +part2 :: a -> Int +part2 = error "part2 not defined for day 23" diff --git a/solutions/Days/Day24.hs b/solutions/Days/Day24.hs new file mode 100644 index 0000000..99f1634 --- /dev/null +++ b/solutions/Days/Day24.hs @@ -0,0 +1,16 @@ +module Days.Day24 (day24) where + +import AOC (Solution (..)) +import qualified Data.Text as T + +day24 :: Solution +day24 = Solution parseInput part1 part2 + +parseInput :: T.Text -> a +parseInput = error "parseInput not defined for day 24" + +part1 :: a -> Int +part1 = error "part1 not defined for day 24" + +part2 :: a -> Int +part2 = error "part2 not defined for day 24" diff --git a/solutions/Solutions.hs b/solutions/Solutions.hs new file mode 100644 index 0000000..48317f0 --- /dev/null +++ b/solutions/Solutions.hs @@ -0,0 +1,56 @@ +module Solutions (solutions) where + +import qualified Data.IntMap.Strict as M +import AOC (Solution) + +import Days.Day01 (day01) +import Days.Day02 (day02) +import Days.Day03 (day03) +import Days.Day04 (day04) +import Days.Day05 (day05) +import Days.Day06 (day06) +import Days.Day07 (day07) +import Days.Day08 (day08) +import Days.Day09 (day09) +import Days.Day10 (day10) +import Days.Day11 (day11) +import Days.Day12 (day12) +import Days.Day13 (day13) +import Days.Day14 (day14) +import Days.Day15 (day15) +import Days.Day16 (day16) +import Days.Day17 (day17) +import Days.Day18 (day18) +import Days.Day19 (day19) +import Days.Day20 (day20) +import Days.Day21 (day21) +import Days.Day22 (day22) +import Days.Day23 (day23) +import Days.Day24 (day24) + +solutions :: M.IntMap Solution +solutions = M.fromList [ + (1, day01), + (2, day02), + (3, day03), + (4, day04), + (5, day05), + (6, day06), + (7, day07), + (8, day08), + (9, day09), + (10, day10), + (11, day11), + (12, day12), + (13, day13), + (14, day14), + (15, day15), + (16, day16), + (17, day17), + (18, day18), + (19, day19), + (20, day20), + (21, day21), + (22, day22), + (23, day23), + (24, day24) ]