CmSc310 Artificial Intelligence Homework 05, due 03/31 This assignment is to implement the A* algorithm as applied to the 8-puzzle. The 8-puzzle is a small board game for a single player; it consists of 8 square tiles numbered 1 through 8 and one blank space on a 3 x 3 board. Moves of the puzzle are made by sliding an adjacent tile into the position occupied by the blank space, which has the effect of exchanging the positions of the tile and blank space. Only tiles that are horizontally or vertically adjacent (not diagonally adjacent) may be moved into the blank space. Requirements: 1. The user has a choice to solve the puzzle for a randomly generated board or for a board entered by the user 2. The user chooses the evaluation function – tikes-out-of-place or Manhattan distance In general, the program organization is the same as the organization of the hopover program. The difference lies in the way the next node to be expanded is chosen – in the 8-puzzle program nodes are evaluated and inserted in a priority queue. At each iteration of the “while priority queue not empty” loop, the best node is chosen. Another difference is that in the 8-puzzle program we need to check for repeated states, while in the hop_over program such check is not necessary. Data structures needed: a. node representation (class Puzzle8) an array to represent the 8-puzzle. As discussed in class, this may be a 1D array where each tile is represented by its index in the goal state. For example, let the initial and the goal state be: 5 4 3 1 8 _ 6 7 2 1 2 3 4 5 6 7 8 _ The goal representation would be: 0 1 2 3 4 5 6 7 8 The initial representation would be: 4 3 2 0 7 8 5 6 1 Of course, you may choose a different representation of the board. the position of the empty tile the index of the parent of the node in the search tree the operator applied to obtain the node. The h(n) component of the heuristic function. When h(n) becomes 0, we have reached the goal. 1 The g(n) component of the heuristic function – representing the level of that node in the search tree (the number of the edges from that node to the root. There are 4 operations in the 8-puzzle: slide tile up slide tile down slide tile left slide tile right b. A priority queue c. An array to keep all generated nodes The algorithm for generating new nodes from a given node is: 1. Initialize the starting node with the initial state of the 8-puzzle, compute the heuristic function f = h + g (for the root g = 0), store it in the array of nodes, and insert its index in the priority queue. 2. Main loop: while priority queue not empty and the array of states is not full and goal not found: a. deleteMin an index of a node from the priority queue b. if it is not the goal, b.1. apply the four operations: move up, move down, move left, move right, b.2. for each operation: check its preconditions b.2.1. if applicable, construct the new node b.2.2. if not repeated state store in the array of nodes, insert its index in the priority queue 3. After the loop: print the solution as a sequence of board configurations and the applied operator, and the number of generated nodes, e. g. (the empty position is encoded as “0”) Initial state: 2 4 5 0 1 7 3 8 6 ------ apply op: 0 4 5 2 1 7 3 8 6 move down ------ apply op: 4 0 5 move left 2 2 1 7 3 8 6 .......... ------ apply op: 1 2 3 4 5 6 7 8 0 move up Nodes generated = 5430 Hints: A. program organization You need to create a class Puzzle8 that represents each node in the search tree. It will contain a method to apply the 4 operations (you can use a switch statement similar to the hop_over program) and a method to evaluate each state. It may contain additional supporting methods such as printing the board. The main class will contain the “while queue not empty” loop, and a method to print the solution (see how the solution is printed recursively in the jugs program) B. Implementation 1. Initializing the starting node. You may explicitly specify the array that represents the board (convenient for debugging purposes), use user input (a lot of typing) or write a generator to scramble the goal state. When you test your program, start with an easy example – one that requires two – three movements to get to the goal. If something goes wrong you will be able to trace your program. For a more extensive test you may use initial states taken from http://www.permadi.com/java/puzzle8/ or a generator of initial states. Note that not all configurations of the board have solutions, that is why the generator has to scramble the goal state (by applying the four operations). Design carefully the constructor of the class representing the nodes in the search tree. Besides the board, it will need the position of the empty space (may be passed as an argument, or computed in the constructor. Initialize appropriately the parent of the initial state and the value for the op code. Finally, you need to specify the values “h” and “g” for the heuristic function. Since the evaluation is a method of the puzzle class, the node needs first to be created (without the heuristic values, then evaluated and then values for “h” and “g” have to be recorded in the node. You may write methods that update the values of the private variables, or declare the variables as public and update them in the main method. 2. Accessing the priority queue The priority queue manipulates objects of type Cell with 2 components – the index of the node in the array of nodes and the priority – this is the value of the heuristic function f = h + g. 3 At each insert, you have to create a new object of type Cell, assign appropriate values to its components and then insert it in the priority queue. The deleteMin operation returns an object of type Cell, and from there you have to retrieve the index of the node, access the node, etc. 3. Operations You can implement the four operations in one method using a switch statement within a method of the Puzzle8 class. You have to copy the parent node (the one retrieved by the index removed from the priority queue) into a new node and then apply the operations method to that new node. The call to the operations method is done in the main method within a “for” loop (similar to the hopover program). Copy the parent node immediately before calling the operations method. 4. Checking preconditions Each operation checks if the movement is possible. a. Slide up: not possible if the empty place is in the last row. Check the index of the empty space. b. Slide down: not possible if the empty place is in the first row. Check the index of the empty place. c. Slide left: not possible if the empty position is in the last column. Apply mod 3 operation to the index of the empty place, if the result is 2 then “slide left” is not possible. d. Slide right: not possible if the empty position is in the first column. Apply mod 3 operation to the index of the empty space. If the result is 0 then “slide right” is not possible. 5. Generating a new state If the preconditions are satisfied, the operation is performed and the child node has its board set up. What remains to be done is to assign the appropriate values to the other components of the node – the position of the empty space, the index of the parent, the code of the operation, and the value of the heuristic function for that node. Some of these actions should be done by the operations method, and some – in the main method. 6. Evaluating the state The evaluation method should be part of the Puzzle8 class. Implement the “tiles out of place” heuristics and the “Manhattan distance”. In the “tiles out of place” heuristics, the value of the “h” component of the heuristic function is equal to the number of the tiles not in their position. In the “Manhattan distance” heuristics, the value of the “h” component of the heuristic function is equal to the sum of the distances of each tile from its current position to its target position. The distance is measured in terms of “blocks”. When h = 0 we have reached the goal state. The other component, g, is equal to the level of the node in the search tree, it is equal to its parent's g plus 1. 4 Note that the node records separately the “h” and “g” component of the heuristic value, however in the priority queue the sum of h and g is used as the priority of the node. 7. Checking for repeated states. The check for repeated states should be done along the path form the node under examination to the root of the search tree. It consists in comparing the board of the current node with the board of each node along the path from the current node to the root. See how this is done in the jugs program. You may write a separate method to compare the boards of two nodes. It might be part of the Puzzle8 class, or you may put it in the main program. 8. Checking for a goal state The check for a goal state can be done before a node is inserted in the priority queue or immediately after a node is taken out of the priority queue. If the “h” value for that node is 0, then this is the goal. 9. Printing the solution path The solution path is printed recursively, using the array of states (called ‘array’ below): printPath (currentIndex, array) { if currentIndex = 0 // terminating condition print board of array[0] else parent = array[currentIndex].parent printPath(parent, array) print operation of array[parent] print board of array[parent] } Note that “print board” will depend on how you choose to represent the board. 10. In the main loop there is a check “and array of states not full”. This is convenient to have so that your program does not end abruptly with a run time error in case the number of generated states exceeds the declared size of the array of states. Use as guidelines the comments to the Puzzle8 and PuzzlePlay classes given below: Puzzle8: * Methods: * Constructors * public Puzzle8 () * public Puzzle8(int [] board, int empty, int parent, int g, int h) * Methods: * public boolean operations(int opCode, int parent) * opCode codes for the move: up, down, left, right * the parent is needed to prevent repeating the previous move * 5 * * * * * * * * * * * * * * * * * * * * * The method is organized around a switch statement by opCode For each move the prerequisites are checked and if met, the move is applied, changing the board and the index of the empty tile. Returned value: true if the operation is applied, false otherwise public int evaluate(int heuristics) Evaluates the board heuristics = 1: using number of tiles out of place heuristics = 2: using Manhattan distance public void printTiles(int [] goal) the goal board is needed because the board representation is by indexes of the tiles in the goal state public Puzzle8 copyNode() creates and returns a new node, a copy of the current. used to create children public void printAll() used for debugging purposes PuzzlePlay * PuzzlePlay * * This is the driver for solving the 8-Puzzle using A* algorithm. * * A. Board representation: * The 3 x 3 square is represented by a 1D array. * goal state: array of type int goal: goal[i] = k, k = tile face, * empty tile is 0 * Example: * goal: 1 2 3 * 8 _ 4 * 7 6 5 * representation: [1,2,3,8,0,4,7,6,5] * * The board of each current state is represented in the following way: * Each tile is represented by its index in the goal board. * Example: * current board: * 6 8 1 * _ 7 3 * 5 4 2 * with respect to the goal above, the representation is: * board: [7, 3, 0, 4, 6, 2, 8, 5, 1] * board[8] = 1 represents tile 2 (the last in the current board), which * has index 1 in the representation of the goal * board[0] = 7 represents tile 6, with index 7 in the goal array * * Thus, for any goal when the board satisfies the condition board[i] = i, * we have reached the goal state * * B. Generating the initial state. * there are two options of generating the initial state: * a) automatically * The generation starts with the goal state and then * 100 times randomly choosing moves to apply. * Note that not all moves can be applied * b) entered by the user as a sequence of tiles, 6 * with empty tile represented by 0 * The user input is checked for validity * * C. Heuristics * two evaluation methods are implemented in the Puzzle8 class * number of tiles out of place and Manhatten distance. * The user makes the choice of the heuristics * * * * D. Algorithm * Initial state is generated, the board is evaluated and * the root node is stored in the statesArray * Its index is inserted in the priority queue with priority 0 * * While priority queue is not empty * and the goal is not found * and the states array is not full * Get a parent node index from the priority queue * If not the goal * For each move - up, down, left, right * copy the parent into a child * call the operations method of the Puzzle8 class * to apply the move * If the move was applied * if the child is unique * evaluate the board of the child * store in the states array * insert its index in the priority queue * with priority f = g + h * If goal was found * print the solution * 7