Gmail Auto-Responder for AI Robot Party Game

I'm a big fan of immersive social games! Last fall, I built an escape room for the engineering social at my company. For the three prior organizations I've worked for, I've also run a social mystery game similar to Werewolf/Mafia, but themed around self-aware human-like robots (aka everyone is a Cylon). The game is far more individualized than generic werewolf, including very specific character narratives and powers. I customized each character to have things that only they know how to do, and to create specific connections between characters that need to be uncovered throughout play.

I'm not going to go into the details of the game because I will definitely run it again someday and I don't want to ruin it. However, I wanted to explain one cool innovation I've made to the game mechanics that modernizes the game and makes it way more interactive. I also want to show the tool I built so I could run the game by myself even with 20 or so people playing at once.

The Problems with Werewolf

To me, there are three drawbacks to werewolf that make it both unrealistic and a little awkward to play.

  • voting requires everyone stopping to close our eyes
  • there's no way to verify anything
  • only the werewolves have anything to hide

When I decided I wanted to adapt the game to have a more immersive take, I knew that I needed to re-imagine some of the central mechanics to fix those problems.

Interruption of gameplay

As an adult playing werewolf with my friends, I couldn't help but feeling silly when the Game Master intermittently tells everyone to shut their eyes and put their heads down. This brings the entire game to a stop and makes it so that the game has to be played in a tight circle rather than in a dispersed environment where simultaneous conversations and "subplots" can happen.

Nothing but lies

Really, the biggest problem with Mafia is that the entire structure of the game relies on there being no way to establish definitive facts. For the majority of players, there is no way to "check" if anything someone else says is true. This is actually incredibly unrealistic. In reality, there are almost always external ways to check the validity of someone's alibi, or cross-reference whether they are doing what they say they are doing.

Werewolves under pressure

Related to the above point, in the original game, most of what everyone is trying to do is just detect the physical characteristics of lying on someone's face. This makes the game easy/boring when you are playing with somewhat honest people. Basically only a crew of stone-faced sociopaths can make the game intriguing.

The reason it's so basic is because the only people lying are the werewolves. If you have an honest person, it's easy to rule them out early just by directly asking them. If they are uncomfortable about something then they are almost certainly a werewolf. The game would be better if they might be hiding something, but you aren't sure if what they are hiding is good or bad for your team. Furthermore, it would be even better if you aren't sure whether the werewolves know whether you are a werewolf too.

...

So when I was originally contemplating my redesign, I wanted to break up these three downsides and reinvent them with something new. My goal was to modernize the game both in theme (AI/robots) and in mechanics (modern technologically savvy humans).

A Technological Solution

I was thinking of ways to both make voting more fluid and come up with ways to introduce "true facts." Surprisingly, it turned out that introducing the same tool resolved both issues.

We live within constant connection with our phones to both gather information and inform others of our intentions. Why not leverage our digital extensions to be the primary tool of the game?

This actually sequentially solved each of my main problems. First, I started by building a way for the werewolves to surreptitiously vote on their phones without interrupting the game. However, if only the werewolves were playing with their phones through the game, the game would be obvious. So next, I introduced another symmetric usage for the non-werewolves.

As it turned out, the perfect task that I could give non-werewolves was to obtain verified information about the werewolves. Again, I don't want to go into too much detail, but in general, the non-werewolves were able to query a central database that had unambiguous information about who was a werewolf. By limiting how much information they could gather at a time, the game could be tuned to both enforce cooperation and balance the rate of information gain with the rate of werewolf kills.

Implementation: automated email response bot

To easily incorporate the usage of the phone, I needed to leverage something everyone would have access to. It would have been impossible for me to build a real phone app that would be compatible on everybody's devices. Instead, I leveraged something I knew everyone could use: email.

I built an automated email response bot that could shuffle information back and forth between players, myself, and a central database full of information that they could use to complete the game. The subject lines of the player's emails had a certain syntax that indicated whether they were trying to gather information or cast a vote to kill. The script I wrote just read the emails and followed some simple logic to direct the information around and keep the game moving along.

Gmail API

To set up the Gmail API, the Gmail API Quickstart Tutorial contains instructions and the link to the spot on Google Cloud where you can activate the API. This will allow 100 sent emails per day, which should be enough if you have 20 or so people playing for an hour.

I used this gist to figure out how to send emails, and this code to figure out how to read subject lines.

My code is here, but I'll explain the few bits that are important below.

Code snippets

This connects to your web browser to allow authentication of the Google account that has Gmail API access.

creds = flow.run_local_server()
service = build('gmail', 'v1', credentials=creds)

Next, I grab the unread messages and iterate through them.

     unread_msgs = service.users().messages().list(userId='me',labelIds=['INBOX', 'UNREAD']).execute()
     mssg_list = unread_msgs['messages']
     for mssg in mssg_list:
            message = service.users().messages().get(userId='me', id=m_id).execute() # fetch the message using API

I step through the header items to get the subject and the sender.

            payld = message['payload']
            headr = payld['headers']

            for item in headr: # getting the Subject,Time Sent, and Sender
                if item['name'] == 'Subject':
                    msg_subject = str_clean(item['value'])
                elif item['name'] == 'Date':
                    msg_date = item['value']
                    date_parse = (parser.parse(msg_date))
                elif item['name'] == 'From':
                    msg_from = str_clean(item['value'].split('>')[0].split('<')[1])

At this point I go through several rounds of custom logic to compare this to the central database. I won't go into the details of the logic, but I want to mention how I access this "central database" in a very cheap and easily editable way.

GSheets makes an easy editable database

To handle both the emailing permissions and the werewolf feature database, I just pulled the details from a Google Sheet. I already described how to connect to gsheets in a previous blog post. I reused the same credentials to download a sheet and convert into a python dictionary with all the info needed.

    sheets_service = build('sheets', 'v4', credentials=creds)
    result = sheets_service.spreadsheets().values().get(
        spreadsheetId='game_db_spreadsheet_id', range='db!A:I').execute()

Finally, I sent emails with the requested information back to the players.

        message = MIMEText(message_body)
        message['to'] = to
        message['from'] = 'robot-db@gmail.com'
        message['subject'] = subject
        encoded_message = urlsafe_b64encode(message.as_bytes())
        service.users().messages().send(userId='me', body={'raw': encoded_message.decode()}).execute()

All of this worked by repeatedly calling the API to get the new unread messages and then running through this over and over again. I used the sent email time to prevent people from repeatedly querying the database in less than the alotted time.

Reaction

Each time I ran this game, I did it with more and more people. The first time I ran it, I only had about 8 people playing and I just manually responded to the emails they were sending. The last time I ran it, there were more than 25 people playing, and the automation came in really handy. It also allowed me to manage other aspects of the game at the same time.

Over time, I hope to perfect this game even further. If you are reading this in the SF Bay Area and would like to bring this game to your organization, please leave me a comment and I will get in touch to try to run it for you.

Latest
Previous
Next Post »