You can call this “EBS v. 3.5”, because I’m not showing here so much big improvements. I’m gonna focus on two main points:
- Some little refactoring (thanks to your comments)
- Implementation of custom spell behavior
A little less refactoring
Our already discussed isWeakTo and isStrongAgainst functions were relying on pattern matching in this fashion:
isWeakTo :: TargetableUnit -> Maybe Element -> Maybe Bool m `isWeakTo` elem = case elem of Just e -> checkProperty m ((==e) . succ) _ -> Nothing
Uhm.. we are continuously putting in and getting out things from a context.. yes, this is a work for ours lovely Monads!
isWeakTo :: TargetableUnit -> Maybe Element -> Maybe Bool m `isWeakTo` elem = elem >>= (\e -> checkProperty m ((==e) . succ))
Cool! Only two lines! But what are we really doing? Well, we take a value within a context (a Maybe Element) and we give it for lunch to our >>= operator: it takes our elem, unbox it (revealing an Element e or Nothing if nothing’s there), and giving it to our checkProperty function for dinner. We don’t have to put the result into a context again, because checkProperty returns a Maybe Bool, i.e. a Bool inside the failure context (Maybe). Pretty cool, isn’t it? Same applies for isStrongAgainst
A custom spell behavior
So far we have created a Spell, to cast against some TargetableUnit: we have a simple function called cast that takes a Spell and a TargetableUnit, and does the dirty job:
cast :: Spell -> TargetableUnit -> TargetableUnit cast s t = let coeff = getDmgMult t (spellElem s) in case spellEffect s of Damage hit mana -> t {hp = hp t - floor (fromIntegral hit * coeff), mp = mp t - floor (fromIntegral mana * coeff)} Inflict statList -> let sList = status t in t {status = nub (sList ++ statList)}
But our spell type is quite limited: it can only inflict some damage or some status. As you may remember, some cool spells, or items, could do some special tricks based, for example on the target life. Let’s take two examples:
- An Elixir – Fully restores HP/MP
- The Laser spell (Do you remember? you can learn it from enemies through the Enemy Skill materia in FFVII) – cut by half enemy’s life
How can we do to implement this custom behavior? Fortunately, the solution is pretty easy: let’s add a brand new SpellEffect!
--Essentially the result of a spell cast data SpellEffect = Damage HitPoints ManaPoints | Inflict [Status] | Custom (TargetableUnit -> TargetableUnit) --Essentially a magic data Spell = Spell{spellName :: String, spellDesc :: String, spellCost :: Integer, spellElem :: Maybe Element, spellEffect :: SpellEffect} instance Show Spell where show x = spellDesc x
Note: I’ve added the spellDesc because it can be pretty-printed with show. What have we done? We are currently saying that a Spell can do three things:
- Deals damage
- Inflicts some status
- A custom behavior, i.e. a function we could define and that be used to perform actions based of target properties!!
cast :: Spell -> TargetableUnit -> TargetableUnit cast s t = let coeff = getDmgMult t (spellElem s) in case spellEffect s of Damage hit mana -> t {hp = hp t - floor (fromIntegral hit * coeff), mp = mp t - floor (fromIntegral mana * coeff)} Inflict statList -> let sList = status t in t {status = nub (sList ++ statList)} Custom f -> f $ t --(INSIDE Item.hs) data ItemEffect = Restore HitPoints ManaPoints | Cure [Status] | Custom (TargetableUnit -> TargetableUnit) --Use the item i on the target t use :: Item -> TargetableUnit -> TargetableUnit use i t = case (itemEffect i) of (Restore hp' mp') -> t {hp = hp t + hp', mp = mp t + mp'} (Cure rList) -> t {status = filter (\s -> s `notElem` rList) $ status t} (Custom f) -> f $ t
Obviously same applies to Item type. Ok, cool! So let’s define 1 item and 1 spell, what about an Elixir and the laser spell?
--Inside Item.hs elixirBehaviour :: TargetableUnit -> TargetableUnit elixirBehaviour t = t {hp = maxHp t, mp = maxMp t} elixir = Item "Elixir" "Fully restores HP and MP" (Custom elixirBehaviour) --Inside Spell.hs laserBehaviour :: TargetableUnit -> TargetableUnit laserBehaviour t = t {hp = floor $ (fromIntegral $ maxHp t) / 2.0} laser = Spell "Laser" "Cut by half target's health" 20 Nothing (Custom laserBehaviour)
Ok, now it’s time for the meat: let’s test them!
Ok, modules loaded: EBS.Main, EBS.Elemental, EBS.Target, EBS.Status, EBS.Spell, EBS.Item, EBS.MonsterPark. ghci> piros Unit {name = "Piros", level = 1, hp = 300, maxHp = 300, mp = 50, maxMp = 50, strength = 10, dexterity = 10, vitality = 10, magic = 10, spirit = 10, luck = 10, elemType = Just Fire, status = []} ghci> let damaged_piros = cast laser piros ghci> damaged_piros Unit {name = "Piros", level = 1, hp = 150, maxHp = 300, mp = 50, maxMp = 50, strength = 10, dexterity = 10, vitality = 10, magic = 10, spirit = 10, luck = 10, elemType = Just Fire, status = []} ghci> use elixir piros Unit {name = "Piros", level = 1, hp = 300, maxHp = 300, mp = 50, maxMp = 50, strength = 10, dexterity = 10, vitality = 10, magic = 10, spirit = 10, luck = 10, elemType = Just Fire, status = []}
Wow, they works! The lamba wizard seems to approve:
Ah, bear in mind that I’ve changed the minimal context value of the Spell status to be [] (empty list), because [] is the minimal context for the lists, and we can use cool Monads tricks as usual!
Git repo is in the same place! Enjoy!
Alfredo