post

Posted Tuesday, September 5, 2017 under project 4 minute read

A chatbot that tells branching stories

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:

  • Facebook APIs
  • NoSQL DB (Mongo)
  • Express (Node)
  • Photoshop for pixel art

Facebook Page | Bot Generator Site | View Source

Design

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).

Technology

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:

  • Installation Prerequisites (tutorial)
  • Technical Configuration (chatbot name, access token)
  • Welcome & Error Messages
  • Triggers

The welcome and error messages were provided as staple (with optional exclusion), however the triggers were the interesting part. A trigger event could react to either the user’s text containing an activation string (e.g. ‘buy’), a specific person’s name, or a postback ID (ergo a specific ‘button’). A sequence of response events (separate messages) of either plain-text or ‘structured content’ could then be added. These structured responses could provide images, links, buttons etc. Finally, when the user is satisfied that their bot encompasses all intended interactions, they could click a button to download the JavaScript to run via NodeJS. I shared this on the web development subreddit to a modest amount of positive reception. It was not intended to be fully featured, but rather provide a functioning springboard, similar to a seed project.

Finally, I set up a Facebook page to facilitate my grand design. I created an avatar with some personality, however the rest was not so simple. When linking its responses to the hosted Node app, Facebook itself was rather particular – it needed to receive responses over HTTPS in a timely manner, and it needed a hosted privacy policy and accompanying video demonstration. At the very least, there were a set of features available to trusted accounts representing ‘developers’, so I was able to enrol the help of some friends for testing purposes until I was ready to publicise it (and ergo tackle their requirements in steps). Being a cheapskate, I ended up using a free domain provided by the government of Mali to route messages through. I set up a DigitalOcean instance with ‘Let’s Encrypt’ and NGINX, and ran my node application.

Feedback

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.

 

Scroll Up Copy link

Edited Tuesday, November 26, 2019.