Comedy

Today I want to talk about comedy, because it is an absolutely amazing subject. The fact that an entire dynastic profession exists to make groups of (hopefully drunken) strangers laugh on command just seems kind of unbelievable. Clearly there is something deep and transformative about laughter, but what does it really mean when a person is compelled to laugh at something? For any aspiring jokesters out there, how can a comedian create this situation and get paid?

Well, in scientific terms, laughter is probably caused by something that behaves like a central pattern generator in the nervous system. These neural structures generate rhythmic output patterns without relying on any external feedback, so it is a bit strange to apply this concept to laughter (a person has to hear or see every joke, for example). However, the laughter usually happens only after a person gets the joke, at which point the “joke input” has ended in almost every case.

Therefore we should probably be conceptualizing laughter as an internal rhythmic feedback loop that can be started by some “funny” input. The challenge then is to define a “funny” input. I’ll pause for a second here so you can try that…

But wait! Doesn’t the very incomprehensibility of the challenge suggest something profound about how we should understand humor? Everyone knows that jokes are hard to write because an original comedian has to be the first person to notice that a certain thing is funny. The whole art of comedy revolves around having some of that uncommon and funny knowledge, and choosing to reveal it in the most entertaining way possible. Knowing this, is it possible to imagine something that all funny things must have in common?

Well, sure. They’re all “correct” in some abstract sense. Comedy is the process of being so profoundly correct that other people are compelled to laugh as soon as they realize what is going on. Us college-educated folk can scoff at low-brow humor, but almost any example of “bad” comedy still does reveal more than a few simple truths to more than a few tragically underinformed people, and therefore it can still make a lot of money. The fact that a thing is not funny to every person does not mean that it is not “funny” in some platonic sense. Somewhat disappointingly, there is no such thing. That makes good comedy very hard work, but at least we don’t ever have to fear the funniest joke in the world.

(From this perspective, slapstick humor is a special case where the truth being revealed is basically how badly it must suck for the victim…)

Generally speaking, this is not a new idea at all. A government document says this:

The American comedian Will Rogers was asked how he conceived his jokes. He answered: “I don’t make jokes. I just watch the government and report the facts.” See what I mean? Sometimes the truth is funnier than “comedy.”

Several Woody Allen bits are included as example one-liners, like this one:

I can’t listen to that much Wagner. I start getting the urge to conquer Poland.

It’s funny because it combines and reveals several truths in a clever and efficient way:

  • Wagner was a German imperialist.
  • Music conveys emotion.
  • Germany conquered Poland (and murdered millions of Jews) in World War II.
  • Woody Allen is Jewish.

The joke actually depends on the audience already knowing all of these things, and the “trick” is that he alludes to each in such an efficient and thought-provoking way, in the space of two short sentences. When we realize, all at once, the absurdity contained in the idea of a modern American Jew savoring hypnotic war hymns that ushered in the Second Reich, the effect is very funny for a lot of people, even if they don’t want to think about it!

I’m particularly interested in this method of “humor analysis” because it seems to emerge so naturally from a feedback-dominated model of intelligence. Laughter happens when a person notices something that is interpreted as “true enough” to activate an unconscious neural feedback loop, forcing them to externalize their acknowledgement and understanding. That is the sole evolutionary function of laughter, a phenomenon which almost certainly had a pivotal role in the building of every human civilization.

This is not saying that Adam Sandler is the greatest American ever, or even that we should all start studying Internet memes for the sake of science. But it does mean that we should take a moment and bow our heads in respect to every person who has ever wanted to make another person laugh, and in recognition of the great things they have accomplished for the sake of humanity. Because when a country of people stop what they are doing and start laughing (against their will) at the same idea at the same time, you can probably trust it a bit more than usual.

How would I define a “funny” thing? Funny things are true enough to make people laugh.

Here is someone else’s definition:

There is no simple answer to why something is funny… Something is funny because it captures a moment, it contains an element of simple truth, it is something that we have always known for eternity and yet are hearing it now out loud for the first time.

Notes from Silicon Valley

As fun as this blog might be for me to write, and as many random ideas might play out (and succeed!) here, I still haven’t talked very much or very directly about myself or what I think about my own situation, as it didn’t really occur to me that certain people might care about that too. Family and friends, feel free to consider this The Return On Your Investment, Part 1, or whatever:

– The simulacres phenomenon is very real indeed. Humans may have bootstrapped their own existence all along, but moving to a place like this really underlines how far we’ve come as a species. I’m still not completely able to relax and enjoy myself when the ambient temperature is more than three degrees from whatever would seem “ideal” at any moment, because I have never known survival that does not require a carefully-constructed box. My ancestors fled nameless European tyrants in a box, and each subsequent generation of Northeastern Americans go on reproducing their boxes like some quasi-species of box-creature, just so that come springtime the whole lot of us aren’t frozen up in a giant cube. The problem is, boxes cut both ways, and people get soft. Like me.

