diff --git a/src/main/java/lab/App.java b/src/main/java/lab/App.java index 58fd3f37f039d8171036be098a809c2ee811ccd6..7c60166e57c2302147e706efa2339e890e39430d 100644 --- a/src/main/java/lab/App.java +++ b/src/main/java/lab/App.java @@ -1,10 +1,9 @@ package lab; -import javafx.animation.AnimationTimer; import javafx.application.Application; -import javafx.scene.Group; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.scene.canvas.Canvas; import javafx.stage.Stage; import javafx.stage.WindowEvent; @@ -16,31 +15,28 @@ import javafx.stage.WindowEvent; */ public class App extends Application { + private GameController gameController; public static void main(String[] args) { launch(args); } - private Canvas canvas; - private AnimationTimer timer; - @Override public void start(Stage primaryStage) { try { + System.out.println(getClass().getResource("/lab/LochNess.gif")); // Construct a main window with a canvas. - Group root = new Group(); - canvas = new Canvas(800, 400); - root.getChildren().add(canvas); - Scene scene = new Scene(root, 800, 400); - scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); + FXMLLoader gameLoader = new FXMLLoader(getClass().getResource("/lab/gameWindow.fxml")); + Parent root = gameLoader.load(); + GameController gameController = gameLoader.getController(); + Scene scene = new Scene(root); + String css = this.getClass().getResource("/lab/application.css").toExternalForm(); + scene.getStylesheets().add(css); primaryStage.setScene(scene); primaryStage.resizableProperty().set(false); primaryStage.setTitle("Java 1 - 1th laboratory"); primaryStage.show(); - // Exit program when main window is closed primaryStage.setOnCloseRequest(this::exitProgram); - timer = new DrawingThread(canvas); - timer.start(); } catch (Exception e) { e.printStackTrace(); } @@ -48,7 +44,7 @@ public class App extends Application { @Override public void stop() throws Exception { - timer.stop(); + gameController.stop(); super.stop(); } diff --git a/src/main/java/lab/Background.java b/src/main/java/lab/Background.java index 1e4b308a8ad6138556972f5d666fbbe3bf713088..d31af4b10fa4cb76ade57d3d9f856c4f24b69409 100644 --- a/src/main/java/lab/Background.java +++ b/src/main/java/lab/Background.java @@ -54,4 +54,8 @@ public class Background extends WorldEntity { } } } + + public void setSpeed(double doubleValue) { + speed = new Point2D(doubleValue, 0); + } } diff --git a/src/main/java/lab/Boat.java b/src/main/java/lab/Boat.java index ea0561cd4c708877357de5ae94cad7c37f9642a9..73597b7e19702ce1493f01c90d1e36e4c622259f 100644 --- a/src/main/java/lab/Boat.java +++ b/src/main/java/lab/Boat.java @@ -49,4 +49,10 @@ public class Boat extends WorldEntity implements Collisionable { } } + public void setPosInPercentage(double doubleValue) { + position = new Point2D( + position.getX(), + scene.getSize().getHeight()*doubleValue/100); + } + } diff --git a/src/main/java/lab/DrawingThread.java b/src/main/java/lab/DrawingThread.java index 56c94df054f39bfe2dd77cfdf8a7859445824d20..2131dcde1e186f3c5d072ab68f8004ddf565befe 100644 --- a/src/main/java/lab/DrawingThread.java +++ b/src/main/java/lab/DrawingThread.java @@ -45,4 +45,8 @@ public class DrawingThread extends AnimationTimer { gc.strokeText(Double.toString(1_000_000_000d/timeDelta), 10, 50); } + public Scene getScene() { + return scene; + } + } diff --git a/src/main/java/lab/GameController.java b/src/main/java/lab/GameController.java new file mode 100644 index 0000000000000000000000000000000000000000..b6c34cde97501ae2fce9e6f6ce9c8e4beb230ea5 --- /dev/null +++ b/src/main/java/lab/GameController.java @@ -0,0 +1,57 @@ +package lab; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.Point2D; +import javafx.scene.canvas.Canvas; +import javafx.scene.control.Slider; + +public class GameController { + + @FXML + private Slider boatPosition; + + private DrawingThread timer; + + @FXML + private Canvas canvas; + + @FXML + private Slider speed; + + @FXML + void changePosition(ActionEvent event) { + timer.getScene().getRock().changePosition(); + + } + + @FXML + void initialize() { + assert boatPosition != null : "fx:id=\"angle\" was not injected: check your FXML file 'gameWindow.fxml'."; + assert canvas != null : "fx:id=\"canvas\" was not injected: check your FXML file 'gameWindow.fxml'."; + assert speed != null : "fx:id=\"speed\" was not injected: check your FXML file 'gameWindow.fxml'."; + timer = new DrawingThread(canvas); + timer.start(); + boatPosition.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + timer.getScene().getBoat().setPosInPercentage(newValue.doubleValue()); + } + }); + speed.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + timer.getScene().getBackground().setSpeed(newValue.doubleValue()); + } + }); + + } + + public void stop() { + timer.stop(); + } + + +} diff --git a/src/main/java/lab/Rock.java b/src/main/java/lab/Rock.java index 82c6a2719df0056dc8cbbb028dd8ea540c827695..0162301613ba4fa44241da8d155089fdf51b1f23 100644 --- a/src/main/java/lab/Rock.java +++ b/src/main/java/lab/Rock.java @@ -1,5 +1,7 @@ package lab; +import java.util.Random; + import javafx.geometry.Dimension2D; import javafx.geometry.Point2D; import javafx.scene.canvas.GraphicsContext; @@ -9,6 +11,7 @@ import javafx.scene.transform.Rotate; public class Rock extends WorldEntity { + private static final Random RANDOM = new Random(); private Dimension2D size; private double angle = 0; @@ -36,4 +39,10 @@ public class Rock extends WorldEntity { angle += 0.5; } + public void changePosition() { + position = new Point2D( + RANDOM.nextDouble(scene.getSize().getWidth()*0.3, scene.getSize().getWidth()*0.9), + RANDOM.nextDouble(scene.getSize().getHeight()*0.5, scene.getSize().getHeight()*0.95)); + } + } diff --git a/src/main/java/lab/Scene.java b/src/main/java/lab/Scene.java index fd6798a52ffed256c31c93381ceac469a9743af6..15cfb208115598a7d5efa2bed28a41af959fa470 100644 --- a/src/main/java/lab/Scene.java +++ b/src/main/java/lab/Scene.java @@ -9,12 +9,19 @@ public class Scene { private Dimension2D size; private DrawableSimulable[] sceneEntitites; + private Boat boat; + private Rock rock; + private Background background; + public Scene(double width, double height) { size = new Dimension2D(width, height); sceneEntitites = new DrawableSimulable[8]; - sceneEntitites[0] = new Background(this); - sceneEntitites[1] = new Rock(this, new Point2D(300, 300), new Dimension2D(30, 50)); - sceneEntitites[2] = new Boat(this, new Point2D(20, 200)); + background = new Background(this); + rock = new Rock(this, new Point2D(300, 300), new Dimension2D(30, 50)); + boat = new Boat(this, new Point2D(20, 200)); + sceneEntitites[0] = background; + sceneEntitites[1] = rock; + sceneEntitites[2] = boat; for (int i = 3; i < sceneEntitites.length; i++) { sceneEntitites[i] = new LochNess(this); } @@ -48,4 +55,16 @@ public class Scene { } } + public Boat getBoat() { + return boat; + } + + public Rock getRock() { + return rock; + } + + public Background getBackground() { + return background; + } + } diff --git a/src/main/resources/lab/application.css b/src/main/resources/lab/application.css index 83d6f3343843c65d5dfaf3fedb97b6494c19113d..6b9fb330f149d81344ba2e7edf1381808a377c11 100644 --- a/src/main/resources/lab/application.css +++ b/src/main/resources/lab/application.css @@ -1 +1,6 @@ -/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */ \ No newline at end of file +/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */ + +#changeButton { + -fx-background-image: url("LochNess.gif"); + +} \ No newline at end of file diff --git a/src/main/resources/lab/gameWindow.fxml b/src/main/resources/lab/gameWindow.fxml new file mode 100644 index 0000000000000000000000000000000000000000..b6bc9fadbec86351a12b2fe8319e858d702075df --- /dev/null +++ b/src/main/resources/lab/gameWindow.fxml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.Cursor?> +<?import javafx.scene.canvas.Canvas?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.text.Font?> + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="459.0" prefWidth="824.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.GameController"> + <bottom> + <HBox alignment="TOP_CENTER" prefHeight="66.0" prefWidth="824.0" BorderPane.alignment="CENTER"> + <children> + <Slider fx:id="speed" majorTickUnit="15.0" max="350.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" min="20.0" minorTickCount="5" prefHeight="116.0" prefWidth="717.0" showTickLabels="true" showTickMarks="true" style="-fx-background-image: url('LochNess.gif');" HBox.hgrow="ALWAYS" /> + <Button fx:id="changeButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#changePosition" prefHeight="54.0" prefWidth="99.0" text="Change position" textAlignment="CENTER" textFill="#cf4700" textOverrun="CLIP" wrapText="true"> + <font> + <Font name="System Bold" size="15.0" /> + </font> + <cursor> + <Cursor fx:constant="CROSSHAIR" /> + </cursor> + <opaqueInsets> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </opaqueInsets> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + <center> + <Canvas fx:id="canvas" height="340.0" width="749.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Canvas> + </center> + <left> + <Slider fx:id="boatPosition" blockIncrement="5.0" majorTickUnit="10.0" max="80.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" min="20.0" minorTickCount="5" orientation="VERTICAL" prefHeight="331.0" prefWidth="38.0" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER" /> + </left> +</BorderPane> diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java index c5984fb411138a70f4f07483f1b10b9aaf9e4e3b..ff0654d3b929e8e689fe8db89dcadb7e678cbae9 100644 --- a/src/test/java/jez04/structure/test/ClassStructureTest.java +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -1,15 +1,12 @@ package jez04.structure.test; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -24,88 +21,63 @@ import org.reflections.Reflections; import org.reflections.scanners.Scanners; import org.reflections.util.ConfigurationBuilder; -import javafx.geometry.Rectangle2D; -import javafx.scene.canvas.GraphicsContext; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; class ClassStructureTest { - private static final String drawableSimulableName = "DrawableSimulable"; - private static final String collisionableName = "Collisionable"; - Set<String> allClasses = getNameOfAllClasses(); @Test - void interfaceExistencePropertiesTest() { - classExist(drawableSimulableName); - Class<?> c = getClass(drawableSimulableName); - isInterface(c); - hasMethod(c, "draw", void.class, GraphicsContext.class); - hasMethod(c, "simulate", void.class); + void hasClassWithPropertyWithFxmlAnnotationTest() { + Set<String> names = getNameOfAllClasses(); + long annotationCount = 0; + for (String name : names) { + Class<?> c = getClass(name); + annotationCount += countPropertyWithAnnotation(c, ".*", FXML.class); + annotationCount += countMethodWithAnnotation(c, ".*", FXML.class); + } + assertTrue(annotationCount >= 2, "Only " + annotationCount + " FXML annotation found. Requred atleast 2!"); } - + @Test - void interface2ExistencePropertiesTest() { - classExist(collisionableName); - Class<?> c = getClass(collisionableName); - isInterface(c); - hasMethod(c, "getBoundingBox", Rectangle2D.class); - hasMethod(c, "intersect", boolean.class, Rectangle2D.class); - hasMethod(c, "hitBy", void.class, c); + void gameControllerExistenceTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasPropertyWithAnnotation(c, ".*", FXML.class); + hasMethodRegexp(c, ".*", void.class, ActionEvent.class); } - @Test - void movingClassTest() { - classExist("LochNess"); - Class<?> c = getClass("LochNess"); - hasImplements(c, collisionableName); - hasExtends(c, "WorldEntity"); - - hasMethod(c, "draw", void.class, GraphicsContext.class); - hasMethod(c, "drawInternal", void.class, GraphicsContext.class); + void gameControllerFxmlTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasPropertyWithAnnotation(c, ".*", FXML.class); } - @Test - void abstractClassTest() { - classExist("WorldEntity"); - Class<?> c = getClass("WorldEntity"); - hasImplements(c, drawableSimulableName); - - hasMethod(c, true, false, "draw", void.class, GraphicsContext.class); - hasMethod(c, false, true, "drawInternal", void.class, GraphicsContext.class); + void gameControllerActionEventTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasMethodRegexp(c, ".*", void.class, ActionEvent.class); } @Test - void groupClassPropertyTest() { - classExist("lab.Scene"); - Class<?> c = getClass("lab.Scene"); - hasMethod(c, "draw"); - hasMethod(c, "simulate"); - Class<?> drawable = getClass(drawableSimulableName); - hasProperty(c, ".*", drawable, true); + void backgroundExistenceTest() { + classExist("Background"); } - @Test - void drawingClassExistenceTest() { - Set<String> allClasses = getNameOfAllClasses(); - int detectedClassCount = 0; - for (String className : allClasses) { - try { - Class<?> clazz = Class.forName(className); - List<Constructor<?>> constructorList = Arrays.asList(clazz.getConstructors()); - List<Method> methodList = Arrays.asList(clazz.getMethods()); - long drawMethodCount = methodList.stream().filter(m -> m.getName().contains("draw")).count(); - long simulateMethodCount = methodList.stream().filter(m -> m.getName().contains("sim")).count(); - if (!constructorList.isEmpty() && (drawMethodCount > 0 || simulateMethodCount > 0)) { - System.out.println("DETECT:" + className); - detectedClassCount++; - } - } catch (ClassNotFoundException e) { - System.out.println(String.format("Class '%s' cannot be loaded: %s", className, e.getMessage())); - } - } - assertTrue(detectedClassCount >= 2, ""); + void backgroundExistenceSetAngleTest() { + classExist("Background"); + Class<?> c = getClass("Background"); + hasMethod(c, "setSpeed"); + } + @Test + void backgroundExistenceSetSpeedTest() { + classExist("Background"); + Class<?> c = getClass("Background"); + hasMethod(c, "setSpeed"); } + private void isInterface(Class<?> c) { assertTrue(c.isInterface(), c.getName() + " have to be interface."); } @@ -149,8 +121,35 @@ class ClassStructureTest { + classDef.getName()); } + private void hasPropertyWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation) { + List<Field> fields = Arrays.asList(classDef.getDeclaredFields()); + assertTrue( + fields.stream().filter(f -> f.getName().matches(propertyNameRegexp)) + .flatMap(f -> Arrays.asList( + f.getAnnotations()).stream()).map(a -> a.annotationType()).anyMatch(a -> + a.equals(annotation)), + "No field " + propertyNameRegexp + " with annotation " + annotation.getName() + " in class " + + classDef.getName()); + } + private long countPropertyWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation) { + List<Field> fields = Arrays.asList(classDef.getDeclaredFields()); + long propeertyFxmlAnnotationCount = fields.stream().filter(f -> f.getName().matches(propertyNameRegexp)) + .flatMap(f -> Arrays.asList( + f.getAnnotations()).stream()).map(a -> a.annotationType()).filter(a -> + a.equals(annotation)).count(); + return propeertyFxmlAnnotationCount; + } + private long countMethodWithAnnotation(Class<?> classDef, String propertyNameRegexp, Class<?> annotation) { + List<Method> methods = Arrays.asList(classDef.getDeclaredMethods()); + long methodFxmlAnnotationCount = methods.stream().filter(f -> f.getName().matches(propertyNameRegexp)) + .flatMap(m -> Arrays.asList( + m.getAnnotations()).stream()).map(a -> a.annotationType()).filter(a -> + a.equals(annotation)).count(); + return methodFxmlAnnotationCount; + } + private void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType) { - List<Method> methods = Arrays.asList(interfaceDef.getMethods()); + List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods()); assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName); assertTrue( methods.stream().filter(m -> m.getName().contains(methodName)) @@ -159,7 +158,7 @@ class ClassStructureTest { } private void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, Class<?>... params) { - List<Method> methods = Arrays.asList(interfaceDef.getMethods()); + List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods()); assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName); assertTrue( methods.stream().filter(m -> m.getName().contains(methodName)) @@ -169,6 +168,17 @@ class ClassStructureTest { + Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", "))); } + private void hasMethodRegexp(Class<?> interfaceDef, String methodNameRegexp, Class<?> returnType, Class<?>... params) { + List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods()); + assertTrue(methods.stream().anyMatch(m -> m.getName().matches(methodNameRegexp)), "No method " + methodNameRegexp); + assertTrue( + methods.stream().filter(m -> m.getName().matches(methodNameRegexp)) + .filter(m -> m.getReturnType().equals(returnType)) + .anyMatch(m -> + Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))), + "Method " + methodNameRegexp + " has no all parrams:" + + Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", "))); + } private void hasMethod(Class<?> interfaceDef, boolean finalTag, boolean abstractTag, String methodName, Class<?> returnType, Class<?>... params) { List<Method> methods = Arrays.asList(interfaceDef.getDeclaredMethods()); @@ -218,15 +228,13 @@ class ClassStructureTest { || p.getName().startsWith("javassist")) { continue; } - System.out.println(p.getName()); Configuration conf = new ConfigurationBuilder().addScanners(Scanners.SubTypes.filterResultsBy(pc -> true)) .forPackages(p.getName()); Reflections reflections = new Reflections(conf); allClasses.addAll(reflections.getAll(Scanners.SubTypes.filterResultsBy(c -> { - System.out.println(c); return true; }))); } return allClasses; } -} +} \ No newline at end of file