Untitled

 avatar
unknown
haskell
a year ago
4.3 kB
8
Indexable
{- |

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)