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