package com.hascode.jfx.game;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.HyperlinkBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressBarBuilder;
import javafx.scene.control.ToolBar;
import javafx.scene.control.ToolBarBuilder;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.DropShadowBuilder;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SingleClassNoXmlBallGame extends Application {
private static final String STYLESHEET_FILE = "/stylesheet/style.css";
private static final int INITIAL_BLOCKS_HORIZONTAL = 10;
private static final int INITIAL_BLOCKS_VERTICAL = 5;
private static final int INITIAL_AMOUNT_BLOCKS = INITIAL_BLOCKS_HORIZONTAL
* INITIAL_BLOCKS_VERTICAL;
private final DoubleProperty ballX = new SimpleDoubleProperty();
private final DoubleProperty ballY = new SimpleDoubleProperty();
private final DoubleProperty paddleX = new SimpleDoubleProperty();
private final BooleanProperty gameStopped = new SimpleBooleanProperty();
private final BooleanProperty gameLost = new SimpleBooleanProperty(false);
private final BooleanProperty gameWon = new SimpleBooleanProperty(false);
private final DoubleProperty boxesLeft = new SimpleDoubleProperty(
INITIAL_AMOUNT_BLOCKS);
private boolean movingDown = true;
private boolean movingRight = true;
private double movingSpeed = 1.0;
private double paddleDragX = 0.0;
private double paddleTranslateX = 0.0;
private static final Image ICON = new Image(
SingleClassNoXmlBallGame.class
.getResourceAsStream("/image/head.png"));
private final DropShadow dropshadowEffect = DropShadowBuilder.create()
.offsetY(4.0).offsetX(0.5).color(Color.BLACK).build();
private final ObservableList<ImageView> boxes = FXCollections
.observableArrayList();
private final Circle ball = CircleBuilder.create().radius(10.0)
.fill(Color.BLACK).effect(dropshadowEffect).build();
private final Rectangle borderTop = RectangleBuilder.create().x(0).y(30)
.width(500).height(2).effect(dropshadowEffect).build();
private final Rectangle borderBottom = RectangleBuilder.create().x(0)
.y(500).width(500).height(2).build();
private final Rectangle borderLeft = RectangleBuilder.create().x(0).y(0)
.width(2).height(500).build();
private final Rectangle borderRight = RectangleBuilder.create().x(498).y(0)
.width(2).height(500).build();
private final Rectangle paddle = RectangleBuilder.create().x(200).y(460)
.width(100).layoutX(20).height(15).effect(dropshadowEffect)
.fill(Color.BLACK).cursor(Cursor.HAND)
.onMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent evt) {
paddleTranslateX = paddle.getTranslateX() + 150;
paddleDragX = evt.getSceneX();
}
}).onMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent evt) {
if (!gameStopped.get()) {
double x = paddleTranslateX + evt.getSceneX()
- paddleDragX;
paddleX.setValue(x);
}
}
}).build();
private final Text gameOverText = TextBuilder.create().text("Game Over")
.font(Font.font("Arial", 40.0)).fill(Color.RED).layoutX(150)
.layoutY(330).effect(dropshadowEffect).build();
private final Text winnerText = TextBuilder.create().text("You've won!")
.font(Font.font("Arial", 40.0)).fill(Color.GREEN).layoutX(150)
.layoutY(330).effect(dropshadowEffect).build();
private final Button startButton = ButtonBuilder.create().text("Start")
.onAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
initGame();
gameStopped.set(false);
heartbeat.playFromStart();
}
}).build();
private final Button quitButton = ButtonBuilder.create().text("Quit")
.onAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
Platform.exit();
}
}).build();
private final Hyperlink link = HyperlinkBuilder.create()
.text("www.hascode.com").layoutX(360).layoutY(505).build();
private final ProgressBar progressBar = ProgressBarBuilder.create()
.progress(100).build();
private final Label remainingBlocksLabel = LabelBuilder.create().build();
private final ToolBar toolbar = ToolBarBuilder.create().minWidth(500)
.items(startButton, quitButton, progressBar, remainingBlocksLabel)
.build();
private final ToolBar footerBar = ToolBarBuilder.create().minWidth(500)
.items(link).layoutY(500).build();
private final Group area = GroupBuilder
.create()
.focusTraversable(true)
.children(ball, borderTop, borderBottom, borderLeft, borderRight,
paddle, gameOverText, winnerText, toolbar, footerBar)
.build();
private final EventHandler<ActionEvent> pulseEvent = new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent evt) {
checkWin();
checkCollisions();
double x = movingRight ? movingSpeed : -movingSpeed;
double y = movingDown ? movingSpeed : -movingSpeed;
ballX.set(ballX.get() + x);
ballY.set(ballY.get() + y);
}
};
private final Timeline heartbeat = TimelineBuilder.create()
.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
.cycleCount(Timeline.INDEFINITE).build();
@Override
public void start(final Stage stage) throws Exception {
initGui(stage);
initGame();
}
protected void checkWin() {
if (0 == boxesLeft.get()) {
gameWon.set(true);
gameStopped.set(true);
heartbeat.stop();
}
}
protected void checkCollisions() {
checkBoxCollisions();
if (ball.intersects(paddle.getBoundsInLocal())) {
incrementSpeed();
movingDown = false;
}
if (ball.intersects(borderTop.getBoundsInLocal())) {
incrementSpeed();
movingDown = true;
}
if (ball.intersects(borderBottom.getBoundsInLocal())) {
gameStopped.set(true);
gameLost.set(true);
heartbeat.stop();
}
if (ball.intersects(borderLeft.getBoundsInLocal())) {
incrementSpeed();
movingRight = true;
}
if (ball.intersects(borderRight.getBoundsInLocal())) {
incrementSpeed();
movingRight = false;
}
if (paddle.intersects(borderRight.getBoundsInLocal())) {
paddleX.set(350);
}
if (paddle.intersects(borderLeft.getBoundsInLocal())) {
paddleX.set(0);
}
}
private void checkBoxCollisions() {
for (ImageView r : boxes) {
if (r.isVisible() && ball.intersects(r.getBoundsInParent())) {
boxesLeft.set(boxesLeft.get() - 1);
r.setVisible(false);
}
}
}
private void incrementSpeed() {
if (movingSpeed <= 6)
movingSpeed += movingSpeed * 0.5;
}
private void initGame() {
boxesLeft.set(INITIAL_AMOUNT_BLOCKS);
for (ImageView r : boxes) {
r.setVisible(true);
}
movingSpeed = 1.0;
movingDown = true;
movingRight = true;
ballX.setValue(250);
ballY.setValue(350);
paddleX.setValue(175);
startButton.disableProperty().bind(gameStopped.not());
ball.centerXProperty().bind(ballX);
ball.centerYProperty().bind(ballY);
paddle.xProperty().bind(paddleX);
gameStopped.set(true);
gameLost.set(false);
gameOverText.visibleProperty().bind(gameLost);
gameWon.set(false);
winnerText.visibleProperty().bind(gameWon);
area.requestFocus();
progressBar.progressProperty().bind(
boxesLeft.subtract(INITIAL_AMOUNT_BLOCKS).multiply(-1)
.divide(INITIAL_AMOUNT_BLOCKS));
remainingBlocksLabel.textProperty().bind(
Bindings.format("%.0f boxes left", boxesLeft));
}
private void initBoxes() {
int startX = 15;
int startY = 30;
for (int v = 1; v <= INITIAL_BLOCKS_VERTICAL; v++) {
for (int h = 1; h <= INITIAL_BLOCKS_HORIZONTAL; h++) {
int x = startX + (h * 40);
int y = startY + (v * 40);
ImageView imageView = ImageViewBuilder.create().image(ICON)
.layoutX(x).layoutY(y).build();
boxes.add(imageView);
}
}
area.getChildren().addAll(boxes);
}
private void initGui(final Stage stage) {
Scene scene = SceneBuilder.create().width(500).height(530)
.fill(Color.GRAY).root(area).build();
initBoxes();
stage.setScene(scene);
stage.setTitle("hasCode.com - Java FX 2 Ball Game Tutorial");
stage.getIcons().add(ICON);
scene.getStylesheets().add(STYLESHEET_FILE);
stage.show();
}
public static void main(final String... args) {
Application.launch(args);
}
}