Created
April 3, 2015 14:18
-
-
Save kenechiokolo/4c205eeb9889b9e188bb to your computer and use it in GitHub Desktop.
CS106A Assignment 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* File: Breakout.java | |
* ------------------- | |
* Name: | |
* Section Leader: | |
* | |
* 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.*; | |
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 = 10; | |
/** 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 NLIVES = 3; | |
private static final double DELAY = 1; | |
private static final int COUNTDOWN = 3; | |
private static final int COUNTDOWN_DELAY = 1000; | |
private static final int NLEVELS = 1; | |
/* Method: run() */ | |
/** Runs the Breakout program. */ | |
public void run() { | |
setUp(); | |
welcomeScreen(); | |
gamePlay(); | |
} | |
// sets up the game | |
private void setUp() { | |
drawBricks(); | |
createPaddle(); | |
addMouseListeners(); | |
} | |
// displays a welcome screen with instructions | |
private void welcomeScreen() { | |
GLabel welcome = new GLabel ("Welcome to Kenechi's version of Breakout"); | |
add(welcome); | |
welcome.move(10, 30); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("Click on the paddle to select it."); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("Once selected, you control it by moving the mouse left or right..."); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("The aim of the game is to bounce the ball back up at the bricks..."); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("You win if you destroy all the bricks."); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("You lose if the ball falls beneath the paddle"); | |
pause(COUNTDOWN_DELAY*3); | |
welcome.setLabel("You have "+NLIVES+" lives before the game is over"); | |
pause(COUNTDOWN_DELAY*3); | |
remove(welcome); | |
} | |
// initiates the gameplay | |
private void gamePlay() { | |
levelOne(); | |
if (currentLevel == NLEVELS) { | |
endOfGameMessage(); | |
} else gameover(); | |
} | |
// displays message if you complete the game | |
private void endOfGameMessage() { | |
GLabel endgame = new GLabel ("Congratulations! You have completed the game."); | |
add(endgame); | |
endgame.move(appCentreX - endgame.getWidth() / 2, appCentreY); | |
pause(COUNTDOWN_DELAY*5); | |
// endgame.setLabel("Press 1 if you would like to play again."); | |
// int playAgain = readInt(""); | |
// if (playAgain == 1) { | |
// gamePlay(); | |
// } | |
remove(endgame); | |
} | |
//gameplay for level one (structure needs to be rewritten so brick formation is dependent on levels) | |
private void levelOne() { | |
int initTotalBricks = NBRICK_ROWS * NBRICKS_PER_ROW; | |
bricksLeft = initTotalBricks; | |
countdown(); | |
for (int i=0; i < NLIVES; i++) { | |
createBall(); | |
setBallTrajectory(); | |
while (ball.getY() < APPLICATION_HEIGHT && bricksLeft > 0) { | |
moveBall(); | |
checkBoundaryCollision(); | |
checkGameCollision(); | |
pause(DELAY); | |
} | |
if (bricksLeft == 0) { | |
i = NLIVES; | |
} | |
} | |
if (bricksLeft == 0) { | |
levelComplete(); | |
currentLevel = currentLevel + 1; | |
} else { | |
gameover(); | |
} | |
} | |
// displays a gameover message if player loses | |
private void gameover() { | |
remove(paddle); | |
GLabel gameover = new GLabel ("Game Over...you lose :("); | |
add(gameover); | |
gameover.move(appCentreX - gameover.getWidth() / 2, appCentreY); | |
// endgame.setLabel("Press 1 if you would like to play again."); | |
// int playAgain = readInt(""); | |
// if (playAgain == 1) { | |
// gamePlay(); | |
// } | |
} | |
// countdown before the games starts | |
private void countdown() { | |
for (int i=COUNTDOWN; i > 0; i--) { | |
GLabel countdown = new GLabel (""+i+""); | |
countdown.move(appCentreX - (countdown.getWidth() / 2), appCentreY); | |
add(countdown); | |
pause(COUNTDOWN_DELAY); | |
remove(countdown); | |
} | |
} | |
// sets initial trajectory of ball (including velocity) | |
private void setBallTrajectory() { | |
vx = rgen.nextDouble(0.07, 0.2); | |
if (rgen.nextBoolean(0.5)) vx = -vx; | |
vy = 0.2; | |
} | |
// moves the ball in between collisions, dependent upon velocity & DELAY | |
private void moveBall() { | |
ball.move(vx, vy); | |
} | |
// checks for collision with application boundary | |
private void checkBoundaryCollision() { | |
if (ball.getX() + ballDiameter > APPLICATION_WIDTH) { | |
ball.move(-ball.getX() + APPLICATION_WIDTH - ballDiameter, 0); | |
vx = -vx; | |
} | |
if (ball.getX() < 0) { | |
ball.move(0 - ball.getX(), 0); | |
vx = -vx; | |
} | |
if (ball.getY() < 0) { | |
ball.move(0, 0 - ball.getY()); | |
vy = -vy; | |
} | |
} | |
// checks for collisions between bricks & paddle | |
private void checkGameCollision() { | |
if (collision() == paddle) { // bounces ball if it collides with paddle | |
// if ball collides with right half of paddle while travelling right, vx increases based on % of distance from centre of paddle | |
if ((ball.getX() > paddle.getX() + (PADDLE_WIDTH / 2) && vx > 0)) { | |
vx = vx * 1 + ((ball.getX() - paddle.getX() + (PADDLE_WIDTH / 2)) / PADDLE_WIDTH) / 5; | |
} | |
// if ball collides with left half of paddle while travelling left, vx increases based on % of distance from centre of paddle | |
if ((ball.getX() < paddle.getX() + (PADDLE_WIDTH / 2) && vx < 0)) { | |
vx = vx * 1 + ((ball.getX() - paddle.getX() + (PADDLE_WIDTH / 2)) / PADDLE_WIDTH) / 5; | |
} | |
// if ball collides with right half of paddle while travelling left, vx reverses and increases based on % of distance from centre of paddle | |
if ((ball.getX() > paddle.getX() + (PADDLE_WIDTH / 2) && vx < 0)) { | |
vx = -vx * 1 + ((ball.getX() - paddle.getX() + (PADDLE_WIDTH / 2)) / PADDLE_WIDTH) / 5; | |
} | |
// if ball collides with left half of paddle while travelling right, vx reverses and increases based on % of distance from centre of paddle | |
if ((ball.getX() < paddle.getX() + (PADDLE_WIDTH / 2) && vx > 0)) { | |
vx = -vx * 1 + ((ball.getX() - paddle.getX() + (PADDLE_WIDTH / 2)) / PADDLE_WIDTH) / 5; | |
} | |
vy = -vy; | |
paddleHits++; | |
collider = null; | |
} else if (collision() !=null && ball.getY() < (APPLICATION_HEIGHT - ballDiameter)) { // removes brick and bounces ball (y direction only) if ball collides with brick | |
remove(collider); | |
vy = -vy; | |
bricksLeft--; | |
collider = null; // resets collider to null | |
} | |
// accelerates vy based on NDiffChange | |
if (paddleHits > NDiffChange) { | |
vy = vy * acceleration; | |
paddleHits = 0; | |
NDiffChange = NDiffChange * NDiffChangeDecay; | |
} | |
} | |
// displays a message if you complete the level | |
private void levelComplete() { | |
remove(ball); | |
GLabel levelComplete = new GLabel("Congratulations! You've completed this level!"); | |
add(levelComplete); | |
levelComplete.move(appCentreX - levelComplete.getWidth() / 2, appCentreY); | |
pause(COUNTDOWN_DELAY*5); | |
remove(levelComplete); | |
} | |
// determines if ball collides with bricks or paddle | |
// if collision is present, assigns collider value of whatever | |
// ball collides with, returns this as value of collision() | |
private GObject collision() { | |
double ballX = ball.getX(); | |
double ballY = ball.getY(); | |
if (getElementAt(ballX, ballY) != null) { | |
collider = getElementAt(ballX, ballY); | |
} | |
if (getElementAt(ballX + ballDiameter, ballY) != null) { | |
collider = getElementAt(ballX + ballDiameter, ballY); | |
} | |
if (getElementAt(ballX, ballY + ballDiameter) != null) { | |
collider = getElementAt(ballX, ballY + ballDiameter); | |
} | |
if (getElementAt(ballX + ballDiameter, ballY + ballDiameter) != null) { | |
collider = getElementAt(ballX + ballDiameter, ballY + ballDiameter); | |
} | |
return collider; | |
} | |
// creates and adds a ball to the game. The ball is blue. | |
private void createBall() { | |
ball = new GOval(appCentreX - BALL_RADIUS, appCentreY - BALL_RADIUS, ballDiameter, ballDiameter); | |
ball.setFilled(true); | |
ball.setFillColor(Color.blue); | |
ball.setColor(Color.blue); | |
add(ball); | |
} | |
// draws the bricks and adds them to the gamespace | |
private void drawBricks() { | |
redRows(); | |
orangeRows(); | |
yellowRows(); | |
greenRows(); | |
cyanRows(); | |
} | |
// creates and adds a paddle to the game | |
private void createPaddle() { | |
paddle = new GRect (getWidth() / 2 - PADDLE_WIDTH / 2, getHeight() - PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT); | |
paddle.setFilled(true); | |
add(paddle); | |
} | |
// Called on mouse press to record the coordinates of the click | |
public void mouseClicked(MouseEvent e) { | |
last = new GPoint(e.getPoint()); | |
gobj = getElementAt(last); | |
} | |
// Called on mouse drag to reposition the object | |
public void mouseMoved(MouseEvent e) { | |
if (gobj == paddle) { | |
gobj.move(e.getX() - last.getX(), 0); | |
last = new GPoint(e.getPoint()); | |
} | |
// checks to see if paddle (gobj) is within right wall, if not, moves it back | |
if (gobj.getX() > APPLICATION_WIDTH - PADDLE_WIDTH) { | |
gobj.move(-(gobj.getX() - APPLICATION_WIDTH + PADDLE_WIDTH) , 0); | |
} | |
// checks to see if paddle (gobj) is within left wall, if not, moves it back | |
if (gobj.getX() < 0) { | |
gobj.move(0 - gobj.getX() , 0); | |
} | |
} | |
// draws the red rows (descending order) and adds to canvas | |
private void redRows() { | |
for (int i=0; i<2; i++) { | |
redRow(); | |
resetX(); | |
yShift(); | |
} | |
} | |
// draws a single red row and adds to canvas | |
private void redRow() { | |
for (int i=0; i < NBRICKS_PER_ROW; i++) { | |
add(redBrick()); | |
// x determines bricks positioning, this shifts positioning along for each new brick | |
x = x + (BRICK_WIDTH + BRICK_SEP); | |
} | |
} | |
// draws a single red brick in top left corner (first brick, does not add) | |
private GRect redBrick() { | |
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT); | |
brick.setFilled(true); | |
brick.setFillColor(Color.red); | |
brick.setColor(Color.red); | |
return brick; | |
} | |
// x determines positioning of bricks, this method resets it to first column (think typewriter) | |
private void resetX() { | |
x = BRICK_SEP / 2; | |
} | |
// y determines positioning of bricks, this shifts it to next row | |
private void yShift() { | |
y = y + (BRICK_HEIGHT + BRICK_SEP); | |
} | |
// draws orange rows | |
private void orangeRows() { | |
for (int i=0; i<2; i++) { | |
orangeRow(); | |
resetX(); | |
yShift(); | |
} | |
} | |
// draws a single orange row | |
private void orangeRow() { | |
for (int i=0; i < NBRICKS_PER_ROW; i++) { | |
add(orangeBrick()); | |
x = x + (BRICK_WIDTH + BRICK_SEP); | |
} | |
} | |
// creates a single orange brick (does not add to canvas) | |
private GRect orangeBrick() { | |
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT); | |
brick.setFilled(true); | |
brick.setFillColor(Color.orange); | |
brick.setColor(Color.orange); | |
return brick; | |
} | |
// draws two yellow rows of bricks (descending) and adds them to the canvas | |
private void yellowRows() { | |
for (int i=0; i<2; i++) { | |
yellowRow(); | |
resetX(); | |
yShift(); | |
} | |
} | |
// adds a single yellow row of bricks to the canvas, starting from the left | |
private void yellowRow() { | |
for (int i=0; i < NBRICKS_PER_ROW; i++) { | |
add(yellowBrick()); | |
x = x + (BRICK_WIDTH + BRICK_SEP); | |
} | |
} | |
// draws a single yellow brick, does not add to canvas | |
private GRect yellowBrick() { | |
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT); | |
brick.setFilled(true); | |
brick.setFillColor(Color.yellow); | |
brick.setColor(Color.yellow); | |
return brick; | |
} | |
// draws two green rows of bricks (descending) and adds to canvas | |
private void greenRows() { | |
for (int i=0; i<2; i++) { | |
greenRow(); | |
resetX(); | |
yShift(); | |
} | |
} | |
// draws single green row of bricks (from left to right) and adds to the canvas | |
private void greenRow() { | |
for (int i=0; i < NBRICKS_PER_ROW; i++) { | |
add(greenBrick()); | |
x = x + (BRICK_WIDTH + BRICK_SEP); | |
} | |
} | |
// draws a single green brick but does not add to canvas (x, y) | |
private GRect greenBrick() { | |
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT); | |
brick.setFilled(true); | |
brick.setFillColor(Color.green); | |
brick.setColor(Color.green); | |
return brick; | |
} | |
// draws two cyan rows and adds to canvas (descending) | |
private void cyanRows() { | |
for (int i=0; i<2; i++) { | |
cyanRow(); | |
resetX(); | |
yShift(); | |
} | |
} | |
// draws single cyan row and adds to canvas (from left to right) | |
private void cyanRow() { | |
for (int i=0; i < NBRICKS_PER_ROW; i++) { | |
add(cyanBrick()); | |
x = x + (BRICK_WIDTH + BRICK_SEP); | |
} | |
} | |
// draws single cyan brick, does not add to canvas, position (x, y) | |
private GRect cyanBrick() { | |
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT); | |
brick.setFilled(true); | |
brick.setFillColor(Color.cyan); | |
brick.setColor(Color.cyan); | |
return brick; | |
} | |
// x position of first brick | |
double x = BRICK_SEP / 2; | |
// y position of first brick | |
double y = BRICK_Y_OFFSET; | |
GRect paddle; // the paddle used for gameplay | |
private GOval ball; // the ball used for gameplay | |
private double NDiffChange = 10; // number of paddle hits until vy increases | |
private double acceleration = 1.3; // acceleration of vy | |
private double NDiffChangeDecay = 0.9; // decay of NDiffChange (means exponential increase of vx) | |
private GPoint last; // last position of mouse when pressed | |
private GObject gobj; // generic g object | |
private double ballDiameter = BALL_RADIUS*2; | |
private double appCentreX = APPLICATION_WIDTH / 2; // application centre point on x-axis | |
private double appCentreY = APPLICATION_HEIGHT / 2; // application centre point on y-axis | |
private double vx, vy; | |
private RandomGenerator rgen = RandomGenerator.getInstance(); | |
private GObject collider; // object with which the ball collided | |
private int bricksLeft; | |
private int paddleHits = 1; | |
private int currentLevel = 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment