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