2019-11-19 00:55:13 +00:00
package break_out.model ;
import break_out.Constants ;
2020-01-05 13:14:31 +00:00
import java.awt.* ;
import java.util.ArrayList ;
2019-12-13 00:14:58 +00:00
import java.util.Random ;
2019-11-19 00:55:13 +00:00
/ * *
* This class contains the information about the balls characteristics and behavior
2019-12-01 17:31:50 +00:00
*
2019-12-07 23:15:20 +00:00
* @author iSchumacher ; modified by Gruppe 175 ( Moritz Henseleit , Ruben Meyer )
2019-11-19 00:55:13 +00:00
* /
2019-12-01 17:31:50 +00:00
public class Ball implements IBall {
2019-11-19 00:55:13 +00:00
/ * *
* The balls position on the playground
* /
private Position position ;
2019-12-01 17:31:50 +00:00
2019-11-19 00:55:13 +00:00
/ * *
* The balls direction
* /
private Vector2D direction ;
2019-12-01 17:31:50 +00:00
2019-12-13 00:14:58 +00:00
/ * *
* The balls hit state for paddles ; custom implementation
* /
private boolean hitsPaddle ;
/ * *
* The balls color with default component color
* /
private Color color = Constants . COLOR_COMPONENT ;
2020-01-05 13:14:31 +00:00
/ * *
* The stone which has been hit by the ball
* /
private Stone hitStone = null ;
2019-11-19 00:55:13 +00:00
/ * *
* The constructor of a ball
* The balls position and direction are initialized here .
* /
public Ball ( ) {
this . position = new Position ( 0 , 0 ) ;
2019-12-01 17:31:50 +00:00
this . direction = new Vector2D ( Constants . BALL_SPEED , Constants . BALL_SPEED ) ;
2019-11-19 13:11:56 +00:00
this . direction . rescale ( ) ;
2019-11-19 02:05:45 +00:00
// start at bottom-center
2019-12-01 17:31:50 +00:00
this . position . setX ( ( Constants . SCREEN_WIDTH - Constants . BALL_DIAMETER ) / 2 . 0 ) ;
this . position . setY ( Constants . SCREEN_HEIGHT - Constants . BALL_DIAMETER - Constants . PADDLE_HEIGHT ) ;
2019-11-19 00:55:13 +00:00
}
2019-12-01 17:31:50 +00:00
2019-11-19 00:55:13 +00:00
/ * *
* The getter for the balls position
2019-12-01 17:31:50 +00:00
*
2019-11-19 00:55:13 +00:00
* @return position The balls current position
* /
public Position getPosition ( ) {
return this . position ;
}
2019-12-01 17:31:50 +00:00
2019-11-19 00:55:13 +00:00
/ * *
* The getter for the balls direction
2019-12-01 17:31:50 +00:00
*
2019-11-19 00:55:13 +00:00
* @return direction The balls current direction
* /
public Vector2D getDirection ( ) {
return this . direction ;
}
2019-12-01 17:31:50 +00:00
2019-12-13 00:14:58 +00:00
/ * *
* 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 ( ) ;
2019-12-13 00:51:03 +00:00
// random hue without blue colors to prevent ghosting
// 65 <~ 170/255
float hue = ( random . nextInt ( 65 ) ) / 100f ;
2019-12-13 00:14:58 +00:00
// 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 ; }
2019-11-19 00:55:13 +00:00
/ * *
2019-11-19 14:06:25 +00:00
* updates ball position
2019-11-19 00:55:13 +00:00
* /
public void updatePosition ( ) {
2019-11-19 14:06:25 +00:00
// sets X position
2019-12-01 17:31:50 +00:00
this . position . setX ( this . position . getX ( ) + this . direction . getDx ( ) ) ;
2019-11-19 14:06:25 +00:00
2019-12-01 17:31:50 +00:00
// sets Y position
this . position . setY ( this . position . getY ( ) + this . direction . getDy ( ) ) ;
2019-11-19 00:55:13 +00:00
}
2019-12-01 17:31:50 +00:00
2019-11-19 00:55:13 +00:00
/ * *
2019-11-19 14:06:25 +00:00
* Ball reacts to contact with the borders
2019-11-19 00:55:13 +00:00
* /
public void reactOnBorder ( ) {
2019-12-01 17:31:50 +00:00
// reacts on left border
if ( this . position . getX ( ) < = 0 ) {
this . position . setX ( 0 ) ;
this . direction . setDx ( - ( this . direction . getDx ( ) ) ) ;
2019-11-26 22:45:59 +00:00
}
2019-11-19 02:05:45 +00:00
2019-12-01 17:31:50 +00:00
// reacts on right border (-Diameter because of hitbox)
if ( this . position . getX ( ) > = Constants . SCREEN_WIDTH - Constants . BALL_DIAMETER ) {
2019-11-26 22:45:59 +00:00
this . position . setX ( Constants . SCREEN_WIDTH - Constants . BALL_DIAMETER ) ;
this . direction . setDx ( - ( this . direction . getDx ( ) ) ) ;
}
2019-11-19 02:05:45 +00:00
2019-12-01 17:31:50 +00:00
// reacts on top border
if ( this . position . getY ( ) < = 0 ) {
this . position . setY ( 0 ) ;
this . direction . setDy ( - ( this . direction . getDy ( ) ) ) ;
2019-11-26 22:45:59 +00:00
}
2019-11-19 02:05:45 +00:00
2019-12-01 17:31:50 +00:00
// reacts on bottom border (+Diameter because of hitbox)
if ( this . position . getY ( ) > = Constants . SCREEN_HEIGHT - Constants . BALL_DIAMETER ) {
this . position . setY ( Constants . SCREEN_HEIGHT - Constants . BALL_DIAMETER ) ;
this . direction . setDy ( - ( this . direction . getDy ( ) ) ) ;
2019-11-26 22:45:59 +00:00
}
2019-11-19 00:55:13 +00:00
}
2019-12-01 17:31:50 +00:00
2019-12-02 21:51:46 +00:00
/ * *
2019-12-02 23:03:36 +00:00
* tests whether the ball touches the paddle ' s hit box .
2019-12-02 21:51:46 +00:00
* @param paddle paddle which will be tested
2019-12-02 23:03:36 +00:00
* @return true when ball hits the paddle
2019-12-02 21:51:46 +00:00
* /
public boolean hitsPaddle ( Paddle paddle ) {
2019-12-02 23:03:36 +00:00
// paddles position
Position posPaddle = paddle . getPosition ( ) ;
// balls position
Position posBall = this . getPosition ( ) ;
// test balls y position against paddles y values
2019-12-07 16:24:39 +00:00
// paddles y values can be interpreted as a closed interval therefore if balls y position is in the interval, its true
2019-12-02 23:03:36 +00:00
boolean testPaddleY = (
2019-12-07 16:24:39 +00:00
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 ( )
2019-12-02 23:03:36 +00:00
) ;
// test balls x position against paddles x values
2019-12-07 16:24:39 +00:00
// paddles x values can be interpreted as a closed interval therefore if balls x position is in the interval, its true
2019-12-02 23:03:36 +00:00
boolean testPaddleX = (
2019-12-07 16:24:39 +00:00
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 ( )
2019-12-02 23:03:36 +00:00
) ;
// 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
2019-12-02 21:51:46 +00:00
return false ;
}
/ * *
* Ball got hit by Paddle paddle
* @param paddle hitbox mechanism of paddle
* /
2019-12-07 22:58:50 +00:00
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
2019-12-07 22:21:22 +00:00
//deciding if the paddle is at the top or bottom to adjust if its +or- y direction
2019-12-31 14:21:40 +00:00
if ( paddle . getPosition ( ) . getY ( ) < = Constants . SCREEN_HEIGHT / 2 . 0 ) {
2019-12-07 22:58:50 +00:00
// top paddle
reflectionPoint . setY ( reflectionPoint . getY ( ) - Constants . REFLECTION_OFFSET ) ;
} else {
// bottom paddle
reflectionPoint . setY ( reflectionPoint . getY ( ) + Constants . REFLECTION_OFFSET ) ;
2019-12-07 22:21:22 +00:00
}
2019-12-07 22:58:50 +00:00
// calculating the center of the ball; needed for correct vector calculation
2019-12-07 22:21:22 +00:00
Position ballCenter = new Position (
2019-12-07 22:58:50 +00:00
position . getX ( ) + ( Constants . BALL_DIAMETER / 2 . 0 ) ,
position . getY ( ) + ( Constants . BALL_DIAMETER / 2 . 0 )
) ;
2019-12-07 22:21:22 +00:00
2019-12-07 22:58:50 +00:00
// 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 ( ) ) ;
2019-12-31 14:21:40 +00:00
}
2019-12-02 21:51:46 +00:00
2020-01-05 13:14:31 +00:00
/ * *
* tests whether the ball touches any stone ' s hit box
* @param stones
* @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
* /
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 ( ) ) ;
}
}
}
2019-11-19 00:55:13 +00:00
}