/** * Saves the passed collection to the passed file. * * @param levelCollection the level collection to be saved * @param fileName the file the level is to be saved to * @throws IOException thrown when the level couldn't be saved */ public final void saveCollection(LevelCollection levelCollection, String fileName) throws IOException { // Create a PrintWriter for writing the data to hard disk. PrintWriter collectionFile = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); // Write the collection data if (!levelCollection.getTitle().isEmpty()) { collectionFile.println("Title: " + levelCollection.getTitle()); } Author collectionAuthor = levelCollection.getAuthor(); if (!collectionAuthor.getName().equals(Texts.getText("unknown"))) { collectionFile.println("Author: " + collectionAuthor.getName()); } if (!collectionAuthor.getEmail().isEmpty()) { collectionFile.println("Email: " + collectionAuthor.getEmail()); } if (!collectionAuthor.getWebsiteURL().isEmpty()) { collectionFile.println("Homepage: " + collectionAuthor.getWebsiteURL()); } if (!collectionAuthor.getComment().isEmpty()) { collectionFile.println("Author comment: " + collectionAuthor.getComment()); } if (!levelCollection.getComment().isEmpty()) { collectionFile.println(levelCollection.getComment()); } // Loop over all levels of the collection and write their data to the file. // If the author of the level is identical to the collection author // then don't write the author data for such levels. for (Level level : levelCollection) { Author levelAuthor = level.getAuthor(); if (levelAuthor.equals(collectionAuthor)) { level.setAuthor(new Author()); } writeLevelToFile(level, collectionFile); level.setAuthor(levelAuthor); } // Check the error status. boolean isFileSavingFailed = collectionFile.checkError(); // Close the file. collectionFile.close(); // Throw exception in the case of an error. if (isFileSavingFailed) { throw new IOException(Texts.getText("errorBySaving")); } }
/** * 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; }
/** * Saves the passed level using the passed file name. * * @param level the <code>Level</code> to save * @param fileName the file the level is to be saved to * @throws IOException thrown when the level couldn't be saved */ public final void saveLevel(Level level, String fileName) throws IOException { // Create a PrintWriter for writing the data to hard disk. PrintWriter levelFile = new PrintWriter(fileName); // Write the level data to the file. writeLevelToFile(level, levelFile); // Check the error status. boolean isFileSavingFailed = levelFile.checkError(); // Close the file. levelFile.close(); // Throw exception in the case of an error. if (isFileSavingFailed) { throw new IOException(Texts.getText("errorBySaving")); } }
/** * Writes the passed level data into the passed file. * * @param level the level to be saved * @param PrintWriter the file to write to */ private final void writeLevelToFile(Level level, PrintWriter file) { // Stores the board data of the level. List<String> boardData; file.println(); file.println(); file.println(level.getTitle()); file.println(); // Get the board data of the level. boardData = level.getBoardData(); // Write the board to the file. for (String boardRow : boardData) { file.println(boardRow); } // Empty line between board and transformation data. file.println(); // Save the transformation. if (!Transformation.getTransformationAsString().isEmpty()) { // Write the transformation string. file.write(Transformation.getTransformationAsString()); // Write empty lines. file.println(); file.println(); } // Write the additional level data. if (level.getComment().length() > 0) { file.println(level.getComment()); } Author author = level.getAuthor(); if (!author.getName().equals(Texts.getText("unknown"))) { file.println("Author: " + author.getName()); } if (author.getEmail().length() > 0) { file.println("Email: " + author.getEmail()); } if (author.getWebsiteURL().length() > 0) { file.println("Homepage: " + author.getWebsiteURL()); } if (author.getComment().length() > 0) { file.println("Author comment: " + author.getComment()); } if (level.getDifficulty().length() > 0) { file.println("Difficulty: " + level.getDifficulty()); } // Save the solution information. SolutionsManager solutions = level.getSolutionsManager(); for (int solutionNo = 0; solutionNo < solutions.getSolutionCount(); solutionNo++) { Solution solution = solutions.getSolution(solutionNo); file.println(); file.println("Solution " + solution.movesCount + "/" + solution.pushesCount); // FFS/hm: also write minor metrics? file.println(solution.lurd); if (solution.name.length() > 0) { file.println("Solution name: " + solution.name); } if (solution.isOwnSolution) { file.println("Own solution: yes"); } if (solution.comment.length() > 0) { file.println("Solution comment: " + solution.comment); file.println("Solution comment end:"); } } // Get the LURD-representation of the history. String historyLURD = level.getHistory().getHistoryAsSaveGame(); // Save the history string if there is any if (!historyLURD.isEmpty()) { file.println(); file.println("Savegame:"); file.println(historyLURD); } }
/** * 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); } } } }