{-# 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 (main) where

import Advent.Format (format)
import Data.List (partition)

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 (hp, dmg, armor) <- [format|2015 21 Hit Points: %u%nDamage: %u%nArmor: %u%n|]
    let (wins, losses) = partition (fight hp dmg armor) gearOptions
    print (minimum (map itemCost wins))
    print (maximum (map itemCost losses))

--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 weapon <- [Item]
weapons
    armor  <- chooseUpTo 1 armors
    ring   <- chooseUpTo 2 rings
    return (foldl1 combine (weapon : armor ++ ring))

chooseUpTo :: Int -> [a] -> [[a]]
chooseUpTo :: forall a. Int -> [a] -> [[a]]
chooseUpTo Int
n (a
x:[a]
xs) | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
1 = ([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
chooseUpTo Int
_ [a]
_               = [[]]

fight ::
  Int  {- ^ boss hit points -} ->
  Int  {- ^ boss damage     -} ->
  Int  {- ^ boss armor      -} ->
  Item {- ^ player weapon   -} ->
  Bool {- ^ player wins     -}
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  {- ^ player 1 HP               -} ->
  Int  {- ^ player 1 HP lost per turn -} ->
  Int  {- ^ player 2 HP               -} ->
  Int  {- ^ player 2 HP lost per turn -} ->
  Bool {- ^ player 1 wins             -}
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