RETIRED - BadgerHack
This Tutorial is Retired!
This tutorial covers concepts or technologies that are no longer current. It's still here for you to read and enjoy, but may not be as useful as our newest tutorials.
Make a Game
OK, so the display might be small (don't get your hopes up of running Doom on an 8x7 monochrome display). However, we can use it to display information and even play some basic games.
Remember the game Breakout? I don't, really, but the concept was cool. Let's make a Breakout clone on our BadgerStick!
Hardware
Required Components
We will need a few other components to make a simple controller for the BadgerStick.
More Soldering
To begin, snap off 15 pins from the break-away headers, and solder them to the through-holes on the side opposite the LED array of the BadgerStick.
Solder the Thumb Joystick to the Thumb Joystick Breakout board.
Snap off 5 pins from the break-away headers, and solder them to the through-holes on the Joystick Breakout Board.
Connections
Place the BadgerStick in the breadboard with pin 10 in position i13 and pin 5V in position i27.
Connect the rest of the components as follows:
Component | Breadboard | |||
---|---|---|---|---|
Thumb Joystick Breakout* | i7 (VCC) | i6 (VERT) | i5 (HOR) | i3 (GND) |
Pushbutton | c20 | c22 | f20 | f22 |
Pushbutton | c28 | c30 | f28 | f30 |
Jumper Wire | ( - ) | g30 | ||
Jumper Wire | ( - ) | g25 | ||
Jumper Wire | ( - ) | g22 | ||
Jumper Wire | ( - ) | j3 | ||
Jumper Wire | j7 | g23 | ||
Jumper Wire | j6 | g18 | ||
Jumper Wire | j5 | g17 | ||
Jumper Wire | g21 | g28 |
* Pins not listed are not used.
IMPORTANT: You can leave the battery pack soldered into the BadgerStick if you desire. If you remove the battery pack, you will need to supply power through another means, such as a USB port or a USB extension cable.
You should now have a makeshift game controller with a tiny LED screen!
The Code
In a new Arduino sketch window, copy in the following code:
language:c
/****************************************************************
BadgerHack_Breakout.ino
This code is beerware; if you see me (or any other SparkFun
employee) at the local, and you've found our code helpful, please
buy us a round!
Distributed as-is; no warranty is given.
****************************************************************/
#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);
}
Play
Upload the program to the BadgerStick, and prepare to play!
Use the joystick to move the paddle back and forth to bounce the ball. You win when you "break" all the lights on the top part of the screen. You lose if you let the ball go past your paddle.
You might notice that we do not use the 2 buttons in the game. Breakout only requires a joystick. However, you now have a basic platform that is perfect for creating games!
What other games would you want to make? Here are some ideas: