never executed always true always false
    1 {-# LANGUAGE TemplateHaskell #-}
    2 
    3 {-|
    4 Module:      Y2015.D22
    5 Description: Advent of Code Day 22 Solutions.
    6 License:     MIT
    7 Maintainer:  @tylerjl
    8 
    9 Solutions to the day 22 set of problems for <adventofcode.com>.
   10 -}
   11 
   12 module Y2015.D22
   13     ( Result(..)
   14     , spellBattle
   15     , testSpellBattle
   16     )
   17 where
   18 
   19 import           Control.Lens
   20 import           Data.Map     (Map, insert, keys, member)
   21 import qualified Data.Map     as M
   22 
   23 data Boss = Boss
   24     { _damage :: Int
   25     , _hp     :: Int
   26     } deriving (Eq, Show)
   27 
   28 data Player = Player
   29     { _armor :: Int
   30     , _life  :: Int
   31     , _mana  :: Int
   32     , _spent :: Int
   33     } deriving (Eq, Show)
   34 
   35 data State = PlayerTurn
   36            | BossTurn
   37            deriving (Show)
   38 
   39 data Game = Game
   40     { _boss    :: Boss
   41     , _effects :: Map Spell Int
   42     , _hard    :: Bool
   43     , _player  :: Player
   44     , _state   :: State
   45     } deriving (Show)
   46 
   47 data Spell = MagicMissile
   48            | Drain
   49            | Shield
   50            | Poison
   51            | Recharge
   52            deriving (Enum, Eq, Ord, Show)
   53 
   54 -- |Represents the final result of a sequence of player moves.
   55 data Result = Won Int -- ^ Indicates a player win with the total mana spent.
   56             | Lost    -- ^ Player loss.
   57             deriving (Eq, Ord, Show)
   58 
   59 makeLenses ''Boss
   60 makeLenses ''Game
   61 makeLenses ''Player
   62 
   63 cast :: Spell -> Game -> Game
   64 cast spell = action
   65            . (player.mana -~ cost)
   66            . (player.spent +~ cost)
   67     where
   68         (action, cost) = case spell of
   69             MagicMissile -> (boss.hp -~ 4, 53)
   70             Drain        -> ((boss.hp -~ 2) . (player.life +~ 2), 73)
   71             Shield       -> (e Shield 6, 113)
   72             Poison       -> (e Poison 6, 173)
   73             Recharge     -> (e Recharge 5, 229)
   74         e s t = effects %~ insert s t
   75 
   76 affect :: Spell -> Game -> Game
   77 affect Shield   = player.armor .~ 7
   78 affect Poison   = boss.hp -~ 3
   79 affect Recharge = player.mana +~ 101
   80 affect _        = id
   81 
   82 stepEffects :: Game -> Game
   83 stepEffects game =
   84     foldr affect (game' & effects %~ step) (keys $ game^.effects)
   85     where game' = game & player.armor .~ 0
   86           step  = M.filter (0 <) . M.map pred
   87 
   88 stepGame :: Game -> [Result]
   89 stepGame game
   90     | game^.boss.hp <= 0 =
   91         [Won $ game^.player.spent]
   92     | game^.player.life <= 0 || game^.player.mana < 0 =
   93         [Lost]
   94     | otherwise =
   95         case game^.state of
   96             BossTurn   -> stepGame $ strike game' & state .~ PlayerTurn
   97             PlayerTurn -> stepSpells game'
   98             where game' = stepEffects $ case (game^.hard, game^.state) of
   99                               (True, PlayerTurn) -> game & player.life -~ 1
  100                               _ -> game
  101 
  102 stepSpells :: Game -> [Result]
  103 stepSpells game =
  104     [ result | spell <-
  105         [ s | s <- [MagicMissile ..]
  106             , not $ member s $ game^.effects
  107             ]
  108         , let nextTurn = cast spell game & state .~ BossTurn
  109         , result <- stepGame nextTurn
  110     ]
  111 
  112 strike :: Game -> Game
  113 strike g =
  114     player.life -~ max 1 (g^.boss.damage - g^.player.armor) $ g
  115 
  116 newGame :: Bool -> String -> Game
  117 newGame hardMode input =
  118     Game
  119         { _player  = Player
  120             { _life = 50
  121             , _armor = 0
  122             , _mana = 500
  123             , _spent = 0
  124             }
  125         , _boss    = boss'
  126         , _effects = M.empty
  127         , _state   = PlayerTurn
  128         , _hard    = hardMode
  129         }
  130     where boss' = pBoss input
  131 
  132 pBoss :: String -> Boss
  133 pBoss input = Boss { _hp = hp', _damage = dmg }
  134     where parse f = read $ last $ words $ f $ lines input
  135           hp' = parse head
  136           dmg = parse last
  137 
  138 -- |Finds the minimum required mana to win a game.
  139 spellBattle :: Bool   -- ^ Whether to run the game in "hard mode"
  140             -> String -- ^ Boss stats input string
  141             -> Result -- ^ Best possible battle outcome
  142 spellBattle hardMode = minimum . stepGame . newGame hardMode
  143 
  144 -- |Provided as a quicker method for testing shorter player battles.
  145 testSpellBattle :: Bool   -- ^ Whether to run the game in "hard mode"
  146                 -> String -- ^ Boss stats input string
  147                 -> Result -- ^ Best possible battle outcome
  148 testSpellBattle d = minimum . stepGame
  149                   . (player.mana .~ 250) . (player.life .~ 10)
  150                   . newGame d