Did you ever Brick?
To those of you who have owned the first few generations of Apple portable devices, you’d be familiar with the image above. If you recall winding your thumb in a circular motion to navigate through the menus, you’d stumble upon two or three simple games (depending on which generation you had) that would momentarily abstract your boredom.
The game depicted in the picture above is called Bricks and I found some background info of what I’m assuming made Apple incorporate it in its devices. The excerpt is from my favorite biographer and I highly recommend reading his literature as well as the following:
One day in the late summer of 1975, Nolan Bushnell [founder of Atari and, um, Chuck E. Cheese’s], defying the prevailing wisdom that paddle games were over, decided to develop a single-player version of Pong; instead of competing against an opponent, the player would volley the ball into a wall that lost a brick whenever it was hit. He called [Steve] Jobs into his office, sketched it out on his little blackboard, and asked him to design it. There would be a bonus, Bushnell told him, for every chip fewer than fifty that he used. Bushnell knew that Jobs was not a great engineer, but he assumed, correctly, that he would recruit [Steve] Wozniak, who was always hanging around. “I looked at it as a two-for-one thing,” Bushnell recalled. “Woz was a better engineer.”
Wozniak was thrilled when Jobs asked him to help and proposed splitting the fee. “This was the most wonderful offer in my life, to actually design a game that people would use,” he recalled. Jobs said it had to be done in four days and with the fewest chips possible. What he hid from Wozniak was that the deadline was one that Jobs had imposed, because he needed to get to the All One Farm to help prepare for the apple harvest. He also didn’t mention that there was a bonus tied to keeping down the number of chips.
“A game like this might take most engineers a few months,” Wozniak recalled. “I thought that there was no way I could do it, but Steve made me sure that I could.” So he stayed up four nights in a row and did it. During the day at HP, Wozniak would sketch out his design on paper. Then, after a fast-food meal, he would go right to Atari and stay all night. As Wozniak churned out the design, Jobs sat on a bench to his left implementing it by wire-wrapping the chips onto a breadboard. “While Steve was breadboarding, I spent time playing my favorite game ever, which was the auto racing game Gran Trak 10,” Wozniak said.
Astonishingly, they were able to get the job done in four days, and Wozniak used only forty-five chips. Recollections differ, but by most accounts Jobs simply gave Wozniak half of the base fee and not the bonus Bushnell paid for saving five chips. It would be another ten years before Wozniak discovered (by being shown the tale in a book on the history of Atari titled Zap) that Jobs had been paid this bonus….
Steve Jobs
–Walter Isaacson
Now their version of Pong is what is now known as Breakout and below is my own version implemented in C using the Stanford Portable Library API. (Sorry in advance if it’s a little hard to read, Svbtle’s margins are very slim and I plan on redistributing it on GitHub when I come around to it). In addition, the source code is also a solution to a problem set from Harvard’s CS50; this problem set in particular being one of the more enjoyable to work out. I still have a few things I’d like to add to it (such as difficulty and UI improvements) so maybe when I’m bored I’ll come back to this.
If you have a C compiler at your disposal and you’d like to try it out, download the SPL here.
If there are any comments and concerns (maybe bugs perhaps?) , feel free to shoot me an e-mail.
//
// breakout.c
//
// Computer Science 50
// Problem Set 4
//
// standard libraries
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Stanford Portable Library
#include "gevents.h"
#include "gobjects.h"
#include "gwindow.h"
// height and width of game's window in pixels
#define HEIGHT 600
#define WIDTH 400
//height and width of paddle
#define PHEIGHT 10
#define PWIDTH 60
//height and width of bricks
#define BHEIGHT 12
#define BWIDTH 36
// number of rows of bricks
#define ROWS 5
// number of columns of bricks
#define COLS 10
// radius of ball in pixels
#define RADIUS 10
// lives
#define LIVES 3
// prototypes
void initBricks(GWindow window);
GOval initBall(GWindow window);
GRect initPaddle(GWindow window);
GLabel initScoreboard(GWindow window);
void updateScoreboard(GWindow window, GLabel label, int points);
GObject detectCollision(GWindow window, GOval ball);
int main(void)
{
// seed pseudorandom number generator
srand48(time(NULL));
// instantiate window
GWindow window = newGWindow(WIDTH, HEIGHT);
// instantiate bricks
initBricks(window);
// instantiate ball, centered in middle of window
GOval ball = initBall(window);
// instantiate paddle, centered at bottom of window
GRect paddle = initPaddle(window);
// instantiate scoreboard, centered in middle of window, just above ball
GLabel label = initScoreboard(window);
// number of bricks initially
int bricks = COLS * ROWS;
// number of lives initially
int lives = LIVES;
// number of points initially
int points = 0;
// x and y velocities
double xVelo = drand48() * 2.0;
double yVelo = 2.0;
// displays lives
GLabel text_lives = newGLabel("Lives: ");
setFont(text_lives, "SansSerif-12");
setLocation(text_lives, 320, getHeight(text_lives));
add(window, text_lives);
GLabel num_lives = newGLabel("");
setFont(num_lives, "SansSerif-12");
add(window, num_lives);
//message to start
GLabel start = newGLabel("Click to start!");
setFont(start, "SansSerif-36");
setLocation(start, (getWidth(window) - getWidth(start)) / 2, (getHeight(window) - getHeight(start)) / 2);
add(window, start);
// click to start game
waitForClick();
removeGWindow(window, start);
// keep playing until game over
while (lives > 0 && bricks > 0)
{
// TODO
// update lives
char l[12];
sprintf(l, "%i", lives - 1);
setLabel(num_lives, l);
setLocation(num_lives, getWidth(text_lives) + 320, getHeight(text_lives));
// update score
updateScoreboard(window, label, points);
move(ball, xVelo, yVelo);
pause(10);
GEvent event = getNextEvent(MOUSE_EVENT);
if(event != NULL)
{
// if mouse movement is detected
if(getEventType(event) == MOUSE_MOVED)
{
double x = getX(event) - getWidth(paddle) / 2;
double y = HEIGHT - 100;
// makes sure paddle doesn't extend over right wall
if(getX(event) >= getWidth(window) - getWidth(paddle) / 2)
{
setLocation(paddle, getWidth(window) - getWidth(paddle), y);
}
// makes sure paddle doesn't extend over left wall
else if(getX(event) <= getWidth(paddle) / 2)
{
setLocation(paddle, 0, y);
}
// paddle movement
else
{
setLocation(paddle, x, y);
}
}
}
GObject object = detectCollision(window, ball);
if(object != NULL)
{
// paddle collision
if(object == paddle)
{
yVelo = -yVelo;
}
// brick collision, remove brick, increment score
else if(strcmp(getType(object), "GRect") == 0)
{
removeGWindow(window, object);
yVelo = -yVelo;
points++;
bricks--;
}
}
// bounces ball off right wall
if(getX(ball) + getWidth(ball) >= 400)
{
xVelo = -xVelo;
}
// bounces ball of left wall
else if(getX(ball) <= 0)
{
xVelo = -xVelo;
}
// bounces wall off bottom
else if(getY(ball) + getHeight(ball) >= 600)
{
setLocation(ball, WIDTH / 2 - RADIUS, HEIGHT / 2 + RADIUS);
setLocation(paddle, (WIDTH / 2) - (PWIDTH / 2), HEIGHT - 100);
lives--;
waitForClick();
}
// bounces ball off top wall
else if(getY(ball) <= 0)
{
yVelo = -yVelo;
}
}
// win/lose message
if (bricks > 0)
{
removeGWindow(window, label);
GLabel game_over = newGLabel("YOU LOSE!");
setFont(game_over, "SansSerif-70");
setColor(game_over, "RED");
add(window, game_over);
setLocation(game_over, 15, 300);
}
else
{
removeGWindow(window, label);
GLabel game_over = newGLabel("YOU WIN!");
setFont(game_over, "SansSerif-70");
setColor(game_over, "GREEN");
add(window, game_over);
setLocation(game_over, 15, 300);
}
// wait for click before exiting
waitForClick();
// game over
closeGWindow(window);
return 0;
}
/**
* Initializes window with a grid of bricks.
*/
void initBricks(GWindow window)
{
// TODO
double x = 6, y = 73;
string colors[ROWS] = {"BLACK", "DARK_GRAY", "GRAY", "LIGHT_GRAY", "CYAN"};
for(int i = 0; i < COLS; i++)
{
y = 73;
for(int j = 0; j < ROWS; j++)
{
GRect block = newGRect(x, y, BWIDTH, BHEIGHT);
setColor(block, colors[j]);
setFilled(block, true);
add(window, block);
y+= BHEIGHT + 3;
}
x+= BWIDTH + 3;
}
}
/**
* Instantiates ball in center of window. Returns ball.
*/
GOval initBall(GWindow window)
{
// TODO
GOval ball = newGOval(WIDTH / 2 - RADIUS, HEIGHT / 2 + RADIUS, RADIUS * 2, RADIUS * 2);
setColor(ball, "DARK_GRAY");
setFilled(ball, true);
add(window, ball);
return ball;
}
/**
* Instantiates paddle in bottom-middle of window.
*/
GRect initPaddle(GWindow window)
{
// TODO
GRect paddle = newGRect((WIDTH / 2) - (PWIDTH / 2), HEIGHT - 100, PWIDTH, PHEIGHT);
setColor(paddle, "BLACK");
setFilled(paddle, true);
add(window, paddle);
return paddle;
}
/**
* Instantiates, configures, and returns label for scoreboard.
*/
GLabel initScoreboard(GWindow window)
{
// TODO
GLabel label = newGLabel("");
setFont(label, "SansSerif-36");
add(window, label);
return label;
}
/**
* Updates scoreboard's label, keeping it centered in window.
*/
void updateScoreboard(GWindow window, GLabel label, int points)
{
// update label
char s[12];
sprintf(s, "%i", points);
setLabel(label, s);
// center label in window
double x = (getWidth(window) - getWidth(label)) / 2;
double y = (getHeight(window) - getHeight(label)) / 2;
setLocation(label, x, y);
}
/**
* Detects whether ball has collided with some object in window
* by checking the four corners of its bounding box (which are
* outside the ball's GOval, and so the ball can't collide with
* itself). Returns object if so, else NULL.
*/
GObject detectCollision(GWindow window, GOval ball)
{
// ball's location
double x = getX(ball);
double y = getY(ball);
// for checking for collisions
GObject object;
// check for collision at ball's top-left corner
object = getGObjectAt(window, x, y);
if (object != NULL)
{
return object;
}
// check for collision at ball's top-right corner
object = getGObjectAt(window, x + 2 * RADIUS, y);
if (object != NULL)
{
return object;
}
// check for collision at ball's bottom-left corner
object = getGObjectAt(window, x, y + 2 * RADIUS);
if (object != NULL)
{
return object;
}
// check for collision at ball's bottom-right corner
object = getGObjectAt(window, x + 2 * RADIUS, y + 2 * RADIUS);
if (object != NULL)
{
return object;
}
// no collision
return NULL;
}