BadgerHack: Gaming Add-On Kit

Pages
Contributors: Shawn Hymel

The Code

Plug the USB side of your BadgerStick into your computer. Make sure "BadgerStick" and the associated COM port are selected in the Arduino IDE, and click upload.

language:c
/**
 * BadgerHack Breakout
 * Shawn Hymel @ SparkFun Electronics
 * September 23, 2015
 * 
 * A clone of the famous "Breakout" game on the BadgerHack platform. Use the
 * joystick to move the paddle and try to knock out the upper pixels with the
 * ball.
 * 
 * License: http://opensource.org/licenses/MIT
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <SparkFun_LED_8x7.h>
#include <Chaplex.h>

// Constants
#define DEBUG                1
#define FPS                  60
#define SENSITIVITY          100
#define MAX_X_SPAN           127
#define PADDLE_SIZE          2
#define INITIAL_BALL_SPEED   0.1
#define BALL_SPEED_INC       0.004
#define PAUSE_BEFORE_SHOOT   1000    // ms
#define ROW_SIZE             7
#define COL_SIZE             8
#define FIELD_SIZE           ROW_SIZE * COL_SIZE

// Pin definitions
#define RNG_SEED_PIN  2
#define X_PIN         0
#define Y_PIN         1
#define BUTTON_1_PIN  3
#define BUTTON_2_PIN  4

// Global variables
byte led_pins[] = {2, 3, 4, 5, 6, 7, 8, 9}; // Pins for LEDs
uint16_t horz_zero;
uint16_t vert_zero;
uint8_t paddle_size;

// Setup
void setup() {

#if DEBUG
  Serial.begin(9600);
  Serial.println(F("Breakout demo for BadgerHack"));
#endif

  // Initialize and clear display
  Plex.init(led_pins);
  Plex.clear();
  Plex.display();

  // Seed our random number generator
  randomSeed(analogRead(2));

  // Calibrate our joystick by finding the center
  horz_zero = analogRead(X_PIN);
  vert_zero = analogRead(Y_PIN);
}

// Loop - play the game forever
void loop() {

  // Play the game inifinity times
  playGame();
}

/****************************************************************
 * Functions
 ***************************************************************/

