Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | daniel-mar | 1 | package gdi1sokoban.game; |
2 | |||
3 | import gdi1sokoban.exceptions.ParameterOutOfRangeException; |
||
4 | import gdi1sokoban.exceptions.ParseException; |
||
5 | import gdi1sokoban.globals.Point; |
||
6 | |||
7 | import java.io.BufferedReader; |
||
8 | import java.io.File; |
||
9 | import java.io.FileReader; |
||
10 | import java.io.IOException; |
||
11 | import java.net.URISyntaxException; |
||
12 | import java.util.ArrayList; |
||
13 | import java.util.Arrays; |
||
14 | import java.util.HashMap; |
||
15 | import java.util.LinkedList; |
||
16 | |||
17 | /** |
||
18 | * GameLevel class |
||
19 | * |
||
20 | * Holds level information and supplies functions |
||
21 | * for manipulating objects |
||
22 | */ |
||
23 | public class GameLevel { |
||
24 | |||
25 | private char[][] gameMap = new char[0][0]; // array to save game-field |
||
26 | private int height = 0; // level-height in fields |
||
27 | private int width = 0; // level-width in fields |
||
28 | private int steps = 0; // completed valid steps |
||
29 | // HashMap to store the meaning of the chars |
||
30 | private final HashMap<Character, String> charRepresentation = new HashMap<Character, String>(); |
||
31 | private final GameLevelHistory history = new GameLevelHistory(); |
||
32 | |||
33 | /** |
||
34 | * Reads a File and parses the level |
||
35 | * |
||
36 | * @param lvl |
||
37 | * Levelfile to read |
||
38 | * @throws IOException |
||
39 | */ |
||
40 | public GameLevel(final File lvl) throws IOException, ParseException { |
||
41 | init(lvl); |
||
42 | } |
||
43 | |||
44 | /** |
||
45 | * Reads a filename, opens the file and parses the level |
||
46 | * |
||
47 | * @param fileName |
||
48 | * fileName of the file the level is saved in |
||
49 | * @throws URISyntaxException |
||
50 | * @throws IOException |
||
51 | */ |
||
52 | public GameLevel(final String fileName) throws URISyntaxException, IOException, ParseException { |
||
53 | final File lvl = new File(ClassLoader.getSystemClassLoader().getResource( |
||
54 | fileName).toURI()); |
||
55 | init(lvl); |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * Reads a File and parses the level |
||
60 | * |
||
61 | * @param lvl |
||
62 | * Levelfile to read |
||
63 | * @throws IOException |
||
64 | */ |
||
65 | private void init(final File lvl) throws IOException, ParseException{ |
||
66 | fillCharRepresentation(); |
||
67 | parse(lvl); |
||
68 | history.setStart(gameMap); // set history start |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * fills the HashMap of chars and their objectnames |
||
73 | * |
||
74 | */ |
||
75 | private void fillCharRepresentation() { |
||
76 | charRepresentation.put('@', "player"); |
||
77 | charRepresentation.put(' ', "field"); |
||
78 | charRepresentation.put('#', "wall"); |
||
79 | charRepresentation.put('$', "crate"); |
||
80 | charRepresentation.put('.', "goal"); |
||
81 | charRepresentation.put('*', "crateOnGoal"); |
||
82 | charRepresentation.put('+', "playerOnGoal"); |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * returns the height of the level |
||
87 | * |
||
88 | * @return level-height (in fields) |
||
89 | */ |
||
90 | public int getHeight() { |
||
91 | return height; |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * returns the width of the level |
||
96 | * |
||
97 | * @return level-width (in fields) |
||
98 | */ |
||
99 | public int getWidth() { |
||
100 | return width; |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * returns the object which is placed on the given field |
||
105 | * |
||
106 | * @param top |
||
107 | * top-pos of field |
||
108 | * @param left |
||
109 | * left-pos of field |
||
110 | * @return Name of object on this field |
||
111 | * @throws ParameterOutOfRangeException |
||
112 | */ |
||
113 | public String getObject(final int top, final int left) throws ParameterOutOfRangeException { |
||
114 | if ((top < 0) || (top >= height) || (left < 0) || (left >= width)) |
||
115 | throw new ParameterOutOfRangeException("Koordinaten ausserhalb des Spielfelds"); |
||
116 | return charRepresentation.get(gameMap[top][left]); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * returns the current position of the Player |
||
121 | * @return an Integer Array containing the position of the player (top, left) |
||
122 | * @throws ParameterOutOfRangeException |
||
123 | */ |
||
124 | public Point getPlayerPos() throws ParameterOutOfRangeException { |
||
125 | for (int top = 0; top < gameMap.length; top++) |
||
126 | for (int left = 0; left < gameMap[0].length; left++) |
||
127 | if ((getObject(top, left) == "player") |
||
128 | || (getObject(top, left) == "playerOnGoal")) |
||
129 | return new Point(top, left); |
||
130 | // no player on field |
||
131 | return new Point(-1, -1); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * Parses a level from a given Levelfile |
||
136 | * |
||
137 | * @param lvl |
||
138 | * @throws IOException |
||
139 | * @throws URISyntaxException |
||
140 | */ |
||
141 | private void parse(final File lvlFile) throws IOException, ParseException { |
||
142 | final BufferedReader in = new BufferedReader(new FileReader(lvlFile)); |
||
143 | StringBuffer levelCode = new StringBuffer(128); |
||
144 | String line = null; |
||
145 | while ((line = in.readLine()) != null) // read every line |
||
146 | levelCode.append(line).append("\n"); |
||
147 | in.close(); |
||
148 | // parse level code |
||
149 | parseString(levelCode.toString()); |
||
150 | } |
||
151 | /** |
||
152 | * Parses a level file from a string |
||
153 | * @param lvlString |
||
154 | */ |
||
155 | protected void parseString(final String lvlString) throws IOException, ParseException { |
||
156 | final String[] levelLines = lvlString.split("\n"); |
||
157 | final ArrayList<String> lines = new ArrayList<String>(0); |
||
158 | height = 0; |
||
159 | width = 0; |
||
160 | for(int i = 0; i < levelLines.length; i++){ |
||
161 | //cut unneccessary spaces from right |
||
162 | String line = levelLines[i]; |
||
163 | if (chopSpaces(line).isEmpty()) { // check if this line, which only exists of spaces, can be removed |
||
164 | boolean canChop = true; |
||
165 | for (int k = i+1; k < levelLines.length; k++) { // check if there are more lines with level code |
||
166 | if (! chopSpaces(levelLines[k]).isEmpty()) |
||
167 | canChop = false; // there exists a line with level code, so don't chop this line |
||
168 | } |
||
169 | if (canChop) |
||
170 | line = ""; // remove line from level code |
||
171 | } |
||
172 | |||
173 | // get maximum width |
||
174 | if (width < line.length()) |
||
175 | width = line.length(); |
||
176 | // Add line to ArrayList of lines if it is not empty |
||
177 | if(line.length() > 0){ |
||
178 | lines.add(line); |
||
179 | // the height is the number of lines |
||
180 | height++; |
||
181 | } |
||
182 | } |
||
183 | // Set gamemap to right height and size |
||
184 | gameMap = new char[height][width]; |
||
185 | // copy characters into of gameMap-fields |
||
186 | for (int i = 0; i < height; i++) { |
||
187 | // Fill all fields with blanks (empty fields) |
||
188 | Arrays.fill(gameMap[i], ' '); |
||
189 | // copy all characters of this line into the current row of gameMap |
||
190 | System.arraycopy(lines.get(i).toCharArray(), 0, gameMap[i], 0, |
||
191 | lines.get(i).length()); |
||
192 | } |
||
193 | // check level syntax |
||
194 | if (! checkSyntax()) |
||
195 | throw new ParseException("Fehler: Leveldatei ist syntaktisch nicht korrekt!"); |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * Checks the Level-Array for syntactical correctness |
||
200 | * |
||
201 | * @return true when syntax is correct else false |
||
202 | */ |
||
203 | private boolean checkSyntax() { |
||
204 | try { |
||
205 | return ( (countCrates() > 0) && (countGoals() == countCrates()) && (countPlayer() == 1) && (containsOnlyAllowedChars()) && (checkWallBounding()) ); |
||
206 | } catch (ParameterOutOfRangeException e) { |
||
207 | System.err.println("Interner Fehler: Fehler beim Prüfen der Levelsyntax!"); |
||
208 | return false; |
||
209 | } |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * removes all tailing spaces from a given string |
||
214 | * @param s the string |
||
215 | * @return the string s without tailing spaces |
||
216 | */ |
||
217 | private String chopSpaces(String s){ |
||
218 | while(s.endsWith(" ")) |
||
219 | s = s.substring(0, s.length() - 1); |
||
220 | return s; |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * helper function to count how many times the given character is in gameMap |
||
225 | * |
||
226 | * @param c |
||
227 | * Character to count |
||
228 | * @return number of given character in GameMap |
||
229 | */ |
||
230 | private int countCharacter(final char c) { |
||
231 | int counter = 0; |
||
232 | for (final char[] rows : gameMap) |
||
233 | for (final char element : rows) |
||
234 | if (element == c) |
||
235 | counter++; |
||
236 | return counter; |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * checks if the gameMap only consists of chars used in the |
||
241 | * format-description |
||
242 | * |
||
243 | * @return true if all chars are correct, false otherwise |
||
244 | */ |
||
245 | private boolean containsOnlyAllowedChars() { |
||
246 | // check for every element if it is in the list of allowed chars |
||
247 | for (final char[] rows : gameMap) |
||
248 | for (final char element : rows) |
||
249 | // if a element is not in the list the method returns false |
||
250 | if (!charRepresentation.containsKey(element)) { |
||
251 | return false; |
||
252 | } |
||
253 | return true; |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * checks if an element is in a corner |
||
258 | * @param top top-position of element |
||
259 | * @param left left-position of element |
||
260 | * @return true if element is in a corner, false otherwise |
||
261 | * @throws ParameterOutOfRangeException |
||
262 | */ |
||
263 | private boolean isInCorner(final int top, final int left) throws ParameterOutOfRangeException{ |
||
264 | final boolean isTopLeftCorner = (isWall(top, left-1) && isWall(top-1, left)); |
||
265 | final boolean isTopRightCorner = (isWall(top, left+1) && isWall(top-1, left)); |
||
266 | final boolean isDownLeftCorner = (isWall(top, left-1) && isWall(top+1, left)); |
||
267 | final boolean isDownRightCorner = (isWall(top, left+1) && isWall(top+1, left)); |
||
268 | return (isTopLeftCorner || isTopRightCorner || isDownLeftCorner || isDownRightCorner); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * checks the level for deadlocks |
||
273 | * the level contains a deadlock |
||
274 | * when at least one crate is in a corner |
||
275 | * or four elements (of which at least one is a crate) |
||
276 | * form a square |
||
277 | * @return true if level is in deadlock situation |
||
278 | * false otherwise |
||
279 | * @throws ParameterOutOfRangeException |
||
280 | */ |
||
281 | public boolean hasDeadlock() throws ParameterOutOfRangeException{ |
||
282 | // iterate through every element of gameMap |
||
283 | for(int top=0; top<gameMap.length; top++) |
||
284 | for(int left=0; left<gameMap[0].length; left++) |
||
285 | // if element is a crate |
||
286 | if(getObject(top, left) == "crate"){ |
||
287 | // there are four possibilities to form a square with the current crate |
||
288 | final boolean square1 = (!isFree(top, left-1) && !isFree(top-1, left) && !isFree(top-1, left-1)); |
||
289 | final boolean square2 = (!isFree(top, left+1) && !isFree(top-1, left) && !isFree(top-1, left+1)); |
||
290 | final boolean square3 = (!isFree(top, left-1) && !isFree(top+1, left) && !isFree(top+1, left-1)); |
||
291 | final boolean square4 = (!isFree(top, left+1) && !isFree(top+1, left) && !isFree(top+1, left+1)); |
||
292 | // if a crate is placed in a corner or one of the four possibilities to form a square are true |
||
293 | // the level contains a deadlock and true has to be returned |
||
294 | if(isInCorner(top, left) || square1 || square2 || square3 || square4) // || futureSquare1 && futureSquare2 && futureSquare3 && futureSquare4) |
||
295 | return true; |
||
296 | } |
||
297 | return false; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * sets an object to a certain field without changing the type of the field |
||
302 | * (i.e. field is goal or normal field) |
||
303 | * |
||
304 | * @param top |
||
305 | * @param left |
||
306 | * @param object |
||
307 | * @throws ParameterOutOfRangeException |
||
308 | * @TODO difference between normal fields and goal fields (e.g. a Class for |
||
309 | * field or a second HashMap containing goals) |
||
310 | */ |
||
311 | private void setObject(final int top, final int left, final String object) throws ParameterOutOfRangeException { |
||
312 | // if the field is a goal (i.e. goal, playerOnGoal, cateOnGoal) this |
||
313 | // attribute has to be saved and reassigned to the new field |
||
314 | final String oldObj = getObject(top, left); |
||
315 | // Hashmaps to store the key-value pair of object's old name and |
||
316 | // object's new name |
||
317 | final HashMap<String, String> newObjNames = new HashMap<String, String>(); |
||
318 | // if this field is a goal all characters in the Map have to be replaced |
||
319 | // by their equivalent standing on a Goal (e.g. player => playerOnGoal) |
||
320 | if ((oldObj == "goal") || (oldObj == "crateOnGoal") |
||
321 | || (oldObj == "playerOnGoal")) { |
||
322 | newObjNames.put("field", "goal"); |
||
323 | newObjNames.put("player", "playerOnGoal"); |
||
324 | newObjNames.put("crate", "crateOnGoal"); |
||
325 | newObjNames.put("goal", "goal"); |
||
326 | newObjNames.put("playerOnGoal", "playerOnGoal"); |
||
327 | newObjNames.put("crateOnGoal", "crateOnGoal"); |
||
328 | } else { |
||
329 | newObjNames.put("goal", "field"); |
||
330 | newObjNames.put("playerOnGoal", "player"); |
||
331 | newObjNames.put("crateOnGoal", "crate"); |
||
332 | newObjNames.put("field", "field"); |
||
333 | newObjNames.put("player", "player"); |
||
334 | newObjNames.put("crate", "crate"); |
||
335 | } |
||
336 | for (final char possibleChar : charRepresentation.keySet()) |
||
337 | if (charRepresentation.get(possibleChar) == newObjNames.get(object)) |
||
338 | gameMap[top][left] = possibleChar; |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * checks if the given field is free (i.e. goal or empty field) |
||
343 | * @param top |
||
344 | * @param left |
||
345 | * @return true if field is free, else otherwise |
||
346 | * @throws ParameterOutOfRangeException |
||
347 | */ |
||
348 | private boolean isFree(final int top, final int left) throws ParameterOutOfRangeException{ |
||
349 | if((top < 0) || (left < 0) || (top >= getHeight()) || (left >= getWidth())) |
||
350 | return false; |
||
351 | return ((getObject(top, left) == "field") |
||
352 | || (getObject(top, left) == "goal") |
||
353 | || (getObject(top, left) == "player") |
||
354 | || (getObject(top, left) == "playerOnGoal") ); |
||
355 | } |
||
356 | |||
357 | /** |
||
358 | * checks if the field on the given point is free |
||
359 | * @param p point containing the position of the field |
||
360 | * @return true if field is free, else otherwise |
||
361 | * @throws ParameterOutOfRangeException |
||
362 | */ |
||
363 | private boolean isFree(final Point p) throws ParameterOutOfRangeException{ |
||
364 | return isFree(p.top, p.left); |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * sets an object of the map to another position if the field is free |
||
369 | * |
||
370 | * @param top |
||
371 | * Top-Pos. of element |
||
372 | * @param left |
||
373 | * Left-Pos. of element |
||
374 | * @param destTop |
||
375 | * new Top-Pos. |
||
376 | * @param destLeft |
||
377 | * new Left-Pos. |
||
378 | * @return true if the field is free and the new position is set, false |
||
379 | * otherwise |
||
380 | * @throws ParameterOutOfRangeException |
||
381 | */ |
||
382 | public boolean setPosition(final int top, final int left, final int destTop, final int destLeft) throws ParameterOutOfRangeException { |
||
383 | final boolean canMove = isFree(destTop, destLeft); |
||
384 | if (canMove) { |
||
385 | setObject(destTop, destLeft, getObject(top, left)); |
||
386 | setObject(top, left, "field"); |
||
387 | // if an object is moved the history may not contain any entries after that move |
||
388 | history.deleteAfterCurrent(); |
||
389 | } |
||
390 | return canMove; |
||
391 | } |
||
392 | |||
393 | /** |
||
394 | * helper function needed to check if the player number is correct |
||
395 | * @return number of players in Level |
||
396 | */ |
||
397 | private int countPlayer() { |
||
398 | return countCharacter('@') + countCharacter('+'); |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * counts the number of Crates (all crates) |
||
403 | * |
||
404 | * @return number of crates in this level |
||
405 | */ |
||
406 | public int countCrates() { |
||
407 | return countCharacter('$') + countCharacter('*'); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * counts the number of Crates (only crates on goals) |
||
412 | * |
||
413 | * @return number of crates in this level |
||
414 | */ |
||
415 | public int countCratesOnGoal() { |
||
416 | return countCharacter('*'); |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * counts the number of Walls |
||
421 | * |
||
422 | * @return number of walls in this level |
||
423 | */ |
||
424 | public int countWalls() { |
||
425 | return countCharacter('#'); |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * counts the number of (free) Goals |
||
430 | * |
||
431 | * @return number of Goals in level |
||
432 | */ |
||
433 | public int countGoals() { |
||
434 | return countCharacter('.') + countCharacter('+') + countCharacter('*'); |
||
435 | } |
||
436 | |||
437 | /** |
||
438 | * returns if the element is a crate |
||
439 | * |
||
440 | * @param top |
||
441 | * top position of element |
||
442 | * @param left |
||
443 | * left position of element |
||
444 | * @return true if element is crate false otherwise |
||
445 | * @throws ParameterOutOfRangeException |
||
446 | */ |
||
447 | public boolean isCrate(final int top, final int left) throws ParameterOutOfRangeException { |
||
448 | return ((getObject(top, left) == "crate") |
||
449 | || (getObject(top, left) == "crateOnGoal")); |
||
450 | } |
||
451 | |||
452 | |||
453 | /** |
||
454 | * returns if the element is a goal |
||
455 | * |
||
456 | * @param top |
||
457 | * top position of element |
||
458 | * @param left |
||
459 | * left position of element |
||
460 | * @return true if element is goal false otherwise |
||
461 | * @throws ParameterOutOfRangeException |
||
462 | */ |
||
463 | public boolean isGoal(final int top, final int left) throws ParameterOutOfRangeException { |
||
464 | final String fieldType = getObject(top, left); |
||
465 | return (fieldType == "goal"); |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * returns if the element is a wall |
||
470 | * |
||
471 | * @param top |
||
472 | * top position of element |
||
473 | * @param left |
||
474 | * left position of element |
||
475 | * @return true if element is wall false otherwise |
||
476 | * @throws ParameterOutOfRangeException |
||
477 | */ |
||
478 | public boolean isWall(final int top, final int left) throws ParameterOutOfRangeException { |
||
479 | final String fieldType = getObject(top, left); |
||
480 | return (fieldType == "wall"); |
||
481 | } |
||
482 | |||
483 | /** |
||
484 | * returns if the element is a free field |
||
485 | * |
||
486 | * @param top |
||
487 | * top position of element |
||
488 | * @param left |
||
489 | * left position of element |
||
490 | * @return true if element is field, false otherwise |
||
491 | * @throws ParameterOutOfRangeException |
||
492 | */ |
||
493 | public boolean isField(final int top, final int left) throws ParameterOutOfRangeException { |
||
494 | final String fieldType = getObject(top, left); |
||
495 | return (fieldType == "field"); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * returns the adjacency list (list of neighbors) for the level map |
||
500 | * neighbors are in this case all free fields (i.e. no crates or walls) that are |
||
501 | * left, right, top or down another field. |
||
502 | * @return adjacencyList as HashMap with String key (format "top,left") |
||
503 | * and Integer-Array as value (top = value[0] left = value[1]) |
||
504 | * @throws ParameterOutOfRangeException |
||
505 | */ |
||
506 | private HashMap<String, ArrayList<Point>> getAdjacencyList() throws ParameterOutOfRangeException{ |
||
507 | // HashMap to save a point as key and the neighbors it is connected to as value |
||
508 | final HashMap<String, ArrayList<Point>> adjacencyList = new HashMap<String, ArrayList<Point>>(); |
||
509 | // Fill HashMap |
||
510 | for(int top = 0; top < getHeight(); top++) |
||
511 | for(int left = 0; left < getWidth(); left++){ |
||
512 | // possible positions for neighbors |
||
513 | final Point pos = new Point(top, left); |
||
514 | final Point leftNeighbor = pos.moveDirection('L'); |
||
515 | final Point rightNeighbor = pos.moveDirection('R'); |
||
516 | final Point topNeighbor = pos.moveDirection('U'); |
||
517 | final Point downNeighbor = pos.moveDirection('D'); |
||
518 | // Add all neighbors to adjacency list |
||
519 | final ArrayList<Point> neighbors = new ArrayList<Point>(); // temporary list of neighbors |
||
520 | // check if the position a neighbor should be is free |
||
521 | // and if yes, add this neighbor to the list of neighbors |
||
522 | final boolean leftFree = isFree(leftNeighbor); |
||
523 | final boolean rightFree = isFree(rightNeighbor); |
||
524 | final boolean topFree = isFree(topNeighbor); |
||
525 | final boolean bottomFree = isFree(downNeighbor); |
||
526 | if(leftFree) |
||
527 | neighbors.add(leftNeighbor); |
||
528 | if(rightFree) |
||
529 | neighbors.add(rightNeighbor); |
||
530 | if(topFree) |
||
531 | neighbors.add(topNeighbor); |
||
532 | if(bottomFree) |
||
533 | neighbors.add(downNeighbor); |
||
534 | // add list of neighbors to adjacency list |
||
535 | adjacencyList.put(pos.toString(), neighbors); |
||
536 | } |
||
537 | return adjacencyList; |
||
538 | } |
||
539 | |||
540 | /** |
||
541 | * return the breadth-first tree for the level and a given start position |
||
542 | * the breadfirst tree contains all points of the level |
||
543 | * the path from the given start point to every other point is |
||
544 | * the shortest possible |
||
545 | * @param startTop start position top |
||
546 | * @param startLeft start position left |
||
547 | * @return BfsTree for given start position |
||
548 | * @throws ParameterOutOfRangeException |
||
549 | */ |
||
550 | private HashMap<String, Point> getBfsTree(final int startTop, final int startLeft) throws ParameterOutOfRangeException{ |
||
551 | // load Adjacency List |
||
552 | final HashMap<String, ArrayList<Point>> adjacencyList = getAdjacencyList(); |
||
553 | final LinkedList<Point> queue = new LinkedList<Point>(); |
||
554 | // HashMap to store every element passed when walking the shortest possible path |
||
555 | final HashMap<String, Point> tree = new HashMap<String, Point>(); |
||
556 | // ArrayList to store visited nodes |
||
557 | final ArrayList<String> visited = new ArrayList<String>(); |
||
558 | final Point startPos = new Point(startTop, startLeft); |
||
559 | queue.add(startPos); // Add start element to queue |
||
560 | while(!queue.isEmpty()){ |
||
561 | final Point act = queue.pop(); // removes element from queue |
||
562 | // Iterate through all neighbors of the current element |
||
563 | if(adjacencyList.containsKey(act.toString())) |
||
564 | for(final Point neighbor : adjacencyList.get(act.toString())) |
||
565 | // if the node is not visited yet |
||
566 | if(!visited.contains(neighbor.toString())){ |
||
567 | // add node to list of visited nodes |
||
568 | visited.add(neighbor.toString()); |
||
569 | // the predecessor of the neighbor is the currently visited node |
||
570 | tree.put(neighbor.toString(), act); |
||
571 | // add neighbor to queue to search for its neighbors |
||
572 | queue.add(neighbor); |
||
573 | } |
||
574 | } |
||
575 | return tree; |
||
576 | } |
||
577 | |||
578 | /** |
||
579 | * returns the shortest possible path from a given start position to a given destination in moves-commands. |
||
580 | * A move command is U, R, D or L. The letters represent the directions you have to walk to arrive at the destination. |
||
581 | * To compute the path, the method uses a given breadth-first tree. |
||
582 | * @param startTop top value of start position |
||
583 | * @param startLeft left value of start position |
||
584 | * @param destTop top value of destination |
||
585 | * @param destLeft left value of destination |
||
586 | * @param bfsTree breadth-first tree of the graph on which the path shall be found. |
||
587 | * @return LinkedList of Characters representing directions in which an object has to be moved to arrive the given destination |
||
588 | */ |
||
589 | private LinkedList<Character> getPath(final int startTop, final int startLeft, final int destTop, final int destLeft, final HashMap<String, Point> bfsTree){ |
||
590 | final Point dest = new Point(destTop, destLeft); // destination as point |
||
591 | final LinkedList<Character> path = new LinkedList<Character>(); |
||
592 | // if there is no path or the start and destination are equal, no steps can be made. |
||
593 | // Hence an empty list is returned |
||
594 | if(((startTop == destTop) && (startLeft == destLeft)) || (!bfsTree.containsKey(dest.toString()))) |
||
595 | return new LinkedList<Character>(); |
||
596 | else{ // there is a path |
||
597 | final Point pred = bfsTree.get(dest.toString()); // predecessor of destination |
||
598 | // recursively call the method with the predecessor as new destination |
||
599 | // until start and destination are equal and the whole path is found |
||
600 | // and the direction-chars can be added to the list |
||
601 | path.addAll(getPath(startTop, startLeft, pred.top, pred.left, bfsTree)); |
||
602 | // get char for the position |
||
603 | if(pred.top - destTop == 1) |
||
604 | path.add('U'); |
||
605 | else if(pred.top - destTop == -1) |
||
606 | path.add('D'); |
||
607 | else if(pred.left - destLeft == 1) |
||
608 | path.add('L'); |
||
609 | else if(pred.left - destLeft == -1) |
||
610 | path.add('R'); |
||
611 | } |
||
612 | return path; |
||
613 | } |
||
614 | |||
615 | /** |
||
616 | * returns the shortest possible path from a given start position to a given destination |
||
617 | * @param startTop top position of start |
||
618 | * @param startLeft left position of start |
||
619 | * @param destTop top position of destination |
||
620 | * @param destLeft left position of destination |
||
621 | * @return an LinkedList of Direction-Chars (U, R, D, L) you have to carry out to reach dest |
||
622 | * @throws ParameterOutOfRangeException |
||
623 | */ |
||
624 | public LinkedList<Character> getShortestPath(final int startTop, final int startLeft, final int destTop, final int destLeft) throws ParameterOutOfRangeException{ |
||
625 | // get Bfs Tree for given start position |
||
626 | final HashMap<String, Point> bfsTree = getBfsTree(startTop, startLeft); |
||
627 | // call and return getPath method |
||
628 | return getPath(startTop, startLeft, destTop, destLeft, bfsTree); |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Checks if level is solved |
||
633 | * |
||
634 | * @return true if all crates are on a goal (e.g. the number of crates is 0) |
||
635 | * false otherwise |
||
636 | */ |
||
637 | public boolean isSolved() { |
||
638 | if (countCrates() == countCratesOnGoal()) |
||
639 | return true; |
||
640 | return false; |
||
641 | } |
||
642 | |||
643 | /** |
||
644 | * checks if the level is completely bordered by walls and |
||
645 | * no element is outside the walls |
||
646 | * @return true if every gameobject is inside a wall bounding, |
||
647 | * false otherwise |
||
648 | * @throws ParameterOutOfRangeException |
||
649 | */ |
||
650 | public boolean checkWallBounding() throws ParameterOutOfRangeException{ |
||
651 | // for every object in the game it has to be checked |
||
652 | // that no path to one of the outer fields exists |
||
653 | for(int i=1; i<gameMap.length-1; i++) |
||
654 | for(int j=1; j<gameMap[0].length-1; j++) |
||
655 | if(!isWall(i,j) && !isField(i, j)){ |
||
656 | // iterate through top and down border fields |
||
657 | for(int col = 0; col<gameMap[0].length-1; col++){ |
||
658 | // no object except a wall may be on one of the outer border fields |
||
659 | if((!isWall(0, col) && !isField(0, col)) || |
||
660 | (!isWall(getHeight()-1, col) && !isField(getHeight()-1, col))) |
||
661 | return false; |
||
662 | // check if a path exists to any position |
||
663 | // in the first and last row |
||
664 | if((!getShortestPath(i, j, 0, col).isEmpty()) || // top row |
||
665 | (!getShortestPath(i, j, getHeight()-1, col).isEmpty())) // down row |
||
666 | return false; |
||
667 | } |
||
668 | // iterate through right and left border fields |
||
669 | for(int row = 0; row<gameMap.length-1; row++){ |
||
670 | // no object except a wall may be on one of the outer border fields |
||
671 | if((!isWall(row, 0) && !isField(row, 0)) || |
||
672 | (!isWall(row, getWidth()-1) && !isField(row, getWidth()-1))) |
||
673 | return false; |
||
674 | // check if a path exists to any position |
||
675 | // in the left and right border-column |
||
676 | if ((!getShortestPath(i, j, row, 0).isEmpty()) || // left column |
||
677 | (!getShortestPath(i, j, row , getWidth()-1).isEmpty())) // right column |
||
678 | return false; |
||
679 | } |
||
680 | } |
||
681 | return true; |
||
682 | } |
||
683 | |||
684 | /** |
||
685 | * returns the String representation of the gameMap |
||
686 | * |
||
687 | * @return gameMap as String |
||
688 | */ |
||
689 | @Override |
||
690 | public String toString() { |
||
691 | final StringBuffer outBuf = new StringBuffer(128); |
||
692 | for (int i = 0; i < height; i++) { |
||
693 | String line=""; |
||
694 | for (int j = 0; j < width; j++) |
||
695 | line += gameMap[i][j]; |
||
696 | line = chopSpaces(line); |
||
697 | outBuf.append(line).append("\n"); |
||
698 | } |
||
699 | return outBuf.toString(); |
||
700 | } |
||
701 | |||
702 | /** |
||
703 | * getter-method for gameMap |
||
704 | * |
||
705 | * @return the gameMap |
||
706 | */ |
||
707 | public char[][] getMap() { |
||
708 | return gameMap; |
||
709 | } |
||
710 | |||
711 | /** |
||
712 | * @return steps |
||
713 | */ |
||
714 | public int getSteps() { |
||
715 | return steps; |
||
716 | } |
||
717 | |||
718 | protected void setSteps(final int i) { |
||
719 | steps = i; |
||
720 | } |
||
721 | |||
722 | protected void incSteps() { |
||
723 | steps++; |
||
724 | } |
||
725 | |||
726 | /** |
||
727 | * @return steps |
||
728 | */ |
||
729 | public int getHistorySteps() { |
||
730 | return history.getSteps(); |
||
731 | } |
||
732 | |||
733 | public void setHistorySteps(final int i){ |
||
734 | history.setSteps(i); |
||
735 | } |
||
736 | |||
737 | /** |
||
738 | * resets the level to the start position saved in the history |
||
739 | */ |
||
740 | protected void reset(){ |
||
741 | final char[][] historyMap = history.getStart(); |
||
742 | gameMap = historyMap; |
||
743 | history.setSteps(0); // resets the step counter |
||
744 | } |
||
745 | /** |
||
746 | * add move to History |
||
747 | */ |
||
748 | protected void addMove(final char direction) { |
||
749 | history.addMove(direction); |
||
750 | } |
||
751 | |||
752 | /** |
||
753 | * sets the history-entries to the given value |
||
754 | * @param listOfDirections LinkedList of directions |
||
755 | */ |
||
756 | protected void setHistory(final LinkedList<Character> listOfDirections){ |
||
757 | history.setHistory(listOfDirections); |
||
758 | } |
||
759 | /** |
||
760 | * resets the History and saves the current state of the level as start |
||
761 | * |
||
762 | */ |
||
763 | protected void setNewHistoryStart(){ |
||
764 | history.reset(); |
||
765 | history.setStart(gameMap); |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * returns the history start |
||
770 | * @return history start as String |
||
771 | * |
||
772 | */ |
||
773 | protected String getHistoryStart(){ |
||
774 | final char[][] start = history.getStart(); |
||
775 | final StringBuffer outBuf = new StringBuffer(128); |
||
776 | for (int i = 0; i < start.length; i++) { |
||
777 | String line=""; |
||
778 | for (int j = 0; j < start[0].length; j++) |
||
779 | line += start[i][j]; |
||
780 | line = chopSpaces(line); |
||
781 | outBuf.append(line).append("\n"); |
||
782 | } |
||
783 | return outBuf.toString(); |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * get the move History as LinkedList |
||
788 | * @return moveHistory |
||
789 | */ |
||
790 | protected LinkedList<Character> getMoveHistory(){ |
||
791 | return history.getMoveHistory(); |
||
792 | } |
||
793 | } |