import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; import java.util.Random; /* Java application by which an interactive user can try to solve the ** 15-Puzzle, or a similar puzzle on a grid of dimensions of their choice. ** ** Author: R. McCloskey */ public class SliderPuzzlePlay { private static final char QUIT = 'Q'; private static final String validResponses = QUIT + SliderPuzzleConfig.directions; private static Scanner input; private static boolean echo; // should input be explicitly printed? public static void main(String[] args) { establishScanner(args.length == 0 ? null : args[0]); System.out.println("Welcome to the Slider Puzzle Program."); SliderPuzzleConfig spc = initialConfiguration(); System.out.println(); int numMoves = play(spc); System.out.printf("\nPlay ended after %d moves\n", numMoves); System.out.println("Goodbye."); } /* Returns a Slider Puzzle Configuration having dimensions specified ** by input values. Also specified by input values are the seed for ** a Random object and the number of pseudorandom moves that are to ** be made, starting with the standard goal configuration, to produce ** the configuration that is returned. */ private static SliderPuzzleConfig initialConfiguration() { int numRows = getInt("Enter # of rows in grid: ", 1, 20); int numCols = getInt("Enter # of columns in grid: ", 1, 20); SliderPuzzleConfig spc = new SliderPuzzleConfig(numRows, numCols); int numRandMoves = getInt("Enter # random moves to obtain initial configuration: ", 0,5000); if (numRandMoves != 0) { int seed = getInt("Enter seed for pseudorandom number generation: "); Random rand = new Random(seed); spc.applyRandomMoves(rand, numRandMoves); } return spc; } /* Starting with the given Slider Puzzle Configuration, this method ** repeatedly: ** (1) displays the "current" configuration, ** (2) allows the user to choose a move to be made (or to quit), and ** (3) updates the configuration accordingly. ** ** The value returned is the number of moves that were made to the ** point that the user chose to quit. */ public static int play(SliderPuzzleConfig spc) { int moveCntr = 0; boolean keepGoing = true; while (keepGoing) { spc.display(); System.out.println(); displayMenu(); char dir = getChar("> ", validResponses); if (dir == QUIT) { keepGoing = false; } else if (spc.canMove(dir)) { spc.move(dir); moveCntr++; } else { // valid direction, but cannot move that way System.out.printf("Error: %c is not a valid move\n", dir); } System.out.println(); } return moveCntr; } /* Displays a menu that enumerates all the choices for the user, which ** includes all the values in enum Direction as well as a choice to quit. */ private static void displayMenu() { char up = SliderPuzzleConfig.UP; char down = SliderPuzzleConfig.DOWN; char left = SliderPuzzleConfig.LEFT; char right = SliderPuzzleConfig.RIGHT; System.out.printf("Enter %c for UP, %c for DOWN, " + "%c for LEFT, %c for RIGHT, %c for Quit\n", up, down, left, right, QUIT); } /* If the given parameter is nonnull, assigns to global variable 'input' ** a new Scanner that can read from the file that the parameter names. ** Otherwise assigns to 'input' a new Scanner that reads from standard ** input. */ private static void establishScanner(String fileName) { if (fileName == null) { input = new Scanner(System.in); echo = false; } else { try { input = new Scanner(new File(fileName)); echo = true; } catch (FileNotFoundException e) { System.out.print("No file named " + fileName); System.out.println("; Execution aborted"); System.exit(0); } } } /* Displays the given prompt and reads the response provided via the ** 'input' Scanner. If the response is not within the range [min..max] ** (or it is not interpretable as an int value), an error message is ** displayed and the process repeats. When a value in the appropriate ** range is provided, that value is returned. */ private static int getInt(String prompt, int min, int max) { int result = min; boolean keepGoing = true; while (keepGoing) { System.out.print(prompt); try { String response = input.nextLine().trim(); if (echo) { System.out.printf("%d", response); } result = Integer.parseInt(response); if (min <= result && result <= max) { keepGoing = false; } else { System.out.printf("Error: response must be in range [%d..%d]\n", min, max); } } catch (Exception e) { System.out.printf("Error: non-integer response\n"); input.next(); // skip past invalid token } } return result; } /* Special case of method above, it allows any legal integer response. */ private static int getInt(String prompt) { return getInt(prompt, Integer.MIN_VALUE, Integer.MAX_VALUE); } /* Displays the given prompt and reads the response provided via the ** 'input' Scanner. If the first character of the response is not ** among the characters in the given 'validResponses' string, an ** an error message is displayed and the process repeats. When a ** valid response is provided, that character is returned. */ private static char getChar(String prompt, String validResponses) { char result = ' '; boolean keepGoing = true; while (keepGoing) { System.out.print(prompt); String response = input.nextLine().trim(); if (echo) { System.out.printf("%d", response); } if (response.length() == 0) { System.out.println("Error: Empty string is not a valid response."); } else { result = response.charAt(0); if (validResponses.indexOf(result) == -1) { System.out.printf("Error: response must be a character " + "among these: \"%s\"\n", validResponses); } else { keepGoing = false; // valid response was given } } } return result; } }