A Tale of Two Pongs

When I start learning a new  platform, I have a simple rule: If you don’t know what to do with it, make pong. What I love about pong is that it’s a simple rule set, easy to understand, and implementable on just about anything with a pixel display.  You can generally implement it in a day or less on any platform. And it’s a great example of engaging interaction.  People understand what’s going on right away, and, when implemented well, it’s just challenging enough to keep you engaged for several minutes at least.  That’s good interaction, to me.

I’m a big believer in starting with the application rather than the platform.  I think you do better work when the tools serve the need rather than the other way around.  But sometimes you get stuck with the assignment to learn a particular platform or tool, and you have to make up a project on the spot.  When that happens, make pong.

As an example of this, I built pong for two platforms yesterday: an Arduino Mega with 2 8×8 LED matrices (based on my earlier post), and Processing.  Since Arduino’s programming syntax was based closely on Processing’s, I figured it should be possible to port the code from one to the other pretty quickly. It took about ten minutes to go from Arduino to Processing.  Following, I’ll describe the thought process of putting the game together for both, as a hopeful aid to beginning programmers.

Arduino Mega pong

This all started because I had two 8×8 matrices of LEDs being controlled by an Arduino Mega.  Whee! The excitement lasted about 30 seconds, then I wanted it to do something.   Inspired by Maywa Denki‘s Bitman, Josh Nimoy’s MiniPong,  and many others, I started on pong.

Step 1: what happens?

I start by breaking the problem into specific actions:

  • the paddles have to move up and down.
  • the ball bounces around the screen.  When it hits an edge, it bounces. Specifically:
    • when it hits the top or bottom, it changes direction vertically
    • when it hits the left or right, a point has been scored.  The ball resets to center
    • the default X velocity and Y velocity is 1 pixel per frame.
  • when the ball resets, it pauses for a second so users know a new volley is about to begin

To keep it simple, I’m not going to implement scoring, reset, or anything else.  Just hitting or missing the ball is engaging, and lets me learn the platform I’m working on well enough.

In order to make this happen, I need a display and two inputs, one for each paddle.  If the inputs are digital (i.e. pushbuttons or keyboard keys), the paddle will move one pixel for each button press. If they are analog, I’ll map the input range to the movement range of the paddles.

To keep troubleshooting simple, I’ll break the program down into a few basic routines:

  • read the sensors
  • move the ball
  • refresh the screen

Before diving into the details of the code, take a look at this video, to give you an idea of what happens:

arduino_pong-poster

Step 2: what does it look like?

When programming an animation, whether it’s a game or a simple movement, it’s useful to use an offscreen buffer to hold the image, make any changes in the buffer each time something changes, then move the stuff to the screen to update the image.  This means you can easily re-use the code from one platform to another. All the major changes will be in the routine that refreshes the screen. To that end, my code starts with some constants and variables to describe the screen:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="40" end="44" clean="true"]

Next, a two-dimensional array the size of the screen, 16 pixels wide by 8 pixels high:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="46" end="47" clean="true"]

This buffer array is just a space in memory, so it doesn’t matter whether I’m writing the programming for Arduino, Processing, or any other platform, I can still use the buffer.  I can still use the definitions too, though the syntax will change from one programming language to another.

Now, I need to get specific to the hardware, specifically the Arduino Mega and the LEDs. My pixel array for the Arduino Mega pong is a pair of 8×8 LED matrices that looks like this:

led_matrix

Because of the way the pins on the matrices are laid out and the way I programmed them in my previous example, the LED grid was laid out as shown above, with (0,0 ) on the bottom right and (15, 8 ) on the top left.  But most screen-based computers lay out the pixel grid like this:

led_matrix_2

In order to keep things consistent, I should flip the grid.

The way I control the matrices is by turning on and off the row and column pins in order to light up the LEDs (more details can be found here). Because of that, flipping the grid is simply a matter of rearranging the arrays of pin numbers.  In the previous example, I had a pair of two-dimensional arrays of row and column pin numbers, two arrays with eight pins each. The order went like this:

    columns[2][8] = {{array 1}, {array 2}}

and more specifically:

    columns[2][8] = {{pin 1, pin2, .... pin 8}, {pin 1, pin2, .... pin 8}}

To  flip the grid, I ended up with this arrangement:

    columns[2][8] = {{array 2}, {array 1}}

and more specifically:

    columns[2][8] = {{pin 8, pin7, .... pin 1}, {pin 8, pin7, .... pin 1}}

My final variable declaration for the pin arrays looks like the one from the previous example, but in reverse. Here it is:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="48" end="64" clean="true"]

