package search; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; /** * Eight Queens Implementation * * CS 373 - AI * Assignment #1 - Search * * @author Joey Kiernan and Nathaniel Lim * @version 1.0 * @date 2/18/10 * */ public class EightQueensPuzzle implements ProblemGraph { public EightQueensPuzzle (){ } @Override public EQState getState(String stateString) { return new EQState(stateString); } public class EQState implements State { public static final int BLANK = -1; public static final int QUEEN = 1; private int[][] board = new int[8][8]; private int[] queenPos = new int[8]; //board: 2D array to be able to check for conflicts //queenPos: 1D array to be able to quickly find the position //of a queen in a given row. // All spaces are initialized as BLANK! // board[rownum][colnum] /* *These two are for construction of subsequent states */ public int[][] getBoard(){ return board; } public int[] getQueenPos(){ return queenPos; } public EQState(EQState eqState, EightQueenPuzzleAction a) { //Cloning a 2d array doesn't work so need to manually do it board = new int[eqState.getBoard().length][eqState.getBoard().length]; for(int row=0; row<board.length; row++){ for(int col=0; col<board[0].length; col++){ board[row][col]=eqState.getBoard()[row][col]; } } queenPos = eqState.getQueenPos().clone(); for(int i=0; i<queenPos.length; i++){ if(queenPos[i] == BLANK){ queenPos[i] = a.getRow(); board[a.getRow()][i] = QUEEN; return; } } //if we get through the whole loop (i.e. all columns are full) //then throw an error throw new RuntimeException("Cannot generate new state, board full"); } public EQState(String configurationString){ //Parsing a configurationString and returning a EQState for(int row=0; row<board.length; row++){ for(int col=0; col<board[0].length; col++){ board[row][col]= BLANK; } } for(int i=0; i<queenPos.length; i++){ queenPos[i] = BLANK; } StringTokenizer tokens = new StringTokenizer(configurationString, "() "); if(tokens.countTokens() > 8){ throw new RuntimeException("The string: " + configurationString + " is not a valid Eight Queens state string."); } int numTokens = tokens.countTokens(); for (int i = 0; i < numTokens; i++){ String str = tokens.nextToken(); int val = Integer.decode(str); if (val > 8 || val < 1){ throw new RuntimeException("The string: " + configurationString + " is not a valid 8Queens state string. The value " + val + " is not a valid entry."); } else { board[val-1][i] = QUEEN; queenPos[i] = val-1; } } } public String toString(){ String out = "("; for (int i = 0; i < 8; i++){ if (queenPos[i] != BLANK){ out += "" + (queenPos[i] + 1) + " "; } else { break; } } out += ")"; return out; } public List<GraphNode> expandNode() { List<GraphNode> list = new ArrayList<GraphNode>(); for (EightQueenPuzzleAction a : EightQueenPuzzleAction.values()) { if (this.isValidAction(a)) { EQState newNode = new EQState(this, a); list.add(new GraphNode(newNode)); } } return list; } private boolean isValidAction(EightQueenPuzzleAction a) { EQState potential = new EQState(this, a); return isValidBoard(potential.getBoard()); } /** *Enourmous method that calculates if any Queen is in conflict *with any other based on a 2d board of BLANKS and QUEENS */ private boolean isValidBoard(int[][] board){ //check rows for(int row = 0; row < board.length; row++){ int numQueens = 0; for(int col = 0; col < board[0].length; col++){ if(board[row][col] == QUEEN){ numQueens++; } } if(numQueens > 1){ return false; } } //we know columns are ok based on action generation //so now check diagonals //left-to-right //starting with leftmost column for(int counter=0; counter < board.length; counter++){ int row = counter; int numQueens = 0; int col = 0; while(row >= 0){ if(board[row][col]==QUEEN){ numQueens++; } col++; row--; } if(numQueens > 1){ return false; } } //left-to-right //starting with bottom row for(int counter=0; counter < board[0].length; counter++){ int col = counter; int numQueens = 0; int row = 7; while(col <= 7){ if(board[row][col]==QUEEN){ numQueens++; } col++; row--; } if(numQueens > 1){ return false; } } //right-to-left //starting with rightmost column for(int counter=0; counter < board.length; counter++){ int row = counter; int numQueens = 0; int col = 7; while(row >= 0){ if(board[row][col]==QUEEN){ numQueens++; } col--; row--; } if(numQueens > 1){ return false; } } //right-to-left //starting with bottom row for(int counter=0; counter < board[0].length; counter++){ int col = counter; int numQueens = 0; int row = 7; while(col >= 0){ if(board[row][col]==QUEEN){ numQueens++; } col--; row--; } if(numQueens > 1){ return false; } } return true; } @Override public int h() { //Return the number of queens left to be placed on the board //This is an underestimate, usually of the number of steps left. int numQueens = 0; for (int i = 0; i < queenPos.length; i++){ if (queenPos[i] != BLANK){ numQueens++; } } return 8-numQueens; } @Override public boolean isGoal() { for(int i=0; i<queenPos.length; i++){ if(queenPos[i] == BLANK){ return false; } } return isValidBoard(board); } //Two States are equal if their queenPos arrays are equal //or if they reference the same Object. public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final EQState other = (EQState) obj; if (!Arrays.equals(queenPos, other.queenPos)) return false; return true; } public int hashcode(){ final int PRIME = 31; return PRIME + Arrays.hashCode(queenPos); } } /* * An Enum class to enumerate the actions available to * trying to solve Eight Queens. Tells you what row to place * the queen into, and the action later decides the column. */ enum EightQueenPuzzleAction { MOVEQ1(0), MOVEQ2(1), MOVEQ3(2), MOVEQ4(3), MOVEQ5(4), MOVEQ6(5), MOVEQ7(6), MOVEQ8(7); private final int row; private EightQueenPuzzleAction(int row){ this.row = row; } /** * The change in the x coordinate of the blank space under the action * @return integer x coordinate delta */ public int getRow(){ return row; } } }