diff --git a/pom.xml b/pom.xml index 9ddb6d877ff6b6062f47a8c70248e7771ed92bc9..660a82b05bedb4618daa09a9c4760d186da8f4ec 100644 --- a/pom.xml +++ b/pom.xml @@ -22,26 +22,37 @@ <artifactId>javafx-fxml</artifactId> <version>23</version> </dependency> - <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> + <!-- + https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.11.0</version> <scope>test</scope> </dependency> - <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine --> + <!-- + https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.11.0</version> <scope>test</scope> </dependency> - <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params --> + <!-- + https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.11.0</version> <scope>test</scope> </dependency> + <!-- https://mvnrepository.com/artifact/org.reflections/reflections --> + <dependency> + <groupId>org.reflections</groupId> + <artifactId>reflections</artifactId> + <version>0.10.2</version> + <scope>test</scope> + </dependency> + </dependencies> </project> diff --git a/src/main/java/lab/App.java b/src/main/java/lab/App.java index 3a9e838f99d4c9e57926a33c1d4ffe23ef873f42..735a8b46f274cea82e92e2aa1445583b12e8eb00 100644 --- a/src/main/java/lab/App.java +++ b/src/main/java/lab/App.java @@ -1,54 +1,50 @@ 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; /** - * Class <b>App</b> - extends class Application and it is an entry point of the program - * @author Java I + * Class <b>App</b> - extends class Application and it is an entry point of the + * program + * + * @author Java I */ 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 { - //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()); + // Construct a main window with a canvas. + FXMLLoader gameLoader = new FXMLLoader(getClass().getResource("/lab/gameWindow.fxml")); + Parent root = gameLoader.load(); + GameController gameController = gameLoader.getController(); + Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.resizableProperty().set(false); primaryStage.setTitle("Java 1 - 1th laboratory"); primaryStage.show(); - - //Exit program when main window is closed + // Exit program when main window is closed primaryStage.setOnCloseRequest(this::exitProgram); - timer = new DrawingThread(canvas); - timer.start(); } catch (Exception e) { e.printStackTrace(); } } + @Override public void stop() throws Exception { - timer.stop(); + gameController.stop(); super.stop(); } - + private void exitProgram(WindowEvent evt) { System.exit(0); } diff --git a/src/main/java/lab/DrawingThread.java b/src/main/java/lab/DrawingThread.java index f5e0529bec78c3a83ae93743b43a80eff542258a..a0a63edcc76a220b086fd5dfbe55a8e493a910e3 100644 --- a/src/main/java/lab/DrawingThread.java +++ b/src/main/java/lab/DrawingThread.java @@ -28,4 +28,8 @@ public class DrawingThread extends AnimationTimer { lastTime = now; } + public Level getLevel() { + return level; + } + } diff --git a/src/main/java/lab/GameController.java b/src/main/java/lab/GameController.java new file mode 100644 index 0000000000000000000000000000000000000000..6d2f4a68c0fd38dd48256bd97d10080b9accf44b --- /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 angle; + + private DrawingThread timer; + + @FXML + private Canvas canvas; + + @FXML + private Slider speed; + + @FXML + void spawn(ActionEvent event) { + timer.getLevel().getPlayer().spawn(); + + } + + @FXML + void initialize() { + assert angle != 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(); + angle.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + timer.getLevel().getPlayer().setAngle(newValue.doubleValue()); + } + }); + speed.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + timer.getLevel().getPlayer().setSpeed(newValue.doubleValue()); + } + }); + + } + + public void stop() { + timer.stop(); + } + + +} diff --git a/src/main/java/lab/Level.java b/src/main/java/lab/Level.java index d6f9031fc674d48d37017570351da1a0f5501291..e5497d23432219adf92c622989a93812108d5f32 100644 --- a/src/main/java/lab/Level.java +++ b/src/main/java/lab/Level.java @@ -10,15 +10,17 @@ public class Level { private double width; private double height; private DrawableSimulable[] entities; - + private Player player; + public Level(double width, double height) { this.width = width; this.height = height; + player = new Player(this, new Point2D(20, 250), new Point2D(100, -20)); entities = new DrawableSimulable[9]; entities[0] = new NicerObstacle(this, new Point2D(20, 150)); entities[1] = new Obstacle(this, new Point2D(300, 200), new Dimension2D(80, 40)); entities[2] = new Obstacle(this); - entities[3] = new Player(this, new Point2D(20, 250), new Point2D(100, -20)); + entities[3] = player; for (int i = 4; i < entities.length; i++) { entities[i] = new Monster(this); } @@ -57,4 +59,9 @@ public class Level { public double getHeight() { return height; } + + public Player getPlayer() { + return player; + } + } diff --git a/src/main/java/lab/Player.java b/src/main/java/lab/Player.java index 7450176246da92dce064b66301358f373d964842..dd51953dc6ba03b42203e879e5b8a6ac96196435 100644 --- a/src/main/java/lab/Player.java +++ b/src/main/java/lab/Player.java @@ -12,6 +12,8 @@ import javafx.scene.transform.Rotate; public class Player extends WorldEntity implements Collisionable { private static final Random RANDOM = new Random(); private Point2D speed; + private double speedSize; + private double angle; public Player(Level level, Point2D position, Point2D speed) { super(level, position); @@ -58,4 +60,26 @@ public class Player extends WorldEntity implements Collisionable { } + public void setSpeed(double speedSize) { + this.speedSize = speedSize; + updateSpeed(); + + } + + private void updateSpeed() { + speed = new Point2D(Math.cos(angle) * speedSize, Math.sin(angle) * speedSize); + } + + public void setAngle(double angle) { + this.angle = Math.toRadians(angle); + updateSpeed(); + } + + public void spawn() { + position = new Point2D( + RANDOM.nextDouble(0, level.getWidth()*0.5), + RANDOM.nextDouble(level.getHeight()*0.5, level.getHeight()*0.9)); + + } + } diff --git a/src/main/resources/lab/gameWindow.fxml b/src/main/resources/lab/gameWindow.fxml new file mode 100644 index 0000000000000000000000000000000000000000..55d798a6be852770de8550ce0b785dd141b9fe39 --- /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="200.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" min="50.0" minorTickCount="5" prefHeight="68.0" prefWidth="736.0" showTickLabels="true" showTickMarks="true" HBox.hgrow="ALWAYS" /> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#spawn" prefHeight="46.0" prefWidth="147.0" style="-fx-background-color: blue;" text="Spawn" textAlignment="CENTER" textFill="WHITE" textOverrun="CLIP"> + <font> + <Font name="System Bold" size="22.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="385.0" width="749.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Canvas> + </center> + <left> + <Slider fx:id="angle" blockIncrement="25.0" majorTickUnit="30.0" max="360.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minorTickCount="5" orientation="VERTICAL" 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 new file mode 100644 index 0000000000000000000000000000000000000000..aaf98cc9a4d18a9790d65d4b89f57861e52ad67b --- /dev/null +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -0,0 +1,220 @@ +package jez04.structure.test; + +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.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.reflections.Configuration; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import org.reflections.util.ConfigurationBuilder; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.Rectangle2D; +import javafx.scene.canvas.GraphicsContext; + +class ClassStructureTest { + + private static final String drawableSimulableName = "DrawableSimulable"; + private static final String collisionableName = "Collisionable"; + + Set<String> allClasses = getNameOfAllClasses(); + + @Test + void gameControllerExistenceTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasPropertyWithAnnotation(c, ".*", FXML.class); + hasMethodRegexp(c, ".*", void.class, ActionEvent.class); + } + @Test + void gameControllerFxmlTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasPropertyWithAnnotation(c, ".*", FXML.class); + } + @Test + void gameControllerActionEventTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasMethodRegexp(c, ".*", void.class, ActionEvent.class); + } + + @Test + void cannonExistenceTest() { + classExist("Player"); + } + @Test + void cannonExistenceSetAngleTest() { + classExist("Player"); + Class<?> c = getClass("Player"); + hasMethod(c, "setAngle"); + } + @Test + void cannonExistenceSetSpeedTest() { + classExist("Player"); + Class<?> c = getClass("Player"); + hasMethod(c, "setSpeed"); + } + + + private void isInterface(Class<?> c) { + assertTrue(c.isInterface(), c.getName() + " have to be interface."); + } + + private void classExist(String name) { + assertTrue(allClasses.stream().anyMatch(c -> c.endsWith(name)), "Interface " + name + " not found"); + } + + private Class<?> getClass(String name) { + String className = allClasses.stream().filter(c -> c.endsWith(name)).findAny().orElse(null); + if (className == null) { + Assertions.fail("Class " + name + " not found."); + } + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintStream ps = new PrintStream(baos, true)) { + e.printStackTrace(ps); + } catch (Exception e2) { + Assertions.fail(e2.getMessage()); + } + String stackTrace = baos.toString(); + Assertions.fail("Class " + name + " not found.\n" + stackTrace); + return null; + } + } + + private void hasProperty(Class<?> classDef, String propertyNameRegexp, Class<?> type, boolean array) { + List<Field> fields = Arrays.asList(classDef.getDeclaredFields()); + assertTrue(fields.stream().anyMatch(f -> { + if (f.getName().matches(propertyNameRegexp)) { + if (array) { + return f.getType().isArray() && f.getType().getComponentType().equals(type); + } else { + return f.getType().equals(type); + } + } + return false; + }), "No field " + propertyNameRegexp + " of type " + type.getName() + " (is array " + array + ") in class " + + 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 void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType) { + 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)) + .anyMatch(m -> m.getReturnType().equals(returnType)), + "Method " + methodName + " not return " + returnType.getName()); + } + + private void hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType, Class<?>... params) { + 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)) + .filter(m -> m.getReturnType().equals(returnType)) + .anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))), + "Method " + methodName + " has no all parrams:" + + 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()); + assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName); + assertTrue( + methods.stream().filter(m -> m.getName().contains(methodName)) + .filter(m -> m.getReturnType().equals(returnType) + && (Modifier.isAbstract(m.getModifiers()) == abstractTag) + && (Modifier.isFinal(m.getModifiers()) == finalTag)) + .anyMatch(m -> Arrays.asList(m.getParameterTypes()).containsAll(Arrays.asList(params))), + "Method " + methodName + " has no all params:" + + Arrays.asList(params).stream().map(Class::getName).collect(Collectors.joining(", "))); + } + + private void hasImplements(Class<?> clazz, String... interfaceNames) { + List<Class<?>> interfaces = new ArrayList<>(); + Arrays.asList(interfaceNames).stream().map(name -> getClass(name)).forEach(c -> interfaces.add(c)); + assertTrue(Arrays.asList(clazz.getInterfaces()).containsAll(interfaces), "Class not implements all interfaces:" + + interfaces.stream().map(Class::getName).collect(Collectors.joining(", "))); + } + + private void hasExtends(Class<?> clazz, String parentName) { + Class<?> parent = getClass(parentName); + assertTrue(clazz.getSuperclass().equals(parent), + "Class " + clazz.getName() + " not extends class " + parentName); + } + + private void hasMethod(Class<?> interfaceDef, String methodName) { + List<Method> methods = Arrays.asList(interfaceDef.getMethods()); + assertTrue(methods.stream().anyMatch(m -> m.getName().contains(methodName)), "No method " + methodName); + } + + private Set<String> getNameOfAllClasses() { + List<String> initClassesName = Arrays.asList("lab.Routines", "lab.App", "lab.DrawingThread"); + for (String className : initClassesName) { + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + System.out.println(String.format("Class '%s' cannot be loaded: %s", className, e.getMessage())); + } + } + Set<String> allClasses = new HashSet<>(); + for (Package p : Package.getPackages()) { + if (p.getName().startsWith("java.") || p.getName().startsWith("com.") || p.getName().startsWith("jdk.") + || p.getName().startsWith("javafx.") || p.getName().startsWith("org.") + || p.getName().startsWith("sun.") || p.getName().startsWith("javax.") + || 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; + } +}