We continue with people, this time focusing on opinions. This episode has somewhat more code than previous one, so following along with the shownotes might be a good idea. I’m trying to minimize amount of code I read out aloud.
Intro
One person’s opinion of another is expressed as OpinionScore that ranges from -100 to 100.
Computing the score is based on intelligence player has available to them. Internally we have ReportResult that tracks score, reasons for the score and confidence level about the results. It’s defined as:
data ReportResult =
FeelingLevel OpinionScore
| ReasonsLevel OpinionScore [OpinionReason]
| DetailedLevel OpinionScore [OpinionReason]
deriving (Show, Read, Eq)
We’re going to be adding up these results quite a bit, so we define SemiGroup and Monoid instances for it. When two results are combined, scores are added together, lists of reasons are concatenated and the lowest confidence level is used. This is written as:
instance Semigroup ReportResult where
(FeelingLevel s1) <> (FeelingLevel s2) = FeelingLevel (s1 <> s2)
(FeelingLevel s1) <> (ReasonsLevel s2 _) = FeelingLevel (s1 <> s2)
(FeelingLevel s1) <> (DetailedLevel s2 _) = FeelingLevel (s1 <> s2)
(ReasonsLevel s1 _) <> (FeelingLevel s2) = FeelingLevel (s1 <> s2)
(ReasonsLevel s1 r1) <> (ReasonsLevel s2 r2) = ReasonsLevel (s1 <> s2) (r1 <> r2)
(ReasonsLevel s1 r1) <> (DetailedLevel s2 r2) = ReasonsLevel (s1 <> s2) (r1 <> r2)
(DetailedLevel s1 _) <> (FeelingLevel s2) = FeelingLevel (s1 <> s2)
(DetailedLevel s1 r1) <> (ReasonsLevel s2 r2) = ReasonsLevel (s1 <> s2) (r1 <> r2)
(DetailedLevel s1 r1) <> (DetailedLevel s2 r2) = DetailedLevel (s1 <> s2) (r1 <> r2)
instance Monoid ReportResult where
mempty = DetailedLevel mempty mempty
Opinion based on traits
Current system compares two lists of traits. For example, two brave characters like each other slightly better than if one of them would be coward. Comparison is done by traitPairOpinion function, which definition I’m omitting as it’s rather long and not too interesting. It’s signature is: traitPairOpinion :: TraitType -> TraitType -> Maybe (OpinionScore, OpinionReason). So, given two traits, tells how that pair affects to opinion and reasoning for it.
In order to have nicer format for out data, we introduce a helper function:
traitPairScore :: TraitType -> TraitType -> (OpinionScore, [OpinionReason])
traitPairScore a b =
case traitPairOpinion a b of
Nothing ->
mempty
Just (s, r) ->
(s, [r])
This is because (OpinionScore, OpinionReason) isn’t monoid, but (OpinionScore, [OpinionReason]) is, which means we can combine them with <>.
Actual score calculation based on traits, we do it like this:
traitScore :: [TraitType] -> [PersonIntel] -> [TraitType] -> [PersonIntel] -> ReportResult
traitScore originatorTraits originatorIntel targetTraits targetIntel =
if (Traits `elem` originatorIntel) && (Traits `elem` targetIntel)
then DetailedLevel score reasons
else FeelingLevel score
where
(score, reasons) = mconcat $ traitPairScore <$> originatorTraits <*> targetTraits
The interesting part is mconcat $ traitPairScore <$> originatorTraits <*> targetTraits. Function traitPairScore expects two TraitType values as parameters, but we’re calling it with two li