package ca.josephroque.bowlingcompanion.utilities; import java.util.Locale; import ca.josephroque.bowlingcompanion.Constants; /** * Created by Joseph Roque on 15-03-19. Provides methods for determining bowling scores based on user input */ @SuppressWarnings("CheckStyle") public final class Score { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "Score"; /** Position of the left 2 pin. */ public static final byte LEFT_2_PIN = 0; /** Position of the left 3 pin. */ public static final byte LEFT_3_PIN = 1; /** Position of the head pin. */ public static final byte HEAD_PIN = 2; /** Position of the right 3 pin. */ public static final byte RIGHT_3_PIN = 3; /** Position of the right 2 pin. */ public static final byte RIGHT_2_PIN = 4; /** * Gets the score value of the frame from the balls. * * @param frame the frame to get score of * @param knocked if true, the method will get the value of pins which have been knocked over. If false, it will * get the value of pins which are still standing * @return score of the frame, in a 5 pin game */ public static int getValueOfFrame(boolean[] frame, boolean knocked) { int frameValue = 0; for (byte i = 0; i < frame.length; i++) { if (frame[i] == knocked) { switch (i) { case 0: case 4: frameValue += 2; break; case 1: case 3: frameValue += 3; break; case 2: frameValue += 5; break; default: // do nothing } } } return frameValue; } /** * Gets the score value of the frame from the balls, depending on which balls were already knocked down in the * previous frame. * * @param prevFrame state of the pins in the previous frame * @param frameToGet state of the pins the current frame * @return score of the frame, in a 5 pin game */ public static int getValueOfFrameDifference(boolean[] prevFrame, boolean[] frameToGet) { int frameValue = 0; for (byte i = 0; i < frameToGet.length; i++) { if (frameToGet[i] && !prevFrame[i]) { switch (i) { case 0: case 4: frameValue += 2; break; case 1: case 3: frameValue += 3; break; case 2: frameValue += 5; break; default: // do nothing } } } return frameValue; } /** * Gets textual value of ball. * * @param pins state of the pins * @param ball the ball to get the value of * @param shouldReturnSymbol indicates if a symbol should be returned no matter what * @param isAfterStrike indicates if the ball being counted was after a strike * @return textual value of the ball */ public static String getValueOfBall(boolean[] pins, int ball, boolean shouldReturnSymbol, boolean isAfterStrike) { int ballValue = 0; for (byte i = 0; i < 5; i++) { if (pins[i]) { switch (i) { case 0: case 4: ballValue += 2; break; case 1: case 3: ballValue += 3; break; case 2: ballValue += 5; break; default: // do nothing } } } switch (ballValue) { default: throw new RuntimeException("Invalid value for ball: " + ballValue); case 0: return Constants.BALL_EMPTY; case 2: case 3: case 4: case 6: case 9: case 12: return String.valueOf(ballValue); case 5: if ((ball == 0 || shouldReturnSymbol) && pins[2]) { return Constants.BALL_HEAD_PIN; } else { return "5"; } case 7: if ((ball == 0 || shouldReturnSymbol) && pins[2]) { return Constants.BALL_HEAD_PIN_2; } else { return "7"; } case 8: if ((ball == 0 || shouldReturnSymbol) && pins[2]) { return Constants.BALL_SPLIT; } else return "8"; case 10: if ((ball == 0 || shouldReturnSymbol) && pins[2] && ((pins[0] && pins[1]) || pins[3] && pins[4])) { return Constants.BALL_CHOP_OFF; } else { return "10"; } case 11: if ((ball == 0 || shouldReturnSymbol) && pins[2]) { return Constants.BALL_ACE; } else return "11"; case 13: if ((ball == 0 || shouldReturnSymbol) && !pins[0]) { return Constants.BALL_LEFT; } else if ((ball == 0 || shouldReturnSymbol) && !pins[4]) { return Constants.BALL_RIGHT; } else { return "13"; } case 15: if ((ball == 0 || shouldReturnSymbol)) { return Constants.BALL_STRIKE; } else if (ball == 1 && !isAfterStrike) { return Constants.BALL_SPARE; } else { return "15"; } } } /** * Gets textual value of ball based on surrounding balls. * * @param ballsOfFrame list of all balls in the frame * @param ball the ball to get the value of * @param shouldReturnSymbol indicates if a symbol should be returned no matter what * @param isAfterStrike indicates if the ball being counted was after a strike * @return textual value of the ball */ public static String getValueOfBallDifference(boolean[][] ballsOfFrame, int ball, boolean shouldReturnSymbol, boolean isAfterStrike) { boolean[] pinAlreadyKnockedDown = new boolean[5]; if (ball > 0) { for (byte j = 0; j < 5; j++) { if (ballsOfFrame[ball - 1][j]) { pinAlreadyKnockedDown[j] = true; } } } int ballValue = 0; for (byte i = 0; i < 5; i++) { if (ballsOfFrame[ball][i] && !pinAlreadyKnockedDown[i]) { switch (i) { case 0: case 4: ballValue += 2; break; case 1: case 3: ballValue += 3; break; case 2: ballValue += 5; break; default: // do nothing } } } switch (ballValue) { default: throw new RuntimeException("Invalid value for ball: " + ballValue); case 0: return Constants.BALL_EMPTY; case 2: case 3: case 4: case 6: case 9: case 12: return String.valueOf(ballValue); case 5: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && ballsOfFrame[ball][2] && !pinAlreadyKnockedDown[2]) return Constants.BALL_HEAD_PIN; else return "5"; case 7: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && ballsOfFrame[ball][2]) return Constants.BALL_HEAD_PIN_2; else return "7"; case 8: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && ballsOfFrame[ball][2]) return Constants.BALL_SPLIT; else return "8"; case 10: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && ballsOfFrame[ball][2] && ((ballsOfFrame[ball][0] && ballsOfFrame[ball][1]) || (ballsOfFrame[ball][3] && ballsOfFrame[ball][4]))) return Constants.BALL_CHOP_OFF; else return "10"; case 11: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && ballsOfFrame[ball][2]) return Constants.BALL_ACE; else return "11"; case 13: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && !ballsOfFrame[ball][0]) return Constants.BALL_LEFT; else if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol) && !ballsOfFrame[ball][4]) return Constants.BALL_RIGHT; else return "13"; case 15: if (((ball == 0 && !isAfterStrike) || shouldReturnSymbol)) return Constants.BALL_STRIKE; else if (ball == 1 && !isAfterStrike) return Constants.BALL_SPARE; else return "15"; } } /** * Gets a boolean from a char. * * @param input char to convert to boolean * @return true if input is equal to '1', false otherwise */ public static boolean getBoolean(char input) { return input == '1'; } /** * Creates an int from an array of booleans. * * @param frame array to convert to int * @return a integer representing the booleans if they were binary */ public static int booleanFrameToInt(boolean[] frame) { int ball = 0; for (int i = 0; i < frame.length; i++) { if (frame[i]) ball += Math.pow(2, -i + 4); } return ball; } /** * Converts an int to a boolean array. * * @param ball int from 0-31 * @return boolean array binary representation of {@code ball} */ public static boolean[] ballIntToBoolean(int ball) { if (ball < 0 || ball > 31) throw new IllegalArgumentException("cannot convert value: " + ball); boolean[] pinState = new boolean[5]; String ballBinary = String.format(Locale.CANADA, "%5s", Integer.toBinaryString(ball)).replace(' ', '0'); for (int i = 0; i < pinState.length; i++) pinState[i] = ballBinary.charAt(i) == '1'; return pinState; } /** * Gets the String representation of a number of fouls. * * @param i integer representation of the number of fouls * @return string representation of the number of fouls */ public static String foulIntToString(int i) { switch (i) { default: return "0"; case 24: return "3"; case 25: return "2"; case 26: return "23"; case 27: return "1"; case 28: return "13"; case 29: return "12"; case 30: return "123"; } } /** * Gets the int representation of a String of fouls in a frame. * * @param s string representation of the number of fouls * @return integer representation of the number of fouls */ public static int foulStringToInt(String s) { switch (s) { default: return 0; case "3": return 24; case "2": return 25; case "23": return 26; case "1": return 27; case "13": return 28; case "12": return 29; case "123": return 30; } } /** * Counts the number of pins which are standing in a frame * * @param frame frame to count * @return number of booleans in the array which are {@code true} */ public static byte countStandingPins(boolean[] frame) { byte numberOfPinStanding = 0; for (boolean b : frame) { if (b) numberOfPinStanding++; } return numberOfPinStanding; } /** * Calculates the average of a league based on the games played so far and their scores, along with the base * average of the league (if one has been set). * * @param leagueTotal total of the games recorded in the league * @param leagueNumberOfGames number of games recorded in the league * @param baseAverage base average of the league * @param baseGames number of games which contributed to the base average * @return the average of the league */ public static float getAdjustedAverage(int leagueTotal, int leagueNumberOfGames, short baseAverage, int baseGames) { float average = 0; // If a base average was provided, it is multiplied by the base number of games so the average is calculated // accurately if (baseAverage >= 0) { if (baseGames < 1) baseGames = 1; average = baseAverage * baseGames; } else { // If no base average is provided, base games is set to 0 so it does not affect later calculations baseGames = 0; } // If at least one game was bowled if (leagueNumberOfGames > 0) { // Add the total of the game to the average and divide by the total number of games and base games average += leagueTotal; average /= (float) (leagueNumberOfGames + baseGames); } else if (baseGames > 0) { // Otherwise, if a base average was provided, just divide by the number of base games average /= (float) baseGames; } return average; } /** * Default private constructor. */ private Score() { // does nothing } }