package search; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; /** * An enumeration representing the 4 possible actions in the * FivePuzzle domain (DOWN, UP, LEFT and RIGHT). * * @author pippin * */ enum FivePuzzleAction { DOWN(1, 0), UP(-1, 0), LEFT(0, -1), RIGHT(0, 1); // the change in the x coordinate of the blank space under this action private final int x; // the change in the y coordinate of the blank space under this action private final int y; /** * Each action specifies a particular change to the x and y coordinates * of the blank space in the FivePuzzle problem. For example, the action * LEFT decrements the x coordinate by 1, while leaving the y coordinate * unchanged. * @param x x coordinate delta * @param y y coordinate delta */ private FivePuzzleAction(int x, int y){ this.x = x; this.y = y; } /** * The change in the x coordinate of the blank space under the action * @return integer x coordinate delta */ public int getX(){ return x; } /** * The change in the y coordinate of the blank space under the action * @return integer y coordinate delta */ public int getY() { return y; } } /** * The ProblemGraph implementation for the FivePuzzle problem. * * @author pippin * */ public class FivePuzzle implements ProblemGraph { public static int BLANK = -1; // dimensions of the puzzle public static int XSIZE = 2; public static int YSIZE = 3; private FPState goalNode; /** * Implementation of the State interface for the FivePuzzle problem. * @author pippin * */ public class FPState implements State { // array of tiles. The blank tile has value BLANK private int[][] configuration = new int[XSIZE][YSIZE]; int blankX, blankY; /** * Constructs a state from a string, formatted as a list enclosed in parenthesis. * * For example, the goal state of the five puzzle has the 5 numbered tiles in order, * with the blank tile in the upper left corner. This state would be represented by * the string "(B 1 2 3 4 5)". The first three elements occupy the first row * of the five puzzle, while the last 3 elements occupy the second row. * * This method has fairly minimal error checking. * * @param configurationString */ public FPState(String configurationString) { StringTokenizer tokens = new StringTokenizer(configurationString, "() "); if(tokens.countTokens() != XSIZE*YSIZE) throw new RuntimeException("The string: " + configurationString + " is not a valid 5Puzzle state string."); for(int i = 0; i < XSIZE; i++) for (int j = 0; j < YSIZE; j++) { String str = tokens.nextToken(); if (str.equals("B")) { configuration[i][j] = BLANK; blankX = i; blankY = j; } else { int val = Integer.decode(str); if (val > XSIZE*YSIZE-1 || val < 0) throw new RuntimeException("The string: " + configurationString + " is not a valid 5Puzzle state string. The value " + val + " is not a valid entry."); configuration[i][j] = val; } } } /** * Constructs a new FP state from a parent state, and an action taken in this state. * This method assumes that the test isValidAction() has passed. It is not a public * method. * * @param parent initial state * @param action action taken from initial state */ private FPState(FPState parent, FivePuzzleAction action) { // initialize to clone parent for (int i = 0; i < configuration.length; i++) for (int j = 0; j < configuration[i].length; j++) configuration[i][j] = parent.configuration[i][j]; blankX = parent.blankX; blankY = parent.blankY; // calculate the new location of the blank square int newX = blankX + action.getX(); int newY = blankY + action.getY(); // Switch the two squares configuration[blankX][blankY] = configuration[newX][newY]; configuration[newX][newY] = BLANK; blankX = newX; blankY = newY; } /** * Determines whether a particular action would have a valid effect if taken in * this state. For the FivePuzzle, this method checks whether or not the action * would move the blank square outside the bounds of the puzzle, if executed. * @param action the action being tested * @return true if the action produces a valid next state, false otherwise */ public boolean isValidAction(FivePuzzleAction action) { int newX = blankX + action.getX(); int newY = blankY + action.getY(); if (newX < 0 || newX >= XSIZE || newY < 0 || newY >= YSIZE) return false; return true; } /** * Constructs the set of next states that would result from executing * each of the actions in the action set. * * @return a list of child nodes, one for each valid action */ public List<GraphNode> expandNode() { List<GraphNode> list = new ArrayList<GraphNode>(); for (FivePuzzleAction a : FivePuzzleAction.values()) { if (this.isValidAction(a)) { FPState newNode = new FPState(this, a); list.add(new GraphNode(newNode)); } } return list; } /** * Determines whether or not the state is a goal state. * @return true if a goal state, false otherwise. */ public boolean isGoal() { return this.equals(goalNode); } /** * The hash function is constructed from the two rows of the configuration * array. */ @Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + Arrays.hashCode(configuration[0]); result = PRIME * result + Arrays.hashCode(configuration[1]); return result; } /** * The equals function is constructed from the two rows of the configuration * array. */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final FPState other = (FPState) obj; if (!Arrays.equals(configuration[0], other.configuration[0])) return false; if (!Arrays.equals(configuration[1], other.configuration[1])) return false; return true; } /** * Converts the state to a state string. Follow the formatting guidelines in the * assignment handout for each domain. The state string output should in the same * format as the strings accepted by the constructor for this class. */ public String toString() { String strBuf = "("; for (int i = 0; i < configuration.length; i++) { for (int j = 0; j < configuration[i].length; j++) { if(configuration[i][j] != BLANK) strBuf += configuration[i][j]; else strBuf += "B"; if ((i < configuration.length - 1) || (j < configuration[i].length - 1)) strBuf += " "; } } strBuf += ")"; return strBuf; } //======================================================== /** *returns manhattan distance to goal */ public int h(){ return blankX + blankY; /*int total = 0; for(int row = 0; row < configuration.length; row++;){ for(int col = 0; col < configuration[0].length; col++;){ //correct row = value / #cols } } */ } //======================================================== } /** * Constructs the goal state for the five puzzle instance. * */ public FivePuzzle() { goalNode = new FPState("(B 1 2 3 4 5)"); } /** * Constructs an FPState (which implements the State interface) from a state string. * * This method is part of the ProblemGraph interface. * * @param stateString a string in the correct format (see FPState constructor) * @return an instance of the FPState class */ public FPState getState(String stateString) { return new FPState(stateString); } }