BadgerHack: Gaming Add-On Kit

Pages
Contributors: Shawn Hymel
Favorited Favorite 1

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);
}