diff --git a/90 Tower/java/Tower.java b/90 Tower/java/Tower.java new file mode 100644 index 00000000..ed4c454f --- /dev/null +++ b/90 Tower/java/Tower.java @@ -0,0 +1,527 @@ +import java.lang.Math; +import java.util.Scanner; + +/** + * Game of Tower + *

+ * Based on the BASIC game of Tower here + * https://github.com/coding-horror/basic-computer-games/blob/main/90%20Tower/tower.bas + *

+ * Note: The idea was to create a version of the 1970's BASIC game in Java, without introducing + * new features - no additional text, error checking, etc has been added. + * + * Converted from BASIC to Java by Darren Cardenas. + */ + +public class Tower { + + private final static int MAX_DISK_SIZE = 15; + + private final static int MAX_NUM_COLUMNS = 3; + + private final static int MAX_NUM_MOVES = 128; + + private final static int MAX_NUM_ROWS = 7; + + private final Scanner scan; // For user input + + // Represent all possible disk positions + private int[][] positions; + + private enum Step { + INITIALIZE, SELECT_TOTAL_DISKS, SELECT_DISK_MOVE, SELECT_NEEDLE, CHECK_SOLUTION + } + + + public Tower() { + + scan = new Scanner(System.in); + + // Row 0 and column 0 are not used + positions = new int[MAX_NUM_ROWS + 1][MAX_NUM_COLUMNS + 1]; + + } // End of constructor Tower + + + public class Position { + + public int row; + public int column; + + public Position(int row, int column) { + this.row = row; + this.column = column; + + } // End of constructor Position + + } // End of inner class Position + + + public void play() { + + showIntro(); + startGame(); + + } // End of method play + + + private void showIntro() { + + System.out.println(" ".repeat(32) + "TOWERS"); + System.out.println(" ".repeat(14) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + System.out.println("\n\n"); + + } // End of method showIntro + + + private void startGame() { + + boolean diskMoved = false; + + int column = 0; + int disk = 0; + int needle = 0; + int numDisks = 0; + int numErrors = 0; + int numMoves = 0; + int row = 0; + + Step nextStep = Step.INITIALIZE; + + String userResponse = ""; + + Position diskPosition = new Position(0, 0); + + // Begin outer while loop + while (true) { + + switch (nextStep) { + + + case INITIALIZE: + + // Initialize error count + numErrors = 0; + + // Initialize positions + for (row = 1; row <= MAX_NUM_ROWS; row++) { + for (column = 1; column <= MAX_NUM_COLUMNS; column++) { + positions[row][column] = 0; + } + } + + // Display description + System.out.println(""); + System.out.println("TOWERS OF HANOI PUZZLE.\n"); + System.out.println("YOU MUST TRANSFER THE DISKS FROM THE LEFT TO THE RIGHT"); + System.out.println("TOWER, ONE AT A TIME, NEVER PUTTING A LARGER DISK ON A"); + System.out.println("SMALLER DISK.\n"); + + nextStep = Step.SELECT_TOTAL_DISKS; + break; + + + case SELECT_TOTAL_DISKS: + + while (numErrors <= 2) { + + // Get user input + System.out.print("HOW MANY DISKS DO YOU WANT TO MOVE (" + MAX_NUM_ROWS + " IS MAX)? "); + numDisks = scan.nextInt(); + System.out.println(""); + + numMoves = 0; + + // Ensure the number of disks is valid + if ((numDisks < 1) || (numDisks > MAX_NUM_ROWS)) { + + numErrors++; + + // Handle user input errors + if (numErrors < 3) { + System.out.println("SORRY, BUT I CAN'T DO THAT JOB FOR YOU."); + } + + } + else { + break; // Leave the while loop + } + } + + // Too many user input errors + if (numErrors > 2) { + System.out.println("ALL RIGHT, WISE GUY, IF YOU CAN'T PLAY THE GAME RIGHT, I'LL"); + System.out.println("JUST TAKE MY PUZZLE AND GO HOME. SO LONG."); + return; + } + + // Display detailed instructions + System.out.println("IN THIS PROGRAM, WE SHALL REFER TO DISKS BY NUMERICAL CODE."); + System.out.println("3 WILL REPRESENT THE SMALLEST DISK, 5 THE NEXT SIZE,"); + System.out.println("7 THE NEXT, AND SO ON, UP TO 15. IF YOU DO THE PUZZLE WITH"); + System.out.println("2 DISKS, THEIR CODE NAMES WOULD BE 13 AND 15. WITH 3 DISKS"); + System.out.println("THE CODE NAMES WOULD BE 11, 13 AND 15, ETC. THE NEEDLES"); + System.out.println("ARE NUMBERED FROM LEFT TO RIGHT, 1 TO 3. WE WILL"); + System.out.println("START WITH THE DISKS ON NEEDLE 1, AND ATTEMPT TO MOVE THEM"); + System.out.println("TO NEEDLE 3.\n"); + System.out.println("GOOD LUCK!\n"); + + disk = MAX_DISK_SIZE; + + // Set disk starting positions + for (row = MAX_NUM_ROWS; row > (MAX_NUM_ROWS - numDisks); row--) { + positions[row][1] = disk; + disk = disk - 2; + } + + printPositions(); + + nextStep = Step.SELECT_DISK_MOVE; + break; + + + case SELECT_DISK_MOVE: + + System.out.print("WHICH DISK WOULD YOU LIKE TO MOVE? "); + + numErrors = 0; + + while (numErrors < 2) { + disk = scan.nextInt(); + + // Validate disk numbers + if ((disk - 3) * (disk - 5) * (disk - 7) * (disk - 9) * (disk - 11) * (disk - 13) * (disk - 15) == 0) { + + // Check if disk exists + diskPosition = getDiskPosition(disk); + + // Disk found + if ((diskPosition.row > 0) && (diskPosition.column > 0)) + { + // Disk can be moved + if (isDiskMovable(disk, diskPosition.row, diskPosition.column) == true) { + + break; + + } + // Disk cannot be moved + else { + + System.out.println("THAT DISK IS BELOW ANOTHER ONE. MAKE ANOTHER CHOICE."); + System.out.print("WHICH DISK WOULD YOU LIKE TO MOVE? "); + + } + } + // Mimic legacy handling of valid disk number but disk not found + else { + + System.out.println("THAT DISK IS BELOW ANOTHER ONE. MAKE ANOTHER CHOICE."); + System.out.print("WHICH DISK WOULD YOU LIKE TO MOVE? "); + numErrors = 0; + continue; + + } + + } + // Invalid disk number + else { + + System.out.println("ILLEGAL ENTRY... YOU MAY ONLY TYPE 3,5,7,9,11,13, OR 15."); + numErrors++; + + if (numErrors > 1) { + break; + } + + System.out.print("? "); + + } + } + + if (numErrors > 1) { + + System.out.println("STOP WASTING MY TIME. GO BOTHER SOMEONE ELSE."); + return; + } + + nextStep = Step.SELECT_NEEDLE; + break; + + + case SELECT_NEEDLE: + + numErrors = 0; + + while (true) { + + System.out.print("PLACE DISK ON WHICH NEEDLE? "); + needle = scan.nextInt(); + + // Handle valid needle numbers + if ((needle - 1) * (needle - 2) * (needle - 3) == 0) { + + // Ensure needle is safe for disk move + if (isNeedleSafe(needle, disk, row) == false) { + + System.out.println("YOU CAN'T PLACE A LARGER DISK ON TOP OF A SMALLER ONE,"); + System.out.println("IT MIGHT CRUSH IT!"); + System.out.print("NOW THEN, "); + + nextStep = Step.SELECT_DISK_MOVE; + break; + } + + diskPosition = getDiskPosition(disk); + + // Attempt to move the disk on a non-empty needle + diskMoved = false; + for (row = 1; row <= MAX_NUM_ROWS; row++) { + if (positions[row][needle] != 0) { + row--; + + positions[row][needle] = positions[diskPosition.row][diskPosition.column]; + positions[diskPosition.row][diskPosition.column] = 0; + + diskMoved = true; + break; + } + } + + // Needle was empty, so move disk to the bottom + if (diskMoved == false) { + positions[MAX_NUM_ROWS][needle] = positions[diskPosition.row][diskPosition.column]; + positions[diskPosition.row][diskPosition.column] = 0; + } + + nextStep = Step.CHECK_SOLUTION; + break; + + } + // Handle invalid needle numbers + else { + + numErrors++; + + if (numErrors > 1) { + System.out.println("I TRIED TO WARN YOU, BUT YOU WOULDN'T LISTEN."); + System.out.println("BYE BYE, BIG SHOT."); + return; + } + else { + System.out.println("I'LL ASSUME YOU HIT THE WRONG KEY THIS TIME. BUT WATCH IT,"); + System.out.println("I ONLY ALLOW ONE MISTAKE."); + } + } + + } + + break; + + + case CHECK_SOLUTION: + + printPositions(); + + numMoves++; + + // Puzzle is solved + if (isPuzzleSolved() == true) { + + // Check for optimal solution + if (numMoves == (Math.pow(2, numDisks) - 1)) { + System.out.println(""); + System.out.println("CONGRATULATIONS!!\n"); + } + + System.out.println("YOU HAVE PERFORMED THE TASK IN " + numMoves + " MOVES.\n"); + System.out.print("TRY AGAIN (YES OR NO)? "); + + // Prompt for retries + while (true) { + userResponse = scan.next(); + + if (userResponse.toUpperCase().equals("YES")) { + nextStep = Step.INITIALIZE; + break; + } + else if (userResponse.toUpperCase().equals("NO")) { + System.out.println(""); + System.out.println("THANKS FOR THE GAME!\n"); + return; + } + else { + System.out.print("'YES' OR 'NO' PLEASE? "); + } + } + } + // Puzzle is not solved + else { + + // Exceeded maximum number of moves + if (numMoves > MAX_NUM_MOVES) { + System.out.println("SORRY, BUT I HAVE ORDERS TO STOP IF YOU MAKE MORE THAN"); + System.out.println("128 MOVES."); + return; + } + + nextStep = Step.SELECT_DISK_MOVE; + break; + } + + break; + + default: + System.out.println("INVALID STEP"); + break; + + } + + } // End outer while loop + + } // End of method startGame + + + private boolean isPuzzleSolved() { + + int column = 0; + int row = 0; + + // Puzzle is solved if first 2 needles are empty + for (row = 1; row <= MAX_NUM_ROWS; row++) { + for (column = 1; column <= 2; column++) { + if (positions[row][column] != 0) { + return false; + } + } + } + + return true; + + } // End of method isPuzzleSolved + + + private Position getDiskPosition(int disk) { + + int column = 0; + int row = 0; + + Position pos = new Position(0, 0); + + // Begin loop through all rows + for (row = 1; row <= MAX_NUM_ROWS; row++) { + + // Begin loop through all columns + for (column = 1; column <= MAX_NUM_COLUMNS; column++) { + + // Found the disk + if (positions[row][column] == disk) { + + pos.row = row; + pos.column = column; + return pos; + + } + + } // End loop through all columns + + } // End loop through all rows + + return pos; + + } // End of method getDiskPosition + + + private boolean isDiskMovable(int disk, int row, int column) { + + int ii = 0; // Loop iterator + + // Begin loop through all rows above disk + for (ii = row; ii >= 1; ii--) { + + // Disk can be moved + if (positions[ii][column] == 0) { + continue; + } + + // Disk cannot be moved + if (positions[ii][column] < disk) { + return false; + } + + } // End loop through all rows above disk + + return true; + + } // End of method isDiskMovable + + + private boolean isNeedleSafe(int needle, int disk, int row) { + + for (row = 1; row <= MAX_NUM_ROWS; row++) { + + // Needle is not empty + if (positions[row][needle] != 0) { + + // Disk crush condition + if (disk >= positions[row][needle]) { + return false; + } + } + } + + return true; + + } // End of method isNeedleSafe + + + private void printPositions() { + + int column = 1; + int ii = 0; // Loop iterator + int numSpaces = 0; + int row = 1; + + // Begin loop through all rows + for (row = 1; row <= MAX_NUM_ROWS; row++) { + + numSpaces = 9; + + // Begin loop through all columns + for (column = 1; column <= MAX_NUM_COLUMNS; column++) { + + // No disk at the current position + if (positions[row][column] == 0) { + + System.out.print(" ".repeat(numSpaces) + "*"); + numSpaces = 20; + } + + // Draw a disk at the current position + else { + + System.out.print(" ".repeat(numSpaces - ((int) (positions[row][column] / 2)))); + + for (ii = 1; ii <= positions[row][column]; ii++) { + System.out.print("*"); + } + + numSpaces = 20 - ((int) (positions[row][column] / 2)); + } + + } // End loop through all columns + + System.out.println(""); + + } // End loop through all rows + + } // End of method printPositions + + + public static void main(String[] args) { + + Tower tower = new Tower(); + tower.play(); + + } // End of method main + +} // End of class Tower \ No newline at end of file