Commit b5c037b5 authored by Georgii Aksenov's avatar Georgii Aksenov
Browse files

Merge branch 'iteration-02'

# Conflicts:
#	README.md
#	src/main/java/cz/muni/fi/pb162/project/Demo.java
#	src/main/java/cz/muni/fi/pb162/project/geometry/Vertex2D.java
#	src/test/java/cz/muni/fi/pb162/project/demo/DemoTest.java
#	src/test/java/cz/muni/fi/pb162/project/geometry/Vertex2DTest.java
parents fa8b20df 2bc653f9
Pipeline #71794 passed with stage
in 10 seconds
## First iteration ## Second iteration
Iteration for getting acquainted with objects and encapsulation. The exercise focused on basic work with attributes, methods, arrays, and on the definition of custom constructors.
1. Create a `Vertex2D` class representing a point in 2D space with *X* and *Y* coordinates. 1. Modify the `Vertex2D` class as follows:
* The class will be in the package `cz.muni.fi.pb162.project.geometry` (you must create the appropriate package). * Enable to create a vertex by directly providing two coordinates, e.g., by calling `new Vertex2D(1.2, 3.8)`.
* The class will have two attributes of type `double`, in which it will store the values of the coordinates *X* and *Y*. * Rename the `getInfo()` method to `toString()`.
Their default value will be 0. > `toString()` is the standard method that exists in every class (we'll learn later how it is arranged).
> Never use `float` type. this type is obsolete. > Therefore, add the annotation `@Override` above the method header.
> Attributes should have accurate descriptive names because they can appear anywhere in the class. Therefore, single-letter names are not recommended. However, the letters *X* and *Y* for coordinates are very common, so we can accept these single-letter attributes in this special case. * Remove the `sumCoordinates` and `move` methods. They will no longer be needed.
* Create the `createMiddle` method, which takes one `Vertex2D` as an input parameter, computes the middle point between this given vertex and "me" (a vertex on which the method has been called), and returns the computed vertex. For example, calling the method `createMiddle` on a vertex with coordinates `[2, 3]` and input parameter `[1, 1]` returns a new vertex with coordinates `[1.5, 2]`.
* **Do not** create constructors yet. We will focus on constructors on the next iteration. > The coordinates of the middle point are computed as _[(x<sub>1</sub>+x<sub>2</sub>)/2, (y<sub>1</sub>+y<sub>2</sub>)/2]_.
* Add the so-called _getters_ and _setters_ to the class.
> Choose appropriate names that reflect the names of the attributes you have selected! > Do not worry to deal with vertices (objects of the `Vertex2D` class) inside the `Vertex2D` class definition. It may look weird, but it's okay.
2. Create a `Triangle` class in the package `cz.muni.fi.pb162.project.geometry`.
* Method `String getInfo()` returns a formatted coordinate description according to the following example: * The triangle consists of three vertices (objects of type `Vertex2D`) stored in a single attribute of type **"array of vertices"**.
For a point at the coordinates x=2.0, y=3.0 returns **10 characters** (space included): * The triangle's constructor takes the three vertices as three input parameters.
`[2.0, 3.0]`. * Method `Vertex2D getVertex(int index)` returns the _index_-th vertex. If the _index_ is less than 0 or greater than 2, the method returns `null`. When the _index_ is 0, it returns the first vertex, if 1 the second, if 2 then the third.
* Method `double sumCoordinates()` returns the sum of the coordinates *X* and *Y*. * The same applies for the method `void setVertex(int index, Vertex2D vertex)`, which stores (replaces) the triangle's vertex. If the _index_ is out of range, the method will do nothing.
* Method `void move(Vertex2D vertex)` takes another 2D point as the input parameter and shifts the vertex by the `vertex` coordinates. For example, calling the `move` mwthod on vertex with coordinates `[2, 3]` and input parameter `[1, 1]` shifts the vertex `[2, 3]` into `[3, 4]`. * Method `String toString()` returns the string:
* Adjust the visibility of attributes and methods to meet encapsulation conditions.
2. Edit the executable class `Demo`.
* Leave the class in the package `cz.muni.fi.pb162.project`.
* Fill in the author's name (`@author`).
* Remove the print of `Hello world!`.
* The class creates 2 vertices with coordinates `[2, 3]` and `[1, 1]`.
> Hint: The `Vertex2D` class must be **imported** into the `Demo` class because it is located in another package.
* Then moves the first vertex by the coordinates of the second vertex.
* Then prints information about both vertices.
* The program prints to standard output:
~~~~ ~~~~
[3.0, 4.0] "Triangle: vertices=[x0, y0] [x1, y1] [x2, y2]"
[1.0, 1.0]
~~~~ ~~~~
Don not duplicate code, use what we already have. In this case, use the `toString()` method from the class `Vertex2D` to implement `toString` of the triangle.
3. In this step, we want to divide the triangle into three smaller sub-triangles. Therefore, implement the following methods.
![divided triangle](images/02a.png)
*Original triangle (left) and divided into sub-triangles (right).*
* Sub-triangles are stored in the triangle in a single *"array of triangles"*.
* The triangle is split by calling the `boolean divide()` method. **Three** smaller triangles (black in the picture) are stored in the corresponding attribute and the method returns `true`. If the triangle has already been split, the method will do nothing and return `false`. The vertices of smaller triangles lie in the middle of the edges of the original triangle.
> Don not duplicate code, use what we already have. In this case, use existing method(s) to compute points in the middle of the triangle edges.
* The `boolean isDivided()` method determines if a triangle has already been split
(smaller triangles were created, i.e., they are not `null`).
* The `Triangle getSubTriangle(int index)` method returns the `index` sub-triangle, where the `index` is the number between 0 and 2. If the `index` is outside this range, or the triangle is not already divided, the method returns `null`.
4. Edit the `Demo` class as follows:
* Move the class to the package `cz.muni.fi.pb162.project.demo`.
* Remove the variable creation and text printing.
* The class newly creates a triangle with coordinates _[-100, 0] [0, 100] [100, -100]_.
* On standard output prints the information about the triangle. Once started, the output should look like this:
~~~~
Triangle: vertices=[-100.0, 0.0] [0.0, 100.0] [100.0, -100.0]
~~~~
5. Verify the correct implementation with unit tests.
Then you run the `Draw` class in the `demo` package, you will see [a triangle with three
sub-triangles](https://gitlab.fi.muni.cz/pb162/pb162-course-info/wikis/draw-images).
3. Document both classes using [_JavaDoc_](https://en.wikipedia.org/wiki/Javadoc). 6. Document the classes using [_JavaDoc_](https://en.wikipedia.org/wiki/Javadoc). The name must be in the format `@author FirstName LastName` including space. You can set up name generation automatically as described [here](https://gitlab.fi.muni.cz/pb162/pb162-course-info/wikis/working-with-ide). Setters, getters, overrides (methods annotated with `@Override`), and private methods don't need to be documented using javadoc. checkstyle starts automatically during translation. If you want to run it separately, you can call the command:
> At least the author, the purpose of public methods, their input parameters, and return value have documented.
> The presence of mandatory javadoc items is checked by the *checkstyle* plugin. However, the content is up to you. Be consise but thorough.
> For getters and setters, javadoc does not have to be written, because their purpose and use are obvious.
4. Test your code using unit tests in the package **src/test/java**. After successful testing, submit to a homework vault or git and have it checked by the tutor. Submitted iteration must pass *tests* and *checkstyle*! mvn clean install -Dcheckstyle.fail=true
> Unit tests and style checking are always evaluated whenever you rebuild the project in IDE.
> If unsure, run `mvn clean install` on the command line.
> Tests are also invoked when you submit (push) your code into git. If they fail, then you get an email. Failures during the development and continuous submission are OK. Failures after the final submission are not OK ;-)
> Code of unit tests is weird and messy. To implement test cases, it is oftten necessary to violate encapsulation and other principles of production code. Therefore, don't take the inspiration from this code (it is better to not look inside at all ;-)
package cz.muni.fi.pb162.project; package cz.muni.fi.pb162.project.demo;
import cz.muni.fi.pb162.project.geometry.Triangle;
import cz.muni.fi.pb162.project.geometry.Vertex2D; import cz.muni.fi.pb162.project.geometry.Vertex2D;
/** /**
...@@ -16,26 +17,11 @@ public class Demo { ...@@ -16,26 +17,11 @@ public class Demo {
* @param args command line arguments, will be ignored * @param args command line arguments, will be ignored
*/ */
public static void main(String[] args) { public static void main(String[] args) {
Vertex2D v1 = newVertex(2, 3); Triangle triangle = new Triangle(
Vertex2D v2 = newVertex(1, 1); new Vertex2D(-100.0, 0),
new Vertex2D(0, 100),
new Vertex2D(100, -100));
v1.move(v2); System.out.println(triangle.toString());
System.out.println(v1.getInfo());
System.out.println(v2.getInfo());
}
/**
* Creates new instance of Vertex2D.
*
* @param x coordinate for new vector
* @param y coordinate for new vector
* @return new Vertex2D.
*/
private static Vertex2D newVertex(double x, double y) {
Vertex2D vertex = new Vertex2D();
vertex.setX(x);
vertex.setY(y);
return vertex;
} }
} }
package cz.muni.fi.pb162.project.demo;
import cz.muni.fi.pb162.project.geometry.Triangle;
import cz.muni.fi.pb162.project.geometry.Vertex2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.util.AbstractMap;
/**
* Class drawing 2D objects.
*
* @author Radek Oslejsek, Marek Sabo
*/
public final class Draw extends JFrame {
private static final int PANEL_WIDTH = 800;
private static final int PANEL_HEIGHT = 600;
private static final int HALF_WIDTH = PANEL_WIDTH / 2;
private static final int HALF_HEIGHT = PANEL_HEIGHT / 2;
private static final Color TRIANGLE_COLOR = Color.BLUE;
private static final Triangle DIVIDED_TRIANGLE = createDividedTriangle();
private static Triangle createDividedTriangle() {
Vertex2D v1 = new Vertex2D(-100, 0);
Vertex2D v2 = new Vertex2D(0, 100);
Vertex2D v3 = new Vertex2D(100, -100);
Triangle triangle = new Triangle(v1, v2, v3);
triangle.divide();
return triangle;
}
private Graphics graphics;
/**
* Draws 2D objects.
*
* @param args command line arguments, will be ignored
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(Draw::new);
}
private Draw() {
setBounds(350, 250, PANEL_WIDTH, PANEL_HEIGHT);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("Draw");
JPanel panel = new JPanel() {
@Override
public void paint(Graphics g) {
super.paint(g);
paintScene(g);
}
};
panel.setBackground(Color.WHITE);
add(panel);
setVisible(true);
}
private void paintScene(Graphics g) {
graphics = g;
paintCross();
paintTriangle(DIVIDED_TRIANGLE);
paintSubTriangles();
}
private void paintCross() {
graphics.setColor(Color.LIGHT_GRAY);
graphics.drawLine(0, HALF_HEIGHT, PANEL_WIDTH, HALF_HEIGHT);
graphics.drawLine(HALF_WIDTH, 0, HALF_WIDTH, PANEL_HEIGHT);
}
private void paintSubTriangles() {
for (int i = 0; i < 3; i++) {
paintTriangle(DIVIDED_TRIANGLE.getSubTriangle(i));
}
}
private void paintTriangle(Triangle triangle) {
if (triangle == null) return;
graphics.setColor(TRIANGLE_COLOR);
Polygon polygon = new Polygon();
for (int i = 0; i <= 2; i++) {
AbstractMap.SimpleEntry<Integer, Integer> pair = createTriangleLinePoints(triangle, i);
polygon.addPoint(pair.getKey(), pair.getValue());
}
graphics.drawPolygon(polygon);
}
private AbstractMap.SimpleEntry<Integer, Integer> createTriangleLinePoints(Triangle triangle, int index) {
int a1 = PANEL_WIDTH - ((int) Math.rint(HALF_WIDTH - triangle.getVertex(index).getX()));
int a2 = (int) Math.rint(HALF_HEIGHT - triangle.getVertex(index).getY());
return new AbstractMap.SimpleEntry<>(a1, a2);
}
}
package cz.muni.fi.pb162.project.geometry;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
/**
* This class represents a triangle in 2D space.
*
* @author Georgii Aksenov <xaksenov@fi.muni.cz>
*/
public class Triangle {
private final Vertex2D[] vertices;
private final Triangle[] subTriangles = new Triangle[3];
/**
* Checkstyle asks for the Javadoc here.
* This is a constructor.
* It sets corners ¯\_(ツ)_/¯
*
* @param v1 is the first corner
* @param v2 is the second corner
* @param v3 is the third corner
*/
public Triangle(Vertex2D v1, Vertex2D v2, Vertex2D v3) {
this.vertices = new Vertex2D[]{v1, v2, v3};
}
/**
* Returns the index-th vertex if the index is in range 0..2, otherwise null
*
* @param index is the index of the vector
* @return the index-th vertex
*/
public Vertex2D getVertex(int index) {
if (index < 0 || index > 2) {
return null;
}
return vertices[index];
}
/**
* Sets the index-th vertex (if the index is in range 0..2)
*
* @param index is the index of the vector to be set
* @param vertex to be set
*/
public void setVertex(int index, Vertex2D vertex) {
if (!isValid(index)) {
return;
}
vertices[index] = vertex;
}
@Override
public String toString() {
return "Triangle: vertices=" + stream(vertices)
.map(Vertex2D::toString)
.collect(joining(" "));
}
/**
* Sets inner triangles for this triangle,
* each of them is built by one of the corners of the triangle
* and the middle between this corner and the other.
*
* @return false if triangle was already divided, otherwise true
*/
public boolean divide() {
if (isDivided()) {
return false;
}
subTriangles[0] = createSubTriangle(0, 1, 2);
subTriangles[1] = createSubTriangle(1, 0, 2);
subTriangles[2] = createSubTriangle(2, 0, 1);
return true;
}
/**
* @return true if the triangle was already divided.
*/
public boolean isDivided() {
return subTriangles[0] != null;
}
/**
* Returns the index-th subtriangle if the index is in range 0..2, otherwise null
*
* @param index is the index of the vector
* @return the index-th subtriangle
*/
public Triangle getSubTriangle(int index) {
if (!isValid(index) || !isDivided()) {
return null;
}
return subTriangles[index];
}
private Triangle createSubTriangle(int cornerIndex, int oppositeIndex1, int oppositeIndex2) {
Vertex2D corner = vertices[cornerIndex];
Vertex2D opposite1 = vertices[oppositeIndex1];
Vertex2D opposite2 = vertices[oppositeIndex2];
return new Triangle(corner, corner.createMiddle(opposite1), corner.createMiddle(opposite2));
}
private boolean isValid(int index) {
return index >= 0 && index <= 2;
}
}
package cz.muni.fi.pb162.project.geometry; package cz.muni.fi.pb162.project.geometry;
import static java.lang.String.format;
/** /**
* This class represents a point in 2D space.
*
* @author Georgii Aksenov <xaksenov@fi.muni.cz> * @author Georgii Aksenov <xaksenov@fi.muni.cz>
*/ */
public class Vertex2D { public class Vertex2D {
private double x = 0; private final double x;
private double y = 0; private final double y;
public double getX() { /**
return x; * Checkstyle asks for the Javadoc here.
* This is a constructor.
* It sets coordinates ¯\_(ツ)_/¯
*
* @param x is the x coordinate
* @param y is the y coordinate
*/
public Vertex2D(double x, double y) {
this.x = x;
this.y = y;
} }
public void setX(double x) { public double getX() {
this.x = x; return x;
} }
public double getY() { public double getY() {
return y; return y;
} }
public void setY(double y) {
this.y = y;
}
/** /**
* To get string representation. * To get string representation.
* *
* @return string representation. * @return string representation.
*/ */
public String getInfo() { @Override
return String.format("[%.1f, %.1f]", x, y); public String toString() {
} return format("[%s, %s]", Double.valueOf(x).toString(), Double.valueOf(y).toString());
/**
* To get sum of coordinates.
*
* @return sum of coordinates.
*/
public double sumCoordinates() {
return x + y;
} }
/** /**
* Moves the vertex. * To get string representation.
* *
* @param vertex is added to this vertex. * @param opposite the opposite point to create middle against.
* @return string representation.
*/ */
public void move(Vertex2D vertex) { public Vertex2D createMiddle(Vertex2D opposite) {
x += vertex.x; return new Vertex2D((x + opposite.x) / 2, (y + opposite.y) / 2);
y += vertex.y;
} }
} }
package cz.muni.fi.pb162.project.demo; package cz.muni.fi.pb162.project.demo;
import cz.muni.fi.pb162.project.Demo;
import cz.muni.fi.pb162.project.helper.OutputTester; import cz.muni.fi.pb162.project.helper.OutputTester;
import org.junit.Test; import org.junit.Test;
...@@ -13,8 +12,8 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -13,8 +12,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class DemoTest { public class DemoTest {
private static final String EXPECTED_OUTPUT = "[3.0, 4.0]" + System.lineSeparator() private static final String EXPECTED_OUTPUT =
+ "[1.0, 1.0]" + System.lineSeparator(); "Triangle: vertices=[-100.0, 0.0] [0.0, 100.0] [100.0, -100.0]" + System.lineSeparator();
@Test @Test
public void testMainOutput() { public void testMainOutput() {
......
package cz.muni.fi.pb162.project.geometry;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Class testing Triangle implementation.
*
* @author Marek Sabo
*/
public class TriangleTest {
private Triangle triangle;
private final Vertex2D vertex1 = new Vertex2D(-100, -100);
private final Vertex2D vertex2 = new Vertex2D(0, 100);
private final Vertex2D vertex3 = new Vertex2D(100, -100);
@Before
public void setUp() {
triangle = new Triangle(vertex1, vertex2, vertex3);
}
@Test
public void gettersInRange() {
assertThat(triangle.getVertex(0)).isEqualToComparingFieldByField(vertex1);
assertThat(triangle.getVertex(1)).isEqualToComparingFieldByField(vertex2);
assertThat(triangle.getVertex(2)).isEqualToComparingFieldByField(vertex3);
assertThat(triangle).isEqualToComparingFieldByField(new Triangle(vertex1, vertex2, vertex3));
}
@Test
public void gettersOutOfRange() {
assertThat(triangle.getVertex(3)).isNull();
assertThat(triangle.getVertex(4)).isNull();
assertThat(triangle.getVertex(-1)).isNull();
}
@Test
public void settersOutOfRange() {
triangle.setVertex(3, null);
triangle.setVertex(-1, null);
assertThat(triangle.getVertex(0)).isNotNull();
assertThat(triangle.getVertex(1)).isNotNull();
assertThat(triangle.getVertex(2)).isNotNull();
}
@Test
public void toStringMessage() {
assertThat(triangle.toString()).isEqualTo("Triangle: vertices=[-100.0, -100.0] [0.0, 100.0] [100.0, -100.0]");
Triangle t = new Triangle(
new Vertex2D(-1.2, 0.0),
new Vertex2D(1.2, 0.0),
new Vertex2D(0.0, 2.07846097)
);
assertThat(t.toString()).isEqualTo("Triangle: vertices=[-1.2, 0.0] [1.2, 0.0] [0.0, 2.07846097]");
}
@Test
public void checkIfDivided() {
assertThat(triangle.isDivided()).isFalse();
assertThat(triangle.divide()).isTrue();
assertThat(triangle.isDivided()).isTrue();
assertThat(triangle.divide()).isFalse();
}
@Test
public void subTriangleGettersNull() {
assertThat(triangle.getSubTriangle(-1)).isNull();
assertThat(triangle.getSubTriangle(0)).isNull();
assertThat(triangle.getSubTriangle(1)).isNull();
assertThat(triangle.getSubTriangle(2)).isNull();
assertThat(triangle.getSubTriangle(3)).isNull();
}
@Test
public void subTriangleGettersInRangeNotNull() {
triangle.divide();
assertThat(triangle.getSubTriangle(0)).isNotNull();
assertThat(triangle.getSubTriangle(1)).isNotNull();
assertThat(triangle.getSubTriangle(2)).isNotNull();
}
@Test
public void subTriangleGettersOutOfRangeNull() {
triangle.divide();
assertThat(triangle.getSubTriangle(-1)).isNull();
assertThat(triangle.getSubTriangle(3)).isNull();