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:
- Deal damage
- Inflict one or more negative status
- 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