/********************************************************************************** This class demonstrates a graphic version of puzzle. ************************************************************************************/ import java.awt.event.*; import java.awt.*; import javax.swing.*; import java.util.Stack; import java.io.*; public class NPuzzle extends JFrame implements ActionListener { private int[][] puzzle; //the int array representation of the puzzle private int SIZE; //the size of the puzzle private int blankRow, blankCol; //position of the blank private int num_move; //the number of moves you have done private JButton[][] tiles; //the JButton representation of the puzzle private Stack moveStack; //the stack to store the moves private TileListener[][] listeners; //the listeners responding to the tile buttons private JLabel message; //the place to display message private JPanel board; //the panel containing all the tile buttons private Timer scheduler; //schedules the steps in the animation of solve public NPuzzle() { this(3); } public NPuzzle(int SIZE) { init_board(SIZE); init_frame(); } //Initialize the array puzzle and the panel board containing the tiles private void init_board(int SIZE) { this.SIZE = SIZE; num_move = 0; moveStack = new Stack(); //initialize puzzle puzzle = new int[SIZE][SIZE]; int count=1; for (int row=0; row < SIZE; row++) for (int col=0; col < SIZE; col++) puzzle[row][col]=count++; blankRow = SIZE-1; blankCol = SIZE-1; puzzle[blankRow][blankCol] = 0; //add tiles to the board board = new JPanel(); board.setLayout(new GridLayout(SIZE, SIZE)); tiles = new JButton[SIZE][SIZE]; listeners = new TileListener[SIZE][SIZE]; for (int i=0; i32) { message.setText("size must be between 2 and 32"); return; } Container container = getContentPane(); container.remove(board); init_board(size); container.add(board, BorderLayout.CENTER); pack(); } else if (command.equals("Quit")) { System.exit(0); } } public static void main(String[] args) { NPuzzle frame; //If number of arguments is less than 1, use the default size. //Otherwise, read the size from the first argument. if (args.length >= 1) { int size = Integer.parseInt(args[0]); frame = new NPuzzle(size); } else { frame = new NPuzzle(); } frame.setTitle("puzzle game"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } // Return true if puzzle is solved: public boolean isSolved() { // Are values inside the array OK? int count = 1; for (int row=0; row < SIZE; row++) for (int col=0; col < SIZE; col++) { if (puzzle[row][col] != count) { if (row != SIZE-1 || col != SIZE-1 || puzzle[row][col] != 0) return false; } count++; } return true; } // Executes a sequence of moves as specified in a file // A sample file might contain "E\nS\nW\nN\nE\nS\nW\nS\nE\nE\nN", where each letter // represents a direction to slide a tile. public void readMovesFile(String filename) { String direction=null; BufferedReader inFile; boolean exit = false; try { inFile = new BufferedReader (new FileReader(filename)); try { direction = inFile.readLine(); while (direction != null) { int destRow = -1; int destCol = -1; if (direction.equalsIgnoreCase("N")) { destRow = blankRow+1; destCol = blankCol; } else if (direction.equalsIgnoreCase("S")) { destRow = blankRow-1; destCol = blankCol; } else if (direction.equalsIgnoreCase("E")) { destRow = blankRow; destCol = blankCol-1; } else if (direction.equalsIgnoreCase("W")) { destRow = blankRow; destCol = blankCol+1; } if (!attempt_move(destRow, destCol)) message.setText("File has specified an illegal move!"); direction = inFile.readLine(); } } catch(IOException e) { message.setText("File read went wrong!"); } try { // Close the file! inFile.close(); } catch(IOException e) { } } catch(FileNotFoundException e) { message.setText("The input file does not exist!"); } } //scramble the puzzle num_scratch times and push the moves onto moveStack //Update blankRow and blankCol //Suggest: only keep and count valid moves and store them onto the stack public void scramble(int num_scramble) { boolean successfulMove; int randRow, randCol; //repeat num_scramble times for (int i=0; i= SIZE || destinationCol < 0 || destinationCol >= SIZE); if (outOfBounds) return false; //check if chosen tile is adjacent to blank boolean above = destinationRow == blankRow - 1; boolean below = destinationRow == blankRow + 1; boolean left = destinationCol == blankCol - 1; boolean right = destinationCol == blankCol + 1; boolean sameCol = blankCol == destinationCol; boolean sameRow = blankRow == destinationRow; boolean adjacentToBlank = (((left || right) && sameRow) || ((above || below) && sameCol)); if (!adjacentToBlank) return false; //determine which direction to slide tile String direction=null; if (above) direction = "SOUTH"; else if (below) direction = "NORTH"; else if (left) direction = "EAST"; else if (right) direction = "WEST"; else { message.setText("Error"); return false; } //if prohibitRetrace flag is on, //check that same tile is not being moved back and forth consecutively //(we can determine the last move by peeking at the top of the stack) if (prohibitRetrace && !moveStack.empty()) { String lastMove = (String)moveStack.peek(); if ((direction == "NORTH" && lastMove == "SOUTH") || (direction == "SOUTH" && lastMove == "NORTH") || (direction == "EAST" && lastMove == "WEST") || (direction == "WEST" && lastMove == "EAST")) return false; } //move tile move_tile(destinationRow, destinationCol); //push move onto stack moveStack.push(direction); //increment num_move num_move++; return true; } //Do the actual move of the tile/blank. private void move_tile(int destinationRow, int destinationCol) { //update puzzle accordingly puzzle[blankRow][blankCol] = puzzle[destinationRow][destinationCol]; puzzle[destinationRow][destinationCol] = 0; //update the tiles accordingly tiles[blankRow][blankCol].setText(""+puzzle[blankRow][blankCol]); tiles[destinationRow][destinationCol].setText(""); tiles[blankRow][blankCol].setBackground(Color.yellow); tiles[destinationRow][destinationCol].setBackground(Color.white); //update blank row/col (set equal to randRow/randCol) blankRow = destinationRow; blankCol = destinationCol; } //Perform the task of solve the puzzle by reversing the moves on the moveStack class SolvePerformer implements ActionListener { public void actionPerformed(ActionEvent e) { if (moveStack.empty()) { scheduler.stop(); if (isSolved()) message.setText("The puzzle is now solved"); return; } String direction = (String) moveStack.pop(); // find out how to swap tiles. no switches on strings allowed // do opposite of direction because made direction to get there need to // do opposite to undo it // move a tile south into blank location if(direction.equalsIgnoreCase("NORTH")) move_tile(blankRow-1, blankCol); // move a tile north into blank location else if(direction.equalsIgnoreCase("SOUTH")) move_tile(blankRow+1, blankCol); // move tile east into blank location else if(direction.equalsIgnoreCase("WEST")) move_tile(blankRow, blankCol-1); // move tile west into blank location else if(direction.equalsIgnoreCase("EAST")) move_tile(blankRow, blankCol+1); } } //respond to the tiles and try to move accordingly. class TileListener implements ActionListener { private int row, column; TileListener(int row, int column) { this.row = row; this.column = column; } public void actionPerformed(ActionEvent e) { if (attempt_move(row, column)) { if (isSolved()) message.setText("You solved the puzzle in "+num_move+" steps."); else message.setText("You have moved "+num_move+" steps"); } else { message.setText("Illegal moves!"); } } } }