этот пост следует за этим.
Я реализую простую боевую систему как игрушечный проект, типичную систему, которую вы можете найти в таких играх, как Final Fantasy и подобных. Я решил пресловутую проблему «Загрязнение пространства имен» с помощью типа класса + пользовательских экземпляров. Например:
type HitPoints = Integer
type ManaPoints = Integer
data Status = Sleep | Poison | .. --Omitted
data Element = Fire | ... --Omitted
class Targetable a where
name :: a -> String
level :: a -> Int
hp :: a -> HitPoints
mp :: a -> ManaPoints
status :: a -> Maybe [Status]
data Monster = Monster{monsterName :: String,
monsterLevel :: Int,
monsterHp :: HitPoints,
monsterMp :: ManaPoints,
monsterElemType :: Maybe Element,
monsterStatus :: Maybe [Status]} deriving (Eq, Read)
instance Targetable Monster where
name = monsterName
level = monsterLevel
hp = monsterHp
mp = monsterMp
status = monsterStatus
data Player = Player{playerName :: String,
playerLevel :: Int,
playerHp :: HitPoints,
playerMp :: ManaPoints,
playerStatus :: Maybe [Status]} deriving (Show, Read)
instance Targetable Player where
name = playerName
level = playerLevel
hp = playerHp
mp = playerMp
status = playerStatus
Теперь проблема: у меня есть тип заклинания, и заклинание может нанести урон или наложить статус (например, Яд, Сон, Замешательство и т. д.):
--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)
--For example
fire = Spell "Fire" 20 (Just Fire) (Damage 100 0)
frogSong = Spell "Frog Song" 30 Nothing (Inflict [Frog, Sleep])
Как было предложено в связанной теме, я создал общую функцию «приведения», например:
--cast function
cast :: (Targetable t) => Spell -> t -> t
cast s t =
case spellEffect s of
Damage hp mana -> t
Inflict statList -> t
Как видите, тип возвращаемого значения — t, здесь он показан только для согласованности. Я хочу иметь возможность вернуть новую цель (например, монстра или игрока) с некоторым измененным значением поля (например, нового монстра с меньшим количеством здоровья или с новым статусом). Проблема в том, что я не могу просто сделать следующее:
--cast function
cast :: (Targetable t) => Spell -> t -> t
cast s t =
case spellEffect s of
Damage hp' mana' -> t {hp = hp', mana = mana'}
Inflict statList -> t {status = statList}
потому что хп, мана и статус "не являются допустимым селектором записей". Проблема в том, что я априори не знаю, будет ли это монстр или игрок, и я не хочу указывать «monsterHp» или «playerHp», я хочу написать довольно общую функцию. Я знаю, что Haskell Records неуклюжи и не очень расширяемы...
Есть идеи?
До свидания и счастливого кодирования,
Альфредо