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)