Physical Interaction Design Using Processing

 

Pong

Here's the code from my Pong game.

Here's the Basic Stamp 2 code. My sensors read in a range from about 0 - 12800, so I divided by 64 to get them into a byte range (0-255).

						
inByte VAR BYTE
leftVar VAR WORD
leftByte VAR BYTE
rightVar VAR WORD
rightByte VAR BYTE



main:
	' wait for data in at 9600 bps:
	SERIN 16, 16468, [inByte]
	' raise both sensor inputs high so we can run RCTIME:
	HIGH 12
	HIGH 13
	' pause 1 ms to let pins stabilize:
	PAUSE 1
	' read inputs into variables:
	RCTIME 12, 1, leftVar
	RCTIME 13, 1, rightVar
	' convert variables into bytes:
	leftByte = leftVar/64
	rightByte = rightVar/64
	' send data out:
	SEROUT 16, 16468, [leftByte, rightByte]
GOTO main
						

Here's the Processing code. Note that you'll need a font. You can make your own font in Processing from the Tools menu and replace the one I used with your own. You'll also need to pick the appropriate serial port, but the setup() method prints a list of the serial ports, so you can figure out which one is the one you want.

/* 
 Physical Pong
 for Processing v.91
 
 by Tom Igoe
 Takes values on from the serial port to move the paddles 
 in a pong game.  Score goes to 11.
 
 Space bar toggles calibration mode.  
 
 In calibration mode, hit enter to move through the upper and lower
 extremes of the paddles
 
 Updated 5 July 2005
 */
import processing.serial.*;

int bgcolor;			     // Background color
int fgcolor;			     // Fill color
Serial port;                         // The serial port
int[] serialInArray = new int[3];    // Where we'll put what we receive
int serialCount = 0;                 // A count of how many bytes we receive
float xpos, ypos;		     // Starting position of the ball
boolean firstContact = false;        // Whether we've heard from the microcontroller
int leftPaddle = 0;                  // x position of the left paddle
int rightPaddle = 0;                 // x position of the right paddle
int signalByte = 255;                // first byte we get in from the microcontroller
int ballSize = 20;                   // size of the ball
int paddleSize = 50;                 // height of the paddle

int xDirection = 2;                  // number of pixels to move horizontally each move
int yDirection = 1;                  // number of pixels to move vertically each move
int paddleMargin = 20;               // space from the paddle to the edge of the screen

int leftScore = 0;
int rightScore = 0;
boolean gameOver = true;

int countDownTime = 100;             // time before ball moves after each point, in millis
int countDown = countDownTime;       // countdown timer

boolean calibrate = false;           // calibrate mode flag
int calibrateStep = 0;               // where we are in the calibration routine
int leftBottom;                      // highest value of the left sensor
int rightBottom;                     // highest value of the right sensor
int leftTop = 0;                     // lowest value of the left sensor
int rightTop = 0;                    // lowest value of the right sensor
int leftNumber = 0;                  // rav value incoming from left sensor
int rightNumber = 0;                 // rav value incoming from right sensor

int thisDigit = 0;                   // I think this relates to the digit drawing routines, but I can't remember

PFont myFont;                        // text font

void setup() {
  size(400, 300);  // Window size
  noStroke();      // No border on the next thing drawn

  // Set the starting position of the ball (middle of the stage)
  resetBall();
  // set starting limits of paddles:
  leftBottom = height;
  rightBottom = height;

  // Print a list of the serial ports, for debugging purposes:
  println(Serial.list());

  // initialize font:
  myFont = loadFont("GillSans-14.vlw");
  textFont(myFont, 14);

  // I know that the third port in the serial list on my mac
  // is always my  Keyspan adaptor, so I open Serial.list()[2].

  // Open whatever port is the one you're using.
  port = new Serial(this, Serial.list()[2], 9600);
  port.write(signalByte);    // Send a byte to start the microcontroller sending
}

