Posted Tuesday, September 5, 2017 under project 4 minute read
I made a robot that lets you play a branching-text adventure game through Facebook’s Messenger.
This was a one week project that snowballed. Missing old text adventure books, I created a Facebook Messenger chat-bot to deliver a dynamic, branching-story game experience to users – with persistent state. This entailed writing a 10,000 word space-themed narrative, painting around 50 unique art assets, and reading a lot of API documentation. At the time, I believe it was one of the only games written for Facebook Messenger.
I gained experience using:
The first bit was the fun bit. I used my whiteboard to map out an entire story plan geographically. Taking inspiration from Bethesda’s game-guides, the result was something akin to a huge, sloppy finite-state-machine. Everything should start on a white-board. It stayed stuck on my wall like a big billboard, a constant reminder that for the next few days it was going to be my job to bring it to life.
Next, I wrote and illustrated the story. I had visions of a space janitor – an unknown protagonist forced to clean a pirate ship, who one day decides enough is enough. Plenty of clichés, coffee, and an entire day later, and, somewhat dazed, I had chunked it into JSON (the fields describing a complex connected node graph).
With content ready, it was time to write the engine. This consisted of two main parts: the client and the game. The client aspect required that responses be constructed to align with the Messenger API, and also the semantic flow of interactions. This entailed dealing with errors such as the inability to understand the user’s intent, select a story to play, and converting elements of the model (such as the current story node) into a rich response. To this end, a series of utility functions were produced to reduce the higher-level game loop to a more manageable sequence of sub-routines. The main benefit of the rich messages (‘bubbles’) was that users needn’t manually type responses – they could tap a button, and the ‘postback’ payload would contain the button’s ID. Additional functions such as ‘help’ exist to counteract variance across devices, and are mapped to from user input using simple string-contains checks. The deliverable from the programming phase of this project was a flow that receives user messages, checks their current state within the system, and then responds with more content.
As for the engine itself, while traversing between nodes was relatively simple given there were no errors in the data (and there were!), edge cases arose through the format of the platform. For example, since nothing prevented users from scrolling up and hitting the buttons of a previous choice again, mitigations had to be put in place to record a user’s history throughout the story. Furthermore, items (or other ‘flags’) collected through events had to be zeroed appropriately if the story were to be reset. Therefore, the most complicated aspect of the engine was ensuring that the user’s current memory/expectations of their story aligned with the model described by MongoDB.
Although conceptually the tasks involved to deliver this project were not incredibly complicated, I had worked to simplify aspects of the interactions, and wanted to share this. With this presently data-agnostic boilerplate, I used Bootstrap to produce a generic bot-generator site. A huge amount of work was undertaken to make the interface malleable. The mobile-friendly page included:
Once the page was live, it was magical. This is because I could actually see people interacting with it in real-time. Even now, I receive daily messages, and watch people go on their own adventure. In the future, I would love to write more stories, and use analytics to map people’s preferred routes through the trees – this may lead to insights into where my design-abilities shine or falter. Something I didn’t anticipate were the jibes from my friends – their feedback mainly highlighted plot-holes and the need for extra features – one of which immediately implemented was the ability to check your inventory, and another to restart the game if lost.