When we want to create a new Gostars game, we want to enter the minimum of parameters. Specifically, we want to create a game by setting the size of the universe and density of stars but not the names of individual stars.
So how do we generate readable names from scratch?
Let’s try creating totally random strings. Start with 26 letters and pick them at random until you get bored. Here’s some code that will do just that, select letters at random for random sized words. Give it a go here.
Here’s some words that I generated:
Pthc Dnlbn Ugfb Vndmm Ikvji Leafd Krxpzaelp Ldwnps Pmcnaxr Rcucryh
These do certainly look alien, maybe a little too much!
Krxpzaelp look decent enough but I don’t know that I can pronounce any of the rest. This doesn’t seem friendly for our players.
What if we could generate English sounding words by picking random ‘sounds’. By this I mean graphemes. Great idea, let’s try that - but it is a pain finding a complete list of graphemes! After a lot of looking I eventually found that the best resource is this.
The next tool to use does something similar to selecting random letters. Given a set of graphemes, select random ones a random number of times. The code to do this is here. You’ll need to supply a data file containing a grapheme on each line. Run it like this:
go run graphemetxt.go -source data.txt
Let’s take a look at what kind of names are generated:
guskngcklff nbquwhcggrch lkghged seghssghcc btgufflkghst rgschdpn jzgzwhsssfgh lfzzrpsggguked sejscftbbgezfpn gueuthss
I’m not sure that’s any better.
Here we’ll break out the big guns. Markov chains look at an existing body of data and randomly select the next ‘state’ based on its probability of following the current one. In our case, each letter will be a state.
A quick search of the internet revealed a handy little library that will help us out here. Here is a program that will read in a data file with a new name per line to build a model of how names are constructed. To use it, type the following in to the command line:
go run tools/textgen/textgen.go -source <your data file>
We need some source data to seed the Markov Chain. For this I did a search for place names in the UK, I chose a list of English nouns and place names (counties and towns). As long as the place names are 1 per line, this will server as our data.
You can set the number of words with the
words parameter. There is another parameter too, the ‘prefix’. This is used to set how far ‘back’ back in the chain the algorithm looks when deciding what letter (state) to pick next. I’m still learning how this affects the output but using
2 gave some interesting output:
Implain-landear Adamod Mouningendroold Anchal Olelughtemplail Ariabervaging Hdrack Entivult Rimbe Usholynx
That’s much better! Quite alien but somehow readable.
Some interesting features of this tool are:
- When we meet a newline, it’s the end of the word so pick a new location in the chain
- We filter out the original words by adding them to a set of known words
- Duplicate words are removed by only storing them in our set too
map keys will work very well for a set.
Now we have a large list of names, how do we get them in to our game? So this is where we take advantage of the fact that we’re usingBuffalo. It icludes a tool,
packr, that allows you to embed static resources in to the binary that you produce with
buffalo build. So first of all, we need to produce a list of names (you can find them here). So how do we make them available to the program?
Let’s start with how we will use the names when we need them. The first version of cut of Gostars created new stars when you created the game. When a client does a
/games, the handler looks at the size and star density of the universe defined in the request fields and generates a random number of stars at random positions.When we generate the star, we need to look ip a name from our list, preferably randomly. Otherwise every game would have the same star names. How do we solve this? Well, it turns out that Go has a solution to this. When you
range over map keys, Go will present them in a random order. To simplify our code as much as possible, I’m thinking we need to provide a type that will let us get an arbitrary number of these names from the map keys. Take a look at
names.go for a type that will do this.
The next question is, how do we populate our
nameList?When you are writing a Buffalo application, you don’t jump in to
main, you entry point as a developer is
actions.App(). This is where all the initialisation of resources happens. We need to create a package variable of type
nameList and instantiated it in
App(), passing in a string with all of the data in the file we created earlier. To do this, we create use a
packr.Box that refers to the
data directory in our repo. When we run
buffalo build, the
packr tool will scan the source code and detect where we call
packr.NewBox(...) and store the contents of this directory as data in the binary.To access files in this directory, we need to look them up in the box that they are stored in. For our star names, we just look up
starnames.txt from the box and pass this string to our
When we create the stars, we have access to the static data generated by our Markov Chain tool and can draw on them quickly when we need them.
I hope this is useful.