never executed always true always false
1 {-|
2 Module: Y2015.D21
3 Description: Advent of Code Day 21 Solutions.
4 License: MIT
5 Maintainer: @tylerjl
6
7 Solutions to the day 21 set of problems for <adventofcode.com>.
8 -}
9
10 module Y2015.D21
11 ( battle
12 , cheapestVictory
13 , highestLoss
14 , toBoss
15 , mkTestCombatant
16 )
17 where
18
19 import Control.Monad (replicateM)
20 import Data.List (maximumBy, minimumBy)
21 import Data.Ord (comparing)
22
23 data Item = Item { cost :: Int
24 , armor :: Int
25 , damage :: Int
26 } deriving (Show)
27
28 data Combatant = Combatant { hp :: Int
29 , items :: [Item]
30 } deriving (Show)
31
32 -- |Utility function to generate a cheap test 'Combatant'.
33 mkTestCombatant :: Combatant -- ^ Low-complexity 'Combatant'.
34 mkTestCombatant =
35 Combatant
36 { hp = 8
37 , items = [ item {damage = 5, armor = 5} ]
38 }
39
40 -- |Finds the worst possible loss given combatant stats.
41 highestLoss :: String -- ^ Raw combatant stats.
42 -> Int -- ^ Highest possible loss as an Int
43 highestLoss = battleCostBy maximumBy (not . fst)
44
45 -- |Finds the cheapest possible victory given combatant stats.
46 cheapestVictory :: String -- ^ Raw combatant stats.
47 -> Int -- ^ Cheapest possible victory as an Int
48 cheapestVictory = battleCostBy minimumBy fst
49
50 battleCostBy :: Ord a1
51 => (((a2, a1) -> (a2, a1) -> Ordering) -> [(Bool, Int)] -> (a, c))
52 -> ((Bool, Int) -> Bool)
53 -> String
54 -> c
55 battleCostBy f g = snd . f (comparing snd) . filter g
56 . flip map loadouts . battle . toBoss
57
58 loadouts :: [Combatant]
59 loadouts = map (equip player) [ w:a:rs | w <- weapons
60 , a <- armory
61 , rs <- replicateM 2 rings]
62
63 player :: Combatant
64 player = Combatant { hp = 100, items = [] }
65
66 -- |Parses a string into a 'Combatant'
67 toBoss :: String -- ^ Raw combatant stats
68 -> Combatant -- ^ Resultant combatant record
69 toBoss s = Combatant { hp = hp', items = i }
70 where input = lines s
71 hp' = read $ last $ words $ head input
72 i = map (toI . words) $ tail input
73 toI ["Damage:",d] = item { damage = read d }
74 toI ["Armor:", a] = item { armor = read a }
75 toI _ = item
76
77 item :: Item
78 item = Item {cost = 0, damage = 0, armor = 0}
79
80 weapons :: [Item]
81 weapons = [ item {cost = 8, damage = 4}
82 , item {cost = 10, damage = 5}
83 , item {cost = 25, damage = 6}
84 , item {cost = 40, damage = 7}
85 , item {cost = 74, damage = 8}
86 ]
87
88 armory :: [Item]
89 armory = [ item {cost = 13, armor = 1}
90 , item {cost = 31, armor = 2}
91 , item {cost = 53, armor = 3}
92 , item {cost = 75, armor = 4}
93 , item {cost = 102, armor = 5}
94 , item -- Dummy piece since armor is optional
95 ]
96
97 rings :: [Item]
98 rings = [ item {cost = 25, damage = 1}
99 , item {cost = 50, damage = 2}
100 , item {cost = 100, damage = 3}
101 , item {cost = 20, armor = 1}
102 , item {cost = 40, armor = 2}
103 , item {cost = 80, armor = 3}
104 , item -- Dummy piece since rings are optional
105 ]
106
107 equip :: Combatant -> [Item] -> Combatant
108 equip c@Combatant {items = is} i = c {items = i ++ is}
109
110 attr :: (Item -> Int) -> Combatant -> Int
111 attr f = sum . map f . items
112
113 -- |Simulates the outcome of two 'Combatant's dueling.
114 battle :: Combatant -- ^ Player 1
115 -> Combatant -- ^ Player 2
116 -> (Bool, Int) -- ^ Tuple containing whether first player won, and
117 -- ^ at what price.
118 battle b p | (p `hits` b) <= (b `hits` p) = (True, price)
119 | otherwise = (False, price)
120 where price = attr cost p
121
122 hits :: Combatant -> Combatant -> Int
123 hits a d = ceiling ((fromIntegral (hp d) / fromIntegral minDmg) :: Double)
124 where minDmg = max 1 (attr damage a - attr armor d)