Commits

shemnon committed 997e4e6

first sample app. No convention, no plugin.

Comments (0)

Files changed (56)

sample1/build.gradle

+apply plugin: 'java'
+
+// move into plugin
+buildscript {
+    final javafxHome = System.env['JFXRT_HOME']
+    if (javafxHome) {
+        ext.jfxrtHome = "${javafxHome}"
+    } else {
+        final javaHome = System.env['JAVA_HOME']
+        if (javaHome)
+            ext.jfxrtHome = "${javaHome}/jre/lib"
+    }
+    try {
+        println "JavaFX runtime home dir: ${ext.jfxrtHome}"
+    }
+    catch (MissingPropertyException mpe) {
+        println """    Please set the environment variable JFXRT_HOME
+    to the directory that contains jfxrt.jar."""
+        System.exit 1
+    }
+
+
+}
+
+
+ext {
+    javafx_sign = false
+    javafx_appName = 'Brick Breaker'        // default is $project
+    javafx_mainClass = 'brickbreaker.Main'  // default is [${group}.][${project}].Main
+}
+
+dependencies {
+    compile files("$jfxrtHome/jfxrt.jar")
+}
+
+// Stuff done by the plugin
+ant.taskdef(name: 'fxJar',
+        classname: 'com.sun.javafx.tools.ant.FXJar',
+        classpath: files("$jfxrtHome/../../lib/ant-javafx.jar").asPath)
+ant.taskdef(name: 'fxSignJar',
+        classname: 'com.sun.javafx.tools.ant.FXSignJarTask',
+        classpath: files("$jfxrtHome/../../lib/ant-javafx.jar").asPath)
+ant.taskdef(name: 'fxDeploy',
+        classname: 'com.sun.javafx.tools.ant.DeployFXTask',
+        classpath: files("$jfxrtHome/../../lib/ant-javafx.jar").asPath)
+
+
+
+
+task 'fxJar' {
+    dependsOn(jar.dependsOn)
+
+    doLast {
+
+//        <fx:application id="sampleApp"
+//        name="Some sample app"
+//        mainClass="test.MyApplication"
+//        <-- This application has a preloader class -->
+//                preloaderClass="testpreloader.Preloader"
+//        fallbackClass="test.UseMeIfNoFX"/>
+
+
+        ant.fxJar(destfile: "$libsDir/${archivesBaseName}.jar") {
+            sourceSets.main.output.files.each {
+                if (it.exists()) {
+                    fileset(dir: it)
+                }
+            }
+            application(
+                    name: javafx_appName,
+                    mainClass: javafx_mainClass
+                    //FIXME preloader
+                    //FIXME fallback
+            )
+        }
+//        <manifest>
+//        <attribute name="Implementation-Vendor"
+//        value="${application.vendor}"/>
+//        <attribute name="Implementation-Title"
+//        value="${application.title}"/>
+//        <attribute name="Implementation-Version" value="1.0"/>
+//        </manifest>
+//
+//    <-- Define what files to include -->
+//    <fileset dir="${build.classes.dir}"/>
+//        </fx:jar>
+
+    }
+}
+
+task 'fxSignJar' {
+    dependsOn fxJar
+    enabled = javafx_sign
+    doLast {
+            //FIXME ant.fxSignJar
+//            <fx:signjar keyStore="${basedir}/sample.jks" destdir="dist"
+//            alias="javafx" storePass="****" keyPass="****">
+//            <fileset dir='dist/*.jar'/>
+//            </fx:signjar>
+    }
+}
+
+task 'fxDeploy' {
+    dependsOn fxSignJar
+    doLast {
+        ant.fxDeploy(
+            //width:
+            //height:
+                outDir: distsDir,
+                embedJNLP: true,
+                outFile:javafx_appName,
+                nativeBundles:'all'
+        ) {
+
+            application(
+                    id: javafx_appName,
+                    name: javafx_appName,
+                    mainClass: javafx_mainClass
+                    //FIXME preloader
+                    //FIXME fallback
+            )
+            resources{
+                 fileset(file:"$libsDir/${archivesBaseName}.jar")
+            }
+            info(
+                    title: javafx_appName
+            )
+
+            permissions(elevated:'true')
+
+//            <fx:deploy width="${applet.width}" height="${applet.height}"
+//            outdir="${basedir}/${dist.dir}" embedJNLP="true"
+//            outfile="${application.title}">
+//
+//            <fx:application refId="sampleApp"/>
+//
+//            <fx:resources refid="appRes"/>
+//
+//            <fx:info title="Sample app: ${application.title}"
+//            vendor="${application.vendor}"/>
+//
+//            <-- Request elevated permissions -->
+//            <fx:permissions elevate="true"/>
+//            </fx:deploy>
+
+        }
+    }
+}
+
+
+assemble.dependsOn(fxDeploy)
+
+jar {
+    enabled = false
+}
+