// Play the game
void playGame() {

  // Calculate the new x max based on paddle size
  paddle_size = PADDLE_SIZE;
  uint16_t field_max = MAX_X_SPAN - 
                        (paddle_size - 1) * (MAX_X_SPAN / 7);

  // Create new game variables
  boolean playing;
  boolean win;
  unsigned long frame_start;
  uint8_t millis_per_frame = 1000/FPS;
  unsigned long game_start;
  boolean ball_moving;
  int16_t paddle_move;
  int16_t paddle_field = field_max / 2;
  uint8_t paddle_x;
  float ball_x = 3;
  float ball_y = 5;
  float inc_x;
  float inc_y;
  float ball_speed;
  uint8_t ball_round_x;
  uint8_t ball_round_y;  
  uint16_t ball_theta;
  boolean can_deflect = true;
  int i;
  uint8_t x;
  uint8_t y;
  byte the_wall[] = { 1,1,1,1,1,1,1,
                      1,1,1,1,1,1,1,
                      1,1,1,1,1,1,1,
                      1,1,1,1,1,1,1,
                      0,0,0,0,0,0,0,
                      0,0,0,0,0,0,0,
                      0,0,0,0,0,0,0,
                      0,0,0,0,0,0,0 };
#if DEBUG
  Serial.println("New game!");
  Serial.print("Field span: ");
  Serial.println(field_max);
#endif

  // Note when we start the game (so we can shoot the ball)
  game_start = millis();
  ball_moving = false;

  // Assign an initial direction and speed to the ball
  ball_theta = initBallTheta();
  ball_speed = INITIAL_BALL_SPEED;

  // Play the game until we win or lose
  playing = true;
  while ( playing ) {

    // For each frame, aim for refresh rate
    frame_start = millis();

    // Check for a win condition
    win = true;
    for ( i = 0; i < FIELD_SIZE; i++ ) {
      if ( the_wall[i] > 0 ) {
        win = false;
        break;
      }
    }
    if ( win ) {
      Plex.clear();
      Plex.scrollText("You win!", 1);
      delay(5000);
      Plex.stopScrolling();
      playing = false;
    }

    // Read the value of the joystick and map to a movement
    paddle_move = analogRead(X_PIN) - horz_zero;
    paddle_move = paddle_move / SENSITIVITY;
#if 0
    Serial.print("Moving: ");
    Serial.println(paddle_move);
#endif

    // Move the paddle and calculate its real x position
    paddle_field = paddle_field + paddle_move;
    if ( paddle_field <= 0 ) {
      paddle_field = 0;
    }
    if ( paddle_field >= field_max ) {
      paddle_field = field_max;
    }
    paddle_x = map(paddle_field, 0, field_max,
                   0, 6 - (paddle_size - 1));

    // If the ball has been shot, move it
    if ( ball_moving ) {

      // Calculate the ball's new position
      ball_x += ball_speed * cos(ball_theta * (M_PI / 180));
      ball_y += ball_speed * sin(ball_theta * (M_PI / 180));

      // Check the ball against the paddle
      if ( (ball_y > 6) &&
            (ball_x >= paddle_x) &&
            (ball_x <= (paddle_x + (paddle_size - 1))) &&
            can_deflect ) {
        ball_y = 6 - abs(6 - ball_y);
        ball_theta = 360 - ball_theta;
        can_deflect = false;
      }

      // Allow ball to be deflected once it leaves paddle range
      if ( ball_y <= 6 ) {
        can_deflect = true;
      }

      // Check if the ball moved past the paddle (lose)
      if ( ball_y > 7 ) {
#if DEBUG
        Serial.print("LOSE! x=");
        Serial.print(ball_x);
        Serial.print(" y=");
        Serial.print(ball_y);
        Serial.print(" Paddle:");
        Serial.print(paddle_x);
        Serial.print("-");
        Serial.println(paddle_x + paddle_size - 1);
#endif
        playing = false;
      }

      // Check the ball against the walls (and bounce!)
      if ( ball_y < 0 ) {
        ball_y = abs(ball_y);
        ball_theta = 360 - ball_theta;
      }
      if ( ball_x < 0 ) {
        ball_x = abs(ball_x);
        ball_theta = (540 - ball_theta) % 360;
      }
      if ( ball_x > 6 ) {
        ball_x = 6 - abs(6 - ball_x);
        ball_theta = (540 - ball_theta) % 360;
      }

      // Bounce if we hit a block above the ball
      i = (floor(ball_y) * ROW_SIZE) + roundFloat(ball_x);
      if ( the_wall[i] > 0 ) {
        the_wall[i]--;
        ball_y = (i / ROW_SIZE) + abs((i / ROW_SIZE) - ball_y);
        ball_theta = 360 - ball_theta;
        ball_speed += BALL_SPEED_INC;
      }

      // Bounce if we hit a block below the ball
      i = (ceil(ball_y) * ROW_SIZE) + roundFloat(ball_x);
      if ( the_wall[i] > 0 ) {
        the_wall[i]--;
        ball_y = (i / ROW_SIZE) - abs((i / ROW_SIZE) - ball_y);
        ball_theta = 360 - ball_theta;
        ball_speed += BALL_SPEED_INC;
      }

      // Bounce if we hit a block to the left the ball
      i = (roundFloat(ball_y) * ROW_SIZE) + floor(ball_x);
      if ( the_wall[i] > 0 ) {
        the_wall[i]--;
        ball_y = (i / ROW_SIZE) + abs((i / ROW_SIZE) - ball_y);
        ball_theta = (540 - ball_theta) % 360;
        ball_speed += BALL_SPEED_INC;
      }

      // Bounce if we hit a block to the right the ball
      i = (roundFloat(ball_y) * ROW_SIZE) + ceil(ball_x);
      if ( the_wall[i] > 0 ) {
        the_wall[i]--;
        ball_y = (i / ROW_SIZE) - abs((i / ROW_SIZE) - ball_y);
        ball_theta = (540 - ball_theta) % 360;
        ball_speed += BALL_SPEED_INC;
      }

    } else {

      // See if we need to start moving the ball
      if ( millis() >= game_start + PAUSE_BEFORE_SHOOT ) {
        ball_moving = true;
      }
    }

    // Round the ball's position to the nearest pixel
    ball_round_x = roundFloat(ball_x);
    ball_round_y = roundFloat(ball_y);

    // Draw tbe wall, the paddle, and the ball
    Plex.clear();


    for ( y = 0; y < COL_SIZE; y++ ) {
      for ( x = 0; x < ROW_SIZE; x++ ) {
        if ( the_wall[(x * ROW_SIZE) + (ROW_SIZE - 1 - y)] > 0 ) {
          Plex.pixel(x, y);
        }
      }
    }
    for ( i = 0; i < paddle_size; i++ ) {
      Plex.pixel(7, map(paddle_x + i, 0, 6, 6, 0));
    }
    Plex.pixel(ball_round_y, map(ball_round_x, 0, 6, 6, 0));
    Plex.display();

    // Wait until we reach our target end of frame
    while ( millis() < frame_start + millis_per_frame ) {
      delay(1);
    }
 #if 0
   Serial.print("FPS: ");
   Serial.println( 1000 / (millis() - frame_start) );
 #endif

  }
}

// Create a randomized ball launch angle
unsigned int initBallTheta() {

  unsigned int theta;

  // Choose an angle  in the range of 210-239 deg or 301-330 deg
  theta = random(0, 60);
#if DEBUG
  Serial.print("RNG:");
  Serial.print(theta);
#endif
  if ( theta < 30 ) {
    theta = 210 + theta;
  } else {
    theta = 271 + theta;
  }

#if DEBUG
  Serial.print(" Theta:");
  Serial.println(theta);
#endif

  return theta;
}

// Rounds a floating value to an integer
int roundFloat(float x) {
  if ( x >= 0 ) {
    return (int) (x + 0.5);
  }
  return (int) (x - 0.5);
}