void draw() {
  // draw the background:
  background(0);
  // color for the foreground:
  fill(255);
  // draw the paddles:
  drawPaddles();

  // Get any new serial data
  if (port.available() > 0) {
    serialEvent();
    // Note that have we heard from the microntroller at least once:
    firstContact = true;
  }

  // If there's no initial serial data, send again until we get some.
  // (in case you tend to start Processing before you start your 
  // external device):
  if (firstContact == false) {
    delay(300);
    port.write(signalByte);
  }
  // if we're in the calibration routine, do it:
  if (calibrate) {
    doCalibrate();
  } 
  // if we're not in the calibration routine, play the game:
  else {
    // if the game's not over, keep the ball moving:
    if (!gameOver) {
      animateBall();
      checkScore();
    } 
    else {
      // draw the "GAME OVER":
      drawGameOver(width/2 - 155, height/2 -15);
    }

  }
}

/*
  This method draws the paddles in their current position based on
 the latest serial data.
 */

void drawPaddles() {
  // draw paddles:
  rect(paddleMargin, leftPaddle, 10, paddleSize);
  rect(width-(paddleMargin+10), rightPaddle, 10, paddleSize);

  // draw the center line:
  for (int y=0; y<=height; y=y+(paddleSize + 15)) {
    rect((width/2)-5, y, 10, paddleSize); 
  } 
}

/*
  This routine moves the ball, and keeps track of whether it's
 hit a paddle or a border. The paddle check could use some work.
 
 */
void animateBall() {
  // ball control:
  // if the ball is moving left:
  if (xDirection < 0) {
    if  ((xpos <= paddleMargin)) {
      if ((leftPaddle <= ypos) && (ypos <= leftPaddle + paddleSize)) {
        // reverse the horizontal direction:
        xDirection =-xDirection;
      } 
    }
  }
  // if the ball is moving right: 
  else {
    if  ((xpos >= (width - (paddleMargin + ballSize/2)))) {
      if ((rightPaddle <= ypos) && (ypos <= rightPaddle + paddleSize)) {
        // reverse the horizontal direction:      
        xDirection =-xDirection;
      }
    }
  }

  if (xpos > width) {
    // right loses a point
    leftScore++;
    // make up a random number to change the speed of the ball:
    float someNum = random(2) + 1;
    xDirection = (int)-someNum;
    //set the ball back to the center:
    resetBall();
  }

  if (xpos < 0) {
    // left loses a point
    rightScore++;
    // make up a random number to change the speed of the ball:
    float someNum = random(2) + 1;
    xDirection = (int)someNum;
    //set the ball back to the center:

    resetBall();
  }
  // stop the ball going off the top or the bottom of the screen:
  if ((ypos - ballSize/2 <= 0) || (ypos +ballSize/2 >=height)) {
    // reverse the y direction of the ball:
    yDirection = -yDirection;
  }
  if (countDown >= 0) {
    // count down 2 seconds before starting a new ball:
    delay(10);
    countDown--;
  } 
  else {
    // move that ball!
    xpos = xpos + xDirection;
    ypos = ypos + yDirection;
  }
  // display the score:
  showScores();

  // Draw the ball
  rect(xpos, ypos, ballSize, ballSize);
}
/*
  Keystrokes activate secondary functions:
 
 */
void keyReleased() {
  int thisKey = keyCode;
  switch (thisKey) {
    // tab key resets game:
  case 9:
    resetGame();
    break;
    // return key toggles calibrate mode steps:
  case 10:  
    if (calibrate) {
      calibrateStep++;      
    }
    break;
    // spacebar enters and exits calibrate mode:
  case 32:  
    calibrateStep = 0;
    calibrate = !calibrate;
    break;
    // case 
  default: 
    println(thisKey);
  }
  thisKey = 0;
}

void checkScore() {
  if (leftScore >= 11) {
    gameOver = true;      
  }
  if (rightScore >= 11) {
    gameOver = true;
  }
}

/* 
  This method calibrates the paddles so that
  their ranges match the vertical height of the window
*/
void doCalibrate() {
  // display top and bottom values:
  text(leftBottom, 10, height-20);
  text(rightBottom, width-50, height-20);
  text(leftTop, 10, 20);
  text(rightTop, width-50, 20);
  // depending on which step we are on, reset the appropriate paddle limit:
  switch (calibrateStep) {
  case 0: 
    text("move left paddle to lowest point and press enter.", 10, 10);
    leftBottom = leftNumber;
    break;
  case 1: 
    text("move left paddle to highest point and press enter.", 10, 10);
    leftTop = leftNumber;
    break;
  case 2:
    text("move right paddle to lowest point and press enter.", 10, 10);
    rightBottom = rightNumber;
    text(leftBottom, 10, height-20);
    text(rightBottom, width-50, height-20);
    break;
  case 3: 
    text("move left paddle to highest point and press enter.", 10, 10);
    rightTop = rightNumber;
    break;
  case 4: 
  // leave the calibration routine:
    calibrate = false;
  default: 
  }
}
/*
  This method reads the values in from the serial port
  and sets the paddle values based on what it reads.
*/
void serialEvent() {
  // Add the latest byte from the serial port to array:
  serialInArray[serialCount] = port.read();
  serialCount++;
  // If we have 3 bytes, read them:
  if (serialCount > 2 ) {
    signalByte = serialInArray[0];
    leftNumber = serialInArray[1];
    rightNumber = serialInArray[2];
    // if we're not in the calibrate routine, use the values to move the paddles:
    if (!calibrate) {
      leftPaddle = ((leftNumber - leftTop) * height/(leftBottom - leftTop)) - paddleSize ;
      rightPaddle = ((rightNumber  - rightTop) * height/(rightBottom - rightTop)) - paddleSize;
    }
    // Send a byte to request new sensor readings:
    port.write(signalByte);
    // Reset serialCount:
    serialCount = 0;
  }
}
/*
  This method resets the ball.
*/
void resetBall() {
  xpos = width / 2 - (ballSize/2);
  ypos = height / 2 - (ballSize/2);
  countDown = countDownTime;
}
/*
  This method displays the scores:
*/
void showScores() {
  makeDigit(leftScore % 10, width/2 - 40, 10);
  if (leftScore > 9) {
    makeDigit(leftScore / 10, width/2 - 60, 10);
  }
  makeDigit(rightScore % 10, width/2 + 60, 10);
  if (rightScore > 9) {
    makeDigit(rightScore / 10, width/2 + 40, 10);
  }
}

