400 lines
12 KiB
Java
400 lines
12 KiB
Java
package break_out.model;
|
|
|
|
import break_out.Constants;
|
|
|
|
import java.awt.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Random;
|
|
|
|
/**
|
|
* This class contains the information about the balls characteristics and behavior
|
|
*
|
|
* @author iSchumacher; modified by Gruppe 175 (Moritz Henseleit, Ruben Meyer)
|
|
*/
|
|
public class Ball implements IBall {
|
|
|
|
/**
|
|
* The balls position on the playground
|
|
*/
|
|
private Position position;
|
|
|
|
/**
|
|
* The balls direction
|
|
*/
|
|
private Vector2D direction;
|
|
|
|
/**
|
|
* The balls hit state for paddles; custom implementation
|
|
*/
|
|
private boolean hitsPaddle;
|
|
|
|
/**
|
|
* The balls lost state for upper and lower borders
|
|
*/
|
|
private boolean isLost;
|
|
|
|
/**
|
|
* The balls color with default component color
|
|
*/
|
|
private Color color = Constants.COLOR_COMPONENT;
|
|
|
|
/**
|
|
* The stone which has been hit by the ball
|
|
*/
|
|
private Stone hitStone = null;
|
|
|
|
/**
|
|
* The constructor of a ball
|
|
* The balls position and direction are initialized here.
|
|
*/
|
|
public Ball() {
|
|
this.position = new Position(0, 0);
|
|
this.direction = new Vector2D(Constants.BALL_SPEED, Constants.BALL_SPEED);
|
|
this.direction.rescale();
|
|
|
|
// reset position to bottom-center
|
|
resetPosition();
|
|
}
|
|
|
|
/**
|
|
* The getter for the balls position
|
|
*
|
|
* @return position The balls current position
|
|
*/
|
|
public Position getPosition() {
|
|
return this.position;
|
|
}
|
|
|
|
/**
|
|
* The getter for the balls direction
|
|
*
|
|
* @return direction The balls current direction
|
|
*/
|
|
public Vector2D getDirection() {
|
|
return this.direction;
|
|
}
|
|
|
|
/**
|
|
* The getter for the balls color
|
|
*
|
|
* @return color The balls current color
|
|
*/
|
|
public Color getColor() { return this.color; }
|
|
|
|
/**
|
|
* The setter for the balls color
|
|
*
|
|
* @param color The balls new color
|
|
*/
|
|
public void setColor(Color color) { this.color = color; }
|
|
|
|
/**
|
|
* Creates new random color for the ball and sets it
|
|
*
|
|
* @see <a href="https://stackoverflow.com/a/4247219">Stackoverflow Answer 4247219</a>
|
|
*/
|
|
public void newRandomColor() {
|
|
Random random = new Random();
|
|
|
|
// random hue without blue colors to prevent ghosting
|
|
// 65 <~ 170/255
|
|
float hue = (random.nextInt(65)) / 100f;
|
|
|
|
// saturation between 0.5 and 0.7
|
|
float saturation = (random.nextInt(2000) + 5000) / 10000f;
|
|
float luminance = 0.9f;
|
|
Color randColor = Color.getHSBColor(hue, saturation, luminance);
|
|
|
|
setColor(randColor);
|
|
}
|
|
|
|
/**
|
|
* The getter for the balls hit state
|
|
*
|
|
* @return hitsPaddle The balls current hit state
|
|
*/
|
|
public boolean getHitState() { return this.hitsPaddle; }
|
|
|
|
/**
|
|
* The setter for the balls hit state
|
|
*
|
|
* @param state The balls new hit state
|
|
*/
|
|
public void setHitState(boolean state) { this.hitsPaddle = state; }
|
|
|
|
/**
|
|
* updates ball position
|
|
*/
|
|
public void updatePosition() {
|
|
// sets X position
|
|
this.position.setX(this.position.getX() + this.direction.getDx());
|
|
|
|
// sets Y position
|
|
this.position.setY(this.position.getY() + this.direction.getDy());
|
|
}
|
|
|
|
/**
|
|
* Ball reacts to contact with the borders
|
|
*/
|
|
public void reactOnBorder() {
|
|
// reacts on left border
|
|
if (this.position.getX() <= 0) {
|
|
this.position.setX(0);
|
|
this.direction.setDx(-(this.direction.getDx()));
|
|
}
|
|
|
|
// reacts on right border (-Diameter because of hitbox)
|
|
if (this.position.getX() >= Constants.SCREEN_WIDTH - Constants.BALL_DIAMETER) {
|
|
this.position.setX(Constants.SCREEN_WIDTH - Constants.BALL_DIAMETER);
|
|
this.direction.setDx(-(this.direction.getDx()));
|
|
}
|
|
|
|
// reacts on top border
|
|
if (this.position.getY() <= 0) {
|
|
isLost = true;
|
|
}
|
|
|
|
// reacts on bottom border (+Diameter because of hitbox)
|
|
if (this.position.getY() >= Constants.SCREEN_HEIGHT - Constants.BALL_DIAMETER) {
|
|
isLost = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* tests whether the ball touches the paddle's hit box.
|
|
* @param paddle paddle which will be tested
|
|
* @return true when ball hits the paddle
|
|
*/
|
|
public boolean hitsPaddle(Paddle paddle) {
|
|
// paddles position
|
|
Position posPaddle = paddle.getPosition();
|
|
|
|
// balls position
|
|
Position posBall = this.getPosition();
|
|
|
|
// test balls y position against paddles y values
|
|
// paddles y values can be interpreted as a closed interval therefore if balls y position is in the interval, its true
|
|
boolean testPaddleY = (
|
|
posPaddle.getY() <= posBall.getY() && posBall.getY() <= posPaddle.getY()+paddle.getHeight() ||
|
|
posPaddle.getY() <= posBall.getY()+Constants.BALL_DIAMETER && posBall.getY()+Constants.BALL_DIAMETER <= posPaddle.getY()+paddle.getHeight()
|
|
);
|
|
|
|
// test balls x position against paddles x values
|
|
// paddles x values can be interpreted as a closed interval therefore if balls x position is in the interval, its true
|
|
boolean testPaddleX = (
|
|
posPaddle.getX() <= posBall.getX() && posBall.getX() <= posPaddle.getX()+paddle.getWidth() ||
|
|
posPaddle.getX() <= posBall.getX()+Constants.BALL_DIAMETER && posBall.getX()+Constants.BALL_DIAMETER <= posPaddle.getX()+paddle.getWidth()
|
|
);
|
|
|
|
// if balls y position is in paddles y values, verify x position
|
|
if(testPaddleY) {
|
|
// DEBUG OUTPUT
|
|
//System.out.println("ball is in y area of paddle: "+String.format("x, y, w, h: %s, %s, %s, %s", posPaddle.getX(), posPaddle.getY(), paddle.getWidth(), paddle.getHeight()));
|
|
|
|
// if ball is in paddles hit box
|
|
if(testPaddleX) {
|
|
// DEBUG OUTPUT
|
|
//System.out.println("ball hits paddle: "+String.format("x, y, w, h: %s, %s, %s, %s", posPaddle.getX(), posPaddle.getY(), paddle.getWidth(), paddle.getHeight()));
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// default output, ball doesn't hit paddle
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Ball got hit by Paddle paddle
|
|
* @param paddle hitbox mechanism of paddle
|
|
*/
|
|
public void reflectOnPaddle(Paddle paddle) {
|
|
// reflection point / offset point
|
|
Position reflectionPoint = new Position(
|
|
paddle.getPosition().getX() + (paddle.getWidth() / 2.0),
|
|
paddle.getPosition().getY() + (paddle.getHeight() / 2.0)
|
|
);
|
|
// new direction vector; assignment not here
|
|
Vector2D reflectionVector;
|
|
|
|
// no general solution, estimation required
|
|
// only two paddles defined in the game design, therefore greater or smaller than middle of screen
|
|
|
|
//deciding if the paddle is at the top or bottom to adjust if its +or- y direction
|
|
if (paddle.getPosition().getY() <= Constants.SCREEN_HEIGHT/2.0) {
|
|
// top paddle
|
|
reflectionPoint.setY(reflectionPoint.getY() - Constants.REFLECTION_OFFSET);
|
|
} else {
|
|
// bottom paddle
|
|
reflectionPoint.setY(reflectionPoint.getY() + Constants.REFLECTION_OFFSET);
|
|
}
|
|
|
|
// calculating the center of the ball; needed for correct vector calculation
|
|
Position ballCenter = new Position(
|
|
position.getX() + (Constants.BALL_DIAMETER/2.0),
|
|
position.getY() + (Constants.BALL_DIAMETER/2.0)
|
|
);
|
|
|
|
// The direction is set to the vector between offset point and the ball's center
|
|
reflectionVector = new Vector2D(reflectionPoint, ballCenter);
|
|
|
|
// normalize vector
|
|
reflectionVector.rescale();
|
|
|
|
// replace direction vector
|
|
this.direction.setDx(reflectionVector.getDx());
|
|
this.direction.setDy(reflectionVector.getDy());
|
|
}
|
|
|
|
/**
|
|
* tests whether the ball touches any stone's hit box
|
|
* @param stones list of stones on the playground
|
|
* @return true if the ball touches a stone
|
|
*/
|
|
public boolean hitsStone(ArrayList<Stone> stones) {
|
|
// ball as a Rectangle
|
|
Rectangle rectBall = new Rectangle((int) getPosition().getX(), (int) getPosition().getY(), Constants.BALL_DIAMETER, Constants.BALL_DIAMETER);
|
|
|
|
// size of grid blocks
|
|
int blockWidth = Constants.SCREEN_WIDTH / Constants.SQUARES_X;
|
|
int blockHeight = Constants.SCREEN_HEIGHT / Constants.SQUARES_Y;
|
|
|
|
// foreach stone in stones
|
|
for(Stone stone : stones) {
|
|
if(stone != null) {
|
|
// stone as a Rectangle based on view.Field
|
|
Rectangle rectStone = new Rectangle((int) stone.getPosition().getX()+1, (int) stone.getPosition().getY()+1, blockWidth-1, blockHeight-1);
|
|
|
|
// if ball intersects with stone which is existent and visible
|
|
if(rectBall.intersects(rectStone) && stone.getColor() != null) {
|
|
// set stone and return true
|
|
hitStone = stone;
|
|
|
|
// reflect ball
|
|
reflectOnStone(hitStone);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* returns the stone which got hit, can be null
|
|
* @return the stone which got hit
|
|
*/
|
|
public Stone getHitStone() {
|
|
return hitStone;
|
|
}
|
|
|
|
/**
|
|
* Ball got hit by Stone stone
|
|
* @param stone hitbox mechanism of stone
|
|
*/
|
|
public void reflectOnStone(Stone stone) {
|
|
// reflection model based on view.Field rendering model
|
|
|
|
// size of grid blocks
|
|
int blockWidth = Constants.SCREEN_WIDTH / Constants.SQUARES_X;
|
|
int blockHeight = Constants.SCREEN_HEIGHT / Constants.SQUARES_Y;
|
|
|
|
// ball as a Rectangle
|
|
Rectangle rectBall = new Rectangle((int) getPosition().getX(), (int) getPosition().getY(), Constants.BALL_DIAMETER, Constants.BALL_DIAMETER);
|
|
|
|
// stone as a Rectangle
|
|
Rectangle rectStone = new Rectangle((int) stone.getPosition().getX()+1, (int) stone.getPosition().getY()+1, blockWidth-1, blockHeight-1);
|
|
|
|
// stones borders as Rectangle's
|
|
Rectangle topBorder = new Rectangle((int) stone.getPosition().getX(), (int) stone.getPosition().getY(), blockWidth, 1);
|
|
Rectangle bottomBorder = new Rectangle((int) stone.getPosition().getX(), (int) stone.getPosition().getY()+blockHeight, blockWidth, 1);
|
|
Rectangle leftBorder = new Rectangle((int) stone.getPosition().getX(), (int) stone.getPosition().getY(), 1, blockHeight);
|
|
Rectangle rightBorder = new Rectangle((int) stone.getPosition().getX()+blockWidth, (int) stone.getPosition().getY(), 1, blockHeight);
|
|
|
|
//System.out.println(String.format("ball (%s, %s) stone (%s, %s)", getPosition().getX(), getPosition().getY(), stone.getPosition().getX(), stone.getPosition().getY()));
|
|
|
|
// if stone intersects with any bounds
|
|
if(rectBall.intersects(rectStone.getBounds())) {
|
|
|
|
// corner intersections
|
|
if((rectBall.intersects(leftBorder) || rectBall.intersects(rightBorder)) && (rectBall.intersects(topBorder) || rectBall.intersects(bottomBorder))) {
|
|
// intersections as Rectangle's
|
|
Rectangle intersTop = rectBall.intersection(topBorder);
|
|
Rectangle intersBottom = rectBall.intersection(bottomBorder);
|
|
Rectangle intersLeft = rectBall.intersection(leftBorder);
|
|
Rectangle intersRight = rectBall.intersection(rightBorder);
|
|
|
|
// rectangles as area to determine corner
|
|
double areaIntersTop = intersTop.getHeight() * intersTop.getWidth();
|
|
double areaIntersBottom = intersBottom.getHeight() * intersBottom.getWidth();
|
|
double areaIntersLeft = intersLeft.getHeight() * intersLeft.getWidth();
|
|
double areaIntersRight = intersRight.getHeight() * intersRight.getWidth();
|
|
|
|
//System.out.printf("ut, ub, ul, ur: %s %s %s %s\r\n", areaIntersTop, areaIntersBottom, areaIntersLeft, areaIntersRight);
|
|
|
|
// top side
|
|
if(areaIntersTop > 0) {
|
|
// left|right border
|
|
if(areaIntersLeft > areaIntersTop || areaIntersRight > areaIntersTop) direction.setDx(-getDirection().getDx());
|
|
|
|
// top border
|
|
else direction.setDy(-getDirection().getDy());
|
|
}
|
|
|
|
// bottom side
|
|
if(areaIntersBottom > 0) {
|
|
// left|right border
|
|
if(areaIntersLeft > areaIntersBottom || areaIntersRight > areaIntersBottom) direction.setDx(-getDirection().getDx());
|
|
|
|
// bottom border
|
|
else direction.setDy(-getDirection().getDy());
|
|
}
|
|
|
|
// return; intersection already handled
|
|
return;
|
|
}
|
|
|
|
// vertical bounds
|
|
if(rectBall.intersects(leftBorder) || rectBall.intersects(rightBorder)) {
|
|
//System.out.println("left|right border");
|
|
direction.setDx(-getDirection().getDx());
|
|
}
|
|
|
|
// horizontal bounds
|
|
if(rectBall.intersects(topBorder) || rectBall.intersects(bottomBorder)) {
|
|
//System.out.println("top|bottom border");
|
|
direction.setDy(-getDirection().getDy());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* The getter for balls lost state
|
|
* @return balls lost state
|
|
*/
|
|
public boolean isLost() {
|
|
return isLost;
|
|
}
|
|
|
|
/**
|
|
* The setter for balls lost state
|
|
* @param lost balls lost state
|
|
*/
|
|
public void setLost(boolean lost) {
|
|
isLost = lost;
|
|
}
|
|
|
|
/**
|
|
* resets balls position
|
|
*/
|
|
public void resetPosition() {
|
|
// start at bottom-center
|
|
position.setX((Constants.SCREEN_WIDTH - Constants.BALL_DIAMETER) / 2.0);
|
|
position.setY(Constants.SCREEN_HEIGHT - Constants.BALL_DIAMETER - Constants.PADDLE_HEIGHT);
|
|
}
|
|
|
|
}
|