Did you ever Brick?

ipod-games.jpg

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.png

//
// 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;
}
 
14
Kudos
 
14
Kudos

Now read this

T-Minus 24 Days, 13 Hours, 29 Minutes

I’m not one to brag, but I’d definitely say I have a quintessential taste for music. Need some convincing? My ears are cohesively bounded to the exorbitant genres that personify our culture and I am constantly fascinated by the talent of... Continue →