sample1/src/main/java/brickbreaker/Ball.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.scene.Parent;
+import javafx.scene.image.ImageView;
+
+public class Ball extends Parent {
+    
+    public static final int DEFAULT_SIZE = 2;
+    
+    public static final int MAX_SIZE = 5;
+
+    private int size;
+
+    private int diameter;
+    private ImageView imageView;
+
+    public Ball() {
+        imageView = new ImageView();
+        getChildren().add(imageView);
+        changeSize(DEFAULT_SIZE);
+        setMouseTransparent(true);
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public int getDiameter() {
+        return diameter;
+    }
+
+    public void changeSize(int newSize) {
+        size = newSize;
+        imageView.setImage(Config.getImages().get(Config.IMAGE_BALL_0 + size));
+        diameter = (int) imageView.getImage().getWidth() - Config.SHADOW_WIDTH;
+    }
+
+}
+

sample1/src/main/java/brickbreaker/Bat.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Group;
+import javafx.scene.Parent;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+public class Bat extends Parent {
+
+    public static final int DEFAULT_SIZE = 2;
+
+    public static final int MAX_SIZE = 7;
+
+    private static final Image LEFT = Config.getImages().get(Config.IMAGE_BAT_LEFT);
+    private static final Image CENTER = Config.getImages().get(Config.IMAGE_BAT_CENTER);
+    private static final Image RIGHT = Config.getImages().get(Config.IMAGE_BAT_RIGHT);
+
+    private int size;
+    private int width;
+    private int height;
+
+    private ImageView leftImageView;
+    private ImageView centerImageView;
+    private ImageView rightImageView;
+
+    public int getSize() {
+        return size;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public void changeSize(int newSize) {
+        this.size = newSize;
+        width = size * 12 + 45;
+        double rightWidth = RIGHT.getWidth() - Config.SHADOW_WIDTH;
+        double centerWidth = width - LEFT.getWidth() - rightWidth;
+        centerImageView.setViewport(new Rectangle2D(
+            (CENTER.getWidth() - centerWidth) / 2, 0, centerWidth, CENTER.getHeight()));
+        rightImageView.setTranslateX(width - rightWidth);
+    }
+
+    public Bat() {
+        height = (int)CENTER.getHeight() - Config.SHADOW_HEIGHT; 
+        Group group = new Group();
+        leftImageView = new ImageView();
+        leftImageView.setImage(LEFT);
+        centerImageView = new ImageView();
+        centerImageView.setImage(CENTER);
+        centerImageView.setTranslateX(LEFT.getWidth());
+        rightImageView = new ImageView();
+        rightImageView.setImage(RIGHT);
+        changeSize(DEFAULT_SIZE);
+        group.getChildren().addAll(leftImageView, centerImageView, rightImageView);
+        getChildren().add(group);
+        setMouseTransparent(true);
+    }
+
+}
+

sample1/src/main/java/brickbreaker/Bonus.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.scene.Parent;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+public class Bonus extends Parent {
+
+    public static final int TYPE_SLOW = 0;
+    public static final int TYPE_FAST = 1;
+    public static final int TYPE_CATCH = 2;
+    public static final int TYPE_GROW_BAT = 3;
+    public static final int TYPE_REDUCE_BAT = 4;
+    public static final int TYPE_GROW_BALL = 5;
+    public static final int TYPE_REDUCE_BALL = 6;
+    public static final int TYPE_STRIKE = 7;
+    public static final int TYPE_LIFE = 8;
+
+    public static final int COUNT = 9;
+
+    public static final String[] NAMES = new String[] {
+        "SLOW",
+        "FAST",
+        "CATCH",
+        "GROW BAT",
+        "REDUCE BAT",
+        "GROW BALL",
+        "REDUCE BALL",
+        "STRIKE",
+        "LIFE",
+    };
+
+    private int type;
+    private int width;
+    private int height;
+    private ImageView content;
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public Bonus(int type) {
+        content = new ImageView();
+        getChildren().add(content);
+        this.type = type;
+        Image image = Config.getBonusesImages().get(type);
+        width = (int)image.getWidth() - Config.SHADOW_WIDTH;
+        height = (int)image.getHeight() - Config.SHADOW_HEIGHT;
+        content.setImage(image);
+        setMouseTransparent(true);
+    }
+
+}
+
+

sample1/src/main/java/brickbreaker/Brick.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.scene.Parent;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+public class Brick extends Parent {
+
+    public static final int TYPE_BLUE = 0;
+    public static final int TYPE_BROKEN1 = 1;
+    public static final int TYPE_BROKEN2 = 2;
+    public static final int TYPE_BROWN = 3;
+    public static final int TYPE_CYAN = 4;
+    public static final int TYPE_GREEN = 5;
+    public static final int TYPE_GREY = 6;
+    public static final int TYPE_MAGENTA = 7;
+    public static final int TYPE_ORANGE = 8;
+    public static final int TYPE_RED = 9;
+    public static final int TYPE_VIOLET = 10;
+    public static final int TYPE_WHITE = 11;
+    public static final int TYPE_YELLOW = 12;
+
+    private int type;
+    private ImageView content;
+
+    public Brick(int type) {
+        content = new ImageView();
+        getChildren().add(content);
+        changeType(type);
+        setMouseTransparent(true);
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public boolean kick() {
+        if (type == TYPE_GREY) {
+            return false;
+        }
+        if (type == TYPE_BROKEN1) {
+            changeType(TYPE_BROKEN2);
+            return false;
+        }
+        return true;
+    }
+
+    private void changeType(int newType) {
+        this.type = newType;
+        Image image = Config.getBricksImages().get(type);
+        content.setImage(image);
+        content.setFitWidth(Config.FIELD_WIDTH/15);
+    }
+
+    
+
+    public static int getBrickType(String s) {
+        if (s.equals("L")) {
+            return TYPE_BLUE;
+        } else if (s.equals("2")) {
+            return TYPE_BROKEN1;
+        } else if (s.equals("B")) {
+            return TYPE_BROWN;
+        } else if (s.equals("C")) {
+            return TYPE_CYAN;
+        } else if (s.equals("G")) {
+            return TYPE_GREEN;
+        } else if (s.equals("0")) {
+            return TYPE_GREY;
+        } else if (s.equals("M")) {
+            return TYPE_MAGENTA;
+        } else if (s.equals("O")) {
+            return TYPE_ORANGE;
+        } else if (s.equals("R")) {
+            return TYPE_RED;
+        } else if (s.equals("V")) {
+            return TYPE_VIOLET;
+        } else if (s.equals("W")) {
+            return TYPE_WHITE;
+        } else if (s.equals("Y")) {
+            return TYPE_YELLOW;
+        } else {
+            System.out.println("Unknown brick type '{s}'");
+            return TYPE_WHITE;
+        }
+    }
+
+}
+
+

sample1/src/main/java/brickbreaker/Config.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.collections.ObservableList;
+import javafx.scene.image.Image;
+import javafx.util.Duration;
+
+public final class Config {
+
+    public static final Duration ANIMATION_TIME = Duration.millis(40);
+    public static final int MAX_LIVES = 9;
+    // Screen info
+    public static final int FIELD_BRICK_IN_ROW = 15;
+
+    public static final String IMAGE_DIR = "images/desktop/";
+
+    public static final int WINDOW_BORDER = 3; // on desktop platform
+    public static final int TITLE_BAR_HEIGHT = 19; // on desktop platform
+    public static final int SCREEN_WIDTH = 960;
+    public static final int SCREEN_HEIGHT = 720;
+
+    public static final int INFO_TEXT_SPACE = 10;
+
+    // Game field info
+    public static final int BRICK_WIDTH = 48;
+    public static final int BRICK_HEIGHT = 24;
+    public static final int SHADOW_WIDTH = 10;
+    public static final int SHADOW_HEIGHT = 16;
+
+    public static final double BALL_MIN_SPEED = 6;
+    public static final double BALL_MAX_SPEED = BRICK_HEIGHT;
+    public static final double BALL_MIN_COORD_SPEED = 2;
+    public static final double BALL_SPEED_INC = 0.5f;
+
+    public static final int BAT_Y = SCREEN_HEIGHT - 40;
+    public static final int BAT_SPEED = 8;
+
+    public static final int BONUS_SPEED = 3;
+
+    public static final int FIELD_WIDTH = FIELD_BRICK_IN_ROW * BRICK_WIDTH;
+    public static final int FIELD_HEIGHT = FIELD_WIDTH;
+    public static final int FIELD_Y = SCREEN_HEIGHT - FIELD_HEIGHT;
+
+    private static final String[] BRICKS_IMAGES = new String[] {
+        "blue.png",
+        "broken1.png",
+        "broken2.png",
+        "brown.png",
+        "cyan.png",
+        "green.png",
+        "grey.png",
+        "magenta.png",
+        "orange.png",
+        "red.png",
+        "violet.png",
+        "white.png",
+        "yellow.png",
+    };
+
+    private static ObservableList<Image> bricksImages = javafx.collections.FXCollections.<Image>observableArrayList();
+
+    public static ObservableList<Image> getBricksImages() {
+        return bricksImages;
+    }
+
+    private static final String[] BONUSES_IMAGES = new String[] {
+        "ballslow.png",
+        "ballfast.png",
+        "catch.png",
+        "batgrow.png",
+        "batreduce.png",
+        "ballgrow.png",
+        "ballreduce.png",
+        "strike.png",
+        "extralife.png",
+    };
+
+    private static ObservableList<Image> bonusesImages = javafx.collections.FXCollections.<Image>observableArrayList();
+
+    public static ObservableList<Image> getBonusesImages() {
+        return bonusesImages;
+    }
+
+    public static final int IMAGE_BACKGROUND = 0;
+    public static final int IMAGE_BAT_LEFT = 1;
+    public static final int IMAGE_BAT_CENTER = 2;
+    public static final int IMAGE_BAT_RIGHT = 3;
+    public static final int IMAGE_BALL_0 = 4;
+    public static final int IMAGE_BALL_1 = 5;
+    public static final int IMAGE_BALL_2 = 6;
+    public static final int IMAGE_BALL_3 = 7;
+    public static final int IMAGE_BALL_4 = 8;
+    public static final int IMAGE_BALL_5 = 9;
+    public static final int IMAGE_LOGO = 10;
+    public static final int IMAGE_SPLASH_BRICK = 11;
+    public static final int IMAGE_SPLASH_BRICKSHADOW = 12;
+    public static final int IMAGE_SPLASH_BREAKER = 13;
+    public static final int IMAGE_SPLASH_BREAKERSHADOW = 14;
+    public static final int IMAGE_SPLASH_PRESSANYKEY = 15;
+    public static final int IMAGE_SPLASH_PRESSANYKEYSHADOW = 16;
+    public static final int IMAGE_SPLASH_STRIKE = 17;
+    public static final int IMAGE_SPLASH_STRIKESHADOW = 18;
+    public static final int IMAGE_SPLASH_SUN = 19;
+    public static final int IMAGE_READY = 20;
+    public static final int IMAGE_GAMEOVER = 21;
+
+    private static final String[] IMAGES_NAMES = new String[] {
+        "background.png",
+        "bat/left.png",
+        "bat/center.png",
+        "bat/right.png",
+        "ball/ball0.png",
+        "ball/ball1.png",
+        "ball/ball2.png",
+        "ball/ball3.png",
+        "ball/ball4.png",
+        "ball/ball5.png",
+        "logo.png",
+        "splash/brick.png",
+        "splash/brickshadow.png",
+        "splash/breaker.png",
+        "splash/breakershadow.png",
+        "splash/pressanykey.png",
+        "splash/pressanykeyshadow.png",
+        "splash/strike.png",
+        "splash/strikeshadow.png",
+        "splash/sun.png",
+        "ready.png",
+        "gameover.png",
+    };
+
+    private static ObservableList<Image> images = javafx.collections.FXCollections.<Image>observableArrayList();
+
+    public static ObservableList<Image> getImages() {
+        return images;
+    }
+
+    public static void initialize() {
+        for (String imageName : IMAGES_NAMES) {
+            Image image = new Image(Config.class.getResourceAsStream(IMAGE_DIR+imageName));
+            if (image.isError()) {
+                System.out.println("Image "+imageName+" not found");
+            }
+            images.add(image);
+        }
+        for (String imageName : BRICKS_IMAGES) {
+            final String url = IMAGE_DIR+"brick/"+imageName;
+            Image image = new Image(Config.class.getResourceAsStream(url));
+            if (image.isError()) {
+                System.out.println("Image "+url+" not found");
+            }
+            bricksImages.add(image);
+        }
+        for (String imageName : BONUSES_IMAGES) {
+            final String url = IMAGE_DIR+"bonus/"+imageName;
+            Image image = new Image(Config.class.getResourceAsStream(url));
+            if (image.isError()) {
+                System.out.println("Image "+url+" not found");
+            }
+            bonusesImages.add(image);
+        }
+    }
+
+    private Config() {
+        
+    }
+
+}
+

sample1/src/main/java/brickbreaker/Level.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.geometry.VPos;
+import javafx.scene.Group;
+import javafx.scene.Parent;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.util.Duration;
+import brickbreaker.Main.MainFrame;
+
+public class Level extends Parent {
+
+    private static final double MOB_SCALING = 1.5f;
+    private static final MainFrame mainFrame = Main.getMainFrame();
+
+    private ArrayList<Brick> bricks;
+    private int brickCount;
+    private ArrayList<Brick> fadeBricks;
+    private ArrayList<Bonus> bonuses;
+    private Group group;
+    private ArrayList<Bonus> lives;
+    private int catchedBonus;
+
+    // States
+    // 0 - starting level
+    // 1 - ball is catched
+    // 2 - playing
+    // 3 - game over
+    private static final int STARTING_LEVEL = 0;
+    private static final int BALL_CATCHED = 1;
+    private static final int PLAYING = 2;
+    private static final int GAME_OVER = 3;
+
+    private int state;
+    private int batDirection;
+    private double ballDirX;
+    private double ballDirY;
+    private int levelNumber;
+    private Bat bat;
+    private Ball ball;
+    private Text roundCaption;
+    private Text round;
+    private Text scoreCaption;
+    private Text score;
+    private Text livesCaption;
+    private ImageView message;
+    private Timeline startingTimeline;
+    private Timeline timeline;
+    private Group infoPanel;
+
+    public Level(int levelNumber) {
+        group = new Group();
+        getChildren().add(group);
+        initContent(levelNumber);
+    }
+
+    private void initStartingTimeline() {
+        startingTimeline = new Timeline();
+        KeyFrame kf1 = new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {
+            public void handle(ActionEvent event) {
+                message.setVisible(true);
+                state = STARTING_LEVEL;
+                bat.setVisible(false);
+                ball.setVisible(false);
+            }
+        }, new KeyValue(message.opacityProperty(), 0));
+        KeyFrame kf2 = new KeyFrame(Duration.millis(1500), new KeyValue(message.opacityProperty(), 1));
+        KeyFrame kf3 = new KeyFrame(Duration.millis(3000), new KeyValue(message.opacityProperty(), 1));
+        KeyFrame kf4 = new KeyFrame(Duration.millis(4000), new EventHandler<ActionEvent>() {
+            public void handle(ActionEvent event) {
+                message.setVisible(false);
+
+                bat.setTranslateX((Config.FIELD_WIDTH - bat.getWidth()) / 2.0);
+                ball.setTranslateX((Config.FIELD_WIDTH - ball.getDiameter()) / 2.0);
+                ball.setTranslateY(Config.BAT_Y - ball.getDiameter());
+                ballDirX = (Utils.random(2) * 2 - 1) * Config.BALL_MIN_COORD_SPEED;
+                ballDirY = -Config.BALL_MIN_SPEED;
+
+                bat.setVisible(true);
+                ball.setVisible(true);
+                state = BALL_CATCHED;
+            }
+        }, new KeyValue(message.opacityProperty(), 0));
+
+        startingTimeline.getKeyFrames().addAll(kf1, kf2, kf3, kf4);
+    }
+
+    private void initTimeline() {
+        timeline = new Timeline();
+        timeline.setCycleCount(Timeline.INDEFINITE);
+        KeyFrame kf = new KeyFrame(Config.ANIMATION_TIME, new EventHandler<ActionEvent>() {
+            public void handle(ActionEvent event) {
+                // Process fadeBricks
+                Iterator<Brick> brickIterator = fadeBricks.iterator();
+                while (brickIterator.hasNext()) {
+                    Brick brick = brickIterator.next();
+                    brick.setOpacity(brick.getOpacity() - 0.1);
+                    if (brick.getOpacity() <= 0) {
+                        brick.setVisible(false);
+                        brickIterator.remove();
+                    }
+                }
+                // Move bat if needed
+                if (batDirection != 0 && state != STARTING_LEVEL) {
+                    moveBat(bat.getTranslateX() + batDirection);
+                }
+                // Process bonuses
+                Iterator<Bonus> bonusIterator = bonuses.iterator();
+                while (bonusIterator.hasNext()) {
+                    Bonus bonus = bonusIterator.next();
+                    if (bonus.getTranslateY() > Config.SCREEN_HEIGHT) {
+                        bonus.setVisible(false);
+                        bonusIterator.remove();
+                        group.getChildren().remove(bonus);
+                    } else {
+                        bonus.setTranslateY(bonus.getTranslateY() + Config.BONUS_SPEED);
+                        if (bonus.getTranslateX() + bonus.getWidth() > bat.getTranslateX() &&
+                                bonus.getTranslateX() < bat.getTranslateX() + bat.getWidth() &&
+                                bonus.getTranslateY() + bonus.getHeight() > bat.getTranslateY() &&
+                                bonus.getTranslateY() < bat.getTranslateY() + bat.getHeight()) {
+                            // Bonus is catched
+                            updateScore(100);
+                            catchedBonus = bonus.getType();
+                            bonus.setVisible(false);
+                            bonusIterator.remove();
+                            group.getChildren().remove(bonus);
+                            if (bonus.getType() == Bonus.TYPE_SLOW) {
+                                ballDirX /= 1.5;
+                                ballDirY /= 1.5;
+                                correctBallSpeed();
+                            } else if (bonus.getType() == Bonus.TYPE_FAST) {
+                                ballDirX *= 1.5;
+                                ballDirY *= 1.5;
+                                correctBallSpeed();
+                            } else if (bonus.getType() == Bonus.TYPE_GROW_BAT) {
+                                if (bat.getSize() < Bat.MAX_SIZE) {
+                                    bat.changeSize(bat.getSize() + 1);
+                                    if (bat.getTranslateX() + bat.getWidth() > Config.FIELD_WIDTH) {
+                                        bat.setTranslateX(Config.FIELD_WIDTH - bat.getWidth());
+                                    }
+                                }
+                            } else if (bonus.getType() == Bonus.TYPE_REDUCE_BAT) {
+                                if (bat.getSize() > 0) {
+                                    int oldWidth = bat.getWidth();
+                                    bat.changeSize(bat.getSize() - 1);
+                                    bat.setTranslateX(bat.getTranslateX() + ((oldWidth - bat.getWidth()) / 2));
+                                }
+                            } else if (bonus.getType() == Bonus.TYPE_GROW_BALL) {
+                                if (ball.getSize() < Ball.MAX_SIZE) {
+                                    ball.changeSize(ball.getSize() + 1);
+                                    if (state == BALL_CATCHED) {
+                                        ball.setTranslateY(Config.BAT_Y - ball.getDiameter());
+                                    }
+                                }
+                            } else if (bonus.getType() == Bonus.TYPE_REDUCE_BALL) {
+                                if (ball.getSize() > 0) {
+                                    ball.changeSize(ball.getSize() - 1);
+                                    if (state == BALL_CATCHED) {
+                                        ball.setTranslateY(Config.BAT_Y - ball.getDiameter());
+                                    }
+                                }
+                            } else if (bonus.getType() == Bonus.TYPE_LIFE) {
+                                mainFrame.increaseLives();
+                                updateLives();
+                            }
+                        }
+                    }
+                }
+                if (state != PLAYING) {
+                    return;
+                }
+                double newX = ball.getTranslateX() + ballDirX;
+                double newY = ball.getTranslateY() + ballDirY;
+                boolean inverseX = false;
+                boolean inverseY = false;
+                if (newX < 0) {
+                    newX = -newX;
+                    inverseX = true;
+                }
+                int BALL_MAX_X = Config.FIELD_WIDTH - ball.getDiameter();
+                if (newX > BALL_MAX_X) {
+                    newX = BALL_MAX_X - (newX - BALL_MAX_X);
+                    inverseX = true;
+                }
+                if (newY < Config.FIELD_Y) {
+                    newY = 2 * Config.FIELD_Y - newY;
+                    inverseY = true;
+                }
+                // Determine hit bat and ball
+                if (ballDirY > 0 &&
+                        ball.getTranslateY() + ball.getDiameter() < Config.BAT_Y &&
+                        newY + ball.getDiameter() >= Config.BAT_Y &&
+                        newX >= bat.getTranslateX() - ball.getDiameter() &&
+                        newX < bat.getTranslateX() + bat.getWidth() + ball.getDiameter()) {
+                    inverseY = true;
+                    // Speed up ball
+                    double speed = Math.sqrt(ballDirX * ballDirX + ballDirY * ballDirY);
+                    ballDirX *= (speed + Config.BALL_SPEED_INC) / speed;
+                    ballDirY *= (speed + Config.BALL_SPEED_INC) / speed;
+                    // Correct ballDirX and ballDirY
+                    double offsetX = newX + ball.getDiameter() / 2 - bat.getTranslateX() - bat.getWidth() / 2;
+                    // Don't change direction if center of bat was used
+                    if (Math.abs(offsetX) > bat.getWidth() / 4) {
+                        ballDirX += offsetX / 5;
+                        double MAX_COORD_SPEED = Math.sqrt(speed * speed -
+                            Config.BALL_MIN_COORD_SPEED * Config.BALL_MIN_COORD_SPEED);
+                        if (Math.abs(ballDirX) > MAX_COORD_SPEED) {
+                            ballDirX = Utils.sign(ballDirX) * MAX_COORD_SPEED;
+                        }
+                        ballDirY = Utils.sign(ballDirY) *
+                            Math.sqrt(speed * speed - ballDirX * ballDirX);
+                    }
+                    correctBallSpeed();
+                    if (catchedBonus == Bonus.TYPE_CATCH) {
+                        newY = Config.BAT_Y - ball.getDiameter();
+                        state = BALL_CATCHED;
+                    }
+                }
+                // Determine hit ball and brick
+                int firstCol = (int)(newX / Config.BRICK_WIDTH);
+                int secondCol = (int)((newX + ball.getDiameter()) / Config.BRICK_WIDTH);
+                int firstRow = (int)((newY - Config.FIELD_Y) / Config.BRICK_HEIGHT);
+                int secondRow = (int)((newY - Config.FIELD_Y + ball.getDiameter()) / Config.BRICK_HEIGHT);
+                if (ballDirX > 0) {
+                    int temp = secondCol;
+                    secondCol = firstCol;
+                    firstCol = temp;
+                }
+                if (ballDirY > 0) {
+                    int temp = secondRow;
+                    secondRow = firstRow;
+                    firstRow = temp;
+                }
+                Brick vertBrick = getBrick(firstRow, secondCol);
+                Brick horBrick = getBrick(secondRow, firstCol);
+                if (vertBrick != null) {
+                    kickBrick(firstRow, secondCol);
+                    if (catchedBonus != Bonus.TYPE_STRIKE) {
+                        inverseY = true;
+                    }
+                }
+                if (horBrick != null &&
+                        (firstCol != secondCol || firstRow != secondRow)) {
+                    kickBrick(secondRow, firstCol);
+                    if (catchedBonus != Bonus.TYPE_STRIKE) {
+                        inverseX = true;
+                    }
+                }
+                if (firstCol != secondCol || firstRow != secondRow) {
+                    Brick diagBrick = getBrick(firstRow, firstCol);
+                    if (diagBrick != null && diagBrick != vertBrick &&
+                            diagBrick != horBrick) {
+                        kickBrick(firstRow, firstCol);
+                        if (vertBrick == null && horBrick == null &&
+                                catchedBonus != Bonus.TYPE_STRIKE) {
+                            inverseX = true;
+                            inverseY = true;
+                        }
+                    }
+                }
+                ball.setTranslateX(newX);
+                ball.setTranslateY(newY);
+                if (inverseX) {
+                    ballDirX = - ballDirX;
+                }
+                if (inverseY) {
+                    ballDirY = - ballDirY;
+                }
+                if (ball.getTranslateY() > Config.SCREEN_HEIGHT) {
+                    // Ball was lost
+                    lostLife();
+                }
+            }
+        });
+        timeline.getKeyFrames().add(kf);
+    }
+
+    public void start() {
+        startingTimeline.play();
+        timeline.play();
+        group.getChildren().get(0).requestFocus();
+        updateScore(0);
+        updateLives();
+    }
+
+    public void stop() {
+        startingTimeline.stop();
+        timeline.stop();
+    }
+
+    private void initLevel() {
+        String[] level = LevelData.getLevelData(levelNumber);
+        for (int row = 0; row < level.length; row++) {
+            for (int col = 0; col < Config.FIELD_BRICK_IN_ROW; col++) {
+                String rowString = level[row];
+                Brick brick = null;
+                if (rowString != null && col < rowString.length()) {
+                    String type = rowString.substring(col, col + 1);
+                    if (!type.equals(" ")) {
+                        brick = new Brick(Brick.getBrickType(type));
+                        brick.setTranslateX(col * Config.BRICK_WIDTH);
+                        brick.setTranslateY(Config.FIELD_Y + row * Config.BRICK_HEIGHT);
+                        if (brick.getType() != Brick.TYPE_GREY) {
+                            brickCount++;
+                        }
+                    }
+                }
+                bricks.add(brick);
+            }
+        }
+    }
+
+    private Brick getBrick(int row, int col) {
+        int i = row * Config.FIELD_BRICK_IN_ROW + col;
+        if (col < 0 || col >= Config.FIELD_BRICK_IN_ROW || row < 0 || i >= bricks.size()) {
+            return null;
+        } else {
+            return bricks.get(i);
+        }
+    }
+
+    private void updateScore(int inc) {
+        mainFrame.setScore(mainFrame.getScore() + inc);
+        score.setText(mainFrame.getScore() + "");
+    }
+
+    private void moveBat(double newX) {
+        double x = newX;
+        if (x < 0) {
+            x = 0;
+        }
+        if (x + bat.getWidth() > Config.FIELD_WIDTH) {
+            x = Config.FIELD_WIDTH - bat.getWidth();
+        }
+        if (state == BALL_CATCHED) {
+            double ballX = ball.getTranslateX() + x - bat.getTranslateX();
+            if (ballX < 0) {
+                ballX = 0;
+            }
+            double BALL_MAX_X = Config.FIELD_WIDTH - ball.getDiameter();
+            if (ballX > BALL_MAX_X) {
+                ballX = BALL_MAX_X;
+            }
+            ball.setTranslateX(ballX);
+        }
+        bat.setTranslateX(x);
+    }
+
+    private void kickBrick(int row, int col) {
+        Brick brick = getBrick(row, col);
+        if (brick == null || (catchedBonus != Bonus.TYPE_STRIKE && !brick.kick())) {
+            return;
+        }
+        updateScore(10);
+        if (brick.getType() != Brick.TYPE_GREY) {
+            brickCount--;
+            if (brickCount == 0) {
+                mainFrame.changeState(mainFrame.getState() + 1);
+            }
+        }
+        bricks.set(row * Config.FIELD_BRICK_IN_ROW + col, null);
+        fadeBricks.add(brick);
+        if (Utils.random(8) == 0 && bonuses.size() < 5) {
+            Bonus bonus = new Bonus(Utils.random(Bonus.COUNT));
+            bonus.setTranslateY(brick.getTranslateY());
+            bonus.setVisible(true);
+            bonus.setTranslateX(brick.getTranslateX() + (Config.BRICK_WIDTH - bonus.getWidth()) / 2);
+            group.getChildren().add(bonus);
+            bonuses.add(bonus);
+        }
+    }
+
+    private void updateLives() {
+        while (lives.size() > mainFrame.getLifeCount()) {
+            Bonus lifeBat = lives.get(lives.size() - 1);
+            lives.remove(lifeBat);
+            infoPanel.getChildren().remove(lifeBat);
+        }
+        // Add lifes (but no more than 9)
+        int maxVisibleLifes = 9;
+        double scale = 0.8;
+
+        for (int life = lives.size(); life < Math.min(mainFrame.getLifeCount(), maxVisibleLifes); life++) {
+            Bonus lifeBonus = new Bonus(Bonus.TYPE_LIFE);
+            lifeBonus.setScaleX(scale);
+            lifeBonus.setScaleY(scale);
+            lifeBonus.setTranslateX(livesCaption.getTranslateX() +
+                livesCaption.getBoundsInLocal().getWidth() + (life % 3) * lifeBonus.getWidth());
+            lifeBonus.setTranslateY(livesCaption.getTranslateY() +
+                (life / 3) * lifeBonus.getHeight() * MOB_SCALING);
+            lives.add(lifeBonus);
+            infoPanel.getChildren().add(lifeBonus);
+        }
+    }
+
+    private void correctBallSpeed() {
+        double speed = Math.sqrt(ballDirX * ballDirX + ballDirY * ballDirY);
+        if (speed > Config.BALL_MAX_SPEED) {
+            ballDirX *= Config.BALL_MAX_SPEED / speed;
+            ballDirY *= Config.BALL_MAX_SPEED / speed;
+            speed = Config.BALL_MAX_SPEED;
+        }
+        if (speed < Config.BALL_MIN_SPEED) {
+            ballDirX *= Config.BALL_MIN_SPEED / speed;
+            ballDirY *= Config.BALL_MIN_SPEED / speed;
+            speed = Config.BALL_MIN_SPEED;
+        }
+        if (Math.abs(ballDirX) < Config.BALL_MIN_COORD_SPEED) {
+            ballDirX = Utils.sign(ballDirX) * Config.BALL_MIN_COORD_SPEED;
+            ballDirY = Utils.sign(ballDirY) * Math.sqrt(speed * speed - ballDirX * ballDirX);
+        } else if (Math.abs(ballDirY) < Config.BALL_MIN_COORD_SPEED) {
+            ballDirY = Utils.sign(ballDirY) * Config.BALL_MIN_COORD_SPEED;
+            ballDirX = Utils.sign(ballDirX) * Math.sqrt(speed * speed - ballDirY * ballDirY);
+        }
+    }
+
+    private void lostLife() {
+        mainFrame.decreaseLives();
+        if (mainFrame.getLifeCount() < 0) {
+            state = GAME_OVER;
+            ball.setVisible(false);
+            bat.setVisible(false);
+            message.setImage(Config.getImages().get(Config.IMAGE_GAMEOVER));
+            message.setTranslateX((Config.FIELD_WIDTH - message.getImage().getWidth()) / 2);
+            message.setTranslateY(Config.FIELD_Y +
+                (Config.FIELD_HEIGHT - message.getImage().getHeight()) / 2);
+            message.setVisible(true);
+            message.setOpacity(1);
+        } else {
+            updateLives();
+            bat.changeSize(Bat.DEFAULT_SIZE);
+            ball.changeSize(Ball.DEFAULT_SIZE);
+            bat.setTranslateX((Config.FIELD_WIDTH - bat.getWidth()) / 2);
+            ball.setTranslateX(Config.FIELD_WIDTH / 2 - ball.getDiameter() / 2);
+            ball.setTranslateY(Config.BAT_Y - ball.getDiameter());
+            state = BALL_CATCHED;
+            catchedBonus = 0;
+            ballDirX = (Utils.random(2) * 2 - 1) * Config.BALL_MIN_COORD_SPEED;
+            ballDirY = - Config.BALL_MIN_SPEED;
+        }
+    }
+
+    private void initInfoPanel() {
+        infoPanel = new Group();
+        roundCaption = new Text();
+        roundCaption.setText("ROUND");
+        roundCaption.setTextOrigin(VPos.TOP);
+        roundCaption.setFill(Color.rgb(51, 102, 51));
+        Font f = new Font("Impact", 18);
+        roundCaption.setFont(f);
+        roundCaption.setTranslateX(30);
+        roundCaption.setTranslateY(128);
+        round = new Text();
+        round.setTranslateX(roundCaption.getTranslateX() +
+            roundCaption.getBoundsInLocal().getWidth() + Config.INFO_TEXT_SPACE);
+        round.setTranslateY(roundCaption.getTranslateY());
+        round.setText(levelNumber + "");
+        round.setTextOrigin(VPos.TOP);
+        round.setFont(f);
+        round.setFill(Color.rgb(0, 204, 102));
+        scoreCaption = new Text();
+        scoreCaption.setText("SCORE");
+        scoreCaption.setFill(Color.rgb(51, 102, 51));
+        scoreCaption.setTranslateX(30);
+        scoreCaption.setTranslateY(164);
+        scoreCaption.setTextOrigin(VPos.TOP);
+        scoreCaption.setFont(f);
+        score = new Text();
+        score.setTranslateX(scoreCaption.getTranslateX() +
+            scoreCaption.getBoundsInLocal().getWidth() + Config.INFO_TEXT_SPACE);
+        score.setTranslateY(scoreCaption.getTranslateY());
+        score.setFill(Color.rgb(0, 204, 102));
+        score.setTextOrigin(VPos.TOP);
+        score.setFont(f);
+        score.setText("");
+        livesCaption = new Text();
+        livesCaption.setText("LIFE");
+        livesCaption.setTranslateX(30);
+        livesCaption.setTranslateY(200);
+        livesCaption.setFill(Color.rgb(51, 102, 51));
+        livesCaption.setTextOrigin(VPos.TOP);
+        livesCaption.setFont(f);
+        Color INFO_LEGEND_COLOR = Color.rgb(0, 114, 188);
+        int infoWidth = Config.SCREEN_WIDTH - Config.FIELD_WIDTH;
+        Rectangle black = new Rectangle();
+        black.setWidth(infoWidth);
+        black.setHeight(Config.SCREEN_HEIGHT);
+        black.setFill(Color.BLACK);
+        ImageView verLine = new ImageView();
+        verLine.setImage(new Image(Level.class.getResourceAsStream(Config.IMAGE_DIR+"vline.png")));
+        verLine.setTranslateX(3);
+        ImageView logo = new ImageView();
+        logo.setImage(Config.getImages().get(Config.IMAGE_LOGO));
+        logo.setTranslateX(30);
+        logo.setTranslateY(30);
+        Text legend = new Text();
+        legend.setTranslateX(30);
+        legend.setTranslateY(310);
+        legend.setText("LEGEND");
+        legend.setFill(INFO_LEGEND_COLOR);
+        legend.setTextOrigin(VPos.TOP);
+        legend.setFont(new Font("Impact", 18));
+        infoPanel.getChildren().addAll(black, verLine, logo, roundCaption,
+                round, scoreCaption, score, livesCaption, legend);
+        for (int i = 0; i < Bonus.COUNT; i++) {
+            Bonus bonus = new Bonus(i);
+            Text text = new Text();
+            text.setTranslateX(100);
+            text.setTranslateY(350 + i * 40);
+            text.setText(Bonus.NAMES[i]);
+            text.setFill(INFO_LEGEND_COLOR);
+            text.setTextOrigin(VPos.TOP);
+            text.setFont(new Font("Arial", 12));
+            bonus.setTranslateX(30 + (820 - 750 - bonus.getWidth()) / 2);
+            bonus.setTranslateY(text.getTranslateY() -
+                (bonus.getHeight() - text.getBoundsInLocal().getHeight()) / 2);
+            // Workaround JFXC-2379
+            infoPanel.getChildren().addAll(bonus, text);
+        }
+        infoPanel.setTranslateX(Config.FIELD_WIDTH);
+    }
+
+    private void initContent(int level) {
+        catchedBonus = 0;
+        state = STARTING_LEVEL;
+        batDirection = 0;
+        levelNumber = level;
+        lives = new ArrayList<Bonus>();
+        bricks = new ArrayList<Brick>();
+        fadeBricks = new ArrayList<Brick>();
+        bonuses = new ArrayList<Bonus>();
+        ball = new Ball();
+        ball.setVisible(false);
+        bat = new Bat();
+        bat.setTranslateY(Config.BAT_Y);
+        bat.setVisible(false);
+        message = new ImageView();
+        message.setImage(Config.getImages().get(Config.IMAGE_READY));
+        message.setTranslateX((Config.FIELD_WIDTH - message.getImage().getWidth()) / 2);
+        message.setTranslateY(Config.FIELD_Y +
+            (Config.FIELD_HEIGHT - message.getImage().getHeight()) / 2);
+        message.setVisible(false);
+        initLevel();
+        initStartingTimeline();
+        initTimeline();
+        initInfoPanel();
+        ImageView background = new ImageView();
+        background.setFocusTraversable(true);
+        background.setImage(Config.getImages().get(Config.IMAGE_BACKGROUND));
+        background.setFitWidth(Config.SCREEN_WIDTH);
+        background.setFitHeight(Config.SCREEN_HEIGHT);
+        background.setOnMouseMoved(new EventHandler<MouseEvent>() {
+            public void handle(MouseEvent me) {
+                moveBat(me.getX() - bat.getWidth() / 2);
+            }
+        });
+        background.setOnMouseDragged(new EventHandler<MouseEvent>() {
+            public void handle(MouseEvent me) {
+                // Support touch-only devices like some mobile phones
+                moveBat(me.getX() - bat.getWidth() / 2);
+            }
+        });
+        background.setOnMousePressed(new EventHandler<MouseEvent>() {
+            public void handle(MouseEvent me) {
+                if (state == PLAYING) {
+                    // Support touch-only devices like some mobile phones
+                    moveBat(me.getX() - bat.getWidth() / 2);
+                }
+                if (state == BALL_CATCHED) {
+                    state = PLAYING;
+                }
+                if (state == GAME_OVER) {
+                    mainFrame.changeState(MainFrame.SPLASH);
+                }
+            }
+        });
+        background.setOnKeyPressed(new EventHandler<KeyEvent>() {
+            public void handle(KeyEvent ke) {
+                if ((ke.getCode() == KeyCode.POWER) || (ke.getCode() == KeyCode.X)) {
+                    Platform.exit();
+                }
+                if (state == BALL_CATCHED && (ke.getCode() == KeyCode.SPACE ||
+                        ke.getCode() == KeyCode.ENTER || ke.getCode() == KeyCode.PLAY)) {
+                    state = PLAYING;
+                }
+                if (state == GAME_OVER) {
+                    mainFrame.changeState(MainFrame.SPLASH);
+                }
+                if (state == PLAYING && ke.getCode() == KeyCode.Q) {
+                    // Lost life
+                    lostLife();
+                    return;
+                }
+                if ((ke.getCode() == KeyCode.LEFT || ke.getCode() == KeyCode.TRACK_PREV)) {
+                    batDirection = - Config.BAT_SPEED;
+                }
+                if ((ke.getCode() == KeyCode.RIGHT || ke.getCode() == KeyCode.TRACK_NEXT)) {
+                    batDirection = Config.BAT_SPEED;
+                }
+            }
+        });
+        background.setOnKeyReleased(new EventHandler<KeyEvent>() {
+            public void handle(KeyEvent ke) {
+                if (ke.getCode() == KeyCode.LEFT || ke.getCode() == KeyCode.RIGHT ||
+                    ke.getCode() == KeyCode.TRACK_PREV || ke.getCode() == KeyCode.TRACK_NEXT) {
+                    batDirection = 0;
+                }
+            }
+        });
+        group.getChildren().add(background);
+        for (int row = 0; row < bricks.size()/Config.FIELD_BRICK_IN_ROW; row++) {
+            for (int col = 0; col < Config.FIELD_BRICK_IN_ROW; col++) {
+                Brick b = getBrick(row, col);
+                if (b != null) { //tmp
+                    group.getChildren().add(b);
+                }
+            }
+        }
+
+        group.getChildren().addAll(message, ball, bat, infoPanel);
+    }
+
+}

sample1/src/main/java/brickbreaker/LevelData.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import java.util.Arrays;
+
+import javafx.collections.ObservableList;
+
+public class LevelData {
+
+    private static final String NEXT_LEVEL = "---";
+
+    private static final String[] LEVELS_DATA = new String[] {
+        "",
+        "",
+        "",
+        "",
+        "WWWWWWWWWWWWWWW",
+        "WYYWYYWYWYYWYYW",
+        "WWWWWWWWWWWWWWW",
+        "WWWWWWWWWWWWWWW",
+        "RWRWRWRWRWRWRWR",
+        "WRWRWRWRWRWRWRW",
+        "WWWWWWWWWWWWWWW",
+        "LLLLLLLLLLLLLLL",
+
+        NEXT_LEVEL,
+
+        "",
+        "",
+        "",
+        "",
+        "",
+        "W              ",
+        "WO             ",
+        "WOG            ",
+        "WOGR           ",
+        "WOGRB          ",
+        "WOGRBC         ",
+        "WOGRBCL        ",
+        "WOGRBCLV       ",
+        "WOGRBCLVY      ",
+        "WOGRBCLVYM     ",
+        "WOGRBCLVYMW    ",
+        "WOGRBCLVYMWO   ",
+        "WOGRBCLVYMWOG  ",
+        "WOGRBCLVYMWOGR ",
+        "22222222222222B",
+
+        NEXT_LEVEL,
+
+        "",
+        "",
+        "",
+        "00    000000000",
+        "",
+        "    222 222 222",
+        "    2G2 2G2 2G2",
+        "    222 222 222",
+        "",
+        "  222 222 222  ",
+        "  2R2 2R2 2R2  ",
+        "  222 222 222  ",
+        "",
+        "222 222 222    ",
+        "2L2 2L2 2L2    ",
+        "222 222 222    ",
+
+        NEXT_LEVEL,
+
+        "RRRRRRRRRRRRRRR",
+        "RWWWWWWWWWWWWWR",
+        "RWRRRRRRRRRRRWR",
+        "RWRWWWWWWWWWRWR",
+        "RWRWRRRRRRRWRWR",
+        "RWRWR     RWRWR",
+        "RWRWR     RWRWR",
+        "RWRWR     RWRWR",
+        "RWRWR     RWRWR",
+        "RWRWR     RWRWR",
+        "RWRW2222222WRWR",
+        "",
+        "",
+        "222222222222222",
+
+        NEXT_LEVEL,
+
+        "",
+        "    Y     Y    ",
+        "    Y     Y    ",
+        "     Y   Y     ",
+        "     Y   Y     ",
+        "    2222222    ",
+        "   222222222   ",
+        "   22R222R22   ",
+        "  222R222R222  ",
+        " 2222222222222 ",
+        " 2222222222222 ",
+        " 2222222222222 ",
+        " 2 222222222 2 ",
+        " 2 2       2 2 ",
+        " 2 2       2 2 ",
+        "    222 222    ",
+        "    222 222    ",
+
+        NEXT_LEVEL,
+
+        "OOOOOOOOOOOOOOO",
+        "OOOOOOOOOOOOOOO",
+        "OOOOOOOOOOOOOOO",
+        "",
+        "",
+        "GGGGGGGGGGGGGGG",
+        "GGGGGGGGGGGGGGG",
+        "GGGGGGGGGGGGGGG",
+        "",
+        "",
+        "YYYYYYWWWYYYYYY",
+        "222222WWW222222",
+        "YYYYYYWWWYYYYYY",
+        "YYY0       0YYY",
+        "YY           YY",
+        "Y             Y",
+
+        NEXT_LEVEL,
+
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "R O Y W G B C M",
+        "2 2 2 2 2 2 2 2",
+
+        NEXT_LEVEL,
+
+        "",
+        "",
+        "RRR YYY G G RRR",
+        "  R Y Y G G R R",
+        "  R Y Y G G R R",
+        "  R YYY G G RRR",
+        "  R Y Y G G R R",
+        "  R Y Y G G R R",
+        "RR  Y Y  G  R R",
+        "               ",
+        "    222 2 2    ",
+        "    2   2 2    ",
+        "    2   2 2    ",
+        "    222  2     ",
+        "    2   2 2    ",
+        "    2   2 2    ",
+        "    2   2 2    ",
+    };
+
+    private static ObservableList<Integer> levelsOffsets;
+
+    public static int getLevelsCount() {
+        initLevelsOffsets();
+        return levelsOffsets.size() - 1;
+    }
+
+    public static String[] getLevelData(int level) {
+        initLevelsOffsets();
+        if (level < 1 || level > getLevelsCount()) {
+            return null;
+        } else {
+            return Arrays.copyOfRange(LEVELS_DATA, levelsOffsets.get(level - 1) + 1, levelsOffsets.get(level));
+        }
+    }
+
+    private static void initLevelsOffsets() {
+        if (levelsOffsets == null) {
+            levelsOffsets = javafx.collections.FXCollections.<Integer>observableArrayList();
+            levelsOffsets.add(-1);
+            for (int i = 0; i < LEVELS_DATA.length; i++) {
+                if (LEVELS_DATA[i].equals(NEXT_LEVEL)) {
+                    levelsOffsets.add(i);
+                }
+            }
+            levelsOffsets.add(LEVELS_DATA.length + 1);
+        }
+    }
+
+}
+

sample1/src/main/java/brickbreaker/Main.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+
+public class Main extends Application {
+
+    private static MainFrame mainFrame;
+
+    public static MainFrame getMainFrame() {
+        return mainFrame;
+    }
+    
+    @Override public void start(Stage stage) {
+        Config.initialize();
+        Group root = new Group();
+        mainFrame = new MainFrame(root);
+        stage.setTitle("Brick Breaker");
+        stage.setResizable(false);
+        stage.setWidth(Config.SCREEN_WIDTH + 2*Config.WINDOW_BORDER);
+        stage.setHeight(Config.SCREEN_HEIGHT+ 2*Config.WINDOW_BORDER + Config.TITLE_BAR_HEIGHT);
+        Scene scene = new Scene(root);
+        scene.setFill(Color.BLACK);
+        stage.setScene(scene);
+        mainFrame.changeState(MainFrame.SPLASH);
+        stage.show();
+    }
+
+    public static void main(String[] args) {
+        Application.launch(args);
+    }
+
+    public class MainFrame {
+        // Instance of scene root node
+        private Group root;
+
+        // Instance of splash (if exists)
+        private Splash splash;
+
+        // Instance of level (if exists)
+        private Level level;
+
+        // Number of lifes
+        private int lifeCount;
+
+        // Current score
+        private int score;
+
+        private MainFrame(Group root) {
+            this.root = root;
+        }
+
+        public int getState() {
+            return state;
+        }
+
+        public int getScore() {
+            return score;
+        }
+
+        public void setScore(int score) {
+            this.score = score;
+        }
+
+        public int getLifeCount() {
+            return lifeCount;
+        }
+
+        public void increaseLives() {
+            lifeCount = Math.min(lifeCount + 1, Config.MAX_LIVES);
+        }
+
+        public void decreaseLives() {
+            lifeCount--;
+        }
+
+        // Initializes game (lifes, scores etc)
+        public void startGame() {
+            lifeCount = 3;
+            score = 0;
+            changeState(1);
+        }
+
+        // Current state of the game. The next values are available
+        // 0 - Splash
+        public static final int SPLASH = 0;
+        // 1..Level.LEVEL_COUNT - Level
+        private int state = SPLASH;
+
+        public void changeState(int newState) {
+            this.state = newState;
+            if (splash != null) {
+                splash.stop();
+            }
+            if (level != null) {
+                level.stop();
+            }
+            if (state < 1 || state > LevelData.getLevelsCount()) {
+                root.getChildren().remove(level);
+                level = null;
+                splash = new Splash();
+                root.getChildren().add(splash);
+                splash.start();
+            } else {
+                root.getChildren().remove(splash);
+                splash = null;
+                level = new Level(state);
+                root.getChildren().add(level);
+                level.start();
+            }
+        }
+    }
+
+}
+

sample1/src/main/java/brickbreaker/Splash.java

+/*
+ * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  - Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  - Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the distribution.
+ *  - Neither the name of Oracle Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package brickbreaker;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+
+public class Splash extends Parent {
+
+    private static final int STATE_SHOW_TITLE = 0;
+    private static final int STATE_SHOW_STRIKE = 1;
+    private static final int STATE_SUN = 2;
+
+    private static final int SUN_AMPLITUDE_X = Config.SCREEN_WIDTH * 2 / 3;
+    private static final int SUN_AMPLITUDE_Y = Config.SCREEN_WIDTH / 2;
+    
+    private ImageView background; 
+    private ImageView brick;
+    private ImageView brickShadow;
+    private ImageView breaker;
+    private ImageView breakerShadow;
+    private Timeline timeline;
+    private int state;
+    private int stateArg;
+    private ImageView strike;
+    private ImageView strikeShadow;
+    private ImageView pressanykey;
+    private ImageView pressanykeyShadow;
+    private ImageView sun;
+    private ImageView[] NODES;
+    private ImageView[] NODES_SHADOWS;
+
+    private void initTimeline() {
+        timeline = new Timeline();
+        timeline.setCycleCount(Timeline.INDEFINITE);
+        KeyFrame kf = new KeyFrame(Config.ANIMATION_TIME, new EventHandler<ActionEvent>() {
+            public void handle(ActionEvent event) {
+                if (state == STATE_SHOW_TITLE) {
+                    stateArg++;
+                    int center = Config.SCREEN_WIDTH / 2;
+                    int offset = (int)(Math.cos(stateArg / 4.0) * (40 - stateArg) / 40 * center);
+                    brick.setTranslateX(center - brick.getImage().getWidth() / 2 + offset);
+                    breaker.setTranslateX(center - breaker.getImage().getWidth() / 2 - offset);
+                    if (stateArg == 40) {
+                        stateArg = 0;
+                        state = STATE_SHOW_STRIKE;
+                    }
+                    return;
+                }
+                if (state == STATE_SHOW_STRIKE) {
+                    if (stateArg == 0) {
+                        strike.setTranslateX(breaker.getTranslateX() + brick.getImage().getWidth());
+                        strike.setScaleX(0);
+                        strike.setScaleY(0);
+                        strike.setVisible(true);
+                    }
+                    stateArg++;
+                    double coef = stateArg / 30f;
+                    brick.setTranslateX(breaker.getTranslateX() +
+                        (breaker.getImage().getWidth() - brick.getImage().getWidth()) / 2f * (1 - coef));
+                    strike.setScaleX(coef);
+                    strike.setScaleY(coef);
+                    strike.setRotate((30 - stateArg) * 2);
+                    if (stateArg == 30) {
+                        stateArg = 0;
+                        state = STATE_SUN;
+                    }
+                    return;
+                }
+                // Here state == STATE_SUN
+                if (pressanykey.getOpacity() < 1) {
+                    pressanykey.setOpacity(pressanykey.getOpacity() + 0.05f);
+                }
+                stateArg--;
+                double x = SUN_AMPLITUDE_X * Math.cos(stateArg / 100.0);
+                double y = SUN_AMPLITUDE_Y * Math.sin(stateArg / 100.0);
+                if (y < 0) {
+                    for (Node node : NODES_SHADOWS) {
+                        // Workaround RT-1976
+                        node.setTranslateX(-1000);
+                    }
+                    return;
+                }
+                double sunX = Config.SCREEN_WIDTH / 2 + x;
+                double sunY = Config.SCREEN_HEIGHT / 2 - y;
+                sun.setTranslateX(sunX - sun.getImage().getWidth() / 2);
+                sun.setTranslateY(sunY - sun.getImage().getHeight() / 2);
+                sun.setRotate(-stateArg);
+                for (int i = 0; i < NODES.length; i++) {
+                    NODES_SHADOWS[i].setOpacity(y / SUN_AMPLITUDE_Y / 2);
+                    NODES_SHADOWS[i].setTranslateX(NODES[i].getTranslateX() +
+                        (NODES[i].getTranslateX() + NODES[i].getImage().getWidth() / 2 - sunX) / 20);
+                    NODES_SHADOWS[i].setTranslateY(NODES[i].getTranslateY() +
+                        (NODES[i].getTranslateY() + NODES[i].getImage().getHeight() / 2 - sunY) / 20);
+                }
+            }
+        });
+        timeline.getKeyFrames().add(kf);
+    }
+    
+    public void start() {
+        background.requestFocus();
+        timeline.play();
+    }
+
+    public void stop() {