com.thesett.aima.search.examples.eightpuzzle.EightPuzzleState Maven / Gradle / Ivy
Show all versions of aima Show documentation
/*
* Copyright The Sett Ltd, 2005 to 2014.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.thesett.aima.search.examples.eightpuzzle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.thesett.aima.search.GoalState;
import com.thesett.aima.search.Operator;
import com.thesett.aima.search.TraversableState;
import com.thesett.aima.search.util.OperatorImpl;
/**
* An implementation of the eight puzzle. This is a puzzle with nine squares arranged in a 3x3 grid. One of the squares
* is empty and the others contain tiles with the numbers 1 to 8 on them, initially randomly arranged. The object of the
* game is to slide tiles into the empty square until all the tiles are arranged in order, reading left to right, top to
* bottom, with the empty square in the last position.
*
* For example (E denotes the empty square):
*
*
Random start state:
*
*
* 5 E 3
* 6 1 7
* 8 2 4
*
*
* Goal state: * *
1 | 2 | 3 * |
4 | 5 | 6 * |
7 | 8 | E * |
Instances of this state class represent a single board position. This class also provides for each state, a set of * operators corresponding to the legal moves on a state, that result in new legal board states. * *
For efficient matching of states this state representation provides the {@link #equals} and {@link #hashCode} * methods. The hash function is computed by transforming the board position into a string consisting of its tiles in * order (using the numeric characters plus 'E' for the empty square) and then taking the string hash of this. * *
Their are four possible moves; up, down, left and right. These correspond to moving the empty tile up, down, left * and right and are represented by the operation strings "U", "D", "L" and "R". * *
There is a static convenience method that generates a random solvable board position. Not all board positions are * solvable. Unsolvable positions can get close to a solution but will end up with two neighbouring tiles that need to * be swapped but can't be. Tiles can only swap position with the empty tile. There are effectively two sets of board * states for each puzzle, the legal states and the illegal ones. To get from one state set to the other simply swap two * adjacent non-empty tiles. This is called an illegal swap. To check for solvability the number of illegal swaps needed * to get to the goal position are counted. If this is even then they cancel out so the board position belongs to the * set of legal states. If its odd then they don't cancel out so the board belongs to the set of illegal states and is * not solvable with respect to the goal position. * *
Responsibilities | Collaborations * |
---|---|
Generate a random starting position that is solvable. * | |
Generate a state corresponding to the goal state. * | |
Check if a board position is the goal state. * | |
Supply the valid moves for a board position. * | |
Calculate the cost of a move (always one). * | |
Apply a move to generate a new board position. * | |
Report the goal coordinates for a tile. * | |
Report the coordinates for a tile. * | |
Report the position of the empty tile. * | |
Report the tile at a given position. * | |
Supply equals and hashCode for efficient hashing on puzzle states. * |
Another hashing strategy that could be tried is simply to concatenate all the tiles together into a decimal * number modulo the maximum integer. * * @return A hash code for this board position calculated from the position information. */ public int hashCode() { return toString().hashCode(); } /** * Checks if two board positions are equal. This is computed by using the toString method to concatenate all the * tiles togheter into a string and then using string equality. * *
A faster solution may simply be to iterate over the two boards until a mismatch is found. If there is no * mismatch then they are equal. * * @param o The object to compare this to. * * @return True if the comparison object is a board position identical to this one, false otherwise. */ public boolean equals(Object o) { return toString().equals(o.toString()); } /** * Converts the board position into a string representation by walking over the board left-to-right, top-to-bottom * using 'E' for the empty tile and the number characters for the numbered tiles. * * @return A string representation of this board position. */ public String toString() { char[] result = new char[9]; int resultCounter = 0; for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { result[resultCounter++] = board[j][i]; } } return new String(result); } /** * Pretty prints the board as 3 lines of characters with a space for the empty square. * * @return A pretty printed string representation of this board position. */ public String prettyPrint() { String result = ""; for (int j = 0; j < 3; j++) { result += new String(board[j]) + "\n"; } result = result.replace('E', ' '); return result; } /** * Repeatedly swaps a tile with its neighbours until it reaches the specified location. If the tile is swapped with * the empty tile then this is a legal move. If the tile is swapped with another non-empty tile then this is an * illegal move and the total number of illegal moves is counted. * *
This method updates the board position array in-place, rather than generating a new state.
*
* @param t the tile to move.
* @param x the X position to move the tile to.
* @param y the Y position to move the tile to.
*
* @return the number of illegal swaps performed.
*/
protected int swapTileToLocationCountingIllegal(char t, int x, int y)
{
// Used to hold the count of illegal swaps
int illegal = 0;
// Find out where the tile to move is
int tileX = getXForTile(t);
int tileY = getYForTile(t);
// Shift the tile into the correct column by repeatedly moving it left or right.
while (tileX != x)
{
if ((tileX - x) > 0)
{
if (swapTiles(tileX, tileY, tileX - 1, tileY))
{
illegal++;
}
tileX--;
}
else
{
if (swapTiles(tileX, tileY, tileX + 1, tileY))
{
illegal++;
}
tileX++;
}
}
// Shift the tile into the correct row by repeatedly moving it up or down.
while (tileY != y)
{
// Commented out because tiles never swap down the board during the solvability test because tiles are
// swapped into place left to right, top to bottom. The top row is always filled first so tiles cannot be
// swapped down into it. Then the next row is filled but ones from the row above are never swapped down
// into it because they are alrady in place and never move again and so on.
/* if (tileY - y > 0)
*{*/
if (swapTiles(tileX, tileY, tileX, tileY - 1))
{
illegal++;
}
tileY--;
/*}
* else { if (swapTiles(tileX, tileY, tileX, tileY + 1)) illegal++; tileY++;}*/
}
return illegal;
}
/**
* Applies a move to the board position. This changes the board position stored in this object. This is different
* from the {@link #getChildStateForOperator} method which updates the board position in a new object.
*
* @param op The move to apply to this board; "U" for up, "D" for down, "L" for left and "R" for right.
*/
protected void updateWithOperator(Operator op)
{
// Get the operator as a character by taking the first character of the operator string
char opc = ((String) op.getOp()).charAt(0);
// Move the empty tile according to the specified operation
switch (opc)
{
// Swap the empty tile with the one above it.
case 'U':
{
swapTiles(emptyX, emptyY, emptyX, emptyY - 1);
break;
}
// Swap the empty tile with the one below it.
case 'D':
{
swapTiles(emptyX, emptyY, emptyX, emptyY + 1);
break;
}
// Swap the empty tile with the one to the left of it.
case 'L':
{
swapTiles(emptyX, emptyY, emptyX - 1, emptyY);
break;
}
// Swap the empty tile with the one to the right of it.
case 'R':
{
swapTiles(emptyX, emptyY, emptyX + 1, emptyY);
break;
}
default:
{
throw new RuntimeException("Unkown operator: " + opc + ".");
}
}
}
/**
* Swaps the two tiles at the specified coordinates. One of the tiles may be the empty tile and the empty tile
* position will be correctly updated. If neither of the tiles is empty then this is an illegal swap in which case
* the method returns true.
*
* @param x1 the X position of tile 1
* @param y1 the Y position of tile 1
* @param x2 the X position of tile 2
* @param y2 the Y position of tile 2
*
* @return True if it is an illegal swap.
*/
protected boolean swapTiles(int x1, int y1, int x2, int y2)
{
// Used to indicate that one of the swapped tiles was the empty tile
boolean swappedEmpty = false;
// Get the tile at the first position
char tile1 = board[y1][x1];
// Store the tile from the second position at the first position
char tile2 = board[y2][x2];
board[y1][x1] = tile2;
// Store the first tile in the second position
board[y2][x2] = tile1;
// Check if the first tile was the empty tile and update the empty tile coordinates if so
if (tile1 == 'E')
{
emptyX = x2;
emptyY = y2;
swappedEmpty = true;
}
// Else check if the second tile was the empty tile and update the empty tile coordinates if so
else if (tile2 == 'E')
{
emptyX = x1;
emptyY = y1;
swappedEmpty = true;
}
return !swappedEmpty;
}
/**
* Creates a deep clone of the puzzle state. That is one where the board position is copied into a new array. The
* empty tile position is also copied.
*
* @return A deep clone of this object.
*
* @throws CloneNotSupportedException If cloning fails.
*/
protected Object clone() throws CloneNotSupportedException
{
// Create a new state and copy the existing board position into it
EightPuzzleState newState = (EightPuzzleState) super.clone();
newState.board = new char[3][3];
for (int j = 0; j < 3; j++)
{
System.arraycopy(board[j], 0, newState.board[j], 0, 3);
}
newState.emptyX = emptyX;
newState.emptyY = emptyY;
return newState;
}
/**
* Turns a string representation of the board into a list of characters.
*
* @param boardString The board as a string of nine characters.
*
* @return The board as a List of characters.
*/
private static List