|
1 | 1 | module Days.Day02 (day02) where
|
2 | 2 |
|
3 | 3 | import AOC (Solution (..))
|
4 |
| -import Control.Arrow ((>>>), (***), second) |
| 4 | +import Control.Arrow ((<<<), (***), second) |
5 | 5 | import Data.Maybe (fromMaybe)
|
6 | 6 | import qualified Data.Text as T
|
7 | 7 |
|
8 | 8 | day02 :: Solution
|
9 | 9 | day02 = Solution parseInput part1 part2
|
10 | 10 |
|
| 11 | +-- | an RPS move |
11 | 12 | data RPS = Rock | Paper | Scissors
|
12 | 13 | deriving (Show, Eq, Enum, Bounded)
|
13 | 14 |
|
14 |
| -data Action = Lose | Draw | Win |
| 15 | +-- | a game outcome |
| 16 | +data Outcome = Lose | Draw | Win |
15 | 17 | deriving (Show, Eq)
|
16 | 18 |
|
| 19 | +-- | Parse the input into tuples consisting of (RPS move, "XYZ" Char). |
17 | 20 | parseInput :: T.Text -> [(RPS, T.Text)]
|
18 |
| -parseInput = fmap (T.breakOn " " >>> translate table *** T.tail) . T.lines |
| 21 | +parseInput = fmap (lookup' table *** T.tail <<< T.breakOn " ") . T.lines |
19 | 22 | where
|
20 | 23 | table = [("A", Rock), ("B", Paper), ("C", Scissors)]
|
21 | 24 |
|
| 25 | +-- | Part 1, translate the "XYZ" chars into RPS moves, score the |
| 26 | +-- results, sum them together. |
22 | 27 | part1 :: [(RPS, T.Text)] -> Int
|
23 |
| -part1 = sum . fmap (score . second (translate table)) |
| 28 | +part1 = sum . fmap (score . second (lookup' table)) |
24 | 29 | where
|
25 | 30 | table = [("X", Rock), ("Y", Paper), ("Z", Scissors)]
|
26 | 31 |
|
| 32 | +-- | Part 2, translate the "XYZ" chars into game outcome Actions, |
| 33 | +-- match those actions with the necessary RPS move to achieve the |
| 34 | +-- desired action, score the results, and sum them together. |
27 | 35 | part2 :: [(RPS, T.Text)] -> Int
|
28 |
| -part2 = sum . fmap (score . matchAction . second (translate table)) |
| 36 | +part2 = sum . fmap (score . matchAction . second (lookup' table)) |
29 | 37 | where
|
30 | 38 | table = [("X", Lose), ("Y", Draw), ("Z", Win)]
|
31 | 39 |
|
32 |
| -translate :: (Show a, Eq a) => [(a, b)] -> a -> b |
33 |
| -translate table ch = |
34 |
| - fromMaybe (error $ "Lookup failed: " ++ show ch) $ lookup ch table |
| 40 | +-- | Wrapper around 'lookup' that 'error's out if the lookup fails. |
| 41 | +lookup' :: (Show a, Eq a) => [(a, b)] -> a -> b |
| 42 | +lookup' table a = |
| 43 | + fromMaybe (error $ "Lookup failed: " ++ show a) $ lookup a table |
35 | 44 |
|
36 |
| -matchAction :: (RPS, Action) -> (RPS, RPS) |
| 45 | +-- | Translate a desired 'Outcome' into an 'RPS' move based on the |
| 46 | +-- 'RPS' move given for player 1 in the first tuple position. |
| 47 | +matchAction :: (RPS, Outcome) -> (RPS, RPS) |
37 | 48 | matchAction (rps, action) = (rps, match)
|
38 | 49 | where
|
39 | 50 | match = case action of
|
40 | 51 | Lose -> prevRPS rps
|
41 | 52 | Draw -> rps
|
42 | 53 | Win -> nextRPS rps
|
43 | 54 |
|
| 55 | +-- | Score an RPS round. Uses the derived 'Enum' instance for 'RPS' to |
| 56 | +-- determine a moves individual value. |
44 | 57 | score :: (RPS, RPS) -> Int
|
45 | 58 | score (a, b) = (fromEnum b + 1) + beats a b
|
46 | 59 |
|
| 60 | +-- | Determine the points received by player 2 when facing player 1's |
| 61 | +-- move. |
47 | 62 | beats :: RPS -> RPS -> Int
|
48 |
| -beats a b |
49 |
| - | nextRPS a == b = 6 |
50 |
| - | a == b = 3 |
| 63 | +beats p1 p2 |
| 64 | + | nextRPS p1 == p2 = 6 |
| 65 | + | p1 == p2 = 3 |
51 | 66 | | otherwise = 0
|
52 | 67 |
|
| 68 | +-- | Bounded successor/predecessor for 'RPS' with wrap-around at both |
| 69 | +-- upper and lower bounds. Uses 'RPS's derived 'Enum' and 'Bounded' |
| 70 | +-- instances. |
53 | 71 | nextRPS, prevRPS :: RPS -> RPS
|
54 | 72 | nextRPS rps
|
55 | 73 | | rps == maxBound = minBound
|
|
0 commit comments