Step 3: what are the main variables?

With the screen in order, it’s time to lay in a few global variables.  These variables define the animation, specifically the position and velocity of the ball and the paddles, the speed of the game (timeStamp and interval between frames) and the state of the game (paused or in motion):

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="66" end="76" clean="true"]

These variables all describe the game, but nothing specific to the hardware, so they should work on any platform that I write pong on.

Step 4: set things up

The setup() method sets up the initial conditions for the program, as always.  For the Arduino Mega pong, I need to do two things: initialize the pins that control the LED display, and fill the buffer array with initial values.  Initializing the pins is specific to Arduino, but filling the buffer will work for both platforms. Here’s what the setup looks like for the Mega:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="78" end="100" clean="true"]

Step 5: controlling the screen

Before getting down to the action, it’s useful to make sure you can control the screen.  I usually do this by turning on all the pixels, then turning them off. My previous Mega example does just that. Take a look at the refreshScreen() method from that code, it’s identical in this program.  Later on you’ll see a similar method for Processing. This method does all the actual work of turning on and off pixels. It reads the buffer array, iterates over the pixels on the screen, and turns them on or off depending on the state of the corresponding pixel in the buffer.

This method is dependent on the hardware, because it’s what controls the actual display!  Here’s what it looks like:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="215" end="236" clean="true"]

Step 6: the main loop

The main loop describes what happens over and over. Rather than put all the action here, I use it as a place to describe what happens at an abstract level, and do the real work in the methods that it calls. The main loop should look like the basic interaction description as much as possible. It should also be portable from one platform to another.  Remember that basic interaction loop described above:

  • read the sensors
  • move the ball
  • refresh the screen

Here’s a more detailed description:

  • read the sensors
  • if the game is paused
    • wait for an appropriate interval
  • if the game’s not paused
    • move the ball
  • refresh the screen

In order to pause the ball while still allowing the paddles to move, and in order to control the speed of the ball, I use a timestamp to keep track of time intervals. I use these all the time to control timing in my code. They work like this:

if (millis() = timeStamp > interval) {
   takeAction();
   timeStamp = millis();
}

millis() returns the number of milliseconds since the program started in Processing and Arduino; nearly every programming envronment has a similar command.  The interval variable is simply how many milliseconds you want to pass between each action. Once you’ve done the action, you update the timeStamp with the current time. The main loop checks this statement every time through the loop, but only takes action when the time interval has passed. You can see this block of code in two places below, once to control the speed of the ball movements, and once to control the pause state of the game.  In the latter case, the timeStamp update is elsewhere in the code.

Here’s what the main loop looks like:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="102" end="122" clean="true"]

Step 7: read the sensors

The details of this step depends on the hardware, but the basic algorithm is the same regardless of platform. It works like this:

  • make sure you’re done with  the old sensor readings
  • get the new readings
  • do something with the new readings

It’s pretty normal that you’re going to make things happen based on the change in the sensor readings rather than their actual values at a given instant, so you usually need the old readings and the new ones in order to compare them.  In this program, the sensor readings are mapped to the paddle positions, so I make sure the old paddle positions are turned off before I get readings, then I read the sensors and turn the paddles back on with the new readings.

The sensors for the Arduino Mega pong were two potentiometers, so reading them was simple:  do an analogRead() and map the results to the range of the paddles’ movement. You can see it in the middle of the following block.

Here’s what the code looks like:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="124" end="138" clean="true"]

You can see it calls a method called setPaddles() which is the next step. When I wrote the code, I didn’t fill in the details of setPaddle() until later, I just gave it a name so I knew it needed to happen.

Step 8: set the paddles

setPaddles() turns on or off the pixels in the buffer array depending on the sensor readings passed to it. This isn’t dependent on the hardware, because all I’m doing is manipulating values in the buffer array.  I pass in the position in the array where the center of the paddle should be, and the state of the paddle (off or on). The method itself also checks to see if the pixel above or below the center needs to be turned on.  If the center is at the edge of the screen, I only turn on two pixels, but if it’s in the center of the screen, I turn on three.  Here’s the code:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="140" end="152" clean="true"]

Step 9: move the ball (and set it)

The moveBall() method, like the setPaddles() method, is not dependent on the hardware, because all it does is manipulate a pixel in the buffer array.  It checks to see if the ball’s position is by an edge of the screen, or by a paddle, and if it is, it changes the ball’s direction appropriately. If the ball has gone off the screen, it resets it to the center and pauses the game. This signals the main loop to start counting down time until it can unpause the game.

