/** * Loads the level collection stored in the passed file. * * @param collectionFilePath path and name of the collection to load * @return the <code>LevelCollection</code> created from the read in data * @throws IOException the collection file couldn't be read */ public final LevelCollection getLevelCollectionFromFile(String collectionFilePath) throws IOException { // ArrayList, for storing the read data. List<String> inputData = new ArrayList<String>(1000); // Create BufferedReader for the input file. BufferedReader levelFile = Utilities.getBufferedReader(collectionFilePath); // The file hasn't been found => return null. if (levelFile == null) { throw new FileNotFoundException(Texts.getText("message.fileMissing", collectionFilePath)); } // Read in line by line of the input data. String levelDataRow; try { while ((levelDataRow = levelFile.readLine()) != null) { inputData.add(levelDataRow); } } finally { levelFile.close(); } // Parse the read data and return the collection created from that data. // The level collection to be returned. LevelCollection levelCollection = dataParser.extractData(inputData, collectionFilePath); // Return the collection. return levelCollection; }
/** * Tries to solve the level by generating all possible no-deadlock board positions and returns the * solution path via a global variable. */ protected final void forwardSearch() { // Hold a box position and the new box position. int boxPosition; int newBoxPosition = 0; // Object for the current board position IBoardPositionMoves currentBoardPositionWithMoves; // The board position to be analyzed further. IBoardPositionMoves boardPositionToBeAnalyzed; // Object only used for avoiding too many casts. IBoardPositionMoves oldBoardPositionWithMoves; // If a board position is reached that has been reached before this object holds // the board position that has been reached before. IBoardPosition oldBoardPosition; // Pushes lower bound of a board position. int currentBoardPositionLowerBound = 0; // Number of moves the player has gone for reaching the current board position. short numberOfMovesSoFar = 0; // Number of moves and pushes of the current best known solution. int numberOfMovesBestSolution = Integer.MAX_VALUE; int numberOfPushesBestSolution = Integer.MAX_VALUE; // Number of the pushed box. int pushedBoxNo = 0; // Number of pushes of the board positions. int numberOfPushesOldBoardPosition = 0; int numberOfPushesCurrentBoardPosition = 0; // Lowest number of moves of a board position in the queue so far. int shortestSolutionPathLengthSoFar = 0; // The board position with the lowest estimated solution path length is analyzed further next. while ((boardPositionToBeAnalyzed = getBestBoardPosition()) != null && isCancelled() == false) { // Set the board position. board.setBoardPosition(boardPositionToBeAnalyzed); // Determine the reachable squares of the player. These squares are used even after // the deadlock detection, hence they are calculated in an extra object. playersReachableSquaresMoves.update(); // Get number of the last pushed box. pushedBoxNo = boardPositionToBeAnalyzed.getBoxNo(); // If no box has been pushed the pushed box number is set to -1, so the tunnel detection isn't // performed. if (pushedBoxNo == NO_BOX_PUSHED) { pushedBoxNo = -1; } // Calculate the number of pushes of the new board positions that are created. numberOfPushesCurrentBoardPosition = boardPositionToBeAnalyzed.getPushesCount() + 1; // Loop over all boxes. The last pushed box is considered first. for (int boxCounter = -1, boxNo; boxCounter < board.boxCount; boxCounter++) { // The last pushed box has already been processed (-> boxCounter = -1) if (boxCounter == pushedBoxNo) { continue; } // The last pushed box is considered first. It is checked for being in a tunnel. if (boxCounter == -1) { boxNo = pushedBoxNo; // If the box is in a tunnel only pushes of this box have to be considered! if (isBoxInATunnel(pushedBoxNo, boardPositionToBeAnalyzed.getDirection())) { boxCounter = board.goalsCount; } } else { boxNo = boxCounter; } // Get the position of the box boxPosition = board.boxData.getBoxPosition(boxNo); // Push the box to every direction possible. for (int direction = 0; direction < 4; direction++) { // Calculate the new box position. newBoxPosition = boxPosition + offset[direction]; // Immediately continue with the next direction if the player can't reach the correct // position for pushing or the new box position isn't accessible. if (playersReachableSquaresMoves.isSquareReachable(boxPosition - offset[direction]) == false || board.isAccessibleBox(newBoxPosition) == false) { continue; } // Do push. board.pushBox(boxPosition, newBoxPosition); board.playerPosition = boxPosition; // Calculate the number of moves so far. numberOfMovesSoFar = (short) (boardPositionToBeAnalyzed.getTotalMovesCount() + playersReachableSquaresMoves.getDistance(boxPosition - offset[direction]) + 1); // Immediately continue with the next direction if the the board position isn't // reached better than the current best solution. if (numberOfPushesCurrentBoardPosition > numberOfPushesBestSolution || numberOfPushesCurrentBoardPosition == numberOfPushesBestSolution && numberOfMovesSoFar >= numberOfMovesBestSolution) { board.pushBoxUndo(newBoxPosition, boxPosition); continue; } // Create object of the current board position. currentBoardPositionWithMoves = new RelativeBoardPositionMoves(board, boxNo, direction, boardPositionToBeAnalyzed); // Try to read the current board position of the hash table. oldBoardPosition = positionStorage.getBoardPosition(currentBoardPositionWithMoves); // If the board position had already been saved in the hash table it must be checked // for being better than the one in the hash table (it may have already been reached // by the corral detection hence there has to be a check for a SearchBoardPosition!) if (oldBoardPosition instanceof IBoardPositionMoves) { // For avoiding too many casts. oldBoardPositionWithMoves = (IBoardPositionMoves) oldBoardPosition; // Calculate the number of pushes of the old board position. numberOfPushesOldBoardPosition = oldBoardPositionWithMoves.getPushesCount(); // If the current board position has been reached better than the one in the // hash table it has to be saved / used instead of the old one. if (numberOfPushesCurrentBoardPosition < numberOfPushesOldBoardPosition || numberOfPushesCurrentBoardPosition == numberOfPushesOldBoardPosition && numberOfMovesSoFar < oldBoardPositionWithMoves.getTotalMovesCount()) { // Save the number of moves in the current board position. currentBoardPositionWithMoves.setMovesCount(numberOfMovesSoFar); // Replace the old board position by the new one in the hash table. positionStorage.storeBoardPosition(currentBoardPositionWithMoves); // The current board position is saved for further analyzing. storeBoardPosition(currentBoardPositionWithMoves); } // Undo push and continue with next direction. board.pushBoxUndo(newBoxPosition, boxPosition); continue; } /* * The board position hasn't already been in the hash table, hence it is a new one. */ currentBoardPositionLowerBound = lowerBoundCalcuation.calculatePushesLowerBound(newBoxPosition); // Undo push (the player is new positioned for the next board position anyway) board.pushBoxUndo(newBoxPosition, boxPosition); // Immediately continue with the next direction if the current board position is a // deadlock // or the estimated solution path length is higher/equal than the best known solution path // length. if (currentBoardPositionLowerBound == LowerBoundCalculation.DEADLOCK || numberOfPushesCurrentBoardPosition == numberOfPushesBestSolution && numberOfMovesSoFar + currentBoardPositionLowerBound >= numberOfMovesBestSolution) { continue; } // Save the number of moves in the current board position. currentBoardPositionWithMoves.setMovesCount(numberOfMovesSoFar); // If a solution has been found the number of moves and the board position itself // are saved. This solution is push optimal, but it needn't to be the one with // best moves, too! if (currentBoardPositionLowerBound == 0) { numberOfMovesBestSolution = numberOfMovesSoFar; numberOfPushesBestSolution = numberOfPushesCurrentBoardPosition; solutionBoardPosition = currentBoardPositionWithMoves; if (Debug.isDebugModeActivated) { System.out.println( "Solution Found " + "Moves/Pushes: " + currentBoardPositionWithMoves.getTotalMovesCount() + "/" + currentBoardPositionWithMoves.getPushesCount()); } continue; } // Calculate the number of no deadlock board positions reached during the search. boardPositionsCount++; // Display info about the search (every 5000 board positions and every time the search // depths has been increased) if (boardPositionsCount % 5000 == 0 || shortestSolutionPathLengthSoFar != shortestSolutionPathLength) { if (shortestSolutionPathLengthSoFar != shortestSolutionPathLength) { shortestSolutionPathLengthSoFar = shortestSolutionPathLength; } publish( Texts.getText("numberofpositions") + boardPositionsCount + ", " + Texts.getText("searchdepth") + shortestSolutionPathLength + " " + Texts.getText("moves")); // Throw "out of memory" if less than 15MB RAM is free. if (Utilities.getMaxUsableRAMinMiB() <= 15) { isSolverStoppedDueToOutOfMemory = true; cancel(true); } } // Save the board position in the hash table for further searching. positionStorage.storeBoardPosition(currentBoardPositionWithMoves); storeBoardPosition(currentBoardPositionWithMoves); } } } }