From 87ca37c9caa6bce025818431850985dbbc4d53d5 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 12 Dec 2020 14:34:15 +0100 Subject: [PATCH] created physics system and new Drawable interface --- src/main/java/core/Drawable.java | 18 +++ src/main/java/core/Master.java | 60 +++++-- src/main/java/core/math/Vector2D.java | 6 + src/main/java/core/physics/Collidable.java | 7 + src/main/java/core/physics/Hitbox.java | 5 +- src/main/java/core/physics/RectHitBox.java | 152 +++++++++++++++++- src/main/java/objects/GameObject.java | 62 +++---- src/main/java/objects/ships/Submarine.java | 30 +++- src/main/java/objects/world/Wall.java | 29 +++- .../java/core/{ => math}/Vector2DTest.java | 2 +- .../java/core/physics/RectHitBoxTest.java | 62 +++++++ target/classes/core/Master.class | Bin 4410 -> 5515 bytes target/classes/core/Vector2D.class | Bin 3228 -> 0 bytes target/classes/objects/DebugPos.class | Bin 1055 -> 1080 bytes target/classes/objects/GameObject.class | Bin 2835 -> 3173 bytes target/classes/objects/ships/BattleShip.class | Bin 2526 -> 2526 bytes target/classes/objects/ships/Shell.class | Bin 1092 -> 1137 bytes target/classes/objects/ships/Turret.class | Bin 3567 -> 3653 bytes target/test-classes/core/Vector2DTest.class | Bin 2672 -> 0 bytes 19 files changed, 390 insertions(+), 43 deletions(-) create mode 100644 src/main/java/core/Drawable.java rename src/test/java/core/{ => math}/Vector2DTest.java (99%) create mode 100644 src/test/java/core/physics/RectHitBoxTest.java delete mode 100644 target/classes/core/Vector2D.class delete mode 100644 target/test-classes/core/Vector2DTest.class diff --git a/src/main/java/core/Drawable.java b/src/main/java/core/Drawable.java new file mode 100644 index 0000000..97e36e3 --- /dev/null +++ b/src/main/java/core/Drawable.java @@ -0,0 +1,18 @@ +package core; + +import java.awt.*; + +public interface Drawable { + /** + *

The draw method is called every frame after the update by the master object on each object. Everything + * about drawing should be handled here in this method.

+ *

No general calculations should be made in this method. The game should be able to work just + * fine even without this method being called.

+ *

This function is NOT intended to be called manually.

+ * + * @param g2d The {@code Graphics2D} object given by the master + * @param w The width of the screen + * @param master The master object itself + */ + void draw(Graphics2D g2d, int w, Master master); +} diff --git a/src/main/java/core/Master.java b/src/main/java/core/Master.java index 1e23001..de6056d 100644 --- a/src/main/java/core/Master.java +++ b/src/main/java/core/Master.java @@ -1,6 +1,7 @@ package core; import core.math.Vector2D; +import core.physics.Collidable; import objects.DebugPos; import objects.ships.BattleShip; import objects.GameObject; @@ -11,6 +12,9 @@ import objects.world.Wall; import javax.swing.*; import java.awt.*; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +//TODO master better accessible or something /** * The main object that controls everything @@ -35,6 +39,16 @@ public class Master extends JPanel { */ private final ArrayList objects; + /** + * All GameObjects that can be drawn + */ + private final ArrayList drawables; + + /** + * All physics objects that exist + */ + private final ArrayList collidables; + /** * Stores all GameObjects that were created during a frame */ @@ -48,8 +62,10 @@ public class Master extends JPanel { public Master() { objects = new ArrayList<>(); objectBuffer = new ArrayList<>(); + collidables = new ArrayList<>(); + drawables = new ArrayList<>(); - objects.add(new Grid()); + create(new Grid()); BattleShip battleShip = new BattleShip(Color.DARK_GRAY); @@ -57,15 +73,16 @@ public class Master extends JPanel { /*for (int i = 0; i < 10; i++) { bs.addTurret(new Turret(bs, 25, 10 * i + 1, 50, i % 5)); }*/ - objects.add(bs); - objects.add(battleShip); + create(bs); + create(battleShip); - objects.add(new Submarine(new Vector2D(), new Vector2D(20, 20))); - objects.add(new Wall(20, 80, 50, 2)); + create(new Submarine(new Vector2D(), new Vector2D(20, 20))); + create(new Wall(20, 80, 50, 2)); } /** * The mein drawing method, handles everything about drawing + * * @param g */ private void doDrawing(Graphics g) { @@ -81,7 +98,7 @@ public class Master extends JPanel { Graphics2D g2d = (Graphics2D) g.create(); - objects.forEach(o -> o.draw(g2d, w, this)); + drawables.forEach(o -> o.draw(g2d, w, this)); } @@ -93,11 +110,11 @@ public class Master extends JPanel { /** * Debug a position, creates a green dot at the position + * * @param pos */ - public void debugPos(Vector2D pos){ + public void debugPos(Vector2D pos) { create(new DebugPos(pos, new Vector2D(10, 10))); - System.out.println(pos); } /** @@ -120,6 +137,7 @@ public class Master extends JPanel { /** * Get the current location of the mouse relative to the frame + * * @return The location of the mouse, already normalized */ public Point getMouseLocation() { @@ -134,9 +152,33 @@ public class Master extends JPanel { /** * This method has to be called for every newly created GameObject - * @param obj + * + * @param obj The new object */ public void create(GameObject obj) { objectBuffer.add(obj); + if (obj instanceof Collidable) { + collidables.add((Collidable) obj); + } + drawables.add(obj); + + } + + public void addDrawable(Drawable d){ + drawables.add(d); + } + + public boolean doesCollide(Collidable col) { + boolean collides = false; + + for (Collidable c : collidables) { + double distance = Vector2D.distance(c.getCenterPos(), col.getCenterPos()); + if (!(distance > c.getSize().magnitude() && distance > col.getSize().magnitude())) { + if (c.collidesWith(col)) { + collides = true; + } + } + } + return collides; } } diff --git a/src/main/java/core/math/Vector2D.java b/src/main/java/core/math/Vector2D.java index a6ea0ef..c5bfe7a 100644 --- a/src/main/java/core/math/Vector2D.java +++ b/src/main/java/core/math/Vector2D.java @@ -157,6 +157,12 @@ public class Vector2D { return rotateAround(new Vector2D(), new Vector2D(0, 1), direction); } + public static double distance(Vector2D a, Vector2D b){ + Vector2D dif = subtract(a, b); + return Math.sqrt(dif.x * dif.x + dif.y + dif.y); + } + + /** * Copy this object * @return A copy of this object diff --git a/src/main/java/core/physics/Collidable.java b/src/main/java/core/physics/Collidable.java index 3336ffb..8d1c4cd 100644 --- a/src/main/java/core/physics/Collidable.java +++ b/src/main/java/core/physics/Collidable.java @@ -1,5 +1,12 @@ package core.physics; +import core.math.Vector2D; + public interface Collidable { + boolean collidesWith(Collidable o); + Hitbox getHitbox(); + Vector2D getCenterPos(); + Vector2D getSize(); + } diff --git a/src/main/java/core/physics/Hitbox.java b/src/main/java/core/physics/Hitbox.java index 3ea04e1..5d0717d 100644 --- a/src/main/java/core/physics/Hitbox.java +++ b/src/main/java/core/physics/Hitbox.java @@ -1,5 +1,8 @@ package core.physics; -public abstract class Hitbox { +import core.Drawable; +import objects.GameObject; + +public abstract class Hitbox implements Drawable { } diff --git a/src/main/java/core/physics/RectHitBox.java b/src/main/java/core/physics/RectHitBox.java index a5afae6..b295090 100644 --- a/src/main/java/core/physics/RectHitBox.java +++ b/src/main/java/core/physics/RectHitBox.java @@ -1,4 +1,154 @@ package core.physics; -public class RectHitBox extends Hitbox{ +import core.Master; +import core.math.Vector2D; + +import java.awt.*; + +/** + * A rectangular hitbox + */ +public class RectHitBox extends Hitbox { + + /** + * The corners of the rectangle like this: + *

