diff --git a/81_Splat/java/README.md b/81_Splat/java/README.md index 51edd8d4..b8998fa6 100644 --- a/81_Splat/java/README.md +++ b/81_Splat/java/README.md @@ -1,3 +1,3 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) -Conversion to [Oracle Java](https://openjdk.java.net/) +Conversion to [Java](https://openjdk.java.net/) diff --git a/81_Splat/java/src/Splat.java b/81_Splat/java/src/Splat.java new file mode 100644 index 00000000..b76c82ff --- /dev/null +++ b/81_Splat/java/src/Splat.java @@ -0,0 +1,348 @@ +import java.util.*; + +/** + * SPLAT simulates a parachute jump in which you try to open your parachute at the last possible moment without going + * splat! You may select your own terminal velocity or let the computer do it for you. You many also select the + * acceleration due to gravity or, again, let the computer do it in which case you might wind up on any of eight + * planets (out to Neptune), the moon, or the sun. + *

+ * The computer then tells you the height you’re jumping from and asks for the seconds of free fall. It then divides + * your free fall time into eight intervals and gives you progress reports on your way down. The computer also keeps + * track of all prior jumps in the array A and lets you know how you compared with previous successful jumps. If you + * want to recall information from previous runs, then you should store array A in a disk or take file and read it + * before each run. + *

+ * John Yegge created this program while at the Oak Ridge Associated Universities. + *

+ * Ported from BASIC by jason plumb (@breedx2) + *

+ */ +public class Splat { + private static final Random random = new Random(); + private final Scanner scanner = new Scanner(System.in); + private final List pastSuccessfulJumpDistances = new ArrayList<>(); + + public static void main(String[] args) { + new Splat().run(); + } + + public void run() { + showIntroduction(); + + while (true) { + + InitialJumpConditions initial = buildInitialConditions(); + + System.out.println(); + System.out.printf(" ALTITUDE = %d FT\n", initial.getAltitude()); + System.out.printf(" TERM. VELOCITY = %.2f FT/SEC +/-5%%\n", initial.getOriginalTerminalVelocity()); + System.out.printf(" ACCELERATION = %.2f FT/SEC/SEC +/-5%%\n", initial.getOriginalAcceleration()); + + System.out.println("SET THE TIMER FOR YOUR FREEFALL."); + System.out.print("HOW MANY SECONDS "); + float freefallTime = scanner.nextFloat(); + System.out.println("HERE WE GO.\n"); + System.out.println("TIME (SEC) DIST TO FALL (FT)"); + System.out.println("========== ================="); + + JumpResult jump = executeJump(initial, freefallTime); + showJumpResults(initial, jump); + + if (!playAgain()) { + System.out.println("SSSSSSSSSS."); + return; + } + } + } + + private void showIntroduction() { + System.out.printf("%33s%s\n", " ", "SPLAT"); + System.out.printf("%15s%s\n", " ", "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + System.out.print("\n\n\n"); + System.out.println("WELCOME TO 'SPLAT' -- THE GAME THAT SIMULATES A PARACHUTE"); + System.out.println("JUMP. TRY TO OPEN YOUR CHUTE AT THE LAST POSSIBLE"); + System.out.println("MOMENT WITHOUT GOING SPLAT."); + } + + private InitialJumpConditions buildInitialConditions() { + System.out.print("\n\n"); + float terminalVelocity = promptTerminalVelocity(); + float acceleration = promptGravitationalAcceleration(); + return InitialJumpConditions.create(terminalVelocity, acceleration); + } + + private float promptTerminalVelocity() { + if (askYesNo("SELECT YOUR OWN TERMINAL VELOCITY")) { + System.out.print("WHAT TERMINAL VELOCITY (MI/HR) "); + return mphToFeetPerSec(scanner.nextFloat()); + } + float terminalVelocity = (int) (1000 * random.nextFloat()); + System.out.printf("OK. TERMINAL VELOCITY = %.2f MI/HR\n", terminalVelocity); + return mphToFeetPerSec(terminalVelocity); + } + + private float promptGravitationalAcceleration() { + if (askYesNo("WANT TO SELECT ACCELERATION DUE TO GRAVITY")) { + System.out.print("WHAT ACCELERATION (FT/SEC/SEC) "); + return scanner.nextFloat(); + } + return chooseRandomAcceleration(); + } + + private JumpResult executeJump(InitialJumpConditions initial, float chuteOpenTime) { + JumpResult jump = new JumpResult(initial.getAltitude()); + for (float time = 0.0f; time < chuteOpenTime; time += chuteOpenTime / 8) { + if (!jump.hasReachedTerminalVelocity() && time > initial.getTimeOfTerminalAccelerationReached()) { + jump.setReachedTerminalVelocity(); + System.out.printf("TERMINAL VELOCITY REACHED AT T PLUS %f SECONDS.\n", initial.getTimeOfTerminalAccelerationReached()); + } + float newDistance = computeDistance(initial, time, jump.hasReachedTerminalVelocity()); + jump.setDistance(newDistance); + + if (jump.isSplat()) { + return jump; + } + System.out.printf("%10.2f %f\n", time, jump.getDistance()); + } + return jump; + } + + private float computeDistance(InitialJumpConditions initial, float i, boolean hasReachedTerminalVelocity) { + final float V = initial.getTerminalVelocity(); + final float A = initial.getAcceleration(); + if (hasReachedTerminalVelocity) { + return initial.getAltitude() - ((V * V / (2 * A)) + (V * (i - (V / A)))); + } + return initial.getAltitude() - ((A / 2) * i * i); + } + + private void showJumpResults(InitialJumpConditions initial, JumpResult jump) { + if (jump.isSplat()) { + showSplatMessage(initial, jump); + showCleverSplatMessage(); + return; + } + System.out.println("CHUTE OPEN"); + int worseJumpCount = countWorseHistoricalJumps(jump); + int successfulJumpCt = pastSuccessfulJumpDistances.size(); + pastSuccessfulJumpDistances.add(jump.getDistance()); + + if (pastSuccessfulJumpDistances.size() <= 2) { + List ordinals = Arrays.asList("1ST", "2ND", "3RD"); + System.out.printf("AMAZING!!! NOT BAD FOR YOUR %s SUCCESSFUL JUMP!!!\n", ordinals.get(successfulJumpCt)); + return; + } + + int betterThanCount = successfulJumpCt - worseJumpCount; + if (betterThanCount <= 0.1 * successfulJumpCt) { + System.out.printf("WOW! THAT'S SOME JUMPING. OF THE %d SUCCESSFUL JUMPS\n", successfulJumpCt); + System.out.printf("BEFORE YOURS, ONLY %d OPENED THEIR CHUTES LOWER THAN\n", betterThanCount); + System.out.println("YOU DID."); + } else if (betterThanCount <= 0.25 * successfulJumpCt) { + System.out.printf("PRETTY GOOD! %d SUCCESSFUL JUMPS PRECEDED YOURS AND ONLY\n", successfulJumpCt); + System.out.printf("%d OF THEM GOT LOWER THAN YOU DID BEFORE THEIR CHUTES\n", betterThanCount); + System.out.println("OPENED."); + } else if (betterThanCount <= 0.5 * successfulJumpCt) { + System.out.printf("NOT BAD. THERE HAVE BEEN %d SUCCESSFUL JUMPS BEFORE YOURS.\n", successfulJumpCt); + System.out.printf("YOU WERE BEATEN OUT BY %d OF THEM.\n", betterThanCount); + } else if (betterThanCount <= 0.75 * successfulJumpCt) { + System.out.printf("CONSERVATIVE, AREN'T YOU? YOU RANKED ONLY %d IN THE\n", betterThanCount); + System.out.printf("%d SUCCESSFUL JUMPS BEFORE YOURS.\n", successfulJumpCt); + } else if (betterThanCount <= -0.9 * successfulJumpCt) { + System.out.println("HUMPH! DON'T YOU HAVE ANY SPORTING BLOOD? THERE WERE"); + System.out.printf("%d SUCCESSFUL JUMPS BEFORE YOURS AND YOU CAME IN %d JUMPS\n", successfulJumpCt, worseJumpCount); + System.out.println("BETTER THAN THE WORST. SHAPE UP!!!\n"); + } else { + System.out.printf("HEY! YOU PULLED THE RIP CORD MUCH TOO SOON. %d SUCCESSFUL\n", successfulJumpCt); + System.out.printf("JUMPS BEFORE YOURS AND YOU CAME IN NUMBER %d! GET WITH IT!\n", betterThanCount); + } + } + + private void showSplatMessage(InitialJumpConditions initial, JumpResult jump) { + double timeOfSplat = computeTimeOfSplat(initial, jump); + System.out.printf("%10.2f SPLAT\n", timeOfSplat); + } + + /** + * Returns the number of jumps for which this jump was better + */ + private double computeTimeOfSplat(InitialJumpConditions initial, JumpResult jump) { + final float V = initial.getTerminalVelocity(); + final float A = initial.getAcceleration(); + if (jump.hasReachedTerminalVelocity()) { + return (V / A) + ((initial.getAltitude() - (V * V / (2 * A))) / V); + } + return Math.sqrt(2 * initial.getAltitude() / A); + } + + private int countWorseHistoricalJumps(JumpResult jump) { + return (int) pastSuccessfulJumpDistances.stream() + .filter(distance -> jump.getDistance() < distance) + .count(); + } + + private void showCleverSplatMessage() { + List messages = Arrays.asList( + "REQUIESCAT IN PACE.", + "MAY THE ANGEL OF HEAVEN LEAD YOU INTO PARADISE.", + "REST IN PEACE.", + "SON-OF-A-GUN.", + "#$%&&%!$", + "A KICK IN THE PANTS IS A BOOST IF YOU'RE HEADED RIGHT.", + "HMMM. SHOULD HAVE PICKED A SHORTER TIME.", + "MUTTER. MUTTER. MUTTER.", + "PUSHING UP DAISIES.", + "EASY COME, EASY GO." + ); + System.out.println(messages.get(random.nextInt(10))); + } + + private boolean playAgain() { + if (askYesNo("DO YOU WANT TO PLAY AGAIN ")) { + return true; + } + return askYesNo("PLEASE"); + } + + private float mphToFeetPerSec(float speed) { + return speed * (5280.0f / 3600.0f); + } + + private boolean askYesNo(String prompt) { + System.out.printf("%s (YES OR NO) ", prompt); + while (true) { + String answer = scanner.next(); + switch (answer) { + case "YES": + return true; + case "NO": + return false; + default: + System.out.print("YES OR NO "); + } + } + } + + private float chooseRandomAcceleration() { + Planet planet = Planet.pickRandom(); + System.out.printf("%s %s. ACCELERATION=%.2f FT/SEC/SEC.\n", planet.getMessage(), planet.name(), planet.getAcceleration()); + return planet.getAcceleration(); + } + + enum Planet { + MERCURY("FINE. YOU'RE ON", 12.2f), + VENUS("ALL RIGHT. YOU'RE ON", 28.3f), + EARTH("THEN YOU'RE ON", 32.16f), + MOON("FINE. YOU'RE ON THE", 5.15f), + MARS("ALL RIGHT. YOU'RE ON", 12.5f), + JUPITER("THEN YOU'RE ON", 85.2f), + SATURN("FINE. YOU'RE ON", 37.6f), + URANUS("ALL RIGHT. YOU'RE ON", 33.8f), + NEPTUNE("THEN YOU'RE ON", 39.6f), + SUN("FINE. YOU'RE ON THE", 896.0f); + + private static final Random random = new Random(); + private final String message; + private final float acceleration; + + Planet(String message, float acceleration) { + this.message = message; + this.acceleration = acceleration; + } + + static Planet pickRandom() { + return values()[random.nextInt(Planet.values().length)]; + } + + String getMessage() { + return message; + } + + float getAcceleration() { + return acceleration; + } + } + + // Mutable + static class JumpResult { + private boolean reachedTerminalVelocity = false; + private float distance; // from the ground + + public JumpResult(float distance) { + this.distance = distance; + } + + boolean isSplat() { + return distance <= 0; + } + + boolean hasReachedTerminalVelocity() { + return reachedTerminalVelocity; + } + + float getDistance() { + return distance; + } + + void setDistance(float distance) { + this.distance = distance; + } + + void setReachedTerminalVelocity() { + reachedTerminalVelocity = true; + } + } + + // Immutable + static class InitialJumpConditions { + private final float originalTerminalVelocity; + private final float originalAcceleration; + private final float terminalVelocity; + private final float acceleration; + private final int altitude; + + private InitialJumpConditions(float originalTerminalVelocity, float originalAcceleration, + float terminalVelocity, float acceleration, int altitude) { + this.originalTerminalVelocity = originalTerminalVelocity; + this.originalAcceleration = originalAcceleration; + this.terminalVelocity = terminalVelocity; + this.acceleration = acceleration; + this.altitude = altitude; + } + + // Create initial jump conditions with adjusted velocity/acceleration and a random initial altitude + private static InitialJumpConditions create(float terminalVelocity, float gravitationalAcceleration) { + final int altitude = (int) (9001.0f * random.nextFloat() + 1000); + return new InitialJumpConditions(terminalVelocity, gravitationalAcceleration, + plusMinus5Percent(terminalVelocity), plusMinus5Percent(gravitationalAcceleration), altitude); + } + + private static float plusMinus5Percent(float value) { + return value + ((value * random.nextFloat()) / 20.0f) - ((value * random.nextFloat()) / 20.0f); + } + + float getOriginalTerminalVelocity() { + return originalTerminalVelocity; + } + + float getOriginalAcceleration() { + return originalAcceleration; + } + + float getTerminalVelocity() { + return terminalVelocity; + } + + float getAcceleration() { + return acceleration; + } + + int getAltitude() { + return altitude; + } + + float getTimeOfTerminalAccelerationReached() { + return terminalVelocity / acceleration; + } + } +} \ No newline at end of file