From cc167c70df9aa944a220200de5b46bdda7f57df3 Mon Sep 17 00:00:00 2001 From: jez04 <david.jezek@post.cz> Date: Wed, 23 Oct 2024 22:33:04 +0200 Subject: [PATCH] feat: :tada: solution lab04 --- src/main/java/lab/Background.java | 9 +- src/main/java/lab/Boat.java | 32 ++- src/main/java/lab/Collisionable.java | 13 + src/main/java/lab/DrawableSimulable.java | 9 + src/main/java/lab/LochNess.java | 25 +- src/main/java/lab/Rock.java | 12 +- src/main/java/lab/Scene.java | 44 ++-- src/main/java/lab/WorldEntity.java | 32 +++ .../structure/test/ClassStructureTest.java | 232 ++++++++++++++++++ 9 files changed, 363 insertions(+), 45 deletions(-) create mode 100644 src/main/java/lab/Collisionable.java create mode 100644 src/main/java/lab/DrawableSimulable.java create mode 100644 src/main/java/lab/WorldEntity.java create mode 100644 src/test/java/jez04/structure/test/ClassStructureTest.java diff --git a/src/main/java/lab/Background.java b/src/main/java/lab/Background.java index 0db811c..1e4b308 100644 --- a/src/main/java/lab/Background.java +++ b/src/main/java/lab/Background.java @@ -5,16 +5,15 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.paint.Color; -public class Background { +public class Background extends WorldEntity { - private Scene scene; private Point2D[] imagePosition; private Image[] image; private Point2D speed; private static final double[] speedMultiplier = new double[] { 0.6, 0.8, 1, 1 }; public Background(Scene scene) { - this.scene = scene; + super(scene, new Point2D(0, 0)); image = new Image[4]; imagePosition = new Point2D[4]; image[0] = new Image(Background.class.getResourceAsStream("cityfar400.png")); @@ -28,7 +27,8 @@ public class Background { speed = new Point2D(100, 0); } - public void draw(GraphicsContext gc) { + @Override + public void drawInternal(GraphicsContext gc) { gc.setFill(Color.DARKBLUE); gc.fillRect(0, 0, scene.getSize().getWidth(), scene.getSize().getHeight()); for (int i = 0; i < image.length; i++) { @@ -44,6 +44,7 @@ public class Background { } } + @Override public void simulate(double deltaTime) { Point2D baseSpeed = speed.multiply(deltaTime / 1_000_000_000); for (int i = 0; i < image.length; i++) { diff --git a/src/main/java/lab/Boat.java b/src/main/java/lab/Boat.java index 574c194..ea0561c 100644 --- a/src/main/java/lab/Boat.java +++ b/src/main/java/lab/Boat.java @@ -1,32 +1,52 @@ package lab; +import java.util.Random; + import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; -public class Boat { +public class Boat extends WorldEntity implements Collisionable { - private Scene scene; - private Point2D position; + private static final Random RANDOM = new Random(); + private Point2D speed; private Image image; public Boat(Scene scene, Point2D position) { - this.scene = scene; - this.position = position; + super(scene, position); image = new Image(Boat.class.getResourceAsStream("ship-boat.gif")); + speed = new Point2D(0, 0); } - public void draw(GraphicsContext gc) { + @Override + public void drawInternal(GraphicsContext gc) { gc.drawImage(image, position.getX(), position.getY()); gc.strokeRect(position.getX(), position.getY(), image.getWidth(), image.getHeight()); } + @Override public void simulate(double deltaTime) { + position = position.add(speed.multiply(deltaTime / 1_000_000_000)); + speed = speed.multiply(0.99); } + @Override public Rectangle2D getBoundingBox() { return new Rectangle2D(position.getX(), position.getY(), image.getWidth(), image.getHeight()); } + @Override + public boolean intersect(Rectangle2D another) { + return getBoundingBox().intersects(another); + } + + @Override + public void hitBy(Collisionable another) { + if(another instanceof LochNess lochNess) { + int direction = RANDOM.nextBoolean()?-1:1; + speed = new Point2D(-20, direction * RANDOM.nextDouble(50, 100)); + } + } + } diff --git a/src/main/java/lab/Collisionable.java b/src/main/java/lab/Collisionable.java new file mode 100644 index 0000000..40a8157 --- /dev/null +++ b/src/main/java/lab/Collisionable.java @@ -0,0 +1,13 @@ +package lab; + +import javafx.geometry.Rectangle2D; + +public interface Collisionable { + + Rectangle2D getBoundingBox(); + + boolean intersect(Rectangle2D another); + + void hitBy(Collisionable another); + +} diff --git a/src/main/java/lab/DrawableSimulable.java b/src/main/java/lab/DrawableSimulable.java new file mode 100644 index 0000000..9930e7e --- /dev/null +++ b/src/main/java/lab/DrawableSimulable.java @@ -0,0 +1,9 @@ +package lab; + +import javafx.scene.canvas.GraphicsContext; + +public interface DrawableSimulable { + void draw(GraphicsContext gc); + + void simulate(double deltaTime); +} diff --git a/src/main/java/lab/LochNess.java b/src/main/java/lab/LochNess.java index e27d7e0..79edf18 100644 --- a/src/main/java/lab/LochNess.java +++ b/src/main/java/lab/LochNess.java @@ -7,16 +7,15 @@ import javafx.geometry.Rectangle2D; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; -public class LochNess { +public class LochNess extends WorldEntity implements Collisionable { private static final Random RANDOM = new Random(); - private Scene scene; - private Point2D position; private Point2D speed; private Image image; public LochNess(Scene scene) { + super(scene, new Point2D(0, 0)); this.scene = scene; image = new Image(LochNess.class.getResourceAsStream("LochNess.gif")); position = new Point2D(RANDOM.nextDouble(scene.getSize().getWidth() * 0.3, scene.getSize().getWidth()), @@ -24,22 +23,25 @@ public class LochNess { speed = new Point2D(-RANDOM.nextDouble(50, 150), 0); } - public void draw(GraphicsContext gc) { + @Override + public void drawInternal(GraphicsContext gc) { gc.drawImage(image, position.getX(), position.getY()); gc.strokeRect(position.getX(), position.getY(), image.getWidth(), image.getHeight()); } + @Override public void simulate(double deltaTime) { position = position.add(speed.multiply(deltaTime / 1_000_000_000)); if (position.getX() + image.getWidth() < 0) { position = new Point2D(scene.getSize().getWidth(), position.getY()); } - if (position.getX() > scene.getSize().getWidth()+5) { + if (position.getX() > scene.getSize().getWidth() + 5) { position = new Point2D(scene.getSize().getWidth(), position.getY()); speed = speed.multiply(-1); } } + @Override public Rectangle2D getBoundingBox() { return new Rectangle2D(position.getX(), position.getY(), image.getWidth(), image.getHeight()); } @@ -47,4 +49,17 @@ public class LochNess { public void changeDirection() { speed = speed.multiply(-1); } + + @Override + public boolean intersect(Rectangle2D another) { + return getBoundingBox().intersects(another); + } + + @Override + public void hitBy(Collisionable another) { + if (another instanceof Boat) { + changeDirection(); + } + + } } diff --git a/src/main/java/lab/Rock.java b/src/main/java/lab/Rock.java index b5a1b43..82c6a27 100644 --- a/src/main/java/lab/Rock.java +++ b/src/main/java/lab/Rock.java @@ -7,10 +7,8 @@ import javafx.scene.paint.Color; import javafx.scene.transform.Affine; import javafx.scene.transform.Rotate; -public class Rock { +public class Rock extends WorldEntity { - private Scene scene; - private Point2D position; private Dimension2D size; private double angle = 0; @@ -19,13 +17,12 @@ public class Rock { } public Rock(Scene scene, Point2D position, Dimension2D size) { - this.scene = scene; - this.position = position; + super(scene, position); this.size = size; } - public void draw(GraphicsContext gc) { - gc.save(); + @Override + public void drawInternal(GraphicsContext gc) { gc.setFill(Color.GRAY); gc.setStroke(Color.GREEN); Point2D center = position.add(size.getWidth() / 2, size.getHeight() / 2); @@ -33,7 +30,6 @@ public class Rock { gc.setTransform(new Affine(rotateMatrix)); gc.fillRect(position.getX(), position.getY(), size.getWidth(), size.getHeight()); gc.strokeRect(position.getX(), position.getY(), size.getWidth(), size.getHeight()); - gc.restore(); } public void simulate(double deltaTime) { diff --git a/src/main/java/lab/Scene.java b/src/main/java/lab/Scene.java index eaf6e25..fd6798a 100644 --- a/src/main/java/lab/Scene.java +++ b/src/main/java/lab/Scene.java @@ -7,19 +7,16 @@ import javafx.scene.canvas.GraphicsContext; public class Scene { private Dimension2D size; - private Background background; - private Rock rock; - private Boat boat; - private LochNess[] lochNesses; + private DrawableSimulable[] sceneEntitites; public Scene(double width, double height) { size = new Dimension2D(width, height); - background = new Background(this); - rock = new Rock(this, new Point2D(300, 300), new Dimension2D(30, 50)); - boat = new Boat(this, new Point2D(20, 200)); - lochNesses = new LochNess[5]; - for (int i = 0; i < lochNesses.length; i++) { - lochNesses[i] = new LochNess(this); + 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)); + for (int i = 3; i < sceneEntitites.length; i++) { + sceneEntitites[i] = new LochNess(this); } } @@ -28,22 +25,25 @@ public class Scene { } public void draw(GraphicsContext gc) { - background.draw(gc); - rock.draw(gc); - boat.draw(gc); - for (LochNess lochNess : lochNesses) { - lochNess.draw(gc); + for (DrawableSimulable entity : sceneEntitites) { + entity.draw(gc); } } public void simulate(double deltaTime) { - background.simulate(deltaTime); - rock.simulate(deltaTime); - boat.simulate(deltaTime); - for (LochNess lochNess : lochNesses) { - lochNess.simulate(deltaTime); - if (lochNess.getBoundingBox().intersects(boat.getBoundingBox())) { - lochNess.changeDirection(); + for (DrawableSimulable drawableSimulable : sceneEntitites) { + drawableSimulable.simulate(deltaTime); + } + for (int i = 0; i < sceneEntitites.length; i++) { + if (sceneEntitites[i] instanceof Collisionable c1) { + for (int j = i + 1; j < sceneEntitites.length; j++) { + if (sceneEntitites[j] instanceof Collisionable c2) { + if (c1.intersect(c2.getBoundingBox())) { + c1.hitBy(c2); + c2.hitBy(c1); + } + } + } } } } diff --git a/src/main/java/lab/WorldEntity.java b/src/main/java/lab/WorldEntity.java new file mode 100644 index 0000000..b5ae6f5 --- /dev/null +++ b/src/main/java/lab/WorldEntity.java @@ -0,0 +1,32 @@ +package lab; + +import javafx.geometry.Point2D; +import javafx.scene.canvas.GraphicsContext; + +public abstract class WorldEntity implements DrawableSimulable { + + protected Scene scene; + protected Point2D position; + + public WorldEntity(Scene scene, Point2D position) { + this.scene = scene; + this.position = position; + } + + public Point2D getPosition() { + return position; + } + + public Scene getScene() { + return scene; + } + + @Override + public final void draw(GraphicsContext gc) { + gc.save(); + drawInternal(gc); + gc.restore(); + } + + public abstract void drawInternal(GraphicsContext gc); +} 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 0000000..c5984fb --- /dev/null +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -0,0 +1,232 @@ +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; +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.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 interfaceExistencePropertiesTest() { + classExist(drawableSimulableName); + Class<?> c = getClass(drawableSimulableName); + isInterface(c); + hasMethod(c, "draw", void.class, GraphicsContext.class); + hasMethod(c, "simulate", void.class); + } + + @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); + } + + @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); + } + + @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); + } + + @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); + } + + @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, ""); + } + + 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 hasMethod(Class<?> interfaceDef, String methodName, Class<?> returnType) { + List<Method> methods = Arrays.asList(interfaceDef.getMethods()); + 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.getMethods()); + 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 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; + } +} -- GitLab