MSc Computing Science Group Project - Report II Group 12: Piano Moving Project Supervisor: Alessandra Russo Group Members: Marcus Bristow Dinusha Fonseka Alexander Georgalakis Nicholas Giles Olutosin Oni Matthew Simmonds Date: 28 February 2002 MSc Computing Science Group Project Piano Moving - Report II The basic objectives and possible extensions of the project remain unchanged and are as given in Report I. Significant progress has been made with respect to fulfilling the basic objectives and some of the extensions of the project. The current prototype of the piano moving puzzle offers the following functionality as specified in Report I: The puzzle is displayed on the screen. In addition menus have been added similar to standard menus in commercial software. The user is able to choose from 3 possible puzzles of varying difficulty shown in Fig 1. The blocks in the game are manipulable using the mouse. Illegal moves of the blocks are disallowed. The keyboard can be used in the standard fashion to access options in the menu. A record of past moves is maintained and the user is able to undo or redo single moves. The original specification defines the provision of the ability to redo or undo sequences of moves. This functionality is currently being developed. The current puzzle can be restarted and At any point in the game, animated replays of the game to that point in the game are available. This differs slightly from the original specification, which requires that parts of the game also can be replayed. This functionality is currently being implemented. The following functionality as given in Report I as extensions have been implemented in the current prototype of the game. Solution of the easy and medium boards by an AI routine written in Java and Prolog. Details of the AI routine are discussed later in this report. Y Y X X Y X Fig 1: Showing from left to right, the easy, medium and hard boards. (Picture not to scale). The goal is to move the large block marked X to the top central position marked Y. 28/02/2002 2 MSc Computing Science Group Project Piano Moving - Report II Conceptual Model Report I gives the system being developed as consisting conceptually of the following functional areas: the Graphical User Interface or GUI, the Board and the History. As a result of progress made, it has been necessary to restructure the conceptual model of the project. The main functional areas are the GUI, the Board, the Blocks, the History and the Artificial Intelligence components of the game. (The History as a functional component of the game is shared between the GUI and the Artificial Intelligence routine of the game and is unchanged from the previous report.) In this report, these functional areas are explored as packages. This is inline with the general structure in which the code for the project has been implemented in Java. The Block Package The Block package consists of three classes, the abstract Block class and two subclasses. Block defines the basic attributes for the game’s internal representation of a puzzle piece, and the abstract prototypes for the basic functionality required of these pieces. The instantiable subclasses of Block are NormalBlock and TargetBlock, which specify how the abstract methods work and add functionality. Block #height: int #width: int #TLCorner: Po int #Colour: Color #move(translation:Point): void #addToSnapShot(bss:BoardSnapshot): void #targetBlock(): boolean #getManhattanScore(): int #clone(): Object NormalBlock +move(translation:Point): void +addToSnapShot(bss:BoardSnapshot): void +targetBlock(): boolean +getManhattanScore(): int +clone(): Object TargetBlock -targetC: Po int +move(translation:Point): void +addToSnapShot(bss:BoardSnapshot): void +targetBlock(): boolean +getManhattanScore(): int +clone(): Object The only difference between the attributes for the subclasses is the addition of a private Point object in the TargetBlock class, which defines the winning condition for the game. The differences between the methods in NormalBlock and TargetBlock are as follows: 28/02/2002 3 MSc Computing Science Group Project Piano Moving - Report II The move method for the TargetBlock performs the same translation as for the NormalBlock, but also throws a GameCompleteException if the block has been moved into the winning position. This exception is caught by the calling object, which notifies the GUI that the game has been completed. The addToSnapshot method alters the object received via the BoardSnapshot parameter, by filling out the space the block occupies with a positive integer dependent on block dimensions (for NormalBlock objects) or with zero (to denote TargetBlock objects). An element of –1 in the array denotes an empty space in the BoardSnapshot. (Further specification for the BoardSnapshot is given later.) The targetBlock method returns which subclass of Block the receiver is. The getManhattanScore method is used by the AI routine. It returns zero for NormalBlock objects, the sum of the horizontal and vertical displacements from the winning position for TargetBlock objects. The clone method is called on a Board object, when the calling class requires an exact, separate and complete copy of the Board object. The design and implementation of the three block classes has been rigorously tested and at this stage appears complete. 28/02/2002 4 MSc Computing Science Group Project Piano Moving - Report II The Board Package The classes available in the Board package have the following content and associations: Board -height: int -width: int -gameName: String BoardLi brary -boardList: Vector +saveBoards(): void +add(b:Board): void +delete(b:Board): void boardList 0..n << loads from >> BoardFile +getTag(): String +movePastTag(): void +getInt(attributeTag:String): int +getString(attributeTag:String) : St ring +fullCopy(): Board +getManhattanScore(): int +getSnapshot(): BoardSnapshot +addBlock(b:Block): void +legalMoves(): Vector +makeMove(m:Move): void +moveBlock(p :Po int, pf:Point): void +saveToDisk(): void -loadFro mDisk(bf:BoardFile): void << creates >> bspace BoardSnapshot BoardS pace -height: int -width: int -snapshot: int[][] -height: int -width: int -theSpace: Block[][] +writePattern(x:int, y :int, width:int, height:int, value:int): void +print(): void +setSpace(b:Block): void +clearSpace(b:Block): void +isThereSpace(x:int, y:int, height:int, width:int): boolean +blockAt(p:Point): Block 28/02/2002 5 MSc Computing Science Group Project Piano Moving - Report II An overview of the Board package functionality An instantiated BoardLibrary object acts as the control for creating the Board objects that are available to the game, and returning a list of references to these objects. When the GUI loads, it creates a BoardLibrary object, which loads the boards into the library from disk. It is intended that the custom game option will add new boards to the library, then these additional boards will be loaded the next time the game starts. BoardLibrary Class The BoardLibrary class is a simple wrapper for a Vector object, which contains the references to the boards loaded at startup. The BoardLibrary constructor creates the boards by calling the constructor in the Board class that creates a board from a filename passed as a parameter. Methods exist to perform simple operations on the library, to facilitate addition and deletion of boards. Board Class A Board object contains, updates and returns information on the current state of a game. In more detail: The Board maintains a list of the puzzle pieces, as a Vector of Block objects. The pieces can be moved, via a call to makeMove or moveBlock. Two implementations are given to allow other classes to interact with Board objects in their preferred manner. A list of the possible moves from the current state of the board can be generated. The Manhattan score for the game can be calculated. The Board class includes other methods to load and save the Board to a file, to create a deep clone of itself (an entirely new Board object, with a collection of Block objects), and a method to create a BoardSnapshot object. BoardSpace The BoardSpace is the container for a two-dimensional array of Block object references, which provide a representation of the current state of board. The choice of Block references over integers or boolean values was found to reduce the time spent searching for a Block object given a grid position in the game. Previous implementations which used boolean values had to test whether a point fell within the bounds of a Block objects dimensions, whereas the current version simply retrieves the Block reference from the array. BoardSnapshot This class is used to provide another representation of the Board’s state, which matches the criteria of the AI algorithms. The AI routine requires an array of integers which differentiate between blocks of different types, but is insensitive to different blocks of the same size. So, unlike the BoardSpace representation using Block object references, the following arrangements of blocks A, B and C are considered to be the same: 28/02/2002 6 MSc Computing Science Group Project Piano Moving - Report II The current specification for creating BoardSnapshot object requires the twodimensional integer array to contain the following values: Block type (width x Integer height) code 1x1 1 1x2 2 2x1 3 2x2 4 Empty spaces -1 Target block 0 BoardFile The BoardFile object is defined as an extension of a java.io.RandomAccessFile, with further methods defined to extract specific types of information from a file containing a board definition. The public methods allow the caller to find a tag, move past a tag, and read an integer or a string from the file. The tags that are recognised are: Tag identifier (open tag, close tag) <game>, </game> <block>, </block> <targetblock>, </targetblock> 28/02/2002 Block Details Surrounds details defining Board object Surrounds details of NormalBlock object Surrounds details of TargetBlock object 7 MSc Computing Science Group Project Piano Moving - Report II Each tagged block contains attribute tags, which locate the details that define the objects to be created. Any unrecognised tags are ignored, and the program simply skips over them to reach the requested attribute tag. The attribute tags are: Attribute Tag WIDTH HEIGHT XPOSITION YPOSITION COLOUR XTARGET XTARGET NAME Tag Details Integer: width of object (Board, Block) Integer: height of object (Board, Block) Integer: x-coordinate of object (Block) Integer: y-coordinate of object (Block) Integer: Colour of object (Block) Integer: Target x-coordinate (TargetBlock) Integer: Target y-coordinate (TargetBlock) String: Name of game (Board) The design and implementation of the block package has been rigorously tested and also at this stage, appears complete. GUI package 28/02/2002 8 MSc Computing Science Group Project Piano Moving - Report II The GUI Package In the premier report of this project, the GUI class is given in the class diagram as a single all-encompassing class, which would provide all the game control functionality. This design has been refined subsequently and the GUI now consists of a main GUI class and several sub-classes that provide various aspects of the required game functionality. The Graphical User Interface (GUI) consists of a JFrame class as the main window for the game. Within this, the game window is divided into separate panels overall defined by six classes named as follows: ButtonPanel - the main control area whilst the game is being played. StatusPanel - providing the game status information. PuzzlePanel - a container for the game representation itself. ShapePanel - the graphics canvas for the game. CustomPanel - providing the functionality for the design of a custom game. MessagePanel - offering the user with in-game messages and information. GUIJMenuBar - the menu system for the game window. The implementation of these classes is further defined. Functionality and Design Implementation The main design decision was choosing an implementation for the motion of the blocks and how this affected the internal representation of the board. The original design envisaged restriction of the movement of the blocks using communication between the GUI and Board. For example, a click on a block would set off a chain of communication sessions between the GUI and the board such that the Board passes a list of legal moves of the clicked block to the GUI. The GUI would then disallow illegal moves of the block. However, the current design implements restriction of blocks wholly within the GUI class. This has the effect of reducing the level of coupling between the classes and increasing the speed and efficiency of the graphical representation of the moving blocks. The GUI class has been implemented almost entirely using the Javax.Swing library, which provides the latest ‘lightweight’ components. The GUI itself is a series of panels arranged using the Java built-in gridBag layout manager - the most flexible of the layout managers available in Java. Different colour schemes and customised button designs are being experimented with to ensure the highest possible standard of aesthetic quality. 28/02/2002 9 MSc Computing Science Group Project Piano Moving - Report II The functionality of the GUI class, and the classes it contains are briefly explained below: GUI class: As mentioned above, this is the main container class for the game providing much of the functionality and all of the interfaces between the user and the game. It is the main communicator between the three other main classes (Board, History and AI) and thus has a number of methods to facilitate this communication. For example, if the ‘Redo’ button is pressed, the GUI retrieves from the History class the correct list of moves to be replayed. The GUI class is a container for the following classes. ButtonPanel class: This defines the main control buttons for the game. These include, Restart, Redo, Undo, Replay and Solve buttons. PuzzlePanel class: This is a container panel for the ShapePanel Class. ShapePanel class: This class acts as the main graphical interface for the game. It is responsible for screen drawing of the blocks initially and during block motion and the motion including illegal move restriction of the blocks during a game. During its construction, the list of blocks defined by the current game is obtained from the Board class as a vector. This vector is iterated through and each block is drawn to the display. Its methods such as mouseDragged() and mouseReleased() enable the Board and History classes to be updated when a move is made. StatusPanel class: This StatusPanel class provides the user with in-game information. Its current capabilities include providing the score through an interface with the History class, a timer, which starts once the first move is made, and a miniature representation of the goal of the game. It also shows the current game level. CustomPanel class: The contents of this panel vary depend on the action required by the user. At present it provides control of speed for the replay function. Once completed, this panel will also provide the user with the ability to design their own games through dragging and dropping a selection of blocks from this panel to the game display. MessagePanel class: This class implements the panel that provides the user with important messages before and during the game. It is implemented so that user actions trigger the display of the appropriate message. GUIJMenuBar class: This class implements a fully functional menu system (including short cut keys), providing alternative access to all the main game functions, such as starting a new game, solving or saving a game. On completion, this menu will also provide a help function. The following work remains to be completed on the GUI: Adding to the custom panel functionality for game design. Help function. Enable scoring. Game saving. 28/02/2002 10 MSc Computing Science Group Project Piano Moving - Report II The AI Package Goal of the AI section There are two primary benefits for the addition of the functionality of the program being able to solve puzzles. The first is to be able to determine whether any given puzzle is in fact solvable, and thus whether it is worth the user making an attempt at solving the puzzle. An unsolvable board would not be an enjoyable experience for the user especially after time had already been spent attempting a game. A puzzle solving routine would be particularly useful for boards that are created by the user for which a solution is not guaranteed. With such puzzles, before the user exerts any effort into its solution, the routine could determine their solvability and the user knows they are shooting for a real goal. Knowledge of the existence of a solution does not help with the frustration of not being able to ‘see’ the solution, and the likely doubt that the puzzle is in fact unsolvable. It is in this that the second purpose of an AI routine lies. A user at the end of his tether, either believing that the puzzle is not solvable, or that the number of moves for the alleged solution is underestimated, can in essence ask the game for the solution of the puzzle presented to it. The solution can then be replayed for the user easing any previous suspicion of the puzzles solvability. In addition to these main benefits, having the ability to solve the puzzles means that a scoring system can be put in place, comparing users’ scores to those known to be possible. Finally, it may even be possible to use the AI to offer the user hints when required. The feasibility of this functionality is not presently known. Specification of the AI section The goals of the AI element are to be able to solve the puzzle, and present that solution back to the user. Therefore, a history charting the sequence of moves that takes the board from its current state to one where the winning conditions have been met must be returned. However, in addition to this fundamental requirement, there are some limitations that must be taken into account. Unlike most elements of a simple program, the resources offered by a computer can be a genuine limiting factor for this kind of operation. Whilst text manipulation requires little memory and few CPU cycles, the operation of an AI algorithm can take up a great deal of system resources, and may be an open-ended problem using up system resources till they are exhausted. Therefore, the management of system resources must be seriously considered. In other words, the procedures should minimise memory usage, and perhaps CPU time used. The OS scheduler and the JRE scheduler manage the latter, but the memory constraint can be a problem. These two tasks are not independent of each other. On the one hand, minimization of time used for this complex operation is required. However an increase in speed of operation of the routine would require larger usage of memory. Therefore a balance must be struck between the profligate use of memory, which can speed up a program, and conservation, which may result in increased operation time leaving the user bored, and tired of the game while waiting for a solution to be found by the AI algorithm. 28/02/2002 11 MSc Computing Science Group Project Piano Moving - Report II For purposes of performance comparisons, the AI routine for solving puzzles has been implemented in Java and Prolog. Java Implementation The AI routine is presented with the current state of the board. It must then determine whether the current state fulfils the goal criteria. If the goal has not been reached, then it derives all possible child states of the current state – possible states of the board reachable from the current state by exactly one legal move, and then choose a new state to evaluate. It repeats this process until a solution is found, or possible states for evaluation are exhausted. There are different ways of choosing a state to evaluate from the list of possible child states. Depth First The depth first algorithm is a blind algorithm that is one of the simplest in AI. It is consequently quite fast, but very inefficient. The particular implementation used is a recursive variant on the standard depth first search called bounded depth first. An iterative one may be implemented to economise on memory. Its procedure is as follows: Having been presented with a board, the routine first tests whether the maximum depth has been reached. A maximum depth setting prevents the algorithm from pursuing an unprofitable path to infinite depth. Should the maximum depth be attained, the procedure returns without any further evaluation, thus dropping to one level lower, where a new state can be derived. Assuming that the current state is not at or beyond the maximum depth, the routine then tests if the state has been reached before. If it has, then there is no reason to continue on this path, unless the current path has a lower cost than a state already. In order to determine this, a list of visited states and the cost of reaching them is maintained with all newly evaluated states added to that list. Should a state be found that is already on the list, but is cheaper to than that on the list, the more expensive state is replaced on the list by the newly found state. Also all the child states are derived again with their new costs. In this way, a fully admissible algorithm is guaranteed. Following this, possible moves for the current board are generated, and made one at a time on the board. After each one is made, an attempt is made to solve that board. Only on success does execution continue. Assuming that the solution attempt returns negative, the move made is undone, and another one tried. This process is repeated until no more possible moves are available for the current state of the board. Once this occurs, the procedure returns control to its caller with a negative result. Through the combination of recursive function calls and immediate board evaluation, a backtracking bounded depth first algorithm is implemented. In addition to this, should the search return negative to the very top level, the maximum depth will be increased, and another attempt made. Because this incrementally bounded depth first search will search the entire state space to a certain depth until it finds a solution or runs out of states, it is effectively a form of breadth first search, which is guaranteed 28/02/2002 12 MSc Computing Science Group Project Piano Moving - Report II to locate the optimal solution, as long as the granularity of the depth increment is set appropriately. Breadth First The best first algorithm is an attempt to reduce the number of states evaluated to the minimum possible. An ideal search would visit the number of states exactly equal to the minimum number of moves required to achieve a goal state, indicating that the algorithm chose the 'right' move every time. In practice, this is rarely achieved. A measure of performance is penetrance, which is evaluated as the number of steps taken to reach the current state divided by the total number of evaluated states. The closer to 1 this number is, the better the algorithm. Currently, the best first algorithm achieves a penetrance of approximately 5% on mid-range problems. The best first algorithm functions in as much the same way as the depth first algorithm. However, rather than the instant evaluation of new states, they are added to a list of states to be evaluated, and then the 'best' of these is chosen to be evaluated next. Thus, once the coarse mechanism for this procedure is in place, it is very easy to add in additional elements to the heuristic, which selects the next best state to achieve better penetrance. Currently, the best first algorithm uses a combination of two factors to determine the best state to evaluate next. Firstly, Manhattan distance, the distance of the target block from its goal, the smaller this value the earlier it is evaluated, and secondly, the cost to achieve a state, the number of moves required to reach the state. States that are known to be achievable by a cheaper route are dropped from the open list of sites to be evaluated. Analysis The depth first algorithm is inefficient, but simple. The bounded depth first variation is also guaranteed to find the optimal solution i.e. it is a fully admissible algorithm, only if the increment of the maximum depth is one. This results in a very inefficient and slow operation of the algorithm. This sluggishness typifies the depth first algorithm, and it is only included as an example of how a very basic algorithm works. Its only use at this stage is verification of the optimal result with other search strategies. The use of recursion as a means of implementing backtracking is an elegant method of executing this algorithm but it also limits the effectiveness of the procedure as it makes very inefficient use of memory, saving the complete state at each change of stack frame. Iteration might be a more memory efficient implementation but any efficiency gains may be offset by the performance reduction of a backtracking system. The only piece of real intelligence in the depth first algorithm is the loop trapping mechanism, which has to catch states that have already been visited, and also determine the cheapest route to a state. This minimal intelligence improves 28/02/2002 13 MSc Computing Science Group Project Piano Moving - Report II performance, but the 'thoroughness' of the search means that this approach uses a large amount of memory and time. Conversely, the best first algorithm is much faster, and more memory efficient. Currently, it evaluates approximately half the states of the depth first approach. However, with the addition of extra heuristics for the choice of states to evaluate, that number could be decreased further. The improvement of the speed of search justifies the possibility that the solution may not be optimal. The following table compares the results: Board difficulty Best first Bounded depth first (non optimising) Test 7 moves 7 moves 0.099s 0.658s Easy 46 moves 39 moves 2.045s 13.348s Medium 118 moves 112 moves 18.306s 124.989s Hard N/A N/A The absolute times are dependent on the computer used but the relations them are representative. Currently, the hard puzzle is beyond the reach of either of the algorithms, being too complex for either to get a solution before running out of memory. The finished AI should definitely be able to solve the hard puzzle. This is one goal we are working towards. Strongly tied to this is the development of further heuristics for the best first search. An increase in the penetrance would result in a decrease in the amount of memory used can be reduced, increasing the possibility of solving the hard puzzle. Heuristics such as the proximity of the small blocks or the empty spaces to the target block are being investigated. Prolog Implementation As a goal-oriented language, Prolog is a natural choice for solving puzzles such as the Piano-Moving puzzle. The SICStus Prolog implementation, which is used, includes a set of Java classes (package Jasper) through which it is possible to initialise the SICStus emulator, load Prolog code, construct and execute queries from a Java application. 28/02/2002 14 MSc Computing Science Group Project Piano Moving - Report II Program Structure The Prolog program is split logically into two parts: the search algorithm and the game rules. The game rules are a set of predicates that encapsulate the rules governing the transition of the board from one state to another - a block may only move into an empty adjacent space. The rules also maintain the integrity of the game state representation. They are common to all search strategies, and as such are located in a separate module that is compiled and loaded dynamically when the search file is loaded. Search Algorithm At present the following search methods have been implemented in Prolog – breadth first and best first. In breadth first search, two lists of game states are maintained: a list of nodes that have already been visited (closed list), and a list of nodes that are waiting to be expanded (open list), ordered by age, with the oldest node first. (A node represents a game state, described by the position of the target block and the positions of the remaining blocks. Additionally, for the program to provide useful output, each node must maintain a record of the moves made to reach it.) Initially, the open list contains just one node describing the initial game state, whilst the closed list is empty. The nodes in the open list are expanded in turn, and all child nodes are added to the end of the open list, provided that they do not already occur in either the open list or the closed list. Meanwhile the node that was expanded is removed from the open list and added to the closed list. This procedure ensures each state is only visited once, and avoids much unnecessary search. The best first search is an adaptation of the breadth first search. In this case each node is given a score, reflecting its estimated potential, and the open list is ordered using this score so that the most promising node is expanded first. The score used is the Manhattan Distance of the target block (i.e. the sum of the horizontal and vertical distances from the target position), and states with a lower Manhattan Distance are given preference. In addition to these two search types, it is intended to implement a bounded depth first search. In the initial stages of the project, a very simple program was written that would take advantage of Prolog's in-built backtracking to conduct such a search. However, it was found that although this solved a simplified test game, it was unable to solve the easy puzzle, because no information was kept about states on failed paths, and as a result states were unnecessarily expanded many times on other branches of the search. To avoid this problem, a further adaptation of the breadth first search will be implemented, with the open list ordered again by age, but this time with the youngest node first. The search will be bounded to avoid extremely long paths (and stack overflow errors!), and precautions will be taken so that states that have been visited before but at a greater cost (i.e. number of moves from the initial game state) are not eliminated. 28/02/2002 15 MSc Computing Science Group Project Piano Moving - Report II Comparison of Search Strategies The table below shows the relative performance of the breadth first and best first search programs for the easy and medium puzzles. As yet neither has been able to solve the hard puzzle. Puzzle Visited States Moves in Solution Time Path Easy Breadth first 7193 39 2 minutes Best first 1659 46 2 secs Medium Breadth first 23809 112 24 minutes Best first 7709 136 40 secs Times shown are for queries executed directly from the SICStus Prolog console on an AMD Athlon 1.4GHz processor. Java Interface Fig 2 shows an OMT diagram for the Java classes that provide the interface between the main Java application and the Prolog code. When the user initiates a Prolog search, a new object of type AIPrologBreadthFirst, AIPrologBestFirst or AIPrologDepthFirst is created as appropriate. Theses are all subclasses of the abstract data type AIProlog. Upon creation the object instantiates the SICStus emulator, loads the appropriate Prolog code, and initialises the query parameters using parameters passed to its constructor. The solve method is then called to execute the query. One potential problem that needs to be tackled is how to halt a query once it has been initiated - at present it is necessary to kill the entire application in order to exit from a long search (for example for the hard puzzle or maybe a custom board). 28/02/2002 16 MSc Computing Science Group Project Piano Moving - Report II SICStus Prolog Jasper classes board AIProlog SICStus SPTerm targetBlock spEngine blockList solve(): History winningCond solutionPath statesVisited SPPredicate noMoves solve bound AIPrologBreadthFi rst AIPrologBestFirst AIPrologDepthFirs t $filename: String $filename: String $filename: String solve(): History solve(): History solve(): History FIG 2: OMT Diagram for Prolog/Java Interface 28/02/2002 17