Cuttlepress

Blogging about Hacker School?

Day 30

More CSS/javascript wrangling today. One remaining unsolved challenge is how to get the transcript div to autoscroll to the bottom when a new transcript phrase is added.

This:

1
2
3
4
function autoscroll(id){
  var toScroll = document.getElementById(id);
      toScroll.scrollTop = toScroll.scrollHeight+200;
}

does scroll down, but unfortunately if I call it right after a new phrase div is added, it scrolls before the images render, with the result of scrolling only halfway through the div. Some attempts included adding autoscroll to a “load” event of one of the image divs (no go, because it seems that the images count as “loaded” already by that point, just not rendered), adding a small timeout delay (seems inelegant), and getting the CSS to render the phrase div as the full proper height even before the images load (seems the most promising, but I have yet to get the alchemy quite right).

Here’s what things look like now. Note the sleek scrollbars, but don’t note too closely their bizarre horizontal placement on the page. Multiple repeated icons in the icon drawer are just placeholders. Chat

Day 29

Over the weekend, I decided to turn my Socket.io demo into a little chat application using images instead of text. I drew a handful of icons, but in order to support the easy addition of images later, I wanted to make it easy for the JS on the page to grab all of the images in the assets folder on the server. So the client asks for images:

1
ws.emit("getImages");

and the server responds like this:

1
2
3
4
5
6
7
8
9
10
socket.on('getImages', function(mouseX, mouseY){
  var dir = require('node-dir');
  dir.readFiles(__dirname+"/public/assets", {match: /.png$/}, function(err, content, filename, next) {
          if (err) throw err;
          filename = filename.replace(__dirname+"/public/assets/", "");
          filename = filename.replace(".png", "");
          // console.log('filename:', filename);
          socket.emit('image', filename);
          next();
      });

and the client catches those images and wraps them in divs:

1
2
3
4
5
6
7
ws.on('image', function(buttonname){
      var thisbutton = document.createElement('div');
      thisbutton.innerHTML = "<a><img src=\"assets/"+buttonname+".png\"></a>";
      thisbutton.addEventListener("click", function(){ addToComposingStick(buttonname)});
      thisbutton.setAttribute("class", "button")
      document.querySelector('#buttons').appendChild(thisbutton);
  });

Today I added the area for the user to compose their message, and then spent a fair chunk of time wrangling CSS (in Sass, of course). I got some help from Rishi and Adam. Here’s what it looks like for now:

Chat

Day 28

I spent a fair amount of time today showing off some technologies to other folks: Julia and I went over some of my past Flixel and Box2D games, and Lyndsey and I investigated various methods of casting on. (The latter is knitting, not code, of course.)

I also put together a working demonstration of communication both to and from the server using WebSocket, with help from Julia and Kate. I continue to be unimpressed by the Socket.io docs, which seem to be designed to make the user feel inept.

Here’s some server code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var http = require('http')
  , express = require('express')
  , app = express()
  , port = process.env.PORT || 5000;

var server = http.createServer(app);
server.listen(port);

var io = require('socket.io').listen(server);

io.sockets.on('connection', function(socket){
  socket.on('mouseMoved', function(mouseX, mouseY){
      socket.broadcast.emit('mouseMoved', mouseX, mouseY);
      socket.emit('mouseMoved', mouseX, mouseY);
  });
});

And here’s the client side (after initializing the canvas and setting default variable values and such):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function draw() {
  ctx.clearRect(0,0,canvas.width, canvas.height);
  ctx.fillStyle = "ff0000";
  ctx.fillRect(otherMouseX-5,otherMouseY-5,10, 10);
}

function  mouseMoved(e) {     //e is the event handed to us
    mouseX = e.pageX - canvas.offsetLeft;
    mouseY = e.pageY - canvas.offsetTop;
    ws.emit("mouseMoved", mouseX, mouseY)
}

var ws = io.connect('http://localhost:5000/')

ws.on('mouseMoved', function(mouseX, mouseY){
  otherMouseX = mouseX;
  otherMouseY = mouseY;
})

The result is magically linked canvases: move around in one window, and the square cursor in the other window moves to follow.

Magic canvases!

Day 27

I started off the day by working with Jeff and Timur on deciphering the code from an example websocket implementation. I eventually felt like that was enough prodding to get at the basics of what a socket library should do for me, and decided to write a socket-based thing using some existing libraries. In this case, I’d let Express take over in place of the routing stuff I wrote yesterday, and Socket.io could handle my websockets. I mostly worked from the examples here, here, and here, and got the basic pinging described in the Heroku tutorial working, but not anything bidirectional.

Somewhere in there, I also attended the first part of a lecture by Stefan Karpinski on the vagaries of floating point, but the just-post-lunch spot is not a great one for my attention span during lectures.

In the evening, I went to Emily Short’s lecture on Versu, which was as fantastic as I expected; I left feeling really positive about the possibilities of artistic tools in general and the state of interactive narrative in particular.

Day 26

In the first part of the day, I finished my post about Saturday’s spy games, as you undoubtedly saw. For the rest, I decided to learn about Node.js. (This time, through deploying on Heroku.)

I followed along with this book on writing a basic server, router, and event handlers in Node and this post on deploying Node to Heroku. It was definitely easier to understand after having seen how Flask does these things, which is a bit more automated — I’m glad I didn’t write my own router last week, and it was good that the choice of Jinja templating was made for me; I got bogged down at the end of today in reading about the seemingly-infinite possible templating engines for Node.

Here’s a tiny Heroku app, and here’s the code.

Project Writeup: Spy Game

Last week, I spent almost the entirety of my time at Hacker School working on design and build for a single project, starting on Monday morning and working through until late on Friday night, with a few bugfixes on Saturday. The project was a birthday present for David, and my goal was to produce a game that he and his guests could play at his birthday party on Saturday night.

Game design

The gameplay grew out of several conversations I had with friends during the brainstorming phase of the project. I started off with the design goal that my game would be lighthearted and not necessarily obtrusive: people at the party could be as engaged with it as they wanted to be, players could enter or exit the game casually, and at no point would anybody be put on the spot or made to feel embarrassed. My early thoughts were along the lines of either a generative card game that I could generate and send, and they could print out, or something with a central computer but gameplay requiring exploration of the physical party space. Chris suggested that something less localized would be better, and that I could do a smartphone game. I was squeamish about smartphone games, because I myself do not have a smartphone, which means that 1) I couldn’t be sure that everyone at the party would and 2) I would have a pretty hard time debugging. Then Alex and Izzie floated the idea of using Twilio to do an SMS-based game.

Doing text-based communications at a party at William’s house (a cocktail-laden setting) naturally lends itself to a spy theme, so I started thinking about how to assign interesting spy tasks. One thought I had was to have agents collect images of the letters in “Happy Birthday, David!” and send them in to be collaged. This turned out to be less than possible, because when the Twilio FAQ says that “sending picture messages is as easy as sending regular text messages,” and “it costs 2¢ to send a picture message (within the 500K limit per message) and 1¢ to receive a picture message,” what they really mean is “at this time sending/receiving picture messages over Twilio US long codes is not supported.” Another idea was to send messages to pairs of agents randomly, telling them each that they were to attend a rendezvous at some location in the apartment, and that the other agent would be able to identify them by some thing they were wearing, that they would thus have to scrabble to obtain and wear. (“Agent [whatever] will identify you by your black hat.”) This seemd potentially fun, but also more obtrusive and involving more possible embarrassment than I wanted. One other idea came from my father, who suggested “a variant of what Groucho Marx used to do on the game-show ‘You Bet Your Life’ […] ‘See if anybody can be made to say [secret word].’” Which seemed like a step in the right direction, except that I, the gamemaster, would have no way of knowing when a successful play had occurred.

The next round of ideas was that each agent only had to say the word themself, but that if you thought you heard a suspicious word, you could message it in and get back some information about the identity of the agent saying it, with the goal of identifying as many other agents as possible. In this model, several agents would share the same word, so identifying information was inconclusive. The problem was that there was still no way for me to know, other than indirectly, whether an agent had said their assigned word, and in this model, there was no motivation for them to do so.

So the final iteration of gameplay brainstorming (which occurred later in the week than I’d like to admit) after talking to Becca was that each agent would have both a current secret word and a team affiliation. If a different agent reported in an occurence of the secret word, the first agent would either get a message of encouragement or a message of warning, depending on whether the agents were on the same team, with the idea that each agent would be able to piece together which agents were enemies, and limit their code-containing conversation to only friendly audiences. I also decided to, at some point in the game, give the players the ability to directly message each other, because I thought it would be great to have a situation in which an agent sends a message and then oh-so-casually looks around the room to see who reaches for their pocket.

Technical (a.k.a. “meanwhile…”)

