1

First of all, here is my code for Breakout (pset3):

//
// breakout.c
//
// Computer Science 50
// Problem Set 3
//

// standard libraries
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// Stanford Portable Library
#include <spl/gevents.h>
#include <spl/gobjects.h>
#include <spl/gwindow.h>

// height and width of game's window in pixels
#define HEIGHT 600
#define WIDTH 400

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

    //generate velocity values
    double VelocityX = drand48() + 1;
    double VelocityY = 2;

    // instantiate game 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 scoreboard = initScoreboard(window);
    sendBackward(scoreboard);

    // number of bricks initially
    int bricks = COLS * ROWS;

    // number of lives initially
    int lives = LIVES;

    // number of points initially
    int points = 0;

    // keep playing until game over
    restart:
    waitForClick();
    while (lives > 0 && bricks > 0)
    {
        // move paddle with mouse cursor
        GEvent PaddleMove = getNextEvent(MOUSE_EVENT);

        if (PaddleMove != NULL)
        {
            if (getEventType(PaddleMove) == MOUSE_MOVED)
            {
                double x = getX(PaddleMove) - 30;
                setLocation(paddle, x, 560);
            }
        }

        // sets ball in motion
        move(ball, VelocityX, VelocityY);

        // makes ball bounce off edges of window
        if (getX(ball) + getWidth(ball) >= getWidth(window))
            VelocityX = -VelocityX;
        else if (getX(ball) <= 0)
            VelocityX = -VelocityX;
        else if (getY(ball) + getHeight(ball) >= getHeight(window)) 
        {
            setLocation(ball, WIDTH/2 - RADIUS*2, HEIGHT/2 - RADIUS*2); 
            lives--;
            goto restart;
        }
        else if (getY(ball) <= 0)
            VelocityY = -VelocityY;   

        // set behaviour of collisions
        GObject object = detectCollision(window, ball);

        if (object != NULL)
        {   
            // collisions with paddle        
            if (strcmp(getType(object), "GRect") == 0)
            {
                VelocityY = -VelocityY;

                // collisions with bricks
                if (object != paddle)
                {
                    removeGWindow (window, object);
                    points++;
                    updateScoreboard(window, scoreboard, points);
                }           
            }           
        }
        // allow pauses to avoid slowdown 
        pause(8);
    }

    // wait for click before exiting
    waitForClick();

    // game over
    closeGWindow(window);
    return 0;
}

/**
 * Initializes window with a grid of bricks.
 */
void initBricks(GWindow window)
{
    for (int n = 0; n < ROWS; n++)
    {  
        int BlockYPosition = 55 + n*15; 
        int BlockXPosition = 2;
        for (int m = 0; m < COLS; m++)
        {
            GRect block = newGRect (BlockXPosition + m*40, BlockYPosition, 36, 11);
            if (n == 0)
                setColor (block, "ff4eef");
            if (n == 1)
                setColor (block, "fff84e");
            if (n == 2)
                setColor (block, "58f2f6");
            if (n == 3)
                setColor (block, "ff4e84");
            if (n == 4)
                setColor (block, "ffbe43");
            setFilled (block, true);
            add(window, block);
        }

    }
}

/**
 * Instantiates ball in center of window.  Returns ball.
 */
GOval initBall(GWindow window)
{
    GOval ball = newGOval(WIDTH/2 - RADIUS*2 + 11, HEIGHT/2 - RADIUS*2, RADIUS*2, RADIUS*2);
    setColor (ball, "3664e0");
    setFilled (ball, true);
    add (window, ball);
    return ball;
}

/**
 * Instantiates paddle in bottom-middle of window.
 */
GRect initPaddle(GWindow window)
{
    GRect paddle = newGRect (170, 560, 60, 10);
    setColor (paddle, "3639e0");
    setFilled (paddle, true);
    add (window, paddle);
    return paddle;
}

/**
 * Instantiates, configures, and returns label for scoreboard.
 */
GLabel initScoreboard(GWindow window)
{
      GLabel scoreboard = newGLabel("0");
      setFont(scoreboard, "Helvetica-40");
      double x = (getWidth(window) - getWidth(scoreboard)) / 2;
      double y = (getHeight(window) - getHeight(scoreboard)) / 2;
      setLocation(scoreboard, x, y);
      setColor(scoreboard, "c1e3da");
      add(window, scoreboard);
      sendBackward(scoreboard);
      return scoreboard;
}

/**
 * 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;
}

My code works just fine (maybe just a little bit more antsy than staff implementation). However, from the very beginning I wanted my game to have a black background. Having no success in finding the way to change the color of the game window itself, I decided to make a background by adding this bit of code just after instantiating game window:

GRoundRect background = newGRoundRect (0, 0, WIDTH, HEIGHT, 0);
setColor (background, "BLACK");
setFilled (background, true);
add (window, background);

It's just a black rectangle of the same size as the window. I used GRoundRect instead of GRect just so there would be no problems when it comes to detecting collisions at this part of code:

// collisions with paddle        
   if (strcmp(getType(object), "GRect") == 0)
   {
       VelocityY = -VelocityY;

       // collisions with bricks
       if (object != paddle)
       {
       removeGWindow (window, object);
       points++;
       updateScoreboard(window, scoreboard, points);
       }
   }

When I added this background, strange things started to happen:

ball goes through the paddle ball sticks to paddle, goes through it at edges

I know that the background color is not the most important part of the game, but I spent quite some time trying to figure out what is going on and came to no solution. Nowhere in the code anything interacts with GRoundRect type, yet it seems to create problems to the game. Any ideas?

1

The problem is that the GRoundRect is still a GObject and detectCollision returns it as the ball collides with it, which happens most of the time.

To be more precise, detectCollision basically uses a function from the SPL getGObjectAt, which according to the specs:

Returns a pointer to the topmost GObject containing the point (x, y), or NULL if no such object exists.

furthermore, it separately detects collisions with four points on the ball exactly in that order:

  1. top-left.
  2. top-right.
  3. bottom-left.
  4. bottom-right.

and once a collision with another GObject is detected, this GObject is returned immediately. when the ball first collides with the paddle as it's going down, typically one or two points are collided with the paddle (and those are of course bottom-left and bottom-right).

But since detectCollision checks top-left and top-right first, it still returns GRoundRect and not the paddle so the ball just keeps moving down until the top-left point of the ball collides with the paddle in which case getGObjectAt as well as detectCollision return the topmost GObject which is the paddle at this point.

Unfortunately, the SPL is not bug-free. The easiest solution is probably to modify the code of detectCollision in such a way that it detects collision with all of the four points first and if one of GObjects returned by getGObjectAt is a GRect, you return it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .