Computer Science 120
Introduction to Programming

Spring 2012, Siena College

BetterBreakout Demo

A working demo of BetterBreakout will appear below. Click inside the applet to interact with it.



BetterBreakout BlueJ Project

Click here to download a BlueJ project for BetterBreakout.


BetterBreakout Source Code

The Java source code for BetterBreakout is below. Click on a file name to download it.


Breakout.java

import objectdraw.*;
import java.awt.*;

/* $Id: Breakout.java 1779 2012-01-17 02:35:54Z terescoj $ */

/**
 * Example Breakout: play a simple breakout game
 *
 * @author Jim Teresco, Siena College, CSIS 120, Spring 2011
 *
 */

public class Breakout extends WindowController {

    // position and dimensions of the court
    private static final double BORDER_WIDTH = 25;

    // dimensions of the paddle
    private static final double PADDLE_WIDTH = 50;
    private static final double PADDLE_HEIGHT = 20;

    // dimensions of the ball
    private static final double BALL_DIAMETER = 20;
    
    // numbers and sizes of bricks
    private static final int NUM_BRICKS_PER_ROW = 10;
    private static final int NUM_BRICK_ROWS = 6;
    private static final double BRICK_HEIGHT = 10;
    private static final double BRICK_TOP_OFFSET = 50;
    
    // number of turns per game
    private static final int NUM_TURNS = 3;

    private FilledRect paddle;
    private FramedRect boundary; // the boundary of the playing area.

    // last ball in play
    private BreakoutBall lastBall;

    // the collection of bricks to hit
    private BrickCollection bricks;
    
    // turns remaining
    private int turnsLeft;
    
    // array of FilledOvals representing turns remaining
    private FilledOval[] lives;
    
    /**
     * set up playing area and bricks.
     */
    public void begin() {
        
        new FilledRect(BORDER_WIDTH, BORDER_WIDTH,
                        canvas.getWidth() - BORDER_WIDTH*2, 
                        canvas.getHeight() - BORDER_WIDTH*2, canvas).setColor(Color.gray);
                        
        // make the playing area
        boundary = new FramedRect(BORDER_WIDTH, BORDER_WIDTH,
                        canvas.getWidth() - BORDER_WIDTH*2, 
                        canvas.getHeight() - BORDER_WIDTH*2, canvas);

        // make the paddle
        paddle = new FilledRect(canvas.getWidth()/2,
            canvas.getHeight() - BORDER_WIDTH - PADDLE_HEIGHT -1,
            PADDLE_WIDTH, PADDLE_HEIGHT,
            canvas);
            
        // set up the bricks
        bricks = new BrickCollection(NUM_BRICKS_PER_ROW, NUM_BRICK_ROWS, 
                     BRICK_HEIGHT, BRICK_TOP_OFFSET+BORDER_WIDTH,
                     BORDER_WIDTH, boundary.getWidth(), canvas);
            
        // no ball in play
        lastBall = null;
        
        // initialize number of turns
        turnsLeft = NUM_TURNS;
        lives = new FilledOval[NUM_TURNS];
        
        // create visual display of turns remaining
        double nextXBall = BORDER_WIDTH;
        double yBallPos = (BORDER_WIDTH - BALL_DIAMETER)/2;
        for (int ballNum = 0; ballNum < NUM_TURNS; ballNum++) {
            lives[ballNum] = new FilledOval(nextXBall, yBallPos, 
                                 BALL_DIAMETER, BALL_DIAMETER,
                                 canvas);
            nextXBall = nextXBall + 2*BALL_DIAMETER;
        }
        
    }
    
    /**
     * create a ball if no ball is currently in play
     * 
     * @param point not used
     */
    public void onMousePress(Location point) {
        
        if ((turnsLeft > 0) && (lastBall == null || lastBall.outOfPlay())) {
            lastBall = new BreakoutBall(BALL_DIAMETER, paddle, bricks, boundary, canvas);
            turnsLeft--;
            lives[turnsLeft].removeFromCanvas();
        }
    
    }

    /**
     * move the paddle to follow the mouse's x position
     * 
     * @param point the current Location of the mouse pointer
     */
    public void onMouseMove(Location point) {

        if (point.getX() < boundary.getX()) {
            // place paddle at left edge of the court
            paddle.moveTo(boundary.getX(), paddle.getY());
        } else if (point.getX() + PADDLE_WIDTH > boundary.getX() + boundary.getWidth()) {
            // place paddle at right edge of the court
            paddle.moveTo(boundary.getX() + boundary.getWidth() - PADDLE_WIDTH, paddle.getY());
        } else {
            // keep the edge of the paddle lined up with the mouse
            paddle.moveTo(point.getX(), paddle.getY());
        }
    }
    
}

BreakoutBall.java

import objectdraw.*;
import java.awt.*;

/* $Id: BreakoutBall.java 1602 2011-04-14 02:51:26Z terescoj $ */

/**
 * 
 * A simple active object that controls a ball that bounces around the
 * canvas and interacts with a boundary, a pong paddle, and a set of
 * breakout bricks.
 *
 * @author Jim Teresco, Siena College, CSIS 120, Spring 2011
 *
 */

public class BreakoutBall extends ActiveObject {

    // control speed of falling balls
    private static final double MIN_SPEED = 3;
    private static final double MAX_SPEED = 10;
    private static final int DELAY_TIME = 33;

    // the ball controlled by this instance
    private FilledOval ball;

    // the paddle with which we will interact
    private FilledRect paddle;

    // how far to fall before stopping and disappearing?
    private double yMax;
    // how far up to go before bouncing off the ceiling?
    private double yMin;
    // and what about the walls?
    private double xMin, xMax;

    // is this ball still in play?
    private boolean outOfPlay;

