import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; final class MinesweeperBoard { private static final char MINE_CHAR = '*'; private static final char SPACE_CHAR = ' '; private final List<String> rawRepresentation; private final int numberOfRows; private final int numberOfColumns; MinesweeperBoard(final List<String> rawRepresentation) { validateInputBoard(rawRepresentation); this.rawRepresentation = rawRepresentation; this.numberOfRows = rawRepresentation.size(); this.numberOfColumns = rawRepresentation.isEmpty() ? 0 : rawRepresentation.get(0).length(); } List<String> getAnnotatedRepresentation() throws IllegalArgumentException { final List<String> result = new ArrayList<>(); for (int rowNumber = 0; rowNumber < numberOfRows; rowNumber++) { result.add(getAnnotatedRow(rowNumber)); } return result; } private String getAnnotatedRow(final int rowNumber) { String result = ""; for (int columnNumber = 0; columnNumber < numberOfColumns; columnNumber++) { result += getCellAnnotation(rowNumber, columnNumber); } return result; } private char getCellAnnotation(final int rowNumber, final int columnNumber) { // If (rowNumber, columnNumber) is a mine, we're done. if (rawRepresentation.get(rowNumber).charAt(columnNumber) == MINE_CHAR) { return MINE_CHAR; } final int mineCount = computeMineCountAround(rowNumber, columnNumber); // If computed count is positive, add it to the annotated row. Otherwise, add a blank space. return mineCount > 0 ? Character.forDigit(mineCount, 10) : SPACE_CHAR; } private int computeMineCountAround(final int rowNumber, final int columnNumber) { int result = 0; // Compute row and column ranges to inspect (respecting board edges). final int minRowToInspect = Math.max(rowNumber - 1, 0); final int maxRowToInspect = Math.min(rowNumber + 1, numberOfRows - 1); final int minColToInspect = Math.max(columnNumber - 1, 0); final int maxColToInspect = Math.min(columnNumber + 1, numberOfColumns - 1); // Count mines in the cells surrounding (row, col). for (int rowToInspect = minRowToInspect; rowToInspect <= maxRowToInspect; rowToInspect++) { for (int colToInspect = minColToInspect; colToInspect <= maxColToInspect; colToInspect++) { if (rawRepresentation.get(rowToInspect).charAt(colToInspect) == MINE_CHAR) { result += 1; } } } return result; } private void validateInputBoard(final List<String> inputBoard) throws IllegalArgumentException { validateInputBoardIsNotNull(inputBoard); if (inputBoard.isEmpty()) { return; } validateInputBoardCharacters(inputBoard); validateInputBoardColumnCounts(inputBoard); } private void validateInputBoardIsNotNull(final List<String> inputBoard) throws IllegalArgumentException { if (inputBoard == null) { throw new IllegalArgumentException("Input board may not be null."); } } private void validateInputBoardCharacters(final List<String> inputBoard) throws IllegalArgumentException { final String allBoardCharacters = String.join("", inputBoard); if (!allBoardCharacters.matches("^[ *]*$")) { throw new IllegalArgumentException("Input board can only contain the characters ' ' and '*'."); } } private void validateInputBoardColumnCounts(final List<String> inputBoard) throws IllegalArgumentException { final Set<Integer> setOfColumnCounts = inputBoard.stream().map(String::length).collect(Collectors.toSet()); if (setOfColumnCounts.size() > 1) { throw new IllegalArgumentException("Input board rows must all have the same number of columns."); } } }