PROBLEM: 
40. After hit(100), hit(100) and hit(250), score should be 3-0. 
Message: 40. After hit(100), hit(100) and hit(250), score should
be 3-0.  Team 0 score:  expected:<3> but was:<1>

package hw2;

/**
 * Models a simplified baseball-like game called Fuzzball.
 *
 * @author YOUR_NAME_HERE
 */
public class FuzzballGame {
  /**
   * Number of strikes causing a player to be out.
   */
  public static final int MAX_STRIKES = 2;

  /**
   * Number of balls causing a player to walk.
   */
  public static final int MAX_BALLS = 5;

  /**
   * Number of outs before the teams switch.
   */
  public static final int MAX_OUTS = 3;



  private int numInnings;
  private int currentInning;
  private boolean topOfInning;
  private int team0Score;
  private int team1Score;
  private int ballCount;
  private int calledStrikesCount;
  private int currentOuts;
  private boolean runnerOnFirst;
  private boolean runnerOnSecond;
  private boolean runnerOnThird;

  /**
   * Constructs a game that has the given number of innings and starts at the top of inning 1.
   *
   * @param givenNumInnings number of innings for the game
   */
  public FuzzballGame(int givenNumInnings) {
    numInnings = givenNumInnings;
    currentInning = 1;
    topOfInning = true;
    team0Score = 0;
    team1Score = 0;
    ballCount = 0;
    calledStrikesCount = 0;
    currentOuts = 0;
  }

  /**
   * Returns true if the game is over, false otherwise.
   *
   * @return true if the game is over, false otherwise
   */
  public boolean gameEnded() {
    return currentInning > numInnings;
  }

  /**
   * Returns true if there is a runner on the indicated base, false otherwise.
   * Returns false if the given argument is anything other than 1, 2, or 3.
   *
   * @param which base number to check
   * @return true if there is a runner on the indicated base
   */
  public boolean runnerOnBase(int which) {
    if (which == 1) {
      return runnerOnFirst;
    } else if (which == 2) {
      return runnerOnSecond;
    } else if (which == 3) {
      return runnerOnThird;
    } else {
      return false;
    }
  }

  /**
   * Returns the current inning. Innings are numbered starting at 1.
   * When the game is over, this method returns the game's total number of innings, plus one.
   *
   * @return current inning, or the number of innings plus one in case the game is over
   */
  public int whichInning() {
    if (gameEnded()) {
      return numInnings + 1;
    } else {
      return currentInning;
    }
  }

  /**
   * Returns true if it's the first half of the inning (team 0 is at bat).
   *
   * @return true if it's the first half of the inning, false otherwise
   */
  public boolean isTopOfInning() {
    return topOfInning;
  }

  /**
   * Returns the number of outs for the team currently at bat.
   *
   * @return current number of outs
   */
  public int getCurrentOuts() {
    return currentOuts;
  }

  /**
   * Returns the number of called strikes for the current batter.
   *
   * @return current number of strikes
   */
  public int getCalledStrikes() {
    return calledStrikesCount;
  }

  /**
   * Returns the count of balls for the current batter.
   *
   * @return current number of balls
   */
  public int getBallCount() {
    return ballCount;
  }

  /**
   * Returns the score for team 0.
   *
   * @return score for team 0
   */
  public int getTeam0Score() {
    return team0Score;
  }

  /**
   * Returns the score for team 1.
   *
   * @return score for team 1
   */
  public int getTeam1Score() {
    return team1Score;
  }

  /**
   * Method called to indicate a strike for the current batter.
   * If the swung parameter is true, the batter is immediately out.
   * Otherwise, 1 is added to the batter's current count of called strikes
   * (possibly resulting in the batter being out).
   *
   * @param swung true if the batter swung at the pitch, false if it's a "called" strike
   */
  public void strike(boolean swung) {
    if (!gameEnded()) {
      if (swung) {
        currentOuts++;
        resetCount(); // Reset count when batter is out
      } else {
        if (calledStrikesCount >= MAX_STRIKES - 1) {
          currentOuts++;
          resetCount(); // Reset count when batter is out
        } else {
          calledStrikesCount++;
        }
      }
    }
  }


  /**
   * Method called to indicate that the batter is out due to a caught fly.
   * This method does nothing if the game has ended.
   */
  public void caughtFly() {
    if (!gameEnded()) {
      currentOuts++;
      resetBases(); // Reset bases when a batter is out
      if (currentOuts >= MAX_OUTS) {
        switchTeams();
      }
      resetCount();
    }
  }