– Maybe you think, ah, it must be that famous “crunchy” perspective from the camping trip, but this is not true either. Many people here seem to know their environment better than they know themselves. Some would not even regard that kind of statement as an insult.*

– Ever talk to one of those people who are too clever for their own good? Where we should probably just stuff them in the nearest closet or mental institution for a few hundred years while everyone else grows up? You’ll run into a lot of those people here.

– On the other hand, we have a verifiable shortage of free lunches, as people keep asking me to replace theirs. Actually, I did get a free lunch last week, but of course there was a waiting list involved. And the best free food is still reserved for the best free programmers, somehow.

– If I am willing to deal in money, there are many lunches available for purchase, most of which are delicious.

– As delicious as the marine life might be, Italian Food in City X doesn’t hold a candle to the real thing. Which, of course, doesn’t hold a candle to the real thing.

– No matter what happens, the hipsters have no choice but to become old with me.

– In the end, nothing outlasts a weird uneasiness about this whole endeavor, a sense that the things I write and the things I know might be the only things standing between me, the street, and some totally crazy sign. And that the street itself is the only thing standing between humanity and all those impregnable woods. And that I might actually need to earn a master’s degree before these strangers start taking me seriously. I still don’t want to.

*If there are any specific people who believe I am picking on them, the answer is NO!!! Everything is caricature!!!

World Wide Ouija

(Skip to the demo!)

I have a new open-source software project to share today. It is an idea that has been bouncing around my head for a while: a working, real-time, multiplayer Ouija board game that runs in a web browser. I decided to go ahead and make it last week because there are some amazing new JavaScript tools maturing out of the NodeJS madness, and this kind of project has suddenly become much easier to complete than it would have been just a few years ago.

Specifically, I’m using MeteorJS, a brand-new full-stack NodeJS/MongoDB framework which abstracts away the intimidating problem of integrating a client program with its server resources. To oversimplify, a fast web app can’t wait for every bit of program logic to download from the server, and a centralized server can’t keep every bit of data organized on every client without its own reference copies, so a lot of work gets duplicated. MeteorJS attempts to minimize this issue by setting up a framework where application data can be accessed from either the client or the server at the same logical address or “collection,” using the same JavaScript semantics on either end. When it is possible to synchronize each client’s copy of its application data with the server’s version, MeteorJS automatically passes updates to the server and fetches fresh data, algorithmically eliminating conflicts that the other clients might have introduced.

It’s all wonderfully intricate stuff, but the point is that somebody else is building it and we don’t have to. So let’s cut the techno-babble and make a multiplayer Ouija game! Start by installing MeteorJS. Then, to create a new project and populate it with basic HTML, CSS, and JS files, type this at a command line:

meteor create worldwideouija

To turn off “easy mode” and better control the data that gets shared, disable autopublish:

meteor remove autopublish

Our game needs to store three kinds of “things,” so we’ll start by initializing three collections. Open the “worldwideouija.js” file in the “worldwideouija” folder and add this at the top:

Rooms = new Meteor.Collection("rooms");
Forces = new Meteor.Collection("forces");
Messages = new Meteor.Collection("messages");

The Rooms collection will store metadata about each game room, the Forces collection will store temporary impulse values that push the Ouija marker around the board, and the Messages collection will store chat messages.

We can access these collections from either client or server, but the distinction is still important for the purposes of our application, as we need our Ouija server to manage the data for each room, and update each player’s client as necessary. MeteorJS allows us to conditionally execute JavaScript depending on whether the current environment is the client or the server, which allows all our application logic to be contained in one script. Let’s declare what the client should do now:

if (Meteor.is_client) {
Meteor.startup(function() {
Meteor.subscribe("allrooms");
Meteor.subscribe("allmessages");
//...
});
//...
}

Meteor.startup() takes a callback function that should contain all the code we want to run when the application is ready to start execution. This callback will contain our game loop.

Inside, the Meteor.subscribe() methods tell the client that it should ask the server for access to “allrooms” and “allmessages” that the server hopefully has available. While the Rooms variable always refers to the collection, this “allrooms” subscription tells the client which records to actually synchronize with the server. Because autopublish is disabled, we will have to make these subscriptions available to the client. At the end of the file, add a block that tells the server to publish all Rooms to “allrooms” subscribers and all Messages to “allmessages” subscribers. Security is not a concern when consulting with spirits:

