/**
   * Changes the positions of the worm as a result of a jump from the current position.
   *
   * @post If the worm jumped out of the map it will have been removed.
   *     |this.getWorld().getWorms().contains(this) == false
   * @post The worm jumped to the correct position | while (world.isPassable(tempXpos, tempYpos,
   *     this.getRadius())) | tempXpos = this.jumpStep(t)[0] | tempYpos = this.jumpStep(t)[1] | t +=
   *     timeStep | if ((world.isAdjacent(tempXpos, tempYpos, this.getRadius())) && |
   *     (Math.sqrt(Math.pow((origXpos-tempXpos), 2)+Math.pow((origYpos-tempYpos),
   *     2))>=this.getRadius() )) | new.getXpos() == tempXpos | new.getYpos() == tempYpos
   * @post The worm's actionpoints are reduced to zero. |new.getActionPoints() == 0;
   * @throws IllegalStateException When the worm has no action point left to jump the exception is
   *     thrown. |! canJump()
   */
  @Raw
  public void jump(Double timeStep) throws IllegalStateException {
    if (this.canJump()) {
      World world = this.getWorld();
      double origXpos = this.getXpos();
      double origYpos = this.getYpos();
      double tempXpos = this.getXpos();
      double tempYpos = this.getYpos();
      double t = 0;
      while (world.isPassable(tempXpos, tempYpos, this.getRadius())) {
        tempXpos = this.jumpStep(t)[0];
        tempYpos = this.jumpStep(t)[1];
        t += timeStep;

        if (isOutOfTheMap(tempXpos, tempYpos)) {
          this.killWorm();
          break;
        }
        if ((world.isAdjacent(tempXpos, tempYpos, this.getRadius()))
            && (Math.sqrt(Math.pow((origXpos - tempXpos), 2) + Math.pow((origYpos - tempYpos), 2))
                >= this.getRadius())) {
          this.setXpos(tempXpos);
          this.setYpos(tempYpos);
          this.consumeFood();
          this.setActionPoints(0);
          break;
        }
      }
    } else throw new IllegalStateException();
  }
  /**
   * Returns the time it takes to worm to jump (to his new position).
   *
   * @throws IllegalStateException If the worm can't jump the exception is thrown. | ! canJump()
   */
  @Raw
  public double jumpTime(double timeStep) throws IllegalStateException {
    World world = this.getWorld();
    double origXpos = this.getXpos();
    double origYpos = this.getYpos();
    double tempXpos = this.getXpos();
    double tempYpos = this.getYpos();
    double t = 0;
    if (this.canJump()) {
      while (world.isPassable(tempXpos, tempYpos, this.getRadius())) {
        tempXpos = this.jumpStep(t)[0];
        tempYpos = this.jumpStep(t)[1];
        t += timeStep;

        if ((world.isAdjacent(tempXpos, tempYpos, this.getRadius()))
            && (Math.sqrt(Math.pow((origXpos - tempXpos), 2) + Math.pow((origYpos - tempYpos), 2))
                >= this.getRadius())) {
          return t;
        }
        if (isOutOfTheMap(tempXpos, tempYpos)) {
          return t;
        }
      }
      return t;
    } else throw new IllegalStateException();
  }
  /**
   * Makes the worm fall, if it can fall, until the worm falls on an obstacle or falls out of the
   * map.
   *
   * @post The worm has fallen to a new position that is adjacent. The x-position stays the same but
   *     the y-position changes. |this.getWorld().isAdjacent(new.getXpos(),new.getYpos(),
   *     new.getDirecetion) == true |new.getXpos()==old.getXpos() |while
   *     (world.isPassable(this.getXpos(), this.getYpos(), this.getRadius())) | newYpos =
   *     this.getYpos()-this.getRadius() | if (this.getYpos()<0) | then new.getYpos ==
   *     (this.getRadius()*1.1) | break; | else if ((this.getYpos()>=0)){ | while
   *     (!world.isAdjacent(this.getXpos(), this.getYpos(), this.getRadius())) | newYpos =
   *     (this.getYpos()+0.1*this.getRadius()) | new.getYpos() == newYpos
   * @post The worms hitpoints are correctly reduced. |new.getHitPoints() == old.getHitPoints() -
   *     3*distance
   * @post After the fall the worm could be on food this gets checked. | this.consumeFood()
   * @throws IllegalStateException If the worm can't fall because he is on adjacent terrain the
   *     exception is thrown. | ! canFall()
   */
  @Raw
  public void fall() throws IllegalStateException {
    World world = this.getWorld();
    double distance = 0;
    boolean trigger = false;
    if (canFall()) {
      while (world.isPassable(this.getXpos(), this.getYpos(), this.getRadius())) {
        this.setYpos(this.getYpos() - this.getRadius());
        distance += this.getRadius();
        if (this.getYpos() < 0) {
          this.setYpos(0 + this.getRadius() * 1.1);
          trigger = true;
          break;
        }
      }

      if ((this.getYpos() >= 0) && !trigger) {
        while (!world.isAdjacent(this.getXpos(), this.getYpos(), this.getRadius())) {
          this.setYpos(this.getYpos() + 0.1 * this.getRadius());
          distance -= 0.1 * this.getRadius();
        }
      }
      this.setHitPoints(this.getHitPoints() - (3 * (int) Math.floor(distance)));
      this.consumeFood();
    } else {
      throw new IllegalStateException();
    }
  }
 /**
  * Returns true if the worm is positioned in passable terrain and adjacent to impassable terrain.
  */
 @Raw
 private boolean canMove() {
   World world = this.getWorld();
   return world.isAdjacent(this.getXpos(), this.getYpos(), this.getRadius());
 }
  /**
   * The method makes the worm move to a next position that is adjacent to impassable terrain
   * following the slope of that terrain in the direction. The worm shall aim to maximize the
   * distance while minimizing the divergence. If no such location exists because all locations in
   * the direction +- 0,7875 are impassable the worm shall remain at its current position. If
   * locations in the direction are passable but not adjacent the worm shall move there and then
   * drop passively.
   *
   * @post If the worm can maximize the distance while minimizing the divergence, the worm has moved
   *     to the optimal location. |for (double a = 0.1;a<=this.getRadius();a=a+(0.01*a)) { | x2 =
   *     x+Math.cos(direction)*a; | y2 = y+Math.sin(direction)*a; | if (world.isAdjacent(x2, y2,
   *     this.getRadius()) && | world.isPassable(x2, y2, this.getRadius())) { | double d =
   *     Math.sqrt(Math.pow((x-x2),2)+Math.pow((y-y2),2)); | double s = Math.atan((x-x2)/(y-y2)); |
   *     if ((d>=maxD) && (s<minS)) { | minS=s; | maxD=d; | x2Max = x2; | y2Max= y2; | direction =
   *     direction +0.0175; |new.getXpos()==x2Max |new.getYpos() == y2Max
   * @post If the worm can't maximize the distance while minimizing the divergence and there is only
   *     impassable terrain in the checked directions, the worm will not have moved. |new.getXpos()
   *     == old.getXpos() |new.getYpos() == old.getYpos()
   * @post If the worm can't maximize the distance while minimizing the divergence and there is only
   *     passable terrain in the checked directions that is not adjacent, the worm will move there.
   *     |new.getXpos() == old.getXpos() + cos(direction)*radius |new.getYpos() == old.getYpos() +
   *     sin(direction)*radius
   * @post The worms actionpoints are correctly reduced. |new.getActionPoints ==
   *     old.getActionPoints() - old.computeCost2(old.getXpos(),old.getYpos())
   * @throws IllegalArgumentException If the worm can't move because he has insufficient
   *     actionpoints the exception is thrown. | ! isValidStep()
   * @throws IllegalStateException If the worm can't move because the worm isn't positioned in
   *     passable terrain and adjacent to impassable terrain the exception is thrown. | ! canMove()
   */
  @Raw
  public void move() throws IllegalArgumentException, IllegalStateException {
    if (!isValidStep()) throw new IllegalArgumentException();
    if (canMove()) {
      World world = this.getWorld();
      double x = this.getXpos();
      double y = this.getYpos();
      double prevx = x;
      double prevy = y;
      double x2 = x;
      double y2 = y;
      double x2Max = x2;
      double y2Max = y2;
      double c = -0.7875;
      double direction = this.getDirection() + c;

      double maxD = 0;
      double minS = this.getDirection();

      // geval 1: na gaan of in direction+-45° er een gischike volgende positie is
      //			en zo ja, ernaar verplaatsen.
      for (double a = 0.1; a <= this.getRadius(); a = a + (0.01 * a)) {
        x2 = x + Math.cos(direction) * a;
        y2 = y + Math.sin(direction) * a;
        if (world.isAdjacent(x2, y2, this.getRadius())
            && world.isPassable(x2, y2, this.getRadius())) {
          double d = Math.sqrt(Math.pow((x - x2), 2) + Math.pow((y - y2), 2));
          double s = Math.atan((x - x2) / (y - y2));
          if ((d >= maxD) && (s < minS)) {
            minS = s;
            maxD = d;
            x2Max = x2;
            y2Max = y2;
          }
        }
        direction = direction + 0.0175;
      }
      if (this.isOutOfTheMap(x2Max, y2Max)) {
        this.killWorm();
      } else {
        this.setXpos(x2Max);
        this.setYpos(y2Max);
        this.setActionPoints(this.getActionPoints() - this.computeCost2(prevx, prevy));
      }

      // geval2: Er werd in direction+-45° geen geschikte plaats gevonden
      //			nagaan of er in direction naar een passable locatie kan verplaatst worden,
      //			daarnaar verplaatsen en dan vallen (fall).
      if ((x2Max == x) && (y2Max == y)) {
        double pasXpos = x;
        double pasYpos = y;

        pasXpos = (x + (Math.cos(this.getDirection()) * this.getRadius()));
        pasYpos = (y + (Math.sin(this.getDirection()) * this.getRadius()));
        if (!world.isAdjacent(pasXpos, pasYpos, this.getRadius())
            && world.isPassable(pasXpos, pasYpos, this.getRadius())) {
          if (this.isOutOfTheMap(pasXpos, pasYpos)) {
            this.killWorm();
          } else {
            this.setXpos(pasXpos);
            this.setYpos(pasYpos);
            this.setActionPoints(this.getActionPoints() - this.computeCost2(prevx, prevy));
          }
        }
      }
      this.consumeFood();
    } else throw new IllegalStateException();
  }
 /**
  * Checks if the worm can fall. That is if the worm's position is passable and is not adjacent.
  *
  * @return True if the worm can fall. False if the worm can't fall. |
  *     world.isPassable(this.getXpos(), this.getYpos(), this.getRadius()) &&
  *     |!world.isAdjacent(this.getXpos(), this.getYpos(), this.getRadius()));
  */
 @Raw
 public boolean canFall() {
   World world = this.getWorld();
   return (world.isPassable(this.getXpos(), this.getYpos(), this.getRadius())
       && !world.isAdjacent(this.getXpos(), this.getYpos(), this.getRadius()));
 }