diff --git a/part_3/exercise_3/.gitignore b/part_3/exercise_3/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d578053fb6c37346e61ef9da56c648dac284bc98 --- /dev/null +++ b/part_3/exercise_3/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +/.idea/ +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/part_3/exercise_3/exercise_3.iml b/part_3/exercise_3/exercise_3.iml new file mode 100644 index 0000000000000000000000000000000000000000..c90834f2d607afe55e6104d8aa2cdfffb713f688 --- /dev/null +++ b/part_3/exercise_3/exercise_3.iml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/part_3/exercise_3/src/Circle.java b/part_3/exercise_3/src/Circle.java new file mode 100644 index 0000000000000000000000000000000000000000..523a2207b2518ab7b78f085ed290909f376f3d4f --- /dev/null +++ b/part_3/exercise_3/src/Circle.java @@ -0,0 +1,33 @@ +import java.util.List; + +public class Circle implements ShapeInterface { + public static final String NAME = "circle"; + public static final int NUMBER_OF_POINTS = 2; + + private final List<Point> points; + + public Circle(List<Point> points) { + if (points.size() != NUMBER_OF_POINTS) { + throw new InvalidShapeException("Circle requires 2 points"); + } + + this.points = points; + } + + @Override + public PointBoundary boundaries() { + return PointHelper.boundaries(points); + } + + /** + * area = pi * r^2 ja r = sqrt(dx^2 + dy^2), so area = pi * (dx^2 + dy^2) + */ + @Override + public double area() { + PointBoundary boundary = this.boundaries(); + int xLength = boundary.xLength(); + int yLength = boundary.yLength(); + + return Math.PI * (xLength * xLength + yLength * yLength); + } +} diff --git a/part_3/exercise_3/src/CommandLineApp.java b/part_3/exercise_3/src/CommandLineApp.java new file mode 100644 index 0000000000000000000000000000000000000000..106f7f22579e966e4fd893e4a8958f1f3f4358c2 --- /dev/null +++ b/part_3/exercise_3/src/CommandLineApp.java @@ -0,0 +1,75 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class CommandLineApp { + private final CommandLineHelper commandLineHelper; + private final ShapeFactory shapeFactory; + + public CommandLineApp() { + commandLineHelper = new CommandLineHelper(); + shapeFactory = new ShapeFactory(); + } + + public void run() { + System.out.println("A circle is defined by a centre and a perimeter point, the others by corner points"); + + List<ShapeInterface> shapes = createShapes(); + + CompositeShape compositeShape = new CompositeShape(shapes); + + double area = compositeShape.area(); + PointBoundary boundary = compositeShape.boundaries(); + + System.out.println(); + String output = String.format("Sum of area covered by the patterns:\n%f\n\n", area); + output += String.format( + "The common boundaries of the patterns:\n(%d, %d) x (%d, %d)", + boundary.bottomLeft().x(), + boundary.bottomLeft().y(), + boundary.topRight().x(), + boundary.topRight().y() + ); + commandLineHelper.printSuccessMessage(output); + } + + private List<ShapeInterface> createShapes() { + ArrayList<ShapeInterface> shapes = new ArrayList<>(); + Set<String> supportedTypes = shapeFactory.getSupportedTypes(); + String shapeTypes = String.join(", ", supportedTypes); + + while (true) { + String shapeType = commandLineHelper.getStringInput( + String.format("Enter the pattern type (%s) or blank if you have done", shapeTypes), + "Unsupported shape type", + (type) -> type.isEmpty() || shapeFactory.supports(type) + ); + + if (shapeType.isEmpty()) { + break; + } + + int numberOfPoints = shapeFactory.getNumberOfPoints(shapeType); + List<Point> points = createPoints(numberOfPoints); + ShapeInterface shape = shapeFactory.create(shapeType, points); + shapes.add(shape); + } + + return shapes; + } + + private List<Point> createPoints(int numberOfPoints) { + ArrayList<Point> points = new ArrayList<>(); + + for (int i = 0; i < numberOfPoints; i++) { + int x = commandLineHelper.getIntInput("Enter the x-coordinate of the point"); + int y = commandLineHelper.getIntInput("Enter the y-coordinate of the point"); + System.out.println(); + + Point point = new Point(x, y); + points.add(point); + } + + return points; + } +} diff --git a/part_3/exercise_3/src/CommandLineHelper.java b/part_3/exercise_3/src/CommandLineHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7fa4421a78819d1ce203fbe31d4ceecdba3d329f --- /dev/null +++ b/part_3/exercise_3/src/CommandLineHelper.java @@ -0,0 +1,156 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.function.Predicate; + +public class CommandLineHelper { + private final String ANSI_RESET = "\u001B[0m"; + private final String ANSI_GREEN = "\u001B[32m"; + private final String ANSI_RED = "\u001B[31m"; + + private final Scanner reader; + + public CommandLineHelper() { + reader = new Scanner(System.in); + } + + public void printMenu(List<String> strings) { + this.printMenu(strings, '*'); + } + + public void printMenu(List<String> strings, char character) { + int maxLen = strings.stream() + .mapToInt(String::length) + .max() + .orElse(0); + + System.out.println(repeatCharacter(character, maxLen + 4)); + + for (String str: strings) { + System.out.println( + character + " " + str + + repeatCharacter(' ', maxLen - str.length()) + + " " + character + ); + } + + System.out.println(repeatCharacter(character, maxLen + 4)); + } + + public void printMenu(List<String> strings, char character, String color) { + System.out.print(color); + printMenu(strings, character); + System.out.print(ANSI_RESET); + } + + public void printMenu(List<String> strings, String color) { + this.printMenu(strings, '*', color); + } + + public void printMenu(String string, String color) { + List<String> messages = new ArrayList<>(); + messages.add(string); + + printMenu(messages, color); + } + + public void printSuccessMenu(String string) { + printMenu(string, ANSI_GREEN); + } + + public void printSuccessMessage(String string) { + System.out.println(ANSI_GREEN + string + ANSI_RESET); + } + + public void printErrorMessage(String string) { + System.out.println(ANSI_RED + string + ANSI_RESET); + } + + public String repeatCharacter(char character, int number) { + if (number <= 0) { + return ""; + } + + char[] repeat = new char[number]; + Arrays.fill(repeat, character); + + return new String(repeat); + } + + public void clearScreen() { + System.out.print("\033[H\033[2J"); + System.out.flush(); + } + + public String getStringInput(String message) { + return getStringInput(message, "", null); + } + + public String getStringInput(String message, String errorMessage, Predicate<String> tester) { + String input; + + while (true) { + System.out.print(message + ": "); + input = reader.nextLine(); + + if (null == tester || tester.test(input)) { + break; + } + + printErrorMessage(errorMessage); + } + + return input; + } + + public int getIntInput(String message) { + return getIntInput(message, "Error: please enter a number", null); + } + + public int getIntInput(String message, String errorMessage, Predicate<Integer> tester) { + int input; + + while (true) { + try { + System.out.print(message + ": "); + input = Integer.parseInt(reader.nextLine()); + + if (null == tester || tester.test(input)) { + break; + } + + printErrorMessage(errorMessage); + } catch (NumberFormatException e) { + printErrorMessage(errorMessage); + } + } + + return input; + } + + public long getLongInput(String message, Predicate<Long> tester) { + return getLongInput(message, "Error: please enter a number", tester); + } + + public long getLongInput(String message, String errorMessage, Predicate<Long> tester) { + long input; + + while (true) { + try { + System.out.print(message + ": "); + input = Long.parseLong(reader.nextLine()); + + if (null == tester || tester.test(input)) { + break; + } + + printErrorMessage(errorMessage); + } catch (NumberFormatException e) { + printErrorMessage(errorMessage); + } + } + + return input; + } +} \ No newline at end of file diff --git a/part_3/exercise_3/src/CompositeShape.java b/part_3/exercise_3/src/CompositeShape.java new file mode 100644 index 0000000000000000000000000000000000000000..8fd9cf9ceb388096b7db53e25bd2bdbb23a3aa17 --- /dev/null +++ b/part_3/exercise_3/src/CompositeShape.java @@ -0,0 +1,34 @@ +import java.util.ArrayList; +import java.util.List; + +public class CompositeShape implements ShapeInterface { + private final List<ShapeInterface> shapes; + + public CompositeShape(List<ShapeInterface> shapes) { + this.shapes = shapes; + } + + @Override + public PointBoundary boundaries() { + ArrayList<Point> points = new ArrayList<>(); + + for (ShapeInterface shape: shapes) { + PointBoundary boundary = shape.boundaries(); + points.add(boundary.bottomLeft()); + points.add(boundary.topRight()); + } + + return PointHelper.boundaries(points); + } + + @Override + public double area() { + double sum = 0; + + for (ShapeInterface shape: shapes) { + sum += shape.area(); + } + + return sum; + } +} diff --git a/part_3/exercise_3/src/Main.java b/part_3/exercise_3/src/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..df4ad8da61e7487c27c3cf261bf3ffa5e77f929e --- /dev/null +++ b/part_3/exercise_3/src/Main.java @@ -0,0 +1,6 @@ +public class Main { + public static void main(String[] args) { + CommandLineApp app = new CommandLineApp(); + app.run(); + } +} \ No newline at end of file diff --git a/part_3/exercise_3/src/Point.java b/part_3/exercise_3/src/Point.java new file mode 100644 index 0000000000000000000000000000000000000000..ef22d75926d43f32a2dc8877ec62daadaa04339a --- /dev/null +++ b/part_3/exercise_3/src/Point.java @@ -0,0 +1,2 @@ +public record Point(int x, int y) { +} diff --git a/part_3/exercise_3/src/PointBoundary.java b/part_3/exercise_3/src/PointBoundary.java new file mode 100644 index 0000000000000000000000000000000000000000..1bca27a402ca834b678da91aa05575dc460fcb22 --- /dev/null +++ b/part_3/exercise_3/src/PointBoundary.java @@ -0,0 +1,26 @@ +import java.util.Objects; + +public record PointBoundary(Point bottomLeft, Point topRight) { + public PointBoundary { + Objects.requireNonNull(bottomLeft, "Bottom left should not be null"); + Objects.requireNonNull(topRight, "Top right should not be null"); + + if (topRight.x() < bottomLeft.x() || topRight.y() < bottomLeft.y()) { + throw new InvalidBoundaryException("Top right X should be greater than or equal bottom left X, top right Y should be greater than or equal bottom left Y"); + } + } + + public int xLength() { + return topRight.x() - bottomLeft.x(); + } + + public int yLength() { + return topRight().y() - bottomLeft().y(); + } +} + +class InvalidBoundaryException extends RuntimeException { + public InvalidBoundaryException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/part_3/exercise_3/src/PointHelper.java b/part_3/exercise_3/src/PointHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..ed93fe78c148fdb9fc71495eb72a8cf39031a5cf --- /dev/null +++ b/part_3/exercise_3/src/PointHelper.java @@ -0,0 +1,32 @@ +import java.util.List; + +public class PointHelper { + public static PointBoundary boundaries(List<Point> points) { + int maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE, + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE; + + if (points.size() == 0) { + maxX = 0; + maxY = 0; + minX = 0; + minY = 0; + } + + for (Point point: points) { + maxX = Math.max(maxX, point.x()); + maxY = Math.max(maxY, point.y()); + minX = Math.min(minX, point.x()); + minY = Math.min(minY, point.y()); + } + + assert (maxX >= minX); + assert (maxY >= minY); + + Point bottomLeft = new Point(minX, minY); + Point topRight = new Point(maxX, maxY); + + return new PointBoundary(bottomLeft, topRight); + } +} diff --git a/part_3/exercise_3/src/Quadrilateral.java b/part_3/exercise_3/src/Quadrilateral.java new file mode 100644 index 0000000000000000000000000000000000000000..c1330eed1529c9c1665d5158893ae0bed1e45518 --- /dev/null +++ b/part_3/exercise_3/src/Quadrilateral.java @@ -0,0 +1,33 @@ +import java.util.List; + +public class Quadrilateral implements ShapeInterface { + public static final String NAME = "quadrilateral"; + public static final int NUMBER_OF_POINTS = 2; + + private final List<Point> points; + + public Quadrilateral(List<Point> points) { + if (points.size() != NUMBER_OF_POINTS) { + throw new InvalidShapeException("Quadrilateral requires 2 points"); + } + + this.points = points; + } + + @Override + public PointBoundary boundaries() { + return PointHelper.boundaries(points); + } + + /** + * area = side * other side + */ + @Override + public double area() { + PointBoundary boundary = this.boundaries(); + int xLength = boundary.xLength(); + int yLength = boundary.yLength(); + + return xLength * yLength; + } +} diff --git a/part_3/exercise_3/src/ShapeFactory.java b/part_3/exercise_3/src/ShapeFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..342092436b16da5fa03424e0e9cbe2c92bbe0d9a --- /dev/null +++ b/part_3/exercise_3/src/ShapeFactory.java @@ -0,0 +1,35 @@ +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +public class ShapeFactory { + private final HashMap<String, Integer> numberOfPoints; + + public ShapeFactory() { + numberOfPoints = new HashMap<>(); + numberOfPoints.put(Circle.NAME, Circle.NUMBER_OF_POINTS); + numberOfPoints.put(Quadrilateral.NAME, Quadrilateral.NUMBER_OF_POINTS); + numberOfPoints.put(Triangle.NAME, Triangle.NUMBER_OF_POINTS); + } + + public Set<String> getSupportedTypes() { + return numberOfPoints.keySet(); + } + + public boolean supports(String shapeType) { + return numberOfPoints.containsKey(shapeType); + } + + public int getNumberOfPoints(String shapeType) { + return numberOfPoints.get(shapeType); + } + + public ShapeInterface create(String shapeType, List<Point> points) { + return switch (shapeType) { + case Circle.NAME -> new Circle(points); + case Quadrilateral.NAME -> new Quadrilateral(points); + case Triangle.NAME -> new Triangle(points); + default -> throw new RuntimeException("Unsupported shape"); + }; + } +} diff --git a/part_3/exercise_3/src/ShapeInterface.java b/part_3/exercise_3/src/ShapeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..f60af2d7ec5ae8c5c605fa5fa6bbb35ff8d20cfa --- /dev/null +++ b/part_3/exercise_3/src/ShapeInterface.java @@ -0,0 +1,11 @@ +public interface ShapeInterface { + PointBoundary boundaries(); + + double area(); +} + +class InvalidShapeException extends RuntimeException { + public InvalidShapeException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/part_3/exercise_3/src/Triangle.java b/part_3/exercise_3/src/Triangle.java new file mode 100644 index 0000000000000000000000000000000000000000..6518fd7dd497d540db9c2ec6d7790e51b973cabe --- /dev/null +++ b/part_3/exercise_3/src/Triangle.java @@ -0,0 +1,33 @@ +import java.util.List; + +public class Triangle implements ShapeInterface { + public static final String NAME = "triangle"; + public static final int NUMBER_OF_POINTS = 3; + + private final List<Point> points; + + public Triangle(List<Point> points) { + if (points.size() != NUMBER_OF_POINTS) { + throw new InvalidShapeException("Triangle requires 3 points"); + } + + this.points = points; + } + + @Override + public PointBoundary boundaries() { + return PointHelper.boundaries(points); + } + + /** + * Draw a quadrilateral around it and calculate half of the area + */ + @Override + public double area() { + PointBoundary boundary = this.boundaries(); + int xLength = boundary.xLength(); + int yLength = boundary.yLength(); + + return (double) (xLength * yLength) / 2; + } +}