import be.kuleuven.cs.som.annotate.*; public class Worm { /** * a class with the specifications of the worm * * @invar the location of the worm must be a valid location * |... * @invar * * @version 1.0 * @author Arthur Bols and Leen Dereu */ // region properties //=================================================================================== // Location private Tuple location; // jumping private Tuple oldLocation; private double jumpForce; private double jumpVelocity; /** * this variable contains the orientation of the worm */ private double orientation; /** * this variable contains the radius of the worm */ private double radius; /** * this variable contains the minimum value of the radius */ private final double minRadius; /** * this variable contains the mass of the worm */ private double mass; /** * this variable contains the current action points of the worm */ private int points; /** * this variable contains the maximum points a worm can have */ private int maxPoints; /** * this variable contains the name of the worm */ private String name; //=================================================================================== // endregion // region constructor //=================================================================================== /** * * @param location ... * @param orientation ... * @param name ... * @param radius ... */ public Worm(Tuple location, double orientation, String name, double radius) { this(location, orientation, name, radius, 0.25); } /** * * @param location ... * @param orientation ... * @param name ... * @param radius ... * @param minRadius ... * @throws IllegalArgumentException ... */ public Worm(Tuple location, double orientation, String name, double radius, double minRadius) throws IllegalArgumentException { setLocation(location); this.orientation = orientation; if (minRadius < 0) throw new IllegalArgumentException("minRadius must be larger than 0"); // TODO add decent exception msg setRadius(radius); this.minRadius = minRadius; this.points = this.maxPoints; int validName = isValidName(name); if (validName != -1) throw new IllegalNameException(validName, name); this.name = name; } //=================================================================================== // endregion // region location //=================================================================================== /** * Return the location of the worm * the location of the worm expresses the place of the worm * in the play area */ public Tuple getLocation() { return this.location; } /** * set the location of the worm to the given location * * @param location * the new location for the worm * @post the new location of the worm is equal to the given location * |new.getLocation() == location * @throws IllegalArgumentException * the given location is not a valid location for a worm * |! isValidLocation(location) */ private void setLocation(Tuple location) throws IllegalArgumentException { if (location.equals(null)) throw new IllegalArgumentException("Illegal value for location"); // TODO add decent exception msg this.location = location; } //=================================================================================== // endregion // region Orientation //=================================================================================== /** * set the orientation of the worm to the given orientation * * @param orientation * the new orientation of the worm * @pre the given orientation must be a valid orientation for any worm * |isValidOrientation(orientation) * @post the new orientation of the worm must be equal to the given orientation * |new.getOrientation() == orientation */ public void setOrientation(double orientation) { this.orientation = orientation; } /** * Return the orientation of the worm * the orientation of a worm expresses the direction in which * the worm is looking */ @Basic public double getOrientation() { return orientation; } //=================================================================================== // endregion // region Shape mass/radius //=================================================================================== /** * Return the radius of the worm * the radius of the worm expresses half of the * width of the worm */ @Basic public double getRadius() { return this.radius; } /** * set the radius of the worm to the given radius * * @param radius * the new radius for the worm * @post the new radius of the worm is equal to the given radius * |new.getRadius() == radius * @throws IllegalArgumentException * the given radius is not a valid radius for any worm * |! isValidRadius(radius) */ public void setRadius(double radius) throws IllegalArgumentException { if (radius < this.minRadius) throw new IllegalArgumentException("Radius is smaller than " + this.minRadius); this.radius = radius; setMass(radius); } /** * Return the minimum radius the worm can have * the minimum radius of the worm expresses the minimum length * of half of the width of the worm */ public double getMinRadius() { return this.minRadius; } /** * Return the mass of the worm * the mass of the worm expresses the weight of the worm */ public double getMass() { return this.mass; } /** * set the mass of the worm to the given mass (dependent on the radius) * * @param radius * part of the formula to calculate the mass * @post the new mass of the worm is equal to * rho * (4 / 3 * Math.PI * Math.pow(radius, 3)) * |new.getMass() == rho * (4 / 3 * Math.PI * Math.pow(radius, 3)) */ private void setMass(double radius) { final int rho = 1062; double mass = rho * (4 / 3 * Math.PI * Math.pow(radius, 3)); this.mass = mass; setMaxPoints(mass); } //=================================================================================== // endregion // region Points //=================================================================================== public int getPoints() { return this.points; } public void setPoints(int points) { if (points > this.maxPoints) points = this.maxPoints; else if (points < 0) points = 0; this.points = points; } private void setMaxPoints(double maxPoints) { this.maxPoints = (int) Math.ceil(maxPoints); setPoints(this.points); } private void subtractPoints (int value) { setPoints(this.points - value); } private void subtractPoints (double angle) { setPoints(this.points - (int) Math.ceil(Math.toDegrees(angle) / 6)); } //=================================================================================== // endregion // region name //=================================================================================== /** * Return the name of the worm * the name of the worm expresses the identity of the worm */ public String getName() { return this.name; } /** * set the name of the worm tot the given name * * @param name * the new name for the worm * @post the new name of the worm is equal to the given name * |new.GetName() == name * @throws IllegalNameException(validName, name) * the given name is not a valid name for any worm * |! isValidName(name) */ public void setName(String name) { int validName = isValidName(name); if (validName != -1) throw new IllegalNameException(validName, name); this.name = name; } /** * check whether the given name is a valid name for all worms * * @param name * the name to check * @return -1 if and only if the given name is longer then 2, * the first letter is uppercase and the name only exists * of letters, " ", " ' " and " "" " * |for (i = 0; i < name.length(); i++) * |result == (name.length() > 2 && Character.isUpperCase(name.charAt(0) * |&& Character.isLetter(name.charAt(i)) && * |allowedCharacters.indexOf(name.charAt(i))) */ private int isValidName (String name) { if (name.length() < 2 || !Character.isUpperCase(name.charAt(0))) { return 0; } String allowedCharacters = "'\" "; for (int i = 0; i < name.length(); i++) { if (!Character.isLetter(name.charAt(i))) { if (allowedCharacters.indexOf(name.charAt(i)) == -1) { return i; } } } return -1; } //=================================================================================== // endregion // region move //=================================================================================== /** * move the worm for the given number of steps * * @param numberSteps * the number of steps the worm should take * * @post the x-coordinate of the new location of the worm schould be the location of * the old x-coordinate plus the number of steps multiplied with the distance * that the x-coordinate schould move (distance is equal to the radius multiplied * with the cosinus of the orientation) * |distanceX = this.radius * Math.cos(this.orientation) * |new.xCoordinate = this.location.item1 + numberSteps * distanceX * @post the y-coordinate of the new location of the worm schould be the location of * the old y-coordinate plus the number of steps multiplied with the distance * that the y-coordinate schould move (distance is equal to the radius multiplied * with the sinus of the orientation) * |distanceY = this.radius * Math.sin(this.orientation) * |new.yCoordinate = this.location.item2 + numberSteps * distanceY * @post the current value of action points has changed. The current value of action points * minus the cost of moving (abs(cos(theta)) + abs(4 sin(theta))) * |value = (int) Math.ceil(Math.abs(Math.cos(this.orientation)) + Math.abs(4 * Math.sin(this.orientation))) * |setPoints(this.points - value) * @throws IllegalArgumentException * when the total of steps is lower then 0 or when the cost of action point is more * then the current value of action point * |NumberSteps < 0 || cost > this.points */ public void move(int numberSteps) { if (numberSteps < 0) throw new IllegalArgumentException(); // TODO add decent exception msg int cost = (int) Math.ceil(Math.abs(Math.cos(this.orientation)) + Math.abs(4 * Math.sin(this.orientation))); if (cost > this.points) throw new IllegalArgumentException(); // TODO add decent exception msg double distanceX = this.radius * Math.cos(this.orientation); double distanceY = this.radius * Math.sin(this.orientation); this.location = Tuple.create(this.location.item1 + numberSteps * distanceX, this.location.item2 + numberSteps * distanceY); subtractPoints(cost); } //=================================================================================== // endregion // region turn //=================================================================================== /** * turns the worm with the given angle * * @param angle * the angle that must be added to the orientation * @pre the angle to add must be between 0 and 2pi (including 0) * |0 <= angle < 2pi * @post the new orientation is the old orientation plus the given angle * |new.getOrientation() = this.getOrientation() + angle * @post the resulting angle (= the new orientation) must be between 0 and 2pi (including 0) * |0 <= new.getOrientation() < 2pi * @post current points of action points schould be reduced, for this there is another * method with as parameter the given angle * |substractPoints(angle) */ public void turn(double angle) { assert 0 <= angle && angle < (2 * Math.PI); setOrientation((this.orientation + angle) % (2 * Math.PI)); subtractPoints(angle); } //=================================================================================== // endregion // region Jump //=================================================================================== private final double G = 5.0; private final double FORCE_TIME = 0.5; public void jump() { if (!canJump()) throw new IllegalStateException(); this.oldLocation = this.location; this.jumpVelocity = jumpVelocity(); this.location = Tuple.create(location.item1 + jumpDistance(this.jumpVelocity), location.item2); this.points = 0; } private boolean canJump() { return this.points > 0 && this.orientation < Math.PI; } public double jumpTime() { return jumpDistance(this.jumpVelocity) / (this.jumpVelocity * Math.cos(this.orientation)); } public Tuple jumpStep(double deltaTime) { return Tuple.create(oldLocation.item1 + this.jumpVelocity * Math.cos(this.orientation) * deltaTime, oldLocation.item2 + this.jumpVelocity * Math.sin(this.orientation) * deltaTime - (1/2) * G * Math.pow(deltaTime, 2)); } private double jumpDistance(double v) { return (Math.pow(v, 2) * Math.sin(2 * this.orientation)) / this.G; } private double jumpVelocity() { double force = 5 * this.points + this.mass * G; return (force / this.mass) * FORCE_TIME; } //=================================================================================== // endregion }