Though this method is longer than the others, it’s not really more complex. It’s just a series of if statements to check the position of the ball pixel. Like the setPaddles() method, it makes sure to turn off the previous position of the ball before turning on the new position:

[include file="../../code/Arduino/LED_matrices_pong_0002/LED_matrices_pong_0002.pde" start="154" end="213" clean="true"]

Step 10: play!

That’s it! Here’s the full Arduino Mega Sketch.

But wait! There’s more! Next comes the Processing pong.  If you want to compare them side-by-side as you read, here’s theProcessing Sketch link.

Processing pong

Because the control of the hardware is separate from the control of the animation and the reading of the sensors, it’s pretty easy to move this program over to Processing.  I often work on both platforms, developing ideas on one, then porting them to the other, depending on which is easier to start the idea on.

The Processing pong is going to look like this:

processing_pong

Step 1: review the code and hardware dependencies

Here are the methods from the program, in summary:

setup() – initializes the I/O pins, fills the display buffer array

loop() – reads the sensors, updates the buffer array, refreshes the screen

readSensors() – reads the sensors, maps them to the paddle positions in the buffer array

setPaddle() – updates the paddle positions in the buffer array

moveBall() – updates the ball position in the buffer array

refreshScreen() – updates the LED display

Of these, only three methods — setup(), readSensors(), and refreshScreen() — deal with hardware.  The rest manipulate memory. Those methods, and the global variables, are the only things I need to change in order to port this to Processing. To start. select all, and paste it into a new Processing sketch.

Step 2:  change the global variables

Processing doesn’t use #defines, so I have to change the defines to regular integer variables, like so:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="14" end="18" clean="true"]

I also need to add two new variables, HIGH and LOW, since those constants aren’t known to Processing. I want HIGH to mean off and LOW to mean on, like it did in the Arduino sketch, so I’ll set the values like so:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="20" end="22" clean="true"]

I don’t need the row and column arrays, so they can be deleted.  Also, Processing’s syntax for two-dimensional arrays is different than Arduino’s. The former is based on Java, and the latter on C.  So I’ll have to change the declaration of pixels[][] like so:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="23" end="24" clean="true"]

The rest of the global variables can stay the same. Here they are again:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="26" end="36" clean="true"]

Step 3: change the setup

The hardware-dependent part of the setup() is the block that iterates over the matrices and initializes the pins.  Replace that with a few commands to set up the Processing window, turn on smoothing, and set the frame rate. The resulting setup() looks like this:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="38" end="51" clean="true"]

Step 4: change the main loop

The main loop is identical, because it’s not hardware dependent. But Processing calls its main loop draw() so change the name to draw:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="52" end="52" clean="true"]

Step 5: change the refreshScreen()

The refreshScreen() method is probably the most hardware dependent, so it’ll need the most change.  Instead of turning on LEDs, I’ll draw an array of circles, and change the color of each circle based on the corresponding element of the buffer array. It’s similar to the Arduino refeshScreen() in that it iterates over the buffer array and makes changes to the display depending on the values in that array:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="167" end="183" clean="true"]

Step 6: change the readSensors()

The desktop computer’s easiest inputs to work with are the keyboard and the mouse, of course.  Since I don’t have two mice, I’ll use the keyboard as the input sensors.  I’ll use four keys, two for up and down on the left (‘a’ and ‘z’) and two for up and down on the right (‘l’ and’,’).  So instead of analodRed(), the Processing readSensors() will have to listen for keyboard input.  I’ll add a method to parse the keys if the key is pressed.  Here’s the new readSensors():

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="74" end="103" clean="true"]

I need a new method, keyRead(), to determine which key was pressed and set the new paddle positions.  Here it is:

[include file="../../code/Processing/pong_matrix/pong_matrix.pde" start="185" end="209" clean="true"]

That’s all the changes.  The whole sketch can be downloaded here.  Compare it side-by-side with the Arduino Mega Sketch and you’ll see how the logic is portable from one to the other.

This method is hardly new — in fact, it’s just good programming hygiene.  Three simple rules:

  • Break the action down into describable sections, and use those as your core methods.
  • When possible, separate the code that controls your inputs and outputs from the code that manipulates memory and makes choices
  • Use descriptive names and comment your code within an inch of its life.

if your program is described and laid out sensibly, the troubleshooting is much, much easier, and implementing the idea on a new platform is no problem.  It also makes it simpler to revisit your code years later when you can’t remember how you wrote it the first time!

This entry was posted in arduino/wiring, circuits, Processing and tagged , , , . Bookmark the permalink.