Site Map - skip to main content

Hobby Public Radio

Your ideas, projects, opinions - podcasted.

New episodes Monday through Friday.


hpr2938 :: Naming pets in space game

How to use markov chains to generate names

<< First, < Previous, Latest >>

Hosted by tuturto on 2019-11-06 is flagged as Clean and is released under a CC-BY-SA license.
Tags: haskell, markov chains.
Listen in ogg, spx, or mp3 format. | Comments (0)

Part of the series: Haskell

A series looking into the Haskell (programming language)

Intro

In the two previous episodes we built a weighted list and used that to build markov chains. This time we’re going to use them to generate some names based on examples. I’m skipping over a lot of uninteresting code in this episode, concentrating only the parts that deal with names.

Idea

Person in game might hear scurrying sounds inside walls of their quarters. Then they have option of getting a cat, taming a rat or letting someone else deal with the problem. Depending on their choice, they might end up with a cat or a rat, that of course needs a name. Game offers 3 different options of names that haven’t been used before and person can always opt for completely random one.

Config

While we’re not going to dig very deep into making configurations for markov chains, we can have look at the overall process.

We have list of names to serve as examples and three functions, which implementation I won’t delve into:

  • start for adding starting element
  • links for recording link between two elements
  • end adds ending element

addName function is used to add single name into config:

addName :: Int -> Text -> Config Text -> Config Text
addName n s config =
    links pairs $
            end elements $
            start elements config
    where
        elements = chunksOf n s
        pairs = zip elements (safeTail elements)

First s (name) is split into strings of length n. These elements are then combined into pairs, where consecutive elements form a pair. Final step is to add start and ending elements into config, followed by links between elements of pairs.

We can then fold a list of examples into config:

nameConfig :: [Text] -> Int -> Config Text
nameConfig xs n =
    foldr (addName n) emptyConfig xs

This starts with emptyConfig and calls addName repeatedly until all elements of list containing examples have been processed.

Implementation

Now that we have configuration, we can start generating names. As usual, I like to keep things specific and generate PetName instead of just Text. I happened to have list of ancient greek names at hand, so I used that. Later on we’ll have to add more cultures, like Romans, Parthians, Persians, Germans, Phoenicians and so on.

General implementation of generating infinite list of strings of specific kind is shown below:

names :: (RandomGen g, Eq b) => (Text -> b) -> Config Text -> g -> [b]
names t config g =
    nub $ (t . toTitle . concat) <$> chains config g

It’s easier to read if you start from right. chains config g generates infinite list of markov chains with given configuration. Next we create a new function (t . toTitle . concat), which uses concat to combine list of Text into single Text, toTitle to capitalize is correctly and t to transform it to something (PetName in our case). <$> is then used to apply this function to each element of our infinite list. Finally nub is used to remove duplicate entries.

With names we can then define petNames:

petNames :: (RandomGen g) => g -> [PetName]
petNames =
    names MkPetName greekNameConfig

MkPetName is value constructor that turns Text into PetName (this is t used by names function).

Pets

Pets are currently very much work in progress. They have few attributes and there can be two different kinds of pets:

Pet json
    name PetName
    type PetType
    dateOfBirth StarDate
    dateOfDeath StarDate Maybe
    ownerId PersonId
    deriving Show Read Eq
data PetType
    = Cat
    | Rat
    deriving (Show, Read, Eq, Ord, Enum, Bounded)

The actual beef is namingPetEvent function. When applied with Entity Person, Entity Pet and StarDate, it will create News that can be saved into database and later on showed to player. While the code is shown below, I’m not going to go over it line by line:

namingPetEvent :: (PersistQueryRead backend, MonadIO m,
    BaseBackend backend ~ SqlBackend) =>
    Entity Person -> Entity Pet -> StarDate -> ReaderT backend m News
namingPetEvent personE petE date = do
    pets <- selectList [ PetOwnerId ==. (entityKey personE)
                       , PetDateOfDeath ==. Nothing
                       ] []
    let names = (petName . entityVal) <$> pets
    g <- liftIO getStdGen
    let availableNames = take 3 $ filter (\x -> not (x `elem` names)) $ petNames g
    let content = NamingPet (NamingPetEvent { namingPetEventPersonId = entityKey personE
                                            , namingPetEventPetId = entityKey petE
                                            , namingPetEventPetType = (petType . entityVal) petE
                                            , namingPetEventDate = date
                                            , namingPetNameOptions = availableNames
                                            })
                            [] Nothing
    return $ mkPersonalSpecialNews date (entityKey personE) content

General idea is to use selectList to load living pets of given person and then extract their names. With random generator g, we create a infinite list of PetNames, remove already used names from it and take 3 first ones. These names are then used to create NamingPetEvent.

In closing

Names are probably one of the most common applications of markov chains in games. Same technique can be used to generate nonsense books and articles that look realistic on a glance.

Questions, comments and feedback is welcomed, best way to reach is email or in fediverse where I’m tuturto@mastodon.social. Or even better, record your own episode for Hacker Public Radio.

ad astra!


Comments

Subscribe to the comments RSS feed.

<< First, < Previous, Latest >>

Leave Comment

Note to Verbose Commenters
If you can't fit everything you want to say in the comment below then you really should record a response show instead.

Note to Spammers
All comments are moderated. All links are checked by humans. We strip out all html. Feel free to record a show about yourself, or your industry, or any other topic we may find interesting. We also check shows for spam :).

Provide feedback
Your Name/Handle:
Title:
Comment:
Anti Spam Question: What does the P in HPR stand for ?
Are you a spammer →
Who hosted this show →
What does HPR mean to you ?