{-# Language QuasiQuotes #-}
{-|
Module      : Main
Description : Day 21 solution
Copyright   : (c) Eric Mertens, 2021
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2015/day/21>

Buy an equipment loadout and see how it fairs against a boss in a battle.

-}
module Main where

import Advent.Format (format)
import Data.List ( maximumBy, minimumBy )
import Data.Ord ( comparing )

data Item = Item { Item -> String
itemName :: String, Item -> Int
itemCost, Item -> Int
itemDamage, Item -> Int
itemArmor :: Int }
  deriving (Int -> Item -> ShowS
[Item] -> ShowS
Item -> String
(Int -> Item -> ShowS)
-> (Item -> String) -> ([Item] -> ShowS) -> Show Item
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Item -> ShowS
showsPrec :: Int -> Item -> ShowS
$cshow :: Item -> String
show :: Item -> String
$cshowList :: [Item] -> ShowS
showList :: [Item] -> ShowS
Show, ReadPrec [Item]
ReadPrec Item
Int -> ReadS Item
ReadS [Item]
(Int -> ReadS Item)
-> ReadS [Item] -> ReadPrec Item -> ReadPrec [Item] -> Read Item
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS Item
readsPrec :: Int -> ReadS Item
$creadList :: ReadS [Item]
readList :: ReadS [Item]
$creadPrec :: ReadPrec Item
readPrec :: ReadPrec Item
$creadListPrec :: ReadPrec [Item]
readListPrec :: ReadPrec [Item]
Read)

-- | >>> :main
-- 78
-- 148
main :: IO ()
IO ()
main =
 do (Int
hp,Int
dmg,Int
armor) <- [format|2015 21 Hit Points: %u%nDamage: %u%nArmor: %u%n|]
    let win :: Item -> Bool
win = Int -> Int -> Int -> Item -> Bool
fight Int
hp Int
dmg Int
armor
    Int -> IO ()
forall a. Show a => a -> IO ()
print (Int -> IO ()) -> Int -> IO ()
forall a b. (a -> b) -> a -> b
$ [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Item -> Int) -> [Item] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Item -> Int
itemCost
          ([Item] -> [Int]) -> [Item] -> [Int]
forall a b. (a -> b) -> a -> b
$ (Item -> Bool) -> [Item] -> [Item]
forall a. (a -> Bool) -> [a] -> [a]
filter Item -> Bool
win
          ([Item] -> [Item]) -> [Item] -> [Item]
forall a b. (a -> b) -> a -> b
$ [Item]
gearOptions
    Int -> IO ()
forall a. Show a => a -> IO ()
print (Int -> IO ()) -> Int -> IO ()
forall a b. (a -> b) -> a -> b
$ [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Item -> Int) -> [Item] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Item -> Int
itemCost
          ([Item] -> [Int]) -> [Item] -> [Int]
forall a b. (a -> b) -> a -> b
$ (Item -> Bool) -> [Item] -> [Item]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Item -> Bool) -> Item -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Item -> Bool
win)
          ([Item] -> [Item]) -> [Item] -> [Item]
forall a b. (a -> b) -> a -> b
$ [Item]
gearOptions

emptyItem :: String -> Item
emptyItem :: String -> Item
emptyItem String
name = String -> Int -> Int -> Int -> Item
Item String
name Int
0 Int
0 Int
0 

--Weapons:    Cost  Damage  Armor
weapons :: [Item]
weapons :: [Item]
weapons =
  [ String -> Int -> Int -> Int -> Item
Item String
"Dagger"        Int
8     Int
4       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Shortsword"   Int
10     Int
5       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Warhammer"    Int
25     Int
6       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Longsword"    Int
40     Int
7       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Greataxe"     Int
74     Int
8       Int
0
  ]

--Armor:      Cost  Damage  Armor
armors :: [Item]
armors :: [Item]
armors =
  [ String -> Int -> Int -> Int -> Item
Item String
"Leather"      Int
13     Int
0       Int
1
  , String -> Int -> Int -> Int -> Item
Item String
"Chainmail"    Int
31     Int
0       Int
2
  , String -> Int -> Int -> Int -> Item
Item String
"Splintmail"   Int
53     Int
0       Int
3
  , String -> Int -> Int -> Int -> Item
Item String
"Bandedmail"   Int
75     Int
0       Int
4
  , String -> Int -> Int -> Int -> Item
Item String
"Platemail"   Int
102     Int
0       Int
5
  ]

