Cuttlepress

Blogging about Hacker School?

Project Writeup: Twitter Heist

This weekend was Ludum Dare, the thrice-yearly 48-hour gamemaking competition, and the theme was “you only get one.” I liked the idea of enforcing just one playthrough per player, but not enough to implement cookies/IP detection/whatever else (and have those all be insufficient enforcement anyway), especially because other folks would almost certainly do that anyway. Then on the LD homepage, I saw in the embedded Twitter sidebar that folks were using “#YOGO” for their LD-related tweets, which I thought was hilarious. Since one of my goals for this LD was to showcase some of the skills I’ve learned at Hacker School, it was decided: I’d do some sort of game on Twitter.

Twitter text adventures are a thing I’ve thought about before, even to the extent of encouraging gwillen to make a Twitter interface to Parchment, and I definitely did not want to go to that length for this; I like to take Ludum Dare at a leisurely pace. I chatted with Sumana about various formats a Twitter game could take; for example, I could have a clearly-demarcated list of one- or two-letter commands that players could enter all in one tweet to play the game (like this but with a gameplay aspect). I also thought about single-move games, like Rematch. In the end, I decided to go with the simplest option: a choice-based game with one tweet per state change. I would prototype in Twine and store the game states in JSON.

Much of the tech is similar to that of Curated Dannel, in that I used ntwitter and mongodb. Incoming tweets are grabbed through a User Stream. The user id is checked against a database of player’s states, and the text is searched for words associated with a change from that state (as stored in game.json). The new state is looked up in another JSON file, and a response tweet is sent back to user. Properly threading the messages was one challenge, because although most API requests have matching “whatever_id” and “whatever_id_str” parameters (to handle the fact that many IDs on Twitter are too large for JS ints), there is no “in_reply_to_id_str” parameter on a request to the Twitter status update endpoint; you just have to use the “in_reply_to_id” parameter even though you’re sending a string id.

I didn’t get to the actual storytelling until Sunday afternoon, which I think is unfortunately very obvious in the game. I’d been batting around various ideas, but none seemed approachable in the amount of time I had. I ended up going with a very brief heist plot. (Did you know I love heist stories?) I tried to keep it goofy as a default fit with its context, because I didn’t have a lot of time to go beyond that.

Spoiler after the cut:

A couple of hours before deadline, I wanted to try to implement a response to a very open-ended request, such as a plea for a cookie recipe. Of course, I would need to do some automated checking as to whether or not the response was actually a cookie recipe, and I didn’t have a lot of time to implement it. Moshe helped me get started on requesting pages from within Node (just use the “request” module! it even handles redirects), and here’s my cookie-checking code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var request = require("request");

var cookieWords = ["recipe", "butter", "sugar", "eggs", "vegan", "flour", "gluten", "bake", "heat", "cook", "ingredients"];

function verifyCookieRecipe(url, callback) {
  request({url:url, followAllRedirects:true}, function (error, response, body) {
      if (!error) {
          body = body.toLowerCase();
          cookieScore = 0;
          for (var i =0; i<cookieWords.length; i++) {
              if (body.search(cookieWords[i])>=0) {
                  cookieScore += 1;
              }
          }
          var nextstate = "";
          if (cookieScore >= cookieWords.length/2) {
              nextstate = "cookie_success";
          }
          else {
              nextstate = "cookie_failure";
              console.log(url, cookieScore);
          }

          if (callback && typeof callback === "function") {
              callback(nextstate);
          }
      }
      else {
          console.log("Couldn't get url");
          nextstate = "cookie_failure";
          if (callback && typeof callback === "function") {
              callback(nextstate);
          }
      }
  });  
}

One last set of challenges was getting everything on AWS in a very short period of time — I’d put Curated Dannel on AWS, but it had required a lot of noodling around and poking at dependencies and such. Luckily, Kate was around to help me run through it. I got a couple of bug fixes in during “upload hour” (which is the buffer time LD gives people to get their games online after the 48 hours) and shortly thereafter (the later one was a “typo fix,” which is allowed by LD rules), and I think it’s stable now. Lindsey sent me my very first pull request, to fix a typo in some game text.

As a design note, the instantaneous responses from the game are a bit disconcerting, especially within the fiction. They also don’t play well with the slow refresh rate of most users’ Twitter clients. If I were reworking this, one thing I’d change would be to add some artificial delay.

So. Code’s on github; play it here: