Commits

Micha Kops committed bc663eb

Refactored version added.

Comments (0)

Files changed (8)

src/main/java/com/hascode/jfx/game/BallGame.java

+package com.hascode.jfx.game;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.SceneBuilder;
+import javafx.scene.image.Image;
+import javafx.stage.Stage;
+
+public class BallGame extends Application {
+	private static final String VIEW_GAME = "/view/GameView.fxml";
+	private static final String STYLESHEET_FILE = "/stylesheet/style.css";
+	public static final Image ICON = new Image(
+			HyperBallsMain.class.getResourceAsStream("/image/head.png"));
+
+	@Override
+	public void start(final Stage stage) throws Exception {
+		initGui(stage);
+	}
+
+	private void initGui(final Stage stage) throws IOException {
+		Parent root = FXMLLoader.load(getClass().getResource(VIEW_GAME));
+		Scene scene = SceneBuilder.create().root(root).width(500).height(530)
+				.build();
+		scene.getStylesheets().add(STYLESHEET_FILE);
+		stage.setScene(scene);
+		stage.setTitle("hasCode.com - Java FX 2 Ball Game Tutorial");
+		stage.getIcons().add(ICON);
+		stage.show();
+	}
+
+	public static void main(final String... args) {
+		Application.launch(args);
+	}
+
+}

src/main/java/com/hascode/jfx/game/BallGameController.java

+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.effect.DropShadow;
+import javafx.scene.effect.DropShadowBuilder;
+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.Rectangle;
+import javafx.scene.text.Text;
+import javafx.util.Duration;
+
+public class BallGameController implements Initializable {
+	// EFFECTS
+	private final DropShadow dropshadowEffect = DropShadowBuilder.create()
+			.offsetY(4.0).offsetX(0.5).color(Color.BLACK).build();
+
+	// 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();
+		}
+	};
+
+	private final Timeline heartbeat = TimelineBuilder.create()
+			.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
+			.cycleCount(Timeline.INDEFINITE).build();
+
+	@Override
+	public void initialize(final URL url, final ResourceBundle bundle) {
+		applyDropShadowEffects();
+		bindPaddleMouseEvents();
+		bindStartButtonEvents();
+		bindQuitButtonEvents();
+		bindElementsToModel();
+		initializeGame();
+		area.requestFocus();
+	}
+
+	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()));
+	}
+
+	private void initializeGame() {
+		initializeBoxes();
+		model.reset();
+	}
+
+	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());
+	}
+
+	private void bindQuitButtonEvents() {
+		quitButton.setOnAction(new EventHandler<ActionEvent>() {
+			@Override
+			public void handle(final ActionEvent evt) {
+				Platform.exit();
+			}
+		});
+	}
+
+	private void bindStartButtonEvents() {
+		startButton.setOnAction(new EventHandler<ActionEvent>() {
+			@Override
+			public void handle(final ActionEvent evt) {
+				// initGame();
+				model.getGameStopped().set(false);
+				heartbeat.playFromStart();
+			}
+		});
+	}
+
+	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);
+				}
+			}
+		});
+	}
+
+	private void applyDropShadowEffects() {
+		ball.setEffect(dropshadowEffect);
+		paddle.setEffect(dropshadowEffect);
+		gameOverText.setEffect(dropshadowEffect);
+		winnerText.setEffect(dropshadowEffect);
+	}
+
+	private void checkWin() {
+		if (0 == model.getBoxesLeft().get()) {
+			model.getGameWon().set(true);
+			model.getGameStopped().set(true);
+			heartbeat.stop();
+		}
+	}
+
+	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);
+		}
+	}
+
+	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);
+			}
+		}
+	}
+
+	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);
+	}
+}

src/main/java/com/hascode/jfx/game/GameModel.java

+package com.hascode.jfx.game;
+
+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.scene.image.ImageView;
+
+public class GameModel {
+	private final int INITIAL_BLOCKS_HORIZONTAL = 10;
+	private final int INITIAL_BLOCKS_VERTICAL = 5;
+	private final int INITIAL_AMOUNT_BLOCKS = getInitialBlocksHorizontal()
+			* getInitialBlocksVertical();
+
+	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(
+			getInitialAmountBlocks());
+
+	private boolean movingDown = true;
+	private boolean movingRight = true;
+	private double movingSpeed = 1.0;
+	private double paddleDragX = 0.0;
+	private double paddleTranslateX = 0.0;
+
+	private final ObservableList<ImageView> boxes = FXCollections
+			.observableArrayList();
+
+	public double getPaddleTranslateX() {
+		return paddleTranslateX;
+	}
+
+	public double getPaddleDragX() {
+		return paddleDragX;
+	}
+
+	public BooleanProperty getGameStopped() {
+		return gameStopped;
+	}
+
+	public void setPaddleTranslateX(final double d) {
+		this.paddleTranslateX = d;
+
+	}
+
+	public void setPaddleDragX(final double d) {
+		this.paddleDragX = d;
+	}
+
+	public DoubleProperty getPaddleX() {
+		return paddleX;
+	}
+
+	public int getInitialBlocksVertical() {
+		return INITIAL_BLOCKS_VERTICAL;
+	}
+
+	public int getInitialBlocksHorizontal() {
+		return INITIAL_BLOCKS_HORIZONTAL;
+	}
+
+	public ObservableList<ImageView> getBoxes() {
+		return boxes;
+	}
+
+	public void reset() {
+		getBoxesLeft().set(getInitialAmountBlocks());
+		for (ImageView r : boxes) {
+			r.setVisible(true);
+		}
+		setMovingSpeed(1.0);
+		setMovingDown(true);
+		setMovingRight(true);
+		getBallX().setValue(250);
+		getBallY().setValue(350);
+		paddleX.setValue(175);
+		gameStopped.set(true);
+		getGameLost().set(false);
+		getGameWon().set(false);
+	}
+
+	public DoubleProperty getBallX() {
+		return ballX;
+	}
+
+	public DoubleProperty getBallY() {
+		return ballY;
+	}
+
+	public BooleanProperty getGameLost() {
+		return gameLost;
+	}
+
+	public BooleanProperty getGameWon() {
+		return gameWon;
+	}
+
+	public DoubleProperty getBoxesLeft() {
+		return boxesLeft;
+	}
+
+	public int getInitialAmountBlocks() {
+		return INITIAL_AMOUNT_BLOCKS;
+	}
+
+	public void incrementSpeed() {
+		if (getMovingSpeed() <= 6)
+			setMovingSpeed(getMovingSpeed() + getMovingSpeed() * 0.5);
+	}
+
+	public boolean isMovingDown() {
+		return movingDown;
+	}
+
+	public void setMovingDown(boolean movingDown) {
+		this.movingDown = movingDown;
+	}
+
+	public boolean isMovingRight() {
+		return movingRight;
+	}
+
+	public void setMovingRight(boolean movingRight) {
+		this.movingRight = movingRight;
+	}
+
+	public double getMovingSpeed() {
+		return movingSpeed;
+	}
+
+	public void setMovingSpeed(double movingSpeed) {
+		this.movingSpeed = movingSpeed;
+	}
+
+}

src/main/java/com/hascode/jfx/game/HyperBallsMain.java

 import javafx.util.Duration;
 
 public class HyperBallsMain 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
 	private double paddleTranslateX = 0.0;
 
 	private static final Image ICON = new Image(
-			HyperBallsMain.class.getResourceAsStream("/head.png"));
+			HyperBallsMain.class.getResourceAsStream("/image/head.png"));
 
 	private final DropShadow dropshadowEffect = DropShadowBuilder.create()
 			.offsetY(4.0).offsetX(0.5).color(Color.BLACK).build();
 		stage.setScene(scene);
 		stage.setTitle("hasCode.com - Java FX 2 Ball Game Tutorial");
 		stage.getIcons().add(ICON);
+		scene.getStylesheets().add(STYLESHEET_FILE);
 		stage.show();
 	}
 

src/main/resources/head.png

Removed
Old image

src/main/resources/image/head.png

Added
New image

src/main/resources/stylesheet/style.css

+.root {
+	-fx-font-size: 16pt;
+	-fx-font-family: "Arial";
+	-fx-width: 500px;
+	-fx-height:530px;
+}

src/main/resources/view/GameView.fxml

+<?xml version="1.0" encoding="UTF-8"?>
+<?import java.lang.*?>
+<?import javafx.collections.*?>
+<?import javafx.scene.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.effect.*?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.layout.GridPane?>
+<?import javafx.scene.control.ToolBar?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.control.MenuBar?>
+<?import javafx.scene.control.MenuItem?>
+<?import javafx.scene.control.Menu?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.shape.Circle?>
+<?import javafx.scene.shape.Rectangle?>
+<?import javafx.scene.text.Text?>
+<?import javafx.scene.control.ProgressBar?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.Hyperlink?>
+
+<Group xmlns:fx="http://javafx.com/fxml" fx:controller="com.hascode.jfx.game.BallGameController" fx:id="area">
+	<Circle fx:id="ball" radius="10.0" fill="BLACK" />
+	<Rectangle fx:id="borderTop" x="0" y="30" width="500" height="2" />
+	<Rectangle fx:id="borderBottom"  x="0" y="500" width="500" height="2"/>
+	<Rectangle fx:id="borderLeft"  x="0" y="0" width="2" height="500"/>
+	<Rectangle fx:id="borderRight"  x="498" y="0" width="2" height="500"/>
+	<Rectangle fx:id="paddle"  x="200" y="460" width="100" height="15" layoutX="20" fill="BLACK"/>
+	<Text fx:id="gameOverText" text="Game Over" fill="RED" layoutX="150" layoutY="330"/>
+	<Text fx:id="winnerText" text="You've won!" fill="GREEN" layoutX="150" layoutY="330"/>
+	<ToolBar minWidth="500">
+		<Button fx:id="startButton" text="Start"/>
+		<Button fx:id="quitButton" text="Quit"/>
+		<ProgressBar fx:id="progressBar" progress="100"/>
+		<Label fx:id="remainingBlocksLabel"/>
+	</ToolBar>
+	<ToolBar minWidth="500" layoutY="500">
+		<Hyperlink text="www.hascode.com" layoutX="360" layoutY="505"/>
+	</ToolBar>
+</Group>