-- Rings:      Cost  Damage  Armor
rings :: [Item]
rings :: [Item]
rings =
  [ String -> Int -> Int -> Int -> Item
Item String
"Damage +1"    Int
25     Int
1       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Damage +2"    Int
50     Int
2       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Damage +3"   Int
100     Int
3       Int
0
  , String -> Int -> Int -> Int -> Item
Item String
"Defense +1"   Int
20     Int
0       Int
1
  , String -> Int -> Int -> Int -> Item
Item String
"Defense +2"   Int
40     Int
0       Int
2
  , String -> Int -> Int -> Int -> Item
Item String
"Defense +3"   Int
80     Int
0       Int
3
  ]

combine :: Item -> Item -> Item
combine :: Item -> Item -> Item
combine Item
x Item
y = Item
  { itemName :: String
itemName   = Item -> String
itemName   Item
x String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" and " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Item -> String
itemName Item
y
  , itemCost :: Int
itemCost   = Item -> Int
itemCost   Item
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Item -> Int
itemCost   Item
y
  , itemDamage :: Int
itemDamage = Item -> Int
itemDamage Item
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Item -> Int
itemDamage Item
y
  , itemArmor :: Int
itemArmor  = Item -> Int
itemArmor  Item
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Item -> Int
itemArmor  Item
y
  }

gearOptions :: [Item]
gearOptions :: [Item]
gearOptions =
  do Item
weapon <- [Item]
weapons
     Item
armor  <- String -> Item
emptyItem String
"unarmored" Item -> [Item] -> [Item]
forall a. a -> [a] -> [a]
: [Item]
armors
     [Item]
ring   <- Int -> [Item] -> [[Item]]
forall a. Int -> [a] -> [[a]]
chooseUpTo Int
2 [Item]
rings
     Item -> [Item]
forall a. a -> [a]
forall (m :: * -> *) a. Monad m => a -> m a
return ((Item -> Item -> Item) -> [Item] -> Item
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldl1 Item -> Item -> Item
combine (Item
weapon Item -> [Item] -> [Item]
forall a. a -> [a] -> [a]
: Item
armor Item -> [Item] -> [Item]
forall a. a -> [a] -> [a]
: [Item]
ring))

chooseUpTo :: Int -> [a] -> [[a]]
chooseUpTo :: forall a. Int -> [a] -> [[a]]
chooseUpTo Int
0 [a]
_ = [[]]
chooseUpTo Int
_ [] = [[]]
chooseUpTo Int
n (a
x:[a]
xs) = ([a] -> [a]) -> [[a]] -> [[a]]
forall a b. (a -> b) -> [a] -> [b]
map (a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:) (Int -> [a] -> [[a]]
forall a. Int -> [a] -> [[a]]
chooseUpTo (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) [a]
xs) [[a]] -> [[a]] -> [[a]]
forall a. [a] -> [a] -> [a]
++ Int -> [a] -> [[a]]
forall a. Int -> [a] -> [[a]]
chooseUpTo Int
n [a]
xs

fight ::
  Int {- ^ hit points -} ->
  Int {- ^ damage -} ->
  Int {- ^ armor -} ->
  Item ->
  Bool
fight :: Int -> Int -> Int -> Item -> Bool
fight Int
hp Int
dmg Int
armor Item
gear = Int -> Int -> Int -> Int -> Bool
outcome Int
100 (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int
dmg Int -> Int -> Int
forall a. Num a => a -> a -> a
- Item -> Int
itemArmor Item
gear)) Int
hp (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Item -> Int
itemDamage Item
gear Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
armor))

outcome ::
  Int -> Int ->
  Int -> Int ->
  Bool
outcome :: Int -> Int -> Int -> Int -> Bool
outcome Int
hp1 Int
dec1 Int
hp2 Int
dec2 = (Int
hp1Int -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1)Int -> Int -> Int
forall a. Integral a => a -> a -> a
`quot`Int
dec1 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= (Int
hp2Int -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1)Int -> Int -> Int
forall a. Integral a => a -> a -> a
`quot`Int
dec2