0

I'm trying to learn java by following the cs106a course online. Currently I've arrived at the breakout exersize and I'm having some trouble with bugs

I haven't finished it completely but I want to solves these issues first before I go further.

Problem 1:

when the ball collides with the bricks they don't always seem to be removed from the canvas. sometimes the brick will be removed on a second time it collides. But there is one row (of yellow bricks) that doesn't respond at all to the ball collisions.

Problem 2:

my paddle can be moved by dragging the mouse. the only problem is. the bricks can also be moved like the paddle.

I know it has something to do with this piece of code

gobj = getElementAt(lastX, lastY);

If I remove it altogether the bricks aren't moveable anymore. which is good. but I'm still able to move the paddle no matter where I click and drag.

can anyone give me a hint so I can correct the mistakes?

Here's my code below. Thanks

   /*  
 * File: Breakout.java 
 * ------------------- 
 * This file will eventually implement the game of Breakout. 
 */
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

import org.omg.CORBA.PUBLIC_MEMBER;

public class breakout extends GraphicsProgram {
    /** Width and height of application window in pixels */
    public static final int APPLICATION_WIDTH = 400;
    public static final int APPLICATION_HEIGHT = 600;
    /** Dimensions of game board (usually the same) */
    private static final int WIDTH = APPLICATION_WIDTH;
    private static final int HEIGHT = APPLICATION_HEIGHT;
    /** Dimensions of the paddle */
    private static final int PADDLE_WIDTH = 60;
    private static final int PADDLE_HEIGHT = 10;
    /** Offset of the paddle up from the bottom */
    private static final int PADDLE_Y_OFFSET = 30;
    /** Number of bricks per row */
    private static final int NBRICKS_PER_ROW = 10;
    /** Number of rows of bricks */
    private static final int NBRICK_ROWS = 8;
    /** Separation between bricks */
    private static final int BRICK_SEP = 4;
    /** Width of a brick */
    private static final int BRICK_WIDTH = (WIDTH - (NBRICKS_PER_ROW - 1)* BRICK_SEP)/ NBRICKS_PER_ROW;
    /** Height of a brick */
    private static final int BRICK_HEIGHT = 8;
    /** Radius of the ball in pixels */
    private static final int BALL_RADIUS = 10;
    /** Offset of the top brick row from the top */
    private static final int BRICK_Y_OFFSET = 70;
    /** Number of turns */
    private static final int NTURNS = 3;


    private static final int DELAY = 50;
    private static final double X_START = WIDTH / 2;
    private static final double Y_START = 450;

    public void run() {
        world();
        play();
    }

    private void ball() {

        ball = new GOval(X_START, Y_START, BALL_RADIUS, BALL_RADIUS);
        ball.setFillColor(Color.BLACK);
        ball.setFilled(true);
        add(ball);

    }

    private void paddle() {

        paddle = new GRect(100, 500, PADDLE_WIDTH, PADDLE_HEIGHT);
        paddle.setColor(Color.BLACK);
        paddle.setFilled(true);
        add(paddle);
    }


    private void brick(int x, int y, Color c) {

        GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT);
        brick.setColor(getBackground());
        brick.setFillColor(c);
        brick.setFilled(true);
        add(brick);

    }

    private void brickRow(int x, int y, Color c) {
        x = BRICK_SEP / 2;
        y += BRICK_Y_OFFSET;
        for (int i = 0; i < NBRICKS_PER_ROW; i++) {
            brick(x, y, c);
            x += BRICK_WIDTH + BRICK_SEP;
        }

    }