This was pretty much my first webapp project, with the exception of a brief and broken Flask app I wrote all of the previous Friday, with ample help. So a lot of this project was learning baseline webapp skills.

Twilio itself is fairly simple to use. It can route message information to the html page of one’s choosing, via either GET or POST; from there, Flask can grab the form or arguments, process the data, and use the Python wrapper Twilio provides to create (and therefore send) an SMS response. Here’s a basic version of the SMS-handling code at the heart of this project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.route('/twilio', methods=['POST'])
def incomingSMS():
  fromnumber = request.form.get('From', None)
  content = request.form.get('Body', "empty text?")
  agentname = getAgentName(fromnumber, content)
  gameLogic(agentname, content)
  return "Success!"

# gameLogic advances the game state and uses
# sendToRecipient() to respond to agents

def sendToRecipient(content, recipient, sender="HQ"):
  recipientnumber = lookup(collection=players, field="agentname", fieldvalue=recipient, response="phonenumber")
  recipientcolor = lookup(collection=players, field="agentname", fieldvalue=recipient, response="printcolor")
  sendernumber = twilionumber
  time = datetime.datetime.now()
  
  try:
      message = twilioclient.sms.messages.create(body=content, to=recipientnumber, from_=twilionumber)
      transcript.insert({"time":time, "sender":sender, "recipient":recipient, "content":content, "color":recipientcolor})
  except twilio.TwilioRestException as e:
      content = content+" with error: "+e
      transcript.insert({"time":time, "sender":sender, "recipient":recipient, "content":content, "color":recipientcolor})
  return

The confusing thing about the above code is the bits about lookup(), which are there because of the other big new thing to me about this project: proper databases. A database is required because this app is deployed via Heroku, which has an ephemeral file system: other than some environmental variables (such as the ones I’m using to store my Twilio authentication information), nothing is guaranteed to persist in a Heroku app. On the bright side, offloading the things I did want to be persistent onto another site meant that data would persist across my plentiful tiny bugfixes and re-deploys, and that I would acquire new knowledge about a much more scalable way to do things; on the less bright side, I had to, you know, actually acquire that knowledge. Luckily Moshe and Kate were very helpful, and got me set up with a MongoDB (one of the options available as a free Heroku addon), a pymongo interface to it, and some information about how to query it.

The game uses three MongoDB collections: one to store players, a second to store transcript entries, and third collection for the game objects which include configuration information about the current active game, such as the possible player factions, the remaining assignable words, and whether or not various game events have occurred. Here’s most of what initializing a new player looks like:

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
def newPlayer(phonenumber, content):
  r = lambda: random.randint(0,255)
  printcolor = '#%02X%02X%02X'%(r(),r(),r())
  # random color lazily grabbed from 
  # http://stackoverflow.com/questions/13998901/generating-a-random-hex-color-in-python
  name = content
  # this is just for the sake of the gamemaster
  factionlist = lookup(games, "active", "True", "affiliations")
  affiliation = factionlist[random.randint(0, len(factionlist)-1)]
  if phonenumber == mynumber:
      agentname = "Q"
      printcolor = '#008080'
  else:
      agentname = "0"+str(random.randint(10,99))
      while players.find({"agentname": agentname}).count() > 0:
          agentname = "0"+str(random.randint(10,99))
  players.insert({
      "agentname": agentname,
      "phonenumber": phonenumber,
      "printcolor": printcolor,
      "active": "True",
      "task": [],
      "affiliation": affiliation,
      "successfulTransmits":[],
      "interceptedTransmits":[],
      "reportedEnemies":[],
      "spuriousReports":[],
      "name": name,
      "knowsaboutmissions":"False",
      "squelchgamelogic":"True"
      })
  greet(agentname)
  return agentname

“Squelchgamelogic” is an inelegant hack added at the last minute, when I noticed that the structure of the game loop meant that newly initialized players were also receiving a “help” message because it was the fallthrough response. As noted below, the help message is clearly not the right solution, gameplay-wise, anyway. So that is something to change in the next iteration.

I didn’t do a lot with Flask itself. I routed the incoming messages to /twilio, generated a web console for myself (with forms to send individual messages, trigger game events, and send mass announcements), and generated a leaderboard. Most of the work of the latter two was in creating the Jinja templates; for example, to format the time stamps in the transcripts part of my console, I had to create a filter. It goes in the Flask app with a special decorator:

1
2
3
4
@app.template_filter('printtime')
def timeToString(timestamp):
    return str(timestamp)[11:16]