x1 x2

+ *

y1 y2

+ */ + private Vector2D x1, y1, x2, y2; + + /** + * Create a new RectHitbox with the position of the top left point {@code x1} and the size + * + * @param x1 The top left point + * @param size the size + */ + public RectHitBox(Vector2D x1, Vector2D size) { + this.x1 = x1; + this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0)); + this.y1 = Vector2D.add(x1, new Vector2D(0, size.y)); + this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y)); + } + + /** + * Move the hitbox to a new position + * + * @param x1 + * @param size + */ + public void moveTo(Vector2D x1, Vector2D size) { + this.x1 = x1.copy(); + this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0)); + this.y1 = Vector2D.add(x1, new Vector2D(0, size.y)); + this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y)); + } + + /** + * Check whether two hitboxes collide with each other + * + * @param o The other rectangular hitbox, should currently only be a {@link RectHitBox} + * @return {@code true} when they collide {@code false} when they don't + */ + public boolean collidesWith(Hitbox o) { + if (o instanceof RectHitBox) { + return collidesWith((RectHitBox) o); + } else { + return false; + } + } + + /** + * Check whether two hitboxes collide with each other + * + * @param o The other rectangular hitbox + * @return {@code true} when they collide {@code false} when they don't + */ + public boolean collidesWith(RectHitBox o) { + + //all points right of x2, left of x1, above x1, below y1 -> no collision + + //if all points are right of x2 + boolean allRight = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).x < x2.x) { + allRight = false; + break; + } + } + + //if all points are left of x1 + boolean allLeft = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).x > x1.x) { + allLeft = false; + break; + } + } + + //if all points are above x1 + boolean allAbove = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).y > x1.y) { + allAbove = false; + break; + } + } + + //if all points are below y1 + boolean allBelow = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).y < y1.y) { + allBelow = false; + break; + } + } + + boolean coll = !(allAbove || allBelow || allLeft || allRight); + return coll; + } + + /** + * Get a point like this + *

0 1

+ *

2 3

+ * + * @param i the point number + * @throws IllegalArgumentException when i is not between 0 and 3 + */ + public Vector2D getPoint(int i) { + return switch (i) { + case 0 -> x1; + case 1 -> x2; + case 2 -> y1; + case 3 -> y2; + default -> throw new IllegalArgumentException("i has to be between 0 and 3"); + }; + } + + public Vector2D getX1() { + return x1; + } + + public Vector2D getY1() { + return y1; + } + + public Vector2D getX2() { + return x2; + } + + public Vector2D getY2() { + return y2; + } + + @Override + public String toString() { + return "RectHitBox{" + x1 + " " + x2 + "\n" + y1 + " " + y2 + "}"; + } + + @Override + public void draw(Graphics2D g2d, int w, Master master) { + g2d.setPaint(Color.BLUE); + g2d.fillRect((int) x1.y, (int) x1.y, (int) (x2.x - x1.x), (int) (y1.y - x1.y)); + } } diff --git a/src/main/java/objects/GameObject.java b/src/main/java/objects/GameObject.java index 6352eb9..218b1cb 100644 --- a/src/main/java/objects/GameObject.java +++ b/src/main/java/objects/GameObject.java @@ -1,7 +1,9 @@ package objects; +import core.Drawable; import core.Master; import core.math.Vector2D; +import core.physics.Collidable; import java.awt.*; @@ -9,7 +11,7 @@ import java.awt.*; * The GameObject class is the superclass of every GameObject that can be displayed on screen. It has the 2 * {@link #update(Master)} and {@link #draw(Graphics2D, int, Master)} methods that have to be */ -public abstract class GameObject { +public abstract class GameObject implements Drawable { protected int w; protected int h; @@ -32,41 +34,40 @@ public abstract class GameObject { mainColor = Color.BLACK; } - /** - *

The draw method is called every frame after the {@link #update(Master)} by the master object on each object. Everything - * about drawing should be handled here in this method.

- *

No general calculations should be made in this method. The game should be able to work just - * fine even without this method being called.

- *

This function is NOT intended to be called manually.

- * @param g2d The {@code Graphics2D} object given by the master - * @param w The width of the screen - * @param master The master object itself - */ - public abstract void draw(Graphics2D g2d, int w, Master master); /** *

The update method is called every frame before the {@link #draw(Graphics2D, int, Master)} method by the master object on each object. Everything * that is needed for the game to work should be here in this method.

*

No drawing should be made in this method. The {@code debug} method can be called on the master.

*

This function is NOT intended to be called manually.

+ * * @param master The master object itself */ public abstract void update(Master master); /** * A simple method to move the object to a Vector2D. This method should be called instead of doing it manually. + * * @param target The target position */ - public void moveTo(Vector2D target){ + public void moveTo(Vector2D target, Master master) { + Vector2D oldPos = position; this.position = target; + + if (this instanceof Collidable) { + if (master.doesCollide((Collidable) this)) { + this.position = oldPos; + } + } } /** * This method draws a rectangle at the current position and size + * * @param g2d The Graphics2D object provided by the master - * @param w The width of the screen + * @param w The width of the screen */ - public void drawRect(Graphics2D g2d, int w){ + public void drawRect(Graphics2D g2d, int w) { this.w = w; h = (int) (this.w / Master.SCREEN_RATIO); int xAbs = (int) getWorldCoords(position.x, true); @@ -80,12 +81,13 @@ public abstract class GameObject { /** * This method draws a rounded rectangle at the current position and size - * @param g2d The Graphics2D object provided by the master - * @param w The width of the screen + * + * @param g2d The Graphics2D object provided by the master + * @param w The width of the screen * @param arcW The arc width of the rectangle * @param arcH The arc height of the rectangle */ - public void drawRoundRect(Graphics2D g2d, int w, int arcW, int arcH){ + public void drawRoundRect(Graphics2D g2d, int w, int arcW, int arcH) { this.w = w; h = (int) (w / Master.SCREEN_RATIO); int xAbs = (int) getWorldCoords(position.x, true); @@ -97,16 +99,16 @@ public abstract class GameObject { g2d.fillRoundRect(xAbs, yAbs, sizeXAbs, sizeYAbs, arcW, arcH); } - public double getWorldCoords(double value, boolean isX){ - if (isX){ + public double getWorldCoords(double value, boolean isX) { + if (isX) { return (value / (Master.SCREEN_Y_COORDINATES * Master.SCREEN_RATIO) * w); } else { return (value / Master.SCREEN_Y_COORDINATES * h); } } - public double getWorldCoordsSize(double value, boolean isX){ - if (isX){ + public double getWorldCoordsSize(double value, boolean isX) { + if (isX) { return (value / Master.SCREEN_Y_COORDINATES * w); } else { return (value / Master.SCREEN_Y_COORDINATES * h); @@ -114,27 +116,31 @@ public abstract class GameObject { } - public double getMapCoords(double value, boolean isX){ - if (isX){ + public double getMapCoords(double value, boolean isX) { + if (isX) { return (position.x + value / 100d * size.x); } else { return (position.y + value / 100d * size.y); } } - public double getMapCoordsSize(double value, boolean useX){ - if (useX){ + public double getMapCoordsSize(double value, boolean useX) { + if (useX) { return (value / 100d * size.x); } else { return (value / 100d * size.y); } } - public int getWorldCoordsFromLocal(double value, boolean isX){ + public int getWorldCoordsFromLocal(double value, boolean isX) { return (int) getWorldCoords(getMapCoords(value, isX), isX); } - public int getWorldCoordsFromLocalSize(double value, boolean useX){ + public int getWorldCoordsFromLocalSize(double value, boolean useX) { return (int) getWorldCoordsSize(getMapCoordsSize(value, useX), useX); } + + public Vector2D getCenterPosition(){ + return new Vector2D(position.x + size.x, position.y + size.y); + } } diff --git a/src/main/java/objects/ships/Submarine.java b/src/main/java/objects/ships/Submarine.java index 01affe5..819ef3a 100644 --- a/src/main/java/objects/ships/Submarine.java +++ b/src/main/java/objects/ships/Submarine.java @@ -2,14 +2,20 @@ package objects.ships; import core.Master; import core.math.Vector2D; +import core.physics.Collidable; +import core.physics.Hitbox; +import core.physics.RectHitBox; import objects.GameObject; import java.awt.*; -public class Submarine extends GameObject { +public class Submarine extends GameObject implements Collidable { + + private RectHitBox hitbox; public Submarine(Vector2D position, Vector2D size) { super(position, size); + this.hitbox = new RectHitBox(position, size); } @Override @@ -21,6 +27,26 @@ public class Submarine extends GameObject { @Override public void update(Master master) { Point mouse = master.getMouseLocation(); - moveTo(new Vector2D(mouse.x, mouse.y)); + moveTo(new Vector2D(mouse.x, mouse.y), master); + } + + @Override + public boolean collidesWith(Collidable o) { + return hitbox.collidesWith(o.getHitbox()); + } + + @Override + public Hitbox getHitbox() { + return hitbox; + } + + @Override + public Vector2D getCenterPos() { + return getCenterPosition(); + } + + @Override + public Vector2D getSize() { + return size; } } diff --git a/src/main/java/objects/world/Wall.java b/src/main/java/objects/world/Wall.java index 4ea16d0..58c5ca5 100644 --- a/src/main/java/objects/world/Wall.java +++ b/src/main/java/objects/world/Wall.java @@ -1,14 +1,21 @@ package objects.world; import core.Master; +import core.math.Vector2D; +import core.physics.Collidable; +import core.physics.Hitbox; +import core.physics.RectHitBox; import objects.GameObject; import java.awt.*; -public class Wall extends GameObject { +public class Wall extends GameObject implements Collidable { + + private RectHitBox hitbox; public Wall(double x, double y, double xSize, double ySize) { super(x, y, xSize, ySize); + this.hitbox = new RectHitBox(new Vector2D(x, y), new Vector2D(xSize, ySize)); } @Override @@ -19,4 +26,24 @@ public class Wall extends GameObject { @Override public void update(Master master) { } + + @Override + public boolean collidesWith(Collidable o) { + return this.hitbox.collidesWith(o.getHitbox()); + } + + @Override + public Hitbox getHitbox() { + return hitbox; + } + + @Override + public Vector2D getCenterPos() { + return position; + } + + @Override + public Vector2D getSize() { + return size; + } } diff --git a/src/test/java/core/Vector2DTest.java b/src/test/java/core/math/Vector2DTest.java similarity index 99% rename from src/test/java/core/Vector2DTest.java rename to src/test/java/core/math/Vector2DTest.java index 1bd52a0..2a71a71 100644 --- a/src/test/java/core/Vector2DTest.java +++ b/src/test/java/core/math/Vector2DTest.java @@ -1,4 +1,4 @@ -package core; +package core.math; import core.math.Vector2D; import org.junit.jupiter.api.Test; diff --git a/src/test/java/core/physics/RectHitBoxTest.java b/src/test/java/core/physics/RectHitBoxTest.java new file mode 100644 index 0000000..b31a5c5 --- /dev/null +++ b/src/test/java/core/physics/RectHitBoxTest.java @@ -0,0 +1,62 @@ +package core.physics; + +import core.math.Vector2D; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RectHitBoxTest { + + @Test + void constructorSimple(){ + RectHitBox a = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + + assertEquals(a.getX1(), new Vector2D(1, 1)); + assertEquals(a.getX2(), new Vector2D(2, 1)); + assertEquals(a.getY1(), new Vector2D(1, 2)); + assertEquals(a.getY2(), new Vector2D(2, 2)); + } + + @Test + void doCollideNoCross(){ + RectHitBox a = new RectHitBox(new Vector2D(-1, 1), new Vector2D(1, 1)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertFalse(a.collidesWith(b)); + } + + @Test + void doCollideCornerTouch(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(1, 1)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertFalse(a.collidesWith(b)); + } + + @Test + void doCollideCornerInside(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(2, 2)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(2, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideEdgeInside(){ + RectHitBox a = new RectHitBox(new Vector2D(-2, 0), new Vector2D(4, 2)); + RectHitBox b = new RectHitBox(new Vector2D(-1, 1), new Vector2D(2, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideCrossCrossing(){ + RectHitBox a = new RectHitBox(new Vector2D(-1, 2), new Vector2D(2, 4)); + RectHitBox b = new RectHitBox(new Vector2D(-2, 1), new Vector2D(4, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideFullyInside(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(3, 3)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertTrue(a.collidesWith(b)); + } + +} \ No newline at end of file diff --git a/target/classes/core/Master.class b/target/classes/core/Master.class index 933f4377d861b9745d131ef818883bc901c3c6d6..4b736b9f72420bb83ce44d0623ab52950595ce0d 100644 GIT binary patch delta 3431 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 B>Y@Mu delta 2327 zcmeCy-lfEK>ff$?3=9lb44)=)9b#r@keK*aoL8KInTvs!L7Im_hCz061)Bn+{A71V zS#CuJC3Xg79tIT#)yescMjUbs>O2gr3>uSHGTKNfG8pkN7&E9cGO)S_xw`ssF~~8P zf~3tR|7Vn8w3sZzB$H&t$iSPNUzDmJmYQ6WUu5LM#bC+6%;sRi00tnQJxGrOBLl03 zi;HF$7lS#26G+6FkwH*wWJPg4gDbDoN5|7l(^o$ZNhERquc7|{sh6sj8 zMh3yG#Ii*F#PSk-_oBptjLhU>Mh3RzqSVBaR7M6-4NV_pc}}nbYj%d{$$`vbTtW;C z46!^6aSZX3zc2@}CGs#NF(gm+VKHP&x(a&kV%Wd)23;u@11I7Kux!#Ej=7>d~$ zN_ZGb8Oj(L1i&uJNz6;v53VdONzG+sV9qZsncT}P#aF>l$<9#4!%)po!^j{!c_Op$ z=K{>hcB&fF6jCb2V2=3$t^Fm>{ARvYf=3^UjnX7Vu1 zVwgRdk1dINF2g)_hWR`U3m6toR^>3{;$oP~u$YHo3B%IK#T>S5D|r}JF|3}vjYENL zJrBbMhSihraO|1awDhkWNjWteujHo4EGrxuroa5VR*#wcyl!m3!_mDKRd%SP`EzlVYtR{U4Ws0 zpPk_qNZ>UO!xe@CMg|_wyu8#R=bXgi;?!bxhIf;<@MuOy0;RQon^k zU^jz!pR?8B7@T7|aC5lv6B#2C044lx`A`@#e23kC)chQka;7#JDU z7?eN?8JHOK7^I-;%ozk3jxro$U|~4Uz`(%4z@+$>frpWife}g_s_BR6qx0d#H zh7>LBZ44P8mokA}$_KVAih;GBA)0}UAqMO$F$N|E1_o}p3%H>!U}QMKa1v^2ECUx< zm7>;ehU`dfNMvh^Fld7#J9isH-X?}Z?QIO@=yoMDNHU}_C@`cVS*6IZyB-{83=E14 zrx;E%fPBCWOSB9o3``7+pro(GvXy}yVIaf>*$k`KR(VkqF5X+Zfsr)<`k1F)%O`G4L=HGYB)3FeosTGN>|? zF=#TBBU!5j4K7B8vkX(Af#d}ZdcGYDUE3HY_-JeGV3?-80iMtyp^z{Ai0 zHVzW{d{E=q8Q2-lF`Ng7$OVRrP=ocsdDE)zwE+kBQ7&sYv!Nx#b!v!^lfq{$R62oN%Mgb;JsRlLB z3Thymw$?U=If#IRIHv^395%Q)Yz$YS=3HaA&cMh}56Y4O3@i*RpoFZeEu*!KVG+7f zAz-7lAZp-7ae%V`D1mT5GYBXv++euOz|6qGa1)*gbiw)<7z9NamThKOA;Pd`6T^z^ z%?#^6*0?i*^8>{D;tXsI@(j!jx4Q PP#78BGQ4N_$RG&-R|vr} diff --git a/target/classes/core/Vector2D.class b/target/classes/core/Vector2D.class deleted file mode 100644 index 8567c8a44e65f40e0f4223b14876e7ece68fa1cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3228 zcmX^0Z`VEs1_mpJDlP^l24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00SP6iGJPId+^9tLg(9!3V<*yNJPZ;Hl8g+T zxrym{nI)wusbH(Ya?BtFG9b6gGBU7gxVUJBfkfna7!(*385uZ=OOr~95<#xk*YH7c zn>89&6O9jYr7{nL3do7f$@#?~z8VjMI+$OanFq2)lZQczL7S0*rzpQ9u_V>8D8Do> zg^|IKfK@J@n3i!c=zV`MNOq#Iqc5Ca2)0S|*AgApTxcy3~Ls&jr`a$-q%W=V!~ zeqM1&VqQrxBZC+yaC{J99b8hBnU`)2if$7g22%zzMh1@b)Dq{M#NuK`24M|NB(-2c zYc2*021|AZD;@@G1{;)=;gVmPl#|NHz@D6+TaZ|k3XKX+P?*{AFxWFVFfyFeWiFuqCJFm82FiGO(6`!hw;21Dv)p^Ya)P z1Yt~<%%arf60jI^N@f~NQEEyAf*Hxkz?+_05}KD;0?Cey44f$leJsiO1(l2p93}aX zguuukf)eErQENs9w$#GX#GGPA22l;96ags{T#88jKyp){OVilkK z{Or;KYb4i03_=z`(HK%$kcun`RUVCG0xW2dgrG(^C+6e?XO^T|b1)P$G6=yuUR`0! zsH0G6%UH|Ez~h;hms$kQZ>h!X3}uWAsu+I7<^^_!3PuJw{7M-a*dYF8P-Ng>U}9ik zU}2DCaAa^|U}SJ+U|?WkaAA;Sa7E&~F))IPbx@uI72XUC3<3;{46F>Y42%r!3=9mB z49pCy3=9nXTH6^IwbZvVuxn{qAiZECrnJ)%6UyQ+z0puS>P-O@;-;n_nL<|h#T3Z;{K{#7$3j;3*XD9Dq5LgfQ z07RJ+*kF($pg4gAN+3fJ)VN{>4zP38wYD*cd8qSEpT)qvn}H*8JA>2)5Dl_HJs)Je zdj56>X;AnuGa#bbn}LaeF*}cd*;)+RP_vISa4>K&FfasaYwci= zvtkuxY1hd1lw{q)z^<`_K`9%=;?>x}pqia-$p+$pgjM0fO4-Sl93U@Cuz?~gS%L%P ztYj1?iGj0h9RoW<0|PHZ6N3svGlMllE7*x14D2Aw8N#9YGLRtx?0p7?K!!*L9&nNm zWQbyLVqjrlXYgW(W&lOH3WF{~3s>q)gy|+sy&Dn*(Y$Cqo=OYuPifgIyyE^6hp8a6I$zZ)cEJ+rglN zZoyoz1(4Jt$`H@M11`}L;OWi^n(hQZhJgYO6ly4e21#{Gk<1Z*raDH3M1~}&M=cmw zz$FeJ%#2+O`V0){u33d-3Lo5*WQG)|DT^6cz}e7IOM4juE6W<~V+?#++Zc?sw=ozm zWng7qqkVvZ!&e8I<##Zc&jbnTKy&^M2J=}A>@3R}SeVx^Ffc$eC?w8zFt9W1WRPIk z%^=ILmqC|dAJ`#~aCd|{gn_}4A(bJGfssL$L4_fm!JC1J0hC?&7}(hP82>PcurqK7 zI)I9~3epqU9Nq&)$3nK$da(+Q2BZHWRrVm_tqJ_DFwg!R^ US8k0Eo7n2fErQ@~yyDFa0ImfhT>t<8 delta 78 zcmdnNF`r|Cq&#nOeo?A^SZZ=fevy$2BLhovenBN8gP?|{4@hRBlew6p2ABy~WDVv{ PoaxEN4`ywA>&*-RtJE1K diff --git a/target/classes/objects/GameObject.class b/target/classes/objects/GameObject.class index ffcdc6ef3519dd5dde6d4b22ca226012b1d6c7a1..8af8567f31fffbf4027b26e87738b56cbb060bfa 100644 GIT binary patch literal 3173 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 Z;2_y;3^sk}2KNv&7~*tBh6=EgDgkkD_Tm5l diff --git a/target/classes/objects/ships/BattleShip.class b/target/classes/objects/ships/BattleShip.class index 34aa79b63f2c71a51d0d279dd78500548e9541bc..9ddd1a5efab7b72702c6ea82fc7be9b617444aae 100644 GIT binary patch delta 81 zcmca7d{20TK0B*0gEoWMWE1vSUP%Tn1}O$n25AOq2ARq8*xNZ38Tc5K7-SigC+l$} lv#K-jGH6Vm#^KGX&A`c^Gx-^ZJEQt!Jx)(XrO6eXX#is^4`l!V delta 81 zcmca7d{20TK0B)rgEoWcWE1vSUI_*+21y1{1}O$<2IL>qV))8-JXklk)9~A45MQ18;h2NoZbX3B;9*4B{Ftn%ErA#bCfNk%wUt z!(>JVj=a?L#FEUiR7M6d4V zXJBBmVPI$AWnf^G*xbj-!&tAqo53S;J45z12LFu=3=GT+>lqjrk{Bcy7#Q>zxEb^r zgc%GNR2U2yOc{(A+!%}*{25FbA{fjVQWz{4Y8h-9S{UpY`WfsQ<}x@ktYmOv*vjC{ zu#dro;S_@#!%YTvh6fBD3?CRg8NM?(GW=!mVdP=(V-#obX8^gkg`u98VJm|Z!!`yU z25yFZ43-Sr8F(0Y7#1+ZGVEaBVc-He6y_a935J~vyBHY3V!Ii5K%QlkVA#X3mw}l< zh2aasJ_a5J76wy>#|--!Kym5DaEajn0}lfmgFnMbhJy?|4D1XM3OF!VEY zG8{Hx;9=loIKsfdpuoWVpJ4$5H#-9dJA)b{J3~Gr1TZlCW(fPmz{O#fon?gF&PyYr800`*w!uU=B~aC@+WuQp>V~VU`uY z_HKrGk&^r%SrG;=5eBdA4AUj~wlgf)#;~}Bn{oO7MnvrSF$gm-FoZA&FoZHFFoZD} zGSr7NSTaN~I5I>rxHCjE#52S(nUYql$&WlHoWw zs#qBu8TNytijBdYVJE{$hEoje4Dk#L8BQ~pFmNzbF|;up1}71C?A1bJ4-#|i45iGB RjG)-7=7mJx8E_Un3jmAXOi};< delta 1351 zcmX>q^In?k)W2Q(7#J9=7*22GYGPF3_est#O4ScbO)kkVGIFuzWH4YbWM?qqVK8Pe zVPxP1$!^}oc!iNWhas1pA&-Y4pP^v#N@fe=B8Fmih7yoE9!3Vfti-ZJ{lxMT{eb+; zyb?}^B8D;^23rOmE`|z*N_K`S9)@a$n#m$8IzkN$jqD6fJPgeYEsPA@AoC}Ov&eDe zFtqV7v@>)}u4R#~@8V(TX6RvL;7w003C+tafjEzmK}f?z6V1U~30_iOESw+85smMuqm9x!!Q|SE=O)@PDy4#P9;e9R2~LahG~optQsyZnqeRjpBX$1 zGZ|(vGVm1Tmn4>?Iu_-Z=A|$)7;2zdj>dKIM7NZkVb0_lRteYH4D&#?%x7dUAYcZX zkst@6mqq!^Yl@WA=Y8Kf9iAn{jD&g0N$ zUB$r2uzK<$4ok*0lkadS^RH!KV6bCgV_;y=U|7enelj1YCF=&T8Xryz#x;{$IpqyO zYQz|r7#JATL24L4K%GIDL4<*kfstV&!zPe=1_mZw26hHs1_nmn%~v^j80$AOFfcGP ztYKhah+`0EU|`T^;ASvj5N0rBP+>4)Fl8`iaAPoG@Mkb(h+r^hNMW#KsAaHYXkoBt z=x16^54#yBT;GSQtzhZZPa&0L7LY!*Pba3_J{M4E_xJ81^ynFt9U3 zFl=Sm&%ndL!O+i8$8dmwhk=vfAOiz~0t54Zh6Sn&-0Tb->O(TZ<3gF&PyYr800`*wyYU=B~a zC@+WuQp>V~VY(H+_HKsRk&^r%SrG;=5eBdA3{xcewlmD##;~A;n{oO7Mnuf{F$gm- zFa$FQFoZBDFoZG~GSr7LSTck&I5I>sxHCjC#52S)e3_lqX8U8cmfum{$13$w~1_y>C40;TF40jo17>+XNG4L}? zWXJ$VA1K2mfMbuBp_ZWu9DCdhpvYRsu)ZFY`WP9&QN_q0!0-VaRZI*D3=hCj#mr#H za2_00EDV+m$H7s>%HYVb9~@O|4DJj&8ICa=XJBWDXIRK^g29A=gQ1F{4IF)3i0G?@ a#vUZ**cpnL85u#bSIrBFzLVfQcM1R}`z@RR diff --git a/target/test-classes/core/Vector2DTest.class b/target/test-classes/core/Vector2DTest.class deleted file mode 100644 index 0d5327927a74795531851cc2b81e69f98a6d5588..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2672 zcmX^0Z`VEs1_mpJA}$6d24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00Sb_Nbc2HxcSqE!8`)Z~)Vqj0~f$;{6yW@O+=1an*qOA~X785z_ye9%2F7ZAji%i&%>a=pvcI;htTAkSdziWz*1aTRKm!>qT!HRpa!P<*%{P%7&Jg3z)@V9R8o|fT*BtS0SOYwj0`+Q`6Y=Zsg6bYrFkig42A>()Ws9iN#+a|>5--Ed5^*cq7F97MowXJB)XfHC5rj018fUM!b=Hk-}< ztB2SJw%(nb4D1XZJPe);UW^Qk6^smwE^H2mJHVWY$xlwq2}>->1o5GQEF~G4#f%KXs3{Q?FxHF=tcfWp{@#oXjENxg zP=d~yk%2LZk%2Lpk%2LVkwG%3G_NExH#ID?I5R0H)iEy*6mO6;D&vDa9YZbQf+ZA? zO=8IL4N?%CnOgvIJWFzFUI`-uM`}fKYC#Do`LRQkrZ6%{Ls*bR0@CE0UzVEUoSIjX zTExg8iZ1I~kqFBB9H|ursmWlYgi*p3W+ih;27m(U;q^~42%qs3=9nV42%qnAQ7$Y42&BY7#Nrsq8Jz$*uaA94ABe> z3_M^qD?;WHX3>-ERVMBh>vo3<(VJ3<(U33=#|> z42cX03``7a3~mfb3<(U(42BHW49N@$3@i*O&`?MP`&k9<=ZOp)49pA+49+0e@`HQ> z@iEAeVE=-`VF!auK8yx=a2taX)PMXSbvh6}!Yg86CrU7IF-S6qFi0~PF~~4DG01|Q zuK=+Q>U=H+OQ`cj81$jeM|El%+^Kh<(US*uDyh)}4t7w~s6m|$jxSA+cXW3!AO!hD z7__%DaDyWVB#Q7AB!Uzfco>uzBp8$#)EHD43>nlIoEX#@!WlFeav3ziK7>S29@K|C z3`tNQN-%^$eW=FZ0goVCtPzwB_w_=kudPA8273Z6f?&x}TStUJcL#$$(+&nBD;C`y z3}&D-X10St4B-VS1~vu;23-a&20aE)fGaQU1s!Lxv1++7e+< z2YZ5vL4iSV8vj=V2$K>Te#zmp^g`WJ6;Lycmo`c&w)FBDb(@yAjfZKumOj< z2!pK%gZ*{}ZeNgt9d|J3gB%Pl2}Bs2MHpPcr2#nHK@or@?Clvi7#tYH7#tat8Jrl5 z7@QgG7+jE?Zx3}o2ZK4>`I^wMS7wj_hd48X5d$wnE=JgMf@)i6K^Vlq0WRk>K+f0N z!r(5#;GMmL!M961T3qt@he>(&B2DGx$1MDUqh#6q}K?O%1Ja{r0*cezC7#QS5 z7=k@OcEFM$$lvNa7(#b2pe8*buyS7p76v~C0S12tIfekR6_D~k9_not25E+T1}_Fi U1_1_suosz8iY`Wm0