Today, we’re going to dissect a project that a couple of Rubyists we are fond of wrote for RailsCamp last month. Paul Annesley (twitter) and Dennis Hotson (twitter) joined forces at RailsCamp Australia and the result was ASCII-based fireworks.
First, though, if you are unfamiliar with RailsCamps, here is a blurb from their site:
Imagine yourself and a posse of like-minded ruby hackers on a country retreat with zero internet for a weekend of fun. You’ll laugh, hack, learn, cry (well, you probably won’t cry… but you know… it felt poetic) and most likely play a crap-load of Guitar Hero.
The point of a RailsCamp, from what I can tell, is to disconnect with everything except Ruby and the other attendees. It is a programming fest where they encourage hackery and creativity, two things that live in abundance in the Ruby community. Everyone that I know who has attended a RailsCamp has come back saying it was a gamechanger for their career. I need to get one scheduled near Charlotte.
The project that Dennis and Paul settled on was entitled ROFLBALT. It is a Ruby port of the somewhat famous Canabalt game that you can play online or download to your mobile device. The goal of Canabalt (and all -balt type games) is to run and jump from building to building, going for as long as you can. Your score is directly proportional to how long you go before you die. It’s just a bit of brain candy in simple gaming form, the kind of candy my brain likes best.
Paul and Dennis aimed to complete ROFLBALT using less than 500 lines of Ruby, and they pulled it off. Let’s see how.
How They Did It
I am going to attempt to breakdown the code for ROFLBALT. I am warning you now that Paul and Dennis are, seemingly and unsurprisingly, much smarter than I am. In some parts of this code, I had no idea how they 1) came up with what they did or 2) what the heck the code does. This is, in no way, a reflection on the code or project, but more on the author, who is, um, “special.”
You can find the code for the project on github.
ROFLBALT is broken into the following classes.
The executable (in the bin dir) simple runs
Game.new.run, so we’ll start there.
Game, as you may have guessed, puts all the bits in place to start the game, as well as handles exiting of the game. It depends on a World and a Screen. In the Game methods, things are pretty high-level, as we’re dealing with a World object and Screen object, both abstractions created by this project. The game starts the fun, basically, in the
render method. It draws the buildings, the player, and the rest of the world objects. Note that “drawing” an object means passing it to the screen’s
draw method, something we’ll cover when we get to Screen. Other than that, the Game listens for a signal interrupt (Ctrl-C in this case) and kills the game when that happens.
World holds all of the renderable items that the game can contain. The Buildings, the background, scoreboard, and a player. it also has a couple of non-visible items, like a Building Generator and a horizon (screen width). The world has a lot of dependencies:
The world also controls the speed of the game and the distance travelled, which is how scoring is calculated. The tick method is each “move” of the world. As the player moves right (or, more appropriately, the buildings move left) the tick method checks to make sure that a building is still under the player, otherwise it’s time to RENDER BLOOD and call PLAYER.DIE (Sorry, just feels like I should capitalize those) If you aren’t dead, it adds one to ticks
Screen is initialized with the width and height of our game “canvas”, along with the game world. If you look at the initialize method, it does some funky
stty magic to clear the screen and disable the cursor. It’s things like this that make me love reading other people’s code. I always see and learn things that I would miss in my everyday coding routine. The Screen is responsible for rendering the initial game “canvas” and drawing Renderables onto the canvas. Renderable are classes that include the Renderable module, which we will get to soon.
The Pixel class represents a game pixel, which is an ASCII character in ROLFBALT. A Pixel instance consists of a foreground color, a background color, and a character. The cool thing I learned from the Pixel class is how to mess with my terminal colors. The
to_s method shows this, using a fancy string interpolation method (http://apidock.com/ruby/String/%25). I was switching my terminal from black-on-white to white-on-black and giggling when my wife walked by and asked what I was giggling about. All I could say was “um, nerd stuff” and realize that it’s situations like this that separate us from the “normals.” Anyway, I digress… back to the code…
Background takes a world as a dependency and is mainly responsible for returning the color behind everything else. For example, when the player is rendered, it passes backround.color() as the background color to the players Pixels. Nice little, focused class. I like it.
The color of the windows on the buildings. BuildingGenerator uses it. That is all
Framebuffer is instantiated by the Screen class and takes a background. The initialize method reminded me of the cool way to pass a block to the Hash intializer to get a hash that has a default value for any member that is accessed. Rendered pixels are fed into the framebuffer, and then other things (like the screen) can grab them later. It also uses Background, passing back background pixels as a default when the Framebuffer does not have a pixel for a requested x,y.
BuildingGenerator takes a World and a Background and, as you may have guessed, it creates the buildings. The methods here focus on destroying building objects (by taking them out of the world.buildings property) and creating builidings as the world
turns moves. The
generate_if_necessary method is a loop that keeps creating builidings while the last building’s x coord is less than the worlds Horizon (which is the width of the screen, if you remember.) It calls the
build method which builds the building. The
next_y function takes the previous building into account and makes sure the new building isn’t too high for our player to jump. This is the kind of domain problem you have to solve when creating a video game. :)
This brings us to the Renderable module, which is included in:
- Blood (hee)
It defines two methods:
right_x. Also, it expects the included class to define a
pixel method, along with
width properties (or methods.) In a nutshell, the
each_pixel method loops over every pixel for the object, and returns the x, y of that pixel, along with the character that represents that pixel for the given Renderable. In a way, it works just like your TV, scanning through the images and rendering them, pixel by pixel. I like how Renderable delegates the
pixel method, making a clear contract with things that include Renderable as well as things that use Renderables.
Looking at our first Renderable, Building, it takes an x,y, which is the left most x and the highest y, a width, and a background object. Moving it left is as simple as decrementing its x property. The complexity happens in the pixel method expected by Renderable. The goal is to figure out, for the given x/y, what character and color to render. Looking at the start of the
if statement, if the distance between the y of the current pixel and the y of the building is zero, then we are at the top of the building, so draw a “=”. The rest of the logic is the same, figuring out which character and color to return. Pretty smart.
Player is another renderable, but it handles what character to draw differently than building. Looking at the code, it uses the delta in between the current x,y and the player x,y to figure out which character to return based on one of three states: dead, walking, or other (jumping). Each state is made up of a two-dimensional array that holds the actual characters, which you can literally see. The code here is very clever in its structure, forming the player in the state, then using the rx/ry indices to grab the character. Pretty ingenious.
The Blood class always uses the same character and color, but I had to give it its own section. I just LOVE that there is a Blood class.
Scoreboard and GameOverBanner
Both of these renderables use a template method to find the character to render. Again, the structure of the code is awesome, as it reads like it looks in the game. This makes it easy to figure out what the code is doing. If only you could make all code this visually obvious.
The coup de grace, our ROFLCopter. The approach here is, again, similar to the other template-based renderables, but here we have multiple templates or frames. The x and y of the coptor change based on current time, using a formula I don’t really understand. I think my favorite bit is the rescue, where it claims that “RoflCopter” crashes from time to time.
Time to Play
Well, Paul and Dennis pulled it off. A port of Canabalt to Ruby that is highly playable, as well as being comprised of interesting code. When I asked the gents for any anecdotes about it, they came back with “Ask the readers to make it work in Ruby 1.8 as a challenge.” So, anyone up for the that? In the meantime, I am going to try and beat my record in ROFLBALT.