{- |
The next task requires to create several data types and functions to
model the given situation.
An evil dragon attacked a village of innocent citizens! After
returning to its lair, the dragon became hungry and ate one of its
treasure chests by accident.
The guild in the village found a brave knight to slay the dragon!
As a reward, the knight can take the treasure chest.
Below is the description of the fight and character specifications:
* A chest contains a non-zero amount of gold and a possible treasure.
When defining the type of a treasure chest, you don't know what
treasures it stores inside, so your chest data type must be able
to contain any possible treasure.
* As a reward, the knight takes all the gold, the treasure and experience.
* Experience is calculated based on the dragon type. A dragon can be
either red, black or green.
* Red dragons grant 100 experience points, black dragons ā 150, and green ā 250.
* Stomachs of green dragons contain extreme acid and they melt any
treasure except gold. So green dragons have only gold as reward.
All other dragons always contain treasure in addition to gold.
* Knight tries to slay a dragon with their sword. Each sword strike
decreases dragon health by the "sword attack" amount. When the
dragon health becomes zero or less, a dragon dies and the knight
takes the reward.
* After each 10 sword strikes, the dragon breathes fire and decreases
knight health by the amount of "dragon fire power". If the
knight's health becomes 0 or less, the knight dies.
* Additionally, each sword strike decreases "knight's endurance" by one.
If a knight's endurance becomes zero, they become tired and are not
able to continue the fight so they run away.
Implement data types to describe treasure, knight and dragon.
And implement a function that takes a knight and a dragon and returns
one of the three possible fight outcomes.
You're free to define any helper functions.
šÆ HINT: If you find the description overwhelming to implement entirely
from scratch, try modelling the problem in stages.
1. Implement all custom data types without using polymorphism.
2. Add @newtype@s for safety where you think is appropriate.
3. Encode the fight result as a sum type.
4. Add polymorphism.
5. Make all invalid states unrepresentable. Think, how you can
change your types to prevent green dragons from having any
treasure besides gold (if you already haven't done this).
-}
-- some help in the beginning ;)
data Knight = Knight
{ knightHealth :: Int
, knightAttack :: Int
, knightEndurance :: Int
}
type Gold = Int
data Chest a = MkChest
{
chestGold :: Gold,
chestTreasure :: a
}
data DragonType
= Red
| Black
| Green
data Dragon a = MkDragon
{
dragonType :: DragonType,
dragonFirePower :: Int,
dragonHealth :: Int,
dragonTreasure :: Chest a
}
data WinReward a
= GoldReward (Gold, Int)
| ChestReward (Chest a, Int)
data Result a
= Win (WinReward a)
| Death
| Flee
experience :: DragonType -> Int
experience Red = 100
experience Black = 150
experience Green = 250
getReward :: Dragon a -> WinReward a
getReward dragon =
case dragonType dragon of
Green -> GoldReward (chestGold (dragonTreasure dragon), experience (dragonType dragon))
_ -> ChestReward (dragonTreasure dragon, experience (dragonType dragon))
dragonAttacked :: Dragon a -> Int -> Dragon a
dragonAttacked dragon dmg = dragon {dragonHealth = dragonHealth dragon - dmg}
knightMoved :: Knight -> Int -> Knight
knightMoved knight dmg = knight {knightHealth = knightHealth knight - dmg, knightEndurance = knightEndurance knight - 1}
dragonFight :: Dragon a -> Knight -> Result a
dragonFight dragon knight = fightHelper dragon knight 1
fightHelper :: Dragon a -> Knight -> Int -> Result a
fightHelper dragon knight turn
| knightHealth knight <= 0 = Death
| knightEndurance knight == 0 = Flee
| dragonHealth dragon <= 0 = Win (getReward dragon)
| mod turn 10 == 0 = fightHelper (dragonAttacked dragon $ knightAttack knight) (knightMoved knight $ dragonFirePower dragon) (succ turn)
| otherwise = fightHelper (dragonAttacked dragon $ knightAttack knight) (knightMoved knight 0) (succ turn)