  /**
   * Method called to indicate a bad pitch at which the batter did not swing.
   * This method adds 1 to the batter's count of balls, possibly resulting in a walk.
   * This method does nothing if the game has ended.
   */
  public void ball() {
    if (!gameEnded()) {
      ballCount++;
      if (ballCount >= MAX_BALLS) {
        if (runnerOnFirst && runnerOnSecond && !runnerOnThird) {
          runnerOnThird = true;
        } else if (runnerOnFirst && !runnerOnSecond) {
          runnerOnSecond = true;
        } else if (!runnerOnFirst) {
          runnerOnFirst = true;
        } else {
          updateScore();
        }
        switchBatter();
        ballCount = 0; // Reset ball count after a walk or run is scored
      }
    }
  }

  /**
   * Method called to indicate that the batter hit the ball.
   *
   * @param distance distance the ball travels (possibly negative)
   */
  public void hit(int distance) {
    if (!gameEnded()) {
      if (distance < 15) {
        currentOuts++;
        resetCount(); // Reset count when an out is recorded
      } else if (distance >= 15 && distance < 150) {
        moveRunners(1);
      } else if (distance >= 150 && distance < 200) {
        moveRunners(2);
      } else if (distance >= 200 && distance < 250) {
        moveRunners(3);
      } else {
        moveRunners(4);
      }

      // Reset ball count after hit
      ballCount = 0;

      if (currentOuts >= MAX_OUTS) {
        resetBases(); // Reset bases if three outs are recorded
        switchTeams();
      }
    }
  }



  private void switchTeams() {
    topOfInning = !topOfInning;
    currentOuts = 0; // Reset current outs to zero
    ballCount = 0;
    calledStrikesCount = 0;
    if (topOfInning) {
      currentInning++; // Increment inning only if it's the top of the inning
    }
  }


  private void switchBatter() {
    ballCount = 0;
    calledStrikesCount = 0;
  }

  private void updateScore() {
    if (topOfInning) {
      team0Score++;
    } else {
      team1Score++;
    }
  }

  private void resetCount() {
    ballCount = 0;
    calledStrikesCount = 0;
    if (currentOuts >= MAX_OUTS) {
      switchTeams();
    }
  }

  private void moveRunners(int numBases) {
    if (numBases == 1) {
      if (runnerOnThird) {
        updateScore();
      }

      runnerOnThird = runnerOnSecond;
      runnerOnSecond = runnerOnFirst;
      runnerOnFirst = true;
    } else if (numBases == 2) {
      if (runnerOnThird) {
        updateScore();
      }
      if (runnerOnSecond) {
        updateScore();
      }
      runnerOnThird = runnerOnFirst;
      runnerOnSecond = true;
      runnerOnFirst = false;
    } else if (numBases == 3) {
      if (runnerOnThird) {
        updateScore();
      }
      if (runnerOnSecond) {
        updateScore();
      }
      if (runnerOnFirst) {
        updateScore();
      }
      runnerOnThird = true;
      runnerOnSecond = false;
      runnerOnFirst = false;
    } else if (numBases == 4) {
      // Check if bases are loaded
      if (runnerOnFirst && runnerOnSecond && runnerOnThird) {
        // Home run with bases loaded
        team0Score += 4; // Score 4 runs
        resetBases(); // Reset all runners
      } else {
        updateScore();
      }
    }
  }


  private void resetBases() {
    runnerOnFirst = false;
    runnerOnSecond = false;
    runnerOnThird = false;
  }


  // TODO: EVERYTHING ELSE
  // Note that this code will not compile until you have put in stubs for all
  // the required methods.




  // The methods below are provided for you and you should not modify them.
  // The compile errors will go away after you have written stubs for the
  // rest of the API methods.
  /**
   * Returns a three-character string representing the players on base, in the
   * order first, second, and third, where 'X' indicates a player is present and
   * 'o' indicates no player. For example, the string "oXX" means that there are
   * players on second and third but not on first.
   *
   * @return three-character string showing players on base
   */
  public String getBases()
  {
    return (runnerOnBase(1) ? "X" : "o") + (runnerOnBase(2) ? "X" : "o")
            + (runnerOnBase(3) ? "X" : "o");
  }

  /**
   * Returns a one-line string representation of the current game state. The
   * format is:
   * <pre>
   *      ooo Inning:1 [T] Score:0-0 Balls:0 Strikes:0 Outs:0
   * </pre>
   * The first three characters represent the players on base as returned by the
   * <code>getBases()</code> method. The 'T' after the inning number indicates
   * it's the top of the inning, and a 'B' would indicate the bottom. The score always
   * shows team 0 first.
   *
   * @return a single line string representation of the state of the game
   */
  public String toString()
  {
    String bases = getBases();
    String topOrBottom = (isTopOfInning() ? "T" : "B");
    String fmt = "%s Inning:%d [%s] Score:%d-%d Balls:%d Strikes:%d Outs:%d";
    return String.format(fmt, bases, whichInning(), topOrBottom, getTeam0Score(),
            getTeam1Score(), getBallCount(), getCalledStrikes(), getCurrentOuts());
  }
}