/*
  This method draws the digits because
  there isn't a Pong font.
*/
void makeDigit(int numeral, int left, int top) {

  switch (numeral) {
  case 1:
    rect(left, top, 5, 35);
    break;
  case 2:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 15);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top+15, 5, 15);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 3:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 30);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    //  rect(left, top+15, 5, 15);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 4:
    // top
    //rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 35);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 15);
    // bottom
    //rect(left, top+30, 20, 5);
    break;
  case 5:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left+15, top+15, 5, 15);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 15);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 6:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top+15, 5, 15);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 30);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 7:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 35);
    //center
    // rect(left, top+15, 20, 5);
    // left vertical
    // rect(left, top+15, 5, 15);
    // bottom
    // rect(left, top+30, 20, 5);
    break;
  case 8:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 30);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 30);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 9:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left + 15, top, 5, 30);
    //center
    rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 15);
    // bottom
    rect(left, top+30, 20, 5);
    break;
  case 0:
    // top
    rect(left, top, 20, 5);
    // right vertical
    rect(left+15, top, 5, 30);
    //center
    //rect(left, top+15, 20, 5);
    // left vertical
    rect(left, top, 5, 30);
    // bottom
    rect(left, top+30, 20, 5);
    break;
    // default:
  }

}
/*
  This method draws GAME OVER because there isn't a Pong font:
*/
void drawGameOver(int x, int y) {
  int left = x;
  int top = y;
  //G
  // line 1
  rect(left, top, 25, 5);
  // line 2
  rect(left, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  // line 4
  rect(left, top+15, 5, 5);
  rect(left+15, top+15, 10, 5);
  // line 5
  rect(left, top+20, 5, 5);
  rect(left+20, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  rect(left+20, top+25, 5, 5);
  // line 7
  rect(left, top+30, 25, 5);
  left += 35;
  //A
  // line 1
  rect(left+10, top, 5, 5);
  // line 2
  rect(left+5, top+5, 5, 5);
  rect(left+15, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  rect(left+20, top+10, 5, 5);
  // line 4
  rect(left, top+15, 5, 5);
  rect(left+20, top+15, 5, 5);
  // line 5
  rect(left, top+20, 25, 5);
  // rect(left+20, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  rect(left+20, top+25, 5, 5);
  // line 7
  rect(left, top+30, 5, 5);
  rect(left+20, top+30, 5, 5);    
  left += 35;

  //M
  // line 1
  rect(left, top, 5, 5);
  rect(left+20, top, 5, 5);  
  // line 2
  rect(left, top+5, 10, 5);
  rect(left+15, top+5, 10, 5);  
  // line 3
  rect(left, top+10, 5, 5);
  rect(left+10, top+10, 5, 5);
  rect(left+20, top+10, 5, 5);
  // line 4
  rect(left, top+15, 5, 5);
  rect(left+20, top+15, 5, 5);
  // line 5
  rect(left, top+20, 5, 5);
  rect(left+20, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  rect(left+20, top+25, 5, 5);
  // line 7
  rect(left, top+30, 5, 5);
  rect(left+20, top+30, 5, 5);    
  left += 35;
  //E
  // line 1
  rect(left, top, 25, 5);
  // line 2
  rect(left, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  // line 4
  rect(left, top+15, 25, 5);
  // rect(left+15, top+15, 10, 5);
  // line 5
  rect(left, top+20, 5, 5);
  //  rect(left+20, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  //  rect(left+20, top+25, 5, 5);
  // line 7
  rect(left, top+30, 25, 5);
  left += 70;
  //O
  // line 1
  rect(left+5, top, 20, 5);

  // line 2
  rect(left, top+5, 5, 5);
  rect(left+25, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  rect(left+25, top+10, 5, 5);

  // line 4
  rect(left, top+15, 5, 5);
  rect(left+25, top+15, 5, 5);
  // line 5
  rect(left, top+20, 5, 5);
  rect(left+25, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  rect(left+25, top+25, 5, 5);
  // line 7
  rect(left+5, top+30, 20, 5);
  left += 35;
  //V
  // line 1
  rect(left, top, 5, 5);
  rect(left+20, top, 5, 5);
  // line 2
  rect(left, top+5, 5, 5);
  rect(left+20, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  rect(left+20, top+10, 5, 5);
  // line 4
  rect(left, top+15, 5, 5);
  rect(left+20, top+15, 5, 5);
  // line 5
  rect(left, top+20, 5, 5);
  rect(left+20, top+20, 5, 5);
  // line 6
  rect(left+5, top+25, 5, 5);
  rect(left+15, top+25, 5, 5);
  // line 7
  rect(left+10, top+30, 5, 5);
  left += 35;
  //E
  // line 1
  rect(left, top, 25, 5);
  // line 2
  rect(left, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  // line 4
  rect(left, top+15, 25, 5);
  // rect(left+15, top+15, 10, 5);
  // line 5
  rect(left, top+20, 5, 5);
  //  rect(left+20, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  //  rect(left+20, top+25, 5, 5);
  // line 7
  rect(left, top+30, 25, 5);

  left += 35;
  //R

  // line 1
  rect(left, top, 20, 5);
  // line 2
  rect(left, top+5, 5, 5);
  rect(left+20, top+5, 5, 5);
  // line 3
  rect(left, top+10, 5, 5);
  rect(left+20, top+10, 5, 5);
  // line 4
  rect(left, top+15, 20, 5);
  // rect(left+15, top+15, 10, 5);
  // line 5
  rect(left, top+20, 5, 5);
  rect(left+10, top+20, 5, 5);
  // line 6
  rect(left, top+25, 5, 5);
  rect(left+15, top+25, 5, 5);
  // line 7
  rect(left, top+30, 5, 5);
  rect(left+20, top+30, 5, 5);

}
/*
  This method resets the game:
*/
void resetGame() {
  leftScore = 0;
  rightScore = 0;
  gameOver = false;
  resetBall();
}