Arduino Pong on S65 Shield

I recently received a S65 Shield from watterott.com and I wanted a simple project to learn how to control it.

So I wrote a game of Pong. Check out the video here : http://www.codetorment.com/2009/11/11/arduino-pong-using-s65-shield/. I’ll post the code once I’ve cleaned it up.

I taught it would be a nice idea to have some sound to go with the visuals. I had a buzzer lying around, I just forgot how painfully loud and irritating it can be >:(. The sound in the video doesn't do justice to the high pitch and loud volume this buzzer produces. That's why the video only last for 30 something seconds. Check for yourself http://www.vimeo.com/7596054

I still didn’t get around to cleaning up the code, I’ve just added some comments…

#include <S65Display.h>
#include <RotaryEncoder.h>
#include <string.h>


#define WIDTH 176
#define HEIGHT 132
#define BAT_WIDTH 5
#define BAT_HEIGHT 30
#define BAT_INIT_X 5
#define BAT_INIT_Y 56
#define BALL 5      
#define BAT_STEP 5
#define AI_STEP 2
#define MAX_TURNS 5            

float ball_x = WIDTH / 2;
float ball_x_prev = ball_x;
float ball_y = HEIGHT / 2;
float ball_y_prev = ball_y;
float ball_dir = 0;
float bat_1_dir = 0;
int bat1_x = BAT_INIT_X;
int bat1_y = BAT_INIT_Y;
int bat1_y_prev = bat1_y;
int bat2_x = WIDTH - BAT_INIT_X - BAT_WIDTH;
int bat2_y = BAT_INIT_Y;
int bat2_y_prev = bat2_y;
int bat2_step = AI_STEP;  
int p1_score = 0;
int p2_score = 0;
int ball_angle = 0;            // angle between ball path and horizontal axis
int ball_angle_sign = 0;       // sign of ball_angle : up = +1, down = -1, horizontal = 0
boolean score = false;
boolean gameover = false;
char buffer[256];              // String buffer

S65Display lcd;
RotaryEncoder encoder;

// Encoder must be serviced regularly.
ISR(TIMER2_OVF_vect)
{
  TCNT2 -= 250; //1000 Hz
  encoder.service();
}


void setup()
{
  lcd.init(2);
  encoder.init();
  // More encoder stuff
  //init Timer2
  TCCR2B  = (1<<CS22); //clk=F_CPU/64
  TCNT2   = 0x00;
  TIMSK2 |= (1<<TOIE2); //enable overflow interupt
  score = false;
  gameover = false;
  lcd.clear(0);
  initTurn();
  startGame();
}

void loop(){
  
  while(!score && !gameover){
    checkControls();
    updateAI();
    updateFields();
    drawScreen();
    delay(10);  
  }

  if(checkPress() && !gameover){      // if a player scored wait for rotary press
    initTurn();
  }
  delay(10);  
}

// initialize fields for the beginning of game/round
void initTurn(void){
  lcd.clear(0);
  ball_x = WIDTH / 2;
  ball_x_prev = ball_x;
  ball_y = HEIGHT / 2;
  ball_y_prev = ball_y;
  ball_dir = 0;
  bat1_x = BAT_INIT_X;
  bat1_y = BAT_INIT_Y;
  bat1_y_prev = bat1_y;
  bat2_x = WIDTH - BAT_INIT_X - BAT_WIDTH;
  bat2_y = BAT_INIT_Y;
  bat2_y_prev = bat2_y;
  ball_angle = 0;        // angle between ball path and horizontal axis
  ball_angle_sign = 0;   // up = +1, down = -1, horizontal = 0
  score = false;
  ball_dir = 1;
  ball_angle = 45;
}

// start the game and wait for press on rotary encoder
void startGame(void){                                             
  lcd.drawText(35, 60, "Click to start", RGB(255,255,255), RGB(0,0,0));
  while(!checkPress()){
    delay(10);
  };
  lcd.clear(0);
}

boolean checkPress(void){                                          // checks if rotary encoder was pressed
  int8_t press;
  press = encoder.sw();
  if (SW_PRESSED == press || SW_PRESSEDLONG == press) {
    return true;
  }
  else{
    return false;
  }
}

void checkRotation(void){
  moveBat(encoder.step());
}

void checkControls(void){
  if(checkPress()){    // pause
    lcd.drawText(45, 60, "Game paused", RGB(255,255,255), RGB(0,0,0));
    while(!checkPress()){
    }
    lcd.clear(0);
  }
  checkRotation();
}

void moveBat(int rot){
  if(!score && !gameover){
    if(rot == 1){                                       // clockwise rotation, move bat up
      if(bat1_y - BAT_STEP >= 0){                       // only move up when there is enough space left
        bat1_y -= BAT_STEP;
      }         
    }
    else if(rot == -1){                                // anti-clockwise rotation, move bat down
      if((bat1_y + BAT_HEIGHT + BAT_STEP) < HEIGHT){   // only move down when there is enough space left
        bat1_y += BAT_STEP;
      }
    }
  }
}

void updateFields(void){
  if(ball_y <= 0 || ball_y + BALL >= HEIGHT){          // top or bottom of screen reached, bounce ball back
    if(ball_angle_sign == -1){                         // ball was going down
      ball_angle_sign = 1;                             // ball is now going up
    }
    else{                                              // ball was going up
      ball_angle_sign = -1;                            // ball is now going down
    }    
  }

  if((ball_x <= 12 && ball_x >= 11) && (ball_dir == 1)){                  // check if ball has is in reach of bat1 (horizontal), for some reason checking for equality doens't seem to work here ?
    if((ball_y + BALL) >= bat1_y && ball_y <= (bat1_y + BAT_HEIGHT)){     // ball is in reach of bat1 (vertical)
      ball_dir = 0;                                                       // ball hits bat1, change direction
    }
  }

  if((ball_x >= 160 && ball_x <= 162) && (ball_dir == 0)){                // check if ball has is in reach of bat2 (horizontal)
    if((ball_y + BALL) >= bat2_y && ball_y <= (bat2_y + BAT_HEIGHT)){     // ball is in reach of bat2 (vertical)
      ball_dir = 1;                                                       // ball hits bat2, change direction
    }
  }

  if( ball_x > 0 && ball_x + BALL < WIDTH){                               // ball is not near left or right edge
    if(ball_dir == 0){                                                    // ball is moving to the right
      ball_x += cos(ball_angle);                                          // cosine is always positive in 1st and 4th quadrant
      if(ball_angle_sign == -1){                                          // check sign of angle
        ball_y  -= sin(ball_angle);                                       // negative if angle in 4th quadrant
      }
      else{
        ball_y  += sin(ball_angle);                                       // positive if angle in 1th quadrant
      }
    }
    else{                                                                 // ball is moving to the left
      ball_x -= cos(ball_angle);                                          // cosine is always negative in 2nd and 3rd quadrant
      if(ball_angle_sign == -1){
        ball_y  -= sin(ball_angle);                                       // sine is always negative in 3rd quadrant
      }
      else{
        ball_y  += sin(ball_angle);                                       // sine is always positive in 2nd quadrant
      }
    }

  } 
  else{                                                                   // ball hits left or right edge
    if(!score && !gameover){                                              
      if(ball_dir == 0){                                                  // ball was going to the right
        if(p1_score++ < MAX_TURNS){                                       
          p1_score++;                                                     // player 1 scores
        }
        else{
          gameover = true; 
        }
      } 
      else{                                                               // ball was going to the left
        if(p2_score < MAX_TURNS){
          p2_score++;                                                     // player 2 scores
        }
        else{
          gameover = true; 
        }
      }
      score = true;
    }
  }
}

void updateAI(void){
  if(ball_y > (bat2_y + (BAT_HEIGHT / 2))){            // bat2 follows vertical position of ball, TODO : implement difficulty levels
    if((bat2_y + BAT_HEIGHT + bat2_step) < HEIGHT){    // TODO : make function moveBat() that does the bound checking
      bat2_y += bat2_step;
    }

  }
  else{
    if((bat2_y - bat2_step) >= 0){   
      bat2_y -= bat2_step;
    } 
  }
}

void drawScreen(void){
  // draw ball
  lcd.drawRect( ball_x_prev, ball_y_prev, ball_x_prev + BALL, ball_y_prev + BALL, RGB(0,0,0));
  lcd.drawRect( ball_x, ball_y, ball_x + BALL, ball_y + BALL, RGB(255,255,255));
  ball_x_prev = ball_x;
  ball_y_prev = ball_y;
  // draw left bat
  lcd.drawRect( bat1_x, bat1_y_prev, bat1_x + BAT_WIDTH, bat1_y_prev + BAT_HEIGHT, RGB(0,0,0));
  lcd.drawRect( bat1_x, bat1_y, bat1_x + BAT_WIDTH, bat1_y + BAT_HEIGHT, RGB(255,255,255));
  bat1_y_prev = bat1_y;
  // draw right bat
  lcd.drawRect( bat2_x, bat2_y_prev, bat2_x + BAT_WIDTH, bat2_y_prev + BAT_HEIGHT, RGB(0,0,0));
  lcd.drawRect( bat2_x, bat2_y, bat2_x + BAT_WIDTH, bat2_y + BAT_HEIGHT, RGB(255,255,255));
  bat2_y_prev = bat2_y;
  drawScore();
}

void drawScore(void){
  sprintf(buffer, "%d - %d", p1_score, p2_score);
  lcd.drawText(72, 5, buffer, RGB(255,255,255), RGB(0,0,0));
}