    // the brick collection with which we interact
    private BrickCollection bricks;

    /**
     * Draw a ball and start it moving around the playing area.
     * 
     * @param diameter size of the ball to create
     * @param paddle the paddle with which to interact
     * @param bricks the collection of bricks with which the ball may collide
     * @param boundary the boundary of the playing area determining the
     *    walls off which the ball will bounce
     * @param aCanvas the DrawingCanvas on which to place the ball
     */
    public BreakoutBall(double diameter, FilledRect paddle, 
    BrickCollection bricks, FramedRect boundary,
    DrawingCanvas aCanvas) {

        // compute/remember our boundaries (of the upper left corner of a ball)
        yMax = boundary.getY()+boundary.getHeight();
        yMin = boundary.getY();
        xMin = boundary.getX();
        xMax = boundary.getX() + boundary.getWidth() - diameter;

        // random generator for the x-coordinate
        RandomDoubleGenerator posGen = new RandomDoubleGenerator(xMin, xMax);

        // draw the ball at its initial position, which is half way down
        // from the top of the playing area, at a randomly-selected x position
        ball = new FilledOval(posGen.nextValue(), (yMin + yMax)/2,
            diameter, diameter, aCanvas);
        ball.setColor(Color.white);

        // remember the paddle
        this.paddle = paddle;

        // remember the bricks
        this.bricks = bricks;

        // set the ball as in play
        outOfPlay = false;

        // activate!
        start();
    }

    /**
     * move the ball repeatedly until it falls off screen, bouncing
     * off the paddle and walls and taking out bricks along the way
     */ 
    public void run() {
        double xSpeed, ySpeed;

        // start by moving downward and some amount to the left or right
        RandomDoubleGenerator speedGen = new RandomDoubleGenerator(MIN_SPEED, MAX_SPEED);

        xSpeed = speedGen.nextValue();
        double initYSpeed = speedGen.nextValue();
        ySpeed = initYSpeed;
        
        // keep moving as long as we haven't fallen off the bottom of
        // the screen
        while (ball.getY() <= yMax) {
            // if we are above the top line, start moving down
            if (ball.getY() < yMin) {
                ySpeed = initYSpeed;
            }

            // if we are in contact with the paddle, start moving up
            if (ball.overlaps(paddle)) {
                if (ball.getY() > (paddle.getY() - ball.getHeight()/2)) {
                    // hit a side, so change xSpeed instead of ySpeed
                    xSpeed = -xSpeed;
                }
                else {
                    ySpeed = -initYSpeed;
                }
            }

            // 
            // bounce off side walls
            if (ball.getX() < xMin || ball.getX() > xMax) {
                xSpeed = -xSpeed;
            }

            // check for contact with bricks
            if (bricks.hitBrick(ball)) {
                ySpeed = -ySpeed;
            }

            // move a little in the appropriate direction
            ball.move(xSpeed, ySpeed);
            pause(DELAY_TIME);
        }

        ball.removeFromCanvas();
        outOfPlay = true;
    }

    /**
     * check if the ball has moved out of the playing area (turn over)
     *
     * @return whether the ball has moved out of the playing area
     */
    public boolean outOfPlay() {

        return outOfPlay;
    }
}

BrickCollection.java

import objectdraw.*;
import java.awt.*;

/*
 * Example Breakout: the collection of bricks
 *
 * Jim Teresco, Siena College, CSIS 120, Spring 2011
 *
 * $Id: BrickCollection.java 1602 2011-04-14 02:51:26Z terescoj $
 */

public class BrickCollection {

    // dimensions of the array of bricks
    private int bricksPerRow;
    private int numRows;
    
    // the array
    private FilledRect[][] bricks;
    
    // some colors to use to define our rows of bricks
    private static final Color[] colors = { Color.red, Color.orange, Color.yellow,
                                            Color.green, Color.blue, Color.magenta };
    
    /**
     * Construct a BrickCollection
     * 
     * @param bricksPerRow the number of bricks per row
     * @param numRows the number of rows of bricks
     * @param brickHeight the height (in pixels) of a row of bricks
     * @param yPos the y-coordinate of the topmost row of bricks
     * @param xMin the x-coordinate of the leftmost brick
     * @param width the width of the playing area to be filled with bricks
     * @param canvas the DrawingCanvas on which to place the bricks
     */
    public BrickCollection(int bricksPerRow, int numRows,
               double brickHeight, double yPos, 
               double xMin, double width, DrawingCanvas canvas) {

        this.bricksPerRow = bricksPerRow;
        this.numRows = numRows;
        
        // construct the array
        bricks = new FilledRect[numRows][bricksPerRow];
        
        double brickWidth = width/bricksPerRow;
        
        for (int row = 0; row < numRows; row++) {
            for (int col = 0; col < bricksPerRow; col++) {
                bricks[row][col] = new FilledRect(xMin + col*brickWidth,
                                        yPos + row*brickHeight, brickWidth,
                                        brickHeight, canvas);
                bricks[row][col].setColor(colors[row%colors.length]);
            }
        }
    }
    
    /**
     * Check if the given ball overlaps any brick, if so, remove it
     * and return true, otherwise return false.
     * 
     * @param ball the FilledOval representing the ball to check for
     * overlap with the existing bricks
     * 
     * @return whether the ball overlapped any brick
     */
    public boolean hitBrick(FilledOval ball) {
        
        boolean hitOne = false;
        for (int row = 0; row < numRows; row++) {
            for (int col = 0; col < bricksPerRow; col++) {
                if (bricks[row][col] != null && bricks[row][col].overlaps(ball)) {
                    bricks[row][col].removeFromCanvas();
                    bricks[row][col] = null;
                    hitOne = true;
                }
            }
        }
        
        return hitOne;
    }
}