private void world() {

        //initialize x and y position for the rows of bricks
        int x = 0;
        int y = 0;
        // set starting color for first row
        Color c = Color.red;

        paddle();

        //create 2 rows of bricks and switch colors
        for (int i = 0; i < NBRICK_ROWS; i++) {
            if (i <= 1) {
                c = Color.ORANGE;

            } else if (i > 1 && i <= 3) {
                c = Color.YELLOW;

            } else if (i > 3 && i <= 5) {
                c = Color.GREEN;

            } else if (i > 5 && i <= 7) {
                c = Color.CYAN;
            }

            brickRow(x, y, c);
            y += BRICK_HEIGHT + BRICK_SEP;
        }

    }



    private void moveBall() {
        ball.move(xVel, yVel);

    }


    public void mousePressed(MouseEvent e) {

        lastX = e.getX();
        lastY = e.getY();
        gobj = getElementAt(lastX, lastY);

    }

    public void mouseDragged(MouseEvent e) {
        if (paddle != null) {
            paddle.move(e.getX() - lastX, getY());
            lastX = e.getX();
            lastY = e.getY();
        }

        //constrain paddle movement
        if (paddle.getX() < 0){
            paddle.setLocation(0, 500);
        }else if (paddle.getX()+BRICK_WIDTH > WIDTH ){
            paddle.setLocation(WIDTH-BRICK_WIDTH, 500);
        }


    }

    private void checkForCollision() {

        // ball collission with walls
        if (ball.getY() > getHeight() - BALL_RADIUS
                || ball.getY() < 0 + BALL_RADIUS) {
            yVel = -yVel;
        } else if (ball.getX() > getWidth() - BALL_RADIUS
                || ball.getX() < 1 + BALL_RADIUS) {
            xVel = -xVel;

            // ball collission with paddle
        } else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
                + BALL_RADIUS) == paddle) {
            yVel = -yVel;

            // check for collision with bricks to remove them but ignore collision with paddle
        } else if (getElementAt(ball.getX(), ball.getY()) != null
                && getElementAt(ball.getX(), ball.getY()) != paddle) {
            remove(getCollidingObject(ball.getX(), ball.getY()));
            yVel = -yVel;

        } else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
                + BALL_RADIUS) != null
                && getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
                        + BALL_RADIUS) != paddle) {
            remove(getCollidingObject(ball.getX() + BALL_RADIUS, ball.getY()
                    + BALL_RADIUS));
            yVel = -yVel;

        } else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
                + BALL_RADIUS) != null
                && getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
                        + BALL_RADIUS) != paddle) {
            remove(getCollidingObject(ball.getX() + BALL_RADIUS, ball.getY()
                    + BALL_RADIUS));
            yVel = -yVel;

        } else if (getElementAt(ball.getX(), ball.getY() + BALL_RADIUS) != null
                && getElementAt(ball.getX(), ball.getY() + BALL_RADIUS) != paddle) {
            remove(getCollidingObject(ball.getX(), ball.getY() + BALL_RADIUS));
            yVel = -yVel;
        }
    }

    private void play() {
        addMouseListeners();
        ball();
        while (true) {
            checkForCollision();
            moveBall();
            pause(DELAY);
        }

    }


    private GObject getCollidingObject(double x, double y) {
        return getElementAt(x, y);

    }

    private double lastX;
    private double lastY;

    private double xVel = 5;
    private double yVel = 15;

    private GOval ball;
    private GRect paddle;
    private GObject gobj;

}
gunr2171
  • 10,315
  • 25
  • 52
  • 75
Ramin
  • 11
  • 1
  • 2
  • 1
    One problem here is that you've got one huge [god-class](http://en.wikipedia.org/wiki/God_object), making your code difficult to analyze or debug. Have you thought of refactoring and decomposing the problem into separate classes? – Hovercraft Full Of Eels Jun 30 '11 at 21:28
  • 1
    @Ramin I normally wouldn't comment on this type of question but I'm bored on my last day at work here. Your code assumes that there will never be an overlap during a collision and the ball will always neatly strike the brick at the top left corner (or bottom right). You should probably re-evaluation your collision detection code. This is a problem that has been solved a million times over - a quick search of "collision detection" will tell you all you need to know. – SRM Jun 30 '11 at 21:29
  • @Hovercraft Full Of Eels I think that you mean my "private void checkForCollision() {" method is way to complicated and messy? you probably have a good point there. I'll see if I can structure this better. – Ramin Jun 30 '11 at 21:42
  • 1
    @Ramin No, I think he means what he said. I can see at least three classes that you could refactor out from there. I would even look at abstracting the eventing system (so you can assign multiple "hard" events, e.g. mouse, keyboard, to the same "soft" event, e.g. MoveUpEvent) – SRM Jun 30 '11 at 21:44
  • ok @SRM I get it. I'll try to break this down into different classes. and I'll look into what you said about the collision detection. I'll get back with an improved version. – Ramin Jun 30 '11 at 21:59
  • @Ramin It will help your grade too. Your prof will be impressed by your mastery of OOP for the lang ;). If you want to go full bore you can create a class for bricks (could be as simple as a wrapper around a rectangle with some collision detection and maybe an index to represent color/score), one for the world (which contains and builds the bricks), one for the ball, one for the paddle, and finally one for the application itself. – SRM Jun 30 '11 at 22:17

2 Answers2

2

Why don't you use suggested method in handout 19:

private GObject getCollidingObject()

use also collider:

GObject collider = getCollidingObject();

First of all separate your conditions with walls collisions and (paddle with bricks) game elements collisions into two separate methods cause to simplify else if cascading (decomposition I think you know what it means). You have only two objects what you should worry about paddle and bricks, so you need to compare only on (collider == paddle) and collider != null. Also you use only radius in comparisons but should use diameter (2 * radius). Have fun :)

Gennadiy Ryabkin
  • 6,364
  • 3
  • 28
  • 37
0

You're y step for each movement of the ball is too large at 15. Since each brick is only 8 in height, at times you're bypassing the brick before evaluating whether that location contained an object or not. E.g. Lets assume you're last evaluation left you're ball with a Y location of 20 and the closest brick is only length of 2 away. You're next evaluation will not include this brick since you move 15 and the height of the brick is 8, meaning range of y values 22 to 30. You'll be out by 5 away from this brick in you're next evaluation. I know the values should be inverted considering the location of the bricks, but you get the point...

With smaller steps in y values collisions will be checked more regularly and provided you make the step small enough, remove this problem.

Simon
  • 1