# naively take the slice of the time string that is the hours and minutes

and then gets used in the transcript-listing portion of the console template:

Transcript:
<ul style="list-style-type:none">
{% for post in information.find() %}
  <li style="color:{{ post.color }}">{{ post.sender }} to {{ post.recipient }}
    @{{ post.time|printtime }}: "{{ post["content"] }}"</li>
{% endfor %}
</ul>

Heroku was mostly unobtrusive to use, at least after having set it up once on the previous Friday, but the overall commit-deploy-wait-wait-wait cycle (necessary because Twilio obviously can’t interface with localhost) did add a layer of frustration to bugfixing. I also need to get a lot better at communing with Git, since the naive “just commit everything in the same branch” approach combined with having to commit every time I want to run the code is not helpful as version control.

One technical stretch goal that I did not achieve was to have timed events independent of my triggers. My brief foray into the topic was met with “well, you should probably use a Redis queue…” and the prospect of learning another database system within a day of my deadline did not appeal. So that will have to wait for another project. It turned out that having manual control over game events was probably better for gameplay anyway.

The full code is available on Github.

Betatest

By which I mean, the party itself. There was no way it was going to be anything more polished than a beta test; I did do a lot of in-progress testing, of course, but it wasn’t possible to emulate the full party in advance.

Some definite technical and design bugs emerged early and often. On the technical side, one was that I’d made a mistake in my “parser” and I was not catching incidences of “report” without a colon; a related but distinct problem is that I replaced all incidences of agent names in direct messages with “from [the sender],” leading to embarrassing output like this classic line from one agent attempting to inform on three others: “From 042: we need to figure out who our enemies are.. I have some numbers: From 042, From 042, From 042, From 042.” I also made an error in the logic of sending a birthday message to David, with the result that he (and only he!) had to try more than once to join the game.

On the design side, my default help response to unparseable input was definitely annoying — I added it in quick response to a comment from the earlier testers that it would be good to have some sort of feedback in the case of unparsed input, but it seems that the repetition quickly got on the nerves of the party crowd. This is, as we know from the world of parser interactive fiction, a finicky problem. Additionally, the text that introduces the game mission and attempts to explain gameplay needs a rewrite; reports are that it took the players a while to experiment enough to figure out what was going on.

Overall, though, the reactions I’ve heard have been enthusiastic. Direct messaging was clearly popular — arguably I could have rolled it out sooner, but the impression I get is that it dominated the party and probably would have been unsustainable in the long run. I also heard that some of the word insertions were just as hilariously obvious as I was hoping for, and some of the spurious word reports are good for a laugh as well. So I’m happy with the overall concept, and I think that the above bugs will be pretty easy to overcome.

Results

Here’s a copy of the transcript from the evening, with personal names changed to capital single letters in slashes. (Note that since the time stamps were for personal use and only relative time mattered, I did not bother to change the time zone.) And here’s the leaderboard, complete with the spurious reports list (complete with evidence of that “report” bug; how embarrassing). Here’s a chart of my Twilio usage throughout the afternoon and evening of Saturday: Spikes.

And here’s a screenshot of what my console looked like: Control center.

Day 25

Today I spent most of the day working on the extended writeup of last week’s project and attending Mary’s functional programming workshop, which is conveniently also online. Note that at the point in the text which mentions lemonade, she actually gave us all lemon sodas, because Mary is the best. (All Hacker School facilitators are the best.)

The Monday lecture was by Philip Guo, and was about the pursuit of a PhD. I can’t say that I found his entire argument convincing, but I loved his research; in particular, I find his Burrito system of logging project progress quite compelling.

Day 24: Halfway

This is the halfway point of Hacker School, which is terrifying. Like yesterday, I’m going to steer clear of the details of today’s work. Suffice it to say that I am pretty excited about my current project, and I think I finally understand how to query Mongo databases. I missed today’s Sass session because I lost track of the time during a conversation with Alex about art.

Day 23

Exciting progress on the Flask/Twilio/MongoDB project, but I’m going to wait until after Saturday to write it up in full.

In Sass, we poked around at the various arcane ways people have found to make equal-height columns.

Day 22

Nothing major but incremental progress on a variety of projects today. I got the MongoDB authentication issue sorted out with some help from Paul. In Sass, Moshe and I discovered the CSS “box-sizing: border-box” property. And I opened up and poked around at Ren’Py, so that we can use it for our Friday PyData workshop for highschoolers.