Average Density: 0.05
  1 package nl.tudelft.jpacman.level;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.io.InputStreamReader;
  7 import java.util.ArrayList;
  8 import java.util.List;
  9 
 10 import nl.tudelft.jpacman.PacmanConfigurationException;
 11 import nl.tudelft.jpacman.board.Board;
 12 import nl.tudelft.jpacman.board.BoardFactory;
 13 import nl.tudelft.jpacman.board.Square;
 14 import nl.tudelft.jpacman.npc.Ghost;
 15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 16 
 17 /**
 18  * Creates new {@link Level}s from text representations.
 19  *
 20  * @author Jeroen Roosen
 21  */
 22 public class MapParser {
 23 
 24     /**
 25      * The factory that creates the levels.
 26      */
 27     private final LevelFactory levelCreator;
 28 
 29     /**
 30      * The factory that creates the squares and board.
 31      */
 32     private final BoardFactory boardCreator;
 33 
 34     /**
 35      * Creates a new map parser.
 36      *
 37      * @param levelFactory
 38      *            The factory providing the NPC objects and the level.
 39      * @param boardFactory
 40      *            The factory providing the Square objects and the board.
 41      */
 42     public MapParser(LevelFactory levelFactory, BoardFactory boardFactory) {
 43         this.levelCreator = levelFactory;
 44         this.boardCreator = boardFactory;
 45     }
 46 
 47     /**
 48      * Parses the text representation of the board into an actual level.
 49      *
 50      * <ul>
 51      * <li>Supported characters:
 52      * <li>' ' (space) an empty square.
 53      * <li>'#' (bracket) a wall.
 54      * <li>'.' (period) a square with a pellet.
 55      * <li>'P' (capital P) a starting square for players.
 56      * <li>'G' (capital G) a square with a ghost.
 57      * </ul>
 58      *
 59      * @param map
 60      *            The text representation of the board, with map[x][y]
 61      *            representing the square at position x,y.
 62      * @return The level as represented by this text.
 63      */
 64     public Level parseMap(char[][] map) {
 65         int width = map.length;
 66         int height = map[0].length;
 67 
 68         Square[][] grid = new Square[width][height];
 69 
 70         List<Ghost> ghosts = new ArrayList<>();
 71         List<Square> startPositions = new ArrayList<>();
 72 
 73         makeGrid(map, width, height, grid, ghosts, startPositions);
 74 
 75         Board board = boardCreator.createBoard(grid);
 76         return levelCreator.createLevel(board, ghosts, startPositions);
 77     }
 78 
 79     private void makeGrid(char[][] map, int width, int height,
 80                           Square[][] grid, List<Ghost> ghosts, List<Square> startPositions) {
 81         for (int x = 0; x < width; x++) {
 82             for (int y = 0; y < height; y++) {
 83                 char c = map[x][y];
 84                 addSquare(grid, ghosts, startPositions, x, y, c);
 85             }
 86         }
 87     }
 88 
 89     /**
 90      * Adds a square to the grid based on a given character. These
 91      * character come from the map files and describe the type
 92      * of square.
 93      *
 94      * @param grid
 95      *            The grid of squares with board[x][y] being the
 96      *            square at column x, row y.
 97      * @param ghosts
 98      *            List of all ghosts that were added to the map.
 99      * @param startPositions
100      *            List of all start positions that were added
101      *            to the map.
102      * @param x
103      *            x coordinate of the square.
104      * @param y
105      *            y coordinate of the square.
106      * @param c
107      *            Character describing the square type.
108      */
109     protected void addSquare(Square[][] grid, List<Ghost> ghosts,
110                              List<Square> startPositions, int x, int y, char c) {
111         switch (c) {
112             case ' ':
113                 grid[x][y] = boardCreator.createGround();
114                 break;
115             case '#':
116                 grid[x][y] = boardCreator.createWall();
117                 break;
118             case '.':
119                 Square pelletSquare = boardCreator.createGround();
120                 grid[x][y] = pelletSquare;
121                 levelCreator.createPellet().occupy(pelletSquare);
122                 break;
123             case 'G':
124                 Square ghostSquare = makeGhostSquare(ghosts, levelCreator.createGhost());
125                 grid[x][y] = ghostSquare;
126                 break;
127             case 'P':
128                 Square playerSquare = boardCreator.createGround();
129                 grid[x][y] = playerSquare;
130                 startPositions.add(playerSquare);
131                 break;
132             default:
133                 throw new PacmanConfigurationException("Invalid character at "
134                     + x + "," + y + ": " + c);
135         }
136     }
137 
138     /**
139      * creates a Square with the specified ghost on it
140      * and appends the placed ghost into the ghost list.
141      *
142      * @param ghosts all the ghosts in the level so far, the new ghost will be appended
143      * @param ghost the newly created ghost to be placed
144      * @return a square with the ghost on it.
145      */
146     protected Square makeGhostSquare(List<Ghost> ghosts, Ghost ghost) {
147         Square ghostSquare = boardCreator.createGround();
148         ghosts.add(ghost);
149         ghost.occupy(ghostSquare);
150         return ghostSquare;
151     }
152 
153     /**
154      * Parses the list of strings into a 2-dimensional character array and
155      * passes it on to {@link #parseMap(char[][])}.
156      *
157      * @param text
158      *            The plain text, with every entry in the list being a equally
159      *            sized row of squares on the board and the first element being
160      *            the top row.
161      * @return The level as represented by the text.
162      * @throws PacmanConfigurationException If text lines are not properly formatted.
163      */
164     public Level parseMap(List<String> text) {
165 
166         checkMapFormat(text);
167 
168         int height = text.size();
169         int width = text.get(0).length();
170 
171         char[][] map = new char[width][height];
172         for (int x = 0; x < width; x++) {
173             for (int y = 0; y < height; y++) {
174                 map[x][y] = text.get(y).charAt(x);
175             }
176         }
177         return parseMap(map);
178     }
179 
180     /**
181      * Check the correctness of the map lines in the text.
182      * @param text Map to be checked
183      * @throws PacmanConfigurationException if map is not OK.
184      */
185     private void checkMapFormat(List<String> text) {
186         if (text == null) {
187             throw new PacmanConfigurationException(
188                 "Input text cannot be null.");
189         }
190 
191         if (text.isEmpty()) {
192             throw new PacmanConfigurationException(
193                 "Input text must consist of at least 1 row.");
194         }
195 
196         int width = text.get(0).length();
197 
198         if (width == 0) {
199             throw new PacmanConfigurationException(
200                 "Input text lines cannot be empty.");
201         }
202 
203         for (String line : text) {
204             if (line.length() != width) {
205                 throw new PacmanConfigurationException(
206                     "Input text lines are not of equal width.");
207             }
208         }
209     }
210 
211     /**
212      * Parses the provided input stream as a character stream and passes it
213      * result to {@link #parseMap(List)}.
214      *
215      * @param source
216      *            The input stream that will be read.
217      * @return The parsed level as represented by the text on the input stream.
218      * @throws IOException
219      *             when the source could not be read.
220      */
221     public Level parseMap(InputStream source) throws IOException {
222         try (BufferedReader reader = new BufferedReader(new InputStreamReader(
223             source, "UTF-8"))) {
224             List<String> lines = new ArrayList<>();
225             while (reader.ready()) {
226                 lines.add(reader.readLine());
227             }
228             return parseMap(lines);
229         }
230     }
231 
232     /**
233      * Parses the provided input stream as a character stream and passes it
234      * result to {@link #parseMap(List)}.
235      *
236      * @param mapName
237      *            Name of a resource that will be read.
238      * @return The parsed level as represented by the text on the input stream.
239      * @throws IOException
240      *             when the resource could not be read.
241      */
242     @SuppressFBWarnings(
243         value = {"OBL_UNSATISFIED_OBLIGATION", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"},
244         justification = "try with resources always cleans up / false positive in java 11"
245     )
246     public Level parseMap(String mapName) throws IOException {
247         try (InputStream boardStream = MapParser.class.getResourceAsStream(mapName)) {
248             if (boardStream == null) {
249                 throw new PacmanConfigurationException("Could not get resource for: " + mapName);
250             }
251             return parseMap(boardStream);
252         }
253     }
254 
255     /**
256      * @return the BoardCreator
257      */
258     protected BoardFactory getBoardCreator() {
259         return boardCreator;
260     }
261 }