Let’s make an Elemental Type System in Haskell – Part II


Ok, so we have our elements, our monster but we can do nothing more instancing some monster and checking for their weakness. We need to think about some design choices and how they could affect our program. The main drawback working with Haskell Records is the namespace pollution: you can’t define two distinct record with one or more fields names in common because Haskell will give you an error. This happens because Haskell under the hood converts that fields into global functions, so we can access our monster property just typing:

name myMonster

Pretty cool, but unfortunately we couldn’t do the same if we have defined another type, e.g. Player, with the same field.

Is everything lost? No! There are several workaround on the net, but thinking about our game I’ve discovered that isn’t a big problem to use a single type in order to model both a monster and a player: both have a name, hp, mp, level, and even a status:

data TargetableUnit = Unit{name :: String,
                           level :: Int,
                           hp :: HitPoints,
                           mp :: ManaPoints,
                           elemType :: Maybe Element,
                           status :: Maybe [Status]} deriving (Eq, Read, Show)

And what about elemType? “A Player can’t have an element!” you may say, but think about that: suppose you have just obtained a brand new ring (e.g. Tetra Elemental), that absorb one or more elemental damage.. with the elemeType field you can model this behaviour! Well, in this first version you can have only ONE Element, but changing Maybe Element in Maybe [Element] seems pretty straightforward. Ok, so we have our TargetableUnit, it’s the time to create some spells!

It’s a kind of magic

In order to have magic, we need to have spells, modeled as a type:

--Essentially the result of a spell cast
data SpellEffect = Damage HitPoints ManaPoints
                 | Inflict [Status] deriving (Show)

--Essentially a magic
data Spell = Spell{spellName :: String,
                   spellCost :: Integer,
                   spellElem :: Maybe Element,
                   spellEffect :: SpellEffect} deriving (Show)

--cast function
cast :: Spell -> TargetableUnit -> TargetableUnit
cast s t =
    case spellEffect s of
       Damage hit mana -> t {hp = hp t - hit, mp = mp t - mana}
       Inflict statList -> case (status t) of
                                (Just sList) -> t {status = Just (sList ++ statList)}
                                Nothing -> t {status = Just statList}

--SPELL DEFINITIONS
fire   = Spell "Fire"   20 (Just Fire) (Damage 100 0)
fira   = Spell "Fira"   40 (Just Fire) (Damage 200 0)
firaga = Spell "Firaga" 80 (Just Fire) (Damage 300 0)

bio = Spell "Bio" 20 Nothing (Inflict [Poison])
frogSong = Spell "Frog Song" 30 Nothing (Inflict [Frog, Sleep])

Ok, so what we have done? Essentially a magic does two thing:

  1. Deal damage
  2. Inflict one or more negative status
  3. Both

In this version just ignore the third case, we’ll work on that later. Now watch the SpellEffect definition: HitPoints and ManaPoints are simply two type synonym (they are Integer), and the type is pretty straightforward, isn’t it? A SpellEffect can be resolved or with Damage or with a status infliction. The Spell type reflect this, with the spellEffect leaved as last field. Nothing to say about the cast function: picks a spell, a TargetableUnit (so this function will be the same for a monster and for the player) and return a new unit (remember, no side effect allowed) with less hp/mp (in case of Damage) or with more status in case of Inflict.

Let’s play with our new system:

ghci> :l EBS/Main.hs
[1 of 5] Compiling EBS.Status       ( EBS/Status.hs, interpreted )
[2 of 5] Compiling EBS.Elemental    ( EBS/Elemental.hs, interpreted )
[3 of 5] Compiling EBS.Target       ( EBS/Target.hs, interpreted )
[4 of 5] Compiling EBS.Spell        ( EBS/Spell.hs, interpreted )
[5 of 5] Compiling EBS.Main         ( EBS/Main.hs, interpreted )
Ok, modules loaded: EBS.Main, EBS.Elemental, EBS.Target, EBS.Status, EBS.Spell.
ghci> let piros = Unit "Piros" 1 100 50 (Just Fire) Nothing
ghci> piros
Unit {name = "Piros", level = 1, hp = 100, mp = 50, elemType = Just Fire, status = Nothing}
ghci> cast bio piros
Unit {name = "Piros", level = 1, hp = 100, mp = 50, elemType = Just Fire, status = Just [Poison]}
ghci> let newMonster = cast bio piros
ghci> newMonster
Unit {name = "Piros", level = 1, hp = 100, mp = 50, elemType = Just Fire, status = Just [Poison]}
ghci> cast frogSong newMonster
Unit {name = "Piros", level = 1, hp = 100, mp = 50, elemType = Just Fire, status = Just [Poison,Frog,Sleep]}

Pretty cool, isn’t it? We are far from have a usable or fun mini game, but now we can cast spell on a monster!

I’ll create a git repo ASAP, so you will find the entire code there!

Stay tuned!

Alfredo

One thought on “Let’s make an Elemental Type System in Haskell – Part II

  1. Pingback: Let’s make an Elemental Type System in Haskell – Part I « Another Word For It

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...