if (Meteor.is_server) {
Meteor.startup(function () {
Meteor.publish("allrooms", function() {
return Rooms.find();
});
Meteor.publish("allmessages", function() {
return Messages.find();
});
//...
});
}

I’ve neglected to publish the Forces collection, but this is on purpose. At first, I imagined that each client could just update the server’s marker position for the current room at any time and the server would sort everything out, but whenever the delay between page updates got worse, the movement became jerky and weird. The Forces collection is how I solved this. It acts as a buffer where each client can write control data. When the server is asked to update the marker position, it reads from this collection, updates the position according to all the available Forces, and then deletes them, before sending the new position back. This means that no client ever has to read directly from this collection. Each client can update its own Forces collection and send new records to the server without subscribing to any published objects. It’s probably better not to waste bandwidth synchronizing this data if the clients don’t need it.

So let’s write some code! Define a game loop that executes every 500 milliseconds by adding a Meteor.setInterval() method to the client block, inside the startup callback function:

if (Meteor.is_client) {
Meteor.startup(function() {
Meteor.subscribe("allrooms");
Meteor.subscribe("allmessages");
Meteor.setInterval(function() {
if (Session.get("room")) {
if (isNaN(Session.get("dx"))) Session.set("dx", 0);
if (isNaN(Session.get("dy"))) Session.set("dy", 0);
Session.set("dx", Session.get("dx") * 0.9);
Session.set("dy", Session.get("dy") * 0.9);
Forces.insert({
room: Session.get("room"),
x: Session.get("dx"),
y: Session.get("dy")
});
Meteor.call("updateMarker", Session.get("room"), function(e,r) {
if (r.x && r.y) {
Session.set("posX", r.x);
Session.set("posY", r.y);
}
});
}
}, 500);
});
//...
}

This loop uses Session.set() and Session.get() to manage temporary session variables, used to save the client’s current impulse value, along with the client’s copy of the marker position. Meteor.call() tells the server to execute its “updateMarker” method that we will have to define, and the callback updates the client’s position using whatever response data comes back.

The rest of the client application uses HTML templates integrated with JavaScript, like this function which returns the current Room ID:

Template.main.currentRoom = function() {
return Session.get("room") || false;
};

This return object can be accessed from the “main” template by including {{currentRoom}} anywhere in context. We won’t go into detail about the rest of the templates but they are all included with the source code and should make enough sense if you can follow this logic. Look in the .html file for all the template HTML.

Templates can also handle jQuery events, and we can use this functionality to read the mouse position and update the client’s impulse values:

Template.room.events = {
"mousemove .gameBoard": function(e) {
var theRoom = Rooms.findOne(Session.get("room"));
var trueX = e.pageX - parseInt($('.gameBoard').css('margin-left'));
Session.set("dx", (trueX - Session.get("posX"))/25);
Session.set("dy", ((e.pageY - 50) - Session.get("posY"))/25);
}
//...
};

This is the events object for the “room” template, with keys that are strings containing a jQuery event type and often a CSS selector that declares which DOM element(s) to bind. Inside, we use a little hackery to compute an impulse vector from the mouse and marker positions, and then we save the session variables that the game loop will read from.

Finally, we have to implement an “updateMarker” method on the server:

if (Meteor.is_server) {
Meteor.startup(function () {
Meteor.publish("rooms", function() {
return Rooms.find({});
});
Meteor.publish("messages", function() {
return Messages.find({});
});
Meteor.methods({
updateMarker: function(id) {
var theRoom = Rooms.findOne(id);
var position = {};
if (theRoom) {
if (isNaN(theRoom.x)) theRoom.x = 480;
if (isNaN(theRoom.y)) theRoom.y = 320;
var dx = 0;
var dy = 0;
var numForces = 0;
var theForces = Forces.find({room: id});
theForces.forEach(function(force) {
dx += parseInt(force.x);
dy += parseInt(force.y);
numForces++;
});
Forces.remove({room: id});
Rooms.update(id, {$set: {players: numForces}});
if (numForces > 0) {
var newX = theRoom.x + dx/numForces;
var newY = theRoom.y + dy/numForces;
if (newX < 100) newX = 100;
if (newX > 860) newX = 860;
if (newY < 100) newY = 100;
if (newY > 540) newY = 540;
Rooms.update(id, {$set: {x: newX}});
Rooms.update(id, {$set: {y: newY}});
position.x = newX;
position.y = newY;
}
}
return position;
}
});
});
}

And that’s (almost) all!

Visit http://github.com/guscost/worldwideouija for the complete source code, and try it out at worldwideouija.meteor.com or worldwideouija.com! I’m still tinkering around so uptime and code quality are not 100% guaranteed.

And before I forget, many thanks to AVGP for the missing chatroom example, which helped a lot.