package com.hascode.jfx.game;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;
public class BallGameController implements Initializable {
// UI ELEMENTS
@FXML
private Group area;
@FXML
private Circle ball;
@FXML
private Rectangle borderTop;
@FXML
private Rectangle borderBottom;
@FXML
private Rectangle borderLeft;
@FXML
private Rectangle borderRight;
@FXML
private Rectangle paddle;
@FXML
private Text gameOverText;
@FXML
private Text winnerText;
@FXML
private Button startButton;
@FXML
private Button quitButton;
@FXML
private ProgressBar progressBar;
@FXML
private Label remainingBlocksLabel;
// GAME MODEL
private final GameModel model = new GameModel();
// GAME HEARTBEAT
private final EventHandler<ActionEvent> pulseEvent = new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
checkWin();
checkCollisions();
updateBallPosition();
}
};
// THE TIMELINE, RUNS EVERY 10MS
private final Timeline heartbeat = TimelineBuilder.create()
.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
.cycleCount(Timeline.INDEFINITE).build();
/*
* (non-Javadoc)
*
* @see javafx.fxml.Initializable#initialize(java.net.URL,
* java.util.ResourceBundle)
*/
@Override
public void initialize(final URL url, final ResourceBundle bundle) {
bindPaddleMouseEvents();
bindStartButtonEvents();
bindQuitButtonEvents();
bindElementsToModel();
initializeBoxes();
initializeGame();
area.requestFocus();
}
/**
* binds ui elements to model state
*/
private void bindElementsToModel() {
startButton.disableProperty().bind(model.getGameStopped().not());
ball.centerXProperty().bind(model.getBallX());
ball.centerYProperty().bind(model.getBallY());
paddle.xProperty().bind(model.getPaddleX());
gameOverText.visibleProperty().bind(model.getGameLost());
winnerText.visibleProperty().bind(model.getGameWon());
progressBar.progressProperty().bind(
model.getBoxesLeft().subtract(model.getInitialAmountBlocks())
.multiply(-1).divide(model.getInitialAmountBlocks()));
remainingBlocksLabel.textProperty().bind(
Bindings.format("%.0f boxes left", model.getBoxesLeft()));
}
/**
* initializes the game, is called for every new game
*/
private void initializeGame() {
model.reset();
}
/**
* initializes the boxes.
*/
private void initializeBoxes() {
int startX = 15;
int startY = 30;
for (int v = 1; v <= model.getInitialBlocksVertical(); v++) {
for (int h = 1; h <= model.getInitialBlocksHorizontal(); h++) {
int x = startX + (h * 40);
int y = startY + (v * 40);
ImageView imageView = ImageViewBuilder.create()
.image(BallGame.ICON).layoutX(x).layoutY(y).build();
model.getBoxes().add(imageView);
}
}
area.getChildren().addAll(model.getBoxes());
}
/**
* creates event handler for the quit button. pressing it immediatly quits
* the application.
*/
private void bindQuitButtonEvents() {
quitButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
Platform.exit();
}
});
}
/**
* binds events to the start button. by pressing the start button, the game
* is initialized and the timeline execution is started.
*/
private void bindStartButtonEvents() {
startButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
initializeGame();
model.getGameStopped().set(false);
heartbeat.playFromStart();
}
});
}
/**
* binds events to drag the paddle using the mouse.
*/
private void bindPaddleMouseEvents() {
paddle.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent evt) {
model.setPaddleTranslateX(model.getPaddleTranslateX() + 150);
model.setPaddleDragX(evt.getSceneX());
}
});
paddle.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent evt) {
if (!model.getGameStopped().get()) {
double x = model.getPaddleTranslateX() + evt.getSceneX()
- model.getPaddleDragX();
model.getPaddleX().setValue(x);
}
}
});
}
/**
* checks if the game is won.
*/
private void checkWin() {
if (0 == model.getBoxesLeft().get()) {
model.getGameWon().set(true);
model.getGameStopped().set(true);
heartbeat.stop();
}
}
/**
* checks if the ball has collisions with the walls or the paddle.
*/
private void checkCollisions() {
checkBoxCollisions();
if (ball.intersects(paddle.getBoundsInLocal())) {
model.incrementSpeed();
model.setMovingDown(false);
}
if (ball.intersects(borderTop.getBoundsInLocal())) {
model.incrementSpeed();
model.setMovingDown(true);
}
if (ball.intersects(borderBottom.getBoundsInLocal())) {
model.getGameStopped().set(true);
model.getGameLost().set(true);
heartbeat.stop();
}
if (ball.intersects(borderLeft.getBoundsInLocal())) {
model.incrementSpeed();
model.setMovingRight(true);
}
if (ball.intersects(borderRight.getBoundsInLocal())) {
model.incrementSpeed();
model.setMovingRight(false);
}
if (paddle.intersects(borderRight.getBoundsInLocal())) {
model.getPaddleX().set(350);
}
if (paddle.intersects(borderLeft.getBoundsInLocal())) {
model.getPaddleX().set(0);
}
}
/**
* checks if the ball collides with one or more of the boxes. if there's a
* collision, the box is removed.
*/
private void checkBoxCollisions() {
for (ImageView r : model.getBoxes()) {
if (r.isVisible() && ball.intersects(r.getBoundsInParent())) {
model.getBoxesLeft().set(model.getBoxesLeft().get() - 1);
r.setVisible(false);
}
}
}
/**
* updates the ball position by calculating the ball's speed, position and
* direction.
*/
private void updateBallPosition() {
double x = model.isMovingRight() ? model.getMovingSpeed() : -model
.getMovingSpeed();
double y = model.isMovingDown() ? model.getMovingSpeed() : -model
.getMovingSpeed();
model.getBallX().set(model.getBallX().get() + x);
model.getBallY().set(model.getBallY().get() + y);
}
}