diff --git a/src/main/java/lab/App.java b/src/main/java/lab/App.java index 58fd3f37f039d8171036be098a809c2ee811ccd6..735a8b46f274cea82e92e2aa1445583b12e8eb00 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,25 @@ 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 { // 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); 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 +41,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/Bullet.java b/src/main/java/lab/Bullet.java index 2172d11a30b2cf2499ded7076f1a283fe755e799..76f904f6b74c1fe996b0cd26d10764a8e66e549b 100644 --- a/src/main/java/lab/Bullet.java +++ b/src/main/java/lab/Bullet.java @@ -44,4 +44,8 @@ public class Bullet extends WorldEntity implements Collisionable{ } + public void setVelocity(Point2D velocity) { + this.velocity = velocity; + } + } diff --git a/src/main/java/lab/BulletAnimated.java b/src/main/java/lab/BulletAnimated.java index 2134c1292e28308fbcac0caed6348e36c8b33412..d74427ca60b113770dec07e4796e5cf4dc43836d 100644 --- a/src/main/java/lab/BulletAnimated.java +++ b/src/main/java/lab/BulletAnimated.java @@ -33,7 +33,7 @@ public class BulletAnimated extends Bullet { public void reload() { position = cannon.getPosition(); - velocity = initVelocity; + velocity = new Point2D(0, 0); } - + } \ No newline at end of file diff --git a/src/main/java/lab/Cannon.java b/src/main/java/lab/Cannon.java index 59f6d2172d4e7debc36115666702dabde6e353cc..e31f1ed50c344dda08b5421ef53f25ee2421489b 100644 --- a/src/main/java/lab/Cannon.java +++ b/src/main/java/lab/Cannon.java @@ -27,9 +27,18 @@ public class Cannon extends WorldEntity{ @Override public void simulate(double deltaT) { - angle += angleDelta * deltaT; - if (angle >= 0 || angle <= -90) { - angleDelta = -angleDelta; - } +// angle += angleDelta * deltaT; +// if (angle >= 0 || angle <= -90) { +// angleDelta = -angleDelta; +// } } + + public double getAngle() { + return angle; + } + + public void setAngle(double angle) { + this.angle = -angle; + } + } diff --git a/src/main/java/lab/DrawingThread.java b/src/main/java/lab/DrawingThread.java index 50574b1f954dbf67486a4cc0d385b46420cdd128..8c0f65f38b384960527c515f15e4d21163ad4527 100644 --- a/src/main/java/lab/DrawingThread.java +++ b/src/main/java/lab/DrawingThread.java @@ -31,4 +31,8 @@ public class DrawingThread extends AnimationTimer { lastTime = now; } + public World getWorld() { + return world; + } + } diff --git a/src/main/java/lab/GameController.java b/src/main/java/lab/GameController.java new file mode 100644 index 0000000000000000000000000000000000000000..3ad72637660c92fecd268de5ee0d3c212a5e9d09 --- /dev/null +++ b/src/main/java/lab/GameController.java @@ -0,0 +1,55 @@ +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; + + @FXML + private Slider speed; + + @FXML + private Canvas canvas; + + private DrawingThread timer; + + @FXML + void fire(ActionEvent event) { + double angle = timer.getWorld().getCannon().getAngle(); + double angleRad = Math.toRadians(angle); + double speedValue = speed.getValue(); + timer.getWorld().getBulletAnimated().reload(); + Point2D velocity = new Point2D( + Math.cos(angleRad)*speedValue, + Math.sin(angleRad)*speedValue); + timer.getWorld().getBulletAnimated().setVelocity(velocity); + } + + @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.getWorld().getCannon().setAngle(newValue.doubleValue()); + } + }); + } + + public void stop() { + timer.stop(); + } + +} diff --git a/src/main/java/lab/World.java b/src/main/java/lab/World.java index 253af0e99f6b6c1fcba88942cfb83f8052757639..1f7b4e53a758e0ba63d8a34ef21d822ccf72b401 100644 --- a/src/main/java/lab/World.java +++ b/src/main/java/lab/World.java @@ -12,16 +12,19 @@ public class World { private final double height; private DrawableSimulable[] entities; + private Cannon cannon; + private BulletAnimated bulletAnimated; public World(double width, double height) { this.width = width; this.height = height; entities = new DrawableSimulable[8]; - Cannon cannon = new Cannon(this, new Point2D(0, height - 20), -45); + cannon = new Cannon(this, new Point2D(0, height - 20), -45); + bulletAnimated = new BulletAnimated(this, cannon, new Point2D(0, height), new Point2D(50, -80), + new Point2D(0, 9.81)); entities[0] = cannon; entities[1] = new Bullet(this, new Point2D(0, height), new Point2D(30, -30), new Point2D(0, 9.81)); - entities[2] = new BulletAnimated(this, cannon, new Point2D(0, height), new Point2D(50, -80), - new Point2D(0, 9.81)); + entities[2] = bulletAnimated; for (int i = 3; i < entities.length; i++) { entities[i] = new Ufo(this); } @@ -63,4 +66,12 @@ public class World { return height; } + public Cannon getCannon() { + return cannon; + } + + public BulletAnimated getBulletAnimated() { + return bulletAnimated; + } + } \ 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..b65e058f8949927a1d3b0baf0007e3373fa33316 --- /dev/null +++ b/src/main/resources/lab/gameWindow.fxml @@ -0,0 +1,42 @@ +<?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="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="lab.GameController"> + <bottom> + <HBox alignment="TOP_CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <children> + <Slider fx:id="angle" majorTickUnit="15.0" max="90.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minorTickCount="5" showTickLabels="true" showTickMarks="true" HBox.hgrow="ALWAYS" /> + <Button mnemonicParsing="false" onAction="#fire" style="-fx-background-color: RED;" text="Fire" textAlignment="CENTER"> + <font> + <Font name="System Bold" size="24.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> + <Slider fx:id="speed" max="200.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" min="50.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="50.0" HBox.hgrow="ALWAYS" /> + </children> + </HBox> + </bottom> + <center> + <Canvas fx:id="canvas" height="287.0" width="582.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Canvas> + </center> +</BorderPane> diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java index ac5f14c0fe6cce1adc3586795317ea18888dc5fa..c545ff810230339a9776aeb920ce4afd9fd75d8a 100644 --- a/src/test/java/jez04/structure/test/ClassStructureTest.java +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -1,92 +1,186 @@ 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.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 ufoClassEexistanceTest() { - Set<String> allClasses = getNameOfAllClasses(); - assertTrue(allClasses.stream().anyMatch(c -> c.endsWith("Ufo")), "Class Ufo not found"); + void gameControllerExistenceTest() { + classExist("GameController"); + Class<?> c = getClass("GameController"); + hasPropertyWithAnnotation(c, ".*", FXML.class); + hasMethodRegexp(c, ".*", void.class, ActionEvent.class); } - @Test - void ufoClassPropertiesTest() { - Set<String> allClasses = getNameOfAllClasses(); - String ufoClassName = allClasses.stream().filter(c -> c.endsWith("Ufo")).findAny().orElse(null); - assertNotNull(ufoClassName, "Class Ufo not found"); - try { - Class<?> ufoClass = Class.forName(ufoClassName); - List<Method> methods = Arrays.asList(ufoClass.getMethods()); - assertTrue(methods.stream().anyMatch(m -> m.getName().contains("draw")), "No method draw"); - assertTrue(methods.stream().anyMatch(m -> m.getName().contains("simulate")), "No method simulate"); - assertTrue(methods.stream().anyMatch(m -> m.getName().contains("getBoundingBox")), - "No method getBoundingBox"); - assertTrue( - methods.stream().filter(m -> m.getName().contains("getBoundingBox")) - .anyMatch(m -> m.getReturnType().equals(Rectangle2D.class)), - "Method getBoundingBox not return Rectangle2D"); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } + 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 worldClassPropertyTest() { - Set<String> allClasses = getNameOfAllClasses(); - String ufoClassName = allClasses.stream().filter(c -> c.endsWith("Ufo")).findAny().orElse(null); - String worldClassName = allClasses.stream().filter(c -> c.endsWith("World")).findAny().orElse(null); - assertNotNull(worldClassName, "Class World not found"); + void cannonExistenceTest() { + classExist("Cannon"); + } + @Test + void cannonExistenceSetAngleTest() { + classExist("Cannon"); + Class<?> c = getClass("Cannon"); + hasMethod(c, "setAngle"); + } + + + 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 { - Class<?> worldClass = Class.forName(worldClassName); - List<Field> fields = Arrays.asList(worldClass.getDeclaredFields()); - Class<?> ufoClass = Class.forName(ufoClassName); - assertTrue( - fields.stream() - .anyMatch(f -> f.getType().isArray() && f.getType().getComponentType().equals(ufoClass)), - "No array of Ufo in World class"); + return Class.forName(className); } catch (ClassNotFoundException e) { - e.printStackTrace(); + 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; } } - @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++; + 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); } - } catch (ClassNotFoundException e) { - System.out.println(String.format("Class '%s' cannot be loaded: %s", className, e.getMessage())); } - } - assertTrue(detectedClassCount >= 2, ""); + 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() { @@ -110,7 +204,10 @@ class ClassStructureTest { 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 -> true))); + allClasses.addAll(reflections.getAll(Scanners.SubTypes.filterResultsBy(c -> { + System.out.println(c); + return true; + }))); } return allClasses; }