Captain’s Log
30 Nov 2016
This was my first entry for National Novel Generation Month – or NaNoGenMo – where participants spend the month of November writing code that generates a “novel” of 50,000+ words. Here I just want to note some of the aspects of the project that were unusual and/or difficult for me, but if you’re interested you can check out the finished project and the other entries from 2016.
The captains that my code generates navigate around a real map, with real places and real co-ordinates. A captain will pick the nearest location to their current one and then set sail, but I wanted to put some of that information in the output, so I needed a way of working out which direction the ship was travelling in and converting that into a cardinal (east, west, etc) direction. Getting the direction in terms of degrees was pretty straightforward, but how to translate that into a cardinal direction? The fastest thing to do would probably have been to have a dictionary with 360 integer keys mapping to the relevant cardinal direction, but that felt messy when I could create a class that would do it for me:
class RangeDict(MutableMapping):
def __init__(self, iterable):
if not isinstance(iterable, dict):
raise TypeError(
"You must pass a dictionary to RangeDict")
self.store = dict()
for (k, v) in iterable.items():
if not isinstance(k, range):
raise TypeError(
"Your dictionary keys must be ranges")
direction = {num: v for num in k}
self.store.update(direction)
You pay a cost when you initialise the RangeDict
, having to loop through the iterable that has been provided, but thereafter it behaves exactly like a normal dictionary. This meant that all I needed to do was generate the angle between two points, convert it into an integer and pass it to my RangeDict
to get the human-readable direction, much cleaner.
Another issue I was seeing in my output was an undesirable repetition of randomly selected phrases. While I was able to pull large lists of things like captain and ship names from places like Wikipedia, and was similarly able to construct lists of adjectives describing destinations with the help of a thesaurus, the more creative lists that I base my output on are smaller. As I was reading through my output, and as more and more of my captains were coming to abrupt ends due to a lack of rum, I would sometimes see two captains finishing with the same “low rum” notice, or even having the same notice multiple times in the lifespan of a single captain. Given the size of the lists this wasn’t surprising, but it was annoying. Rather than spend a long time making the lists bigger I found itertools.cycle
in the Python standard library, which ensured that two entries from my list could never appear next to each other in my output. This function duplicates the iterable in question, but since mine was already so small this wasn’t much of a concern.
Finally I also learned about the .gitattributes
file, which, amongst other things, lets you tell GitHub to ignore certain files completely when calculating the language stats for a repo. Because this project contained my large HTML output file the repo was getting labelled as 99% HTML, which wasn’t really accurate, so I tell GitHub to ignore CSS and HTML files, causing the repo to more accurately be labelled as Python.