-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPathogen.java
359 lines (308 loc) · 11.7 KB
/
Pathogen.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
// Jared Baker (NID: ja907583)
// COP 3503, Fall 2022
// Modify the function solveMaze() that was given from Sean Szumlanski to output all the possible
// paths that a maze can take to find the exit
// Sean Szumlanski
// COP 3503, Fall 2022
// =============================================================================
// POSTING THIS FILE ONLINE OR DISTRIBUTING IT IN ANY WAY, IN PART OR IN WHOLE,
// IS AN ACT OF ACADEMIC MISCONDUCT AND ALSO CONSTITUTES COPYRIGHT INFRINGEMENT.
// =============================================================================
// =============================================================================
// Overview:
// =============================================================================
//
// You should modify the methods in this file to implement your backtracking
// solution for this assignment. You'll want to transform the solveMaze()
// methods into the findPaths() methods required for this assignment.
//
// =============================================================================
// Disclaimer:
// =============================================================================
//
// As usual, the comments in this file are way overkill. They're intended to be
// educational (and to make this code easier for you to work with), and are not
// indicative of the kind of comments we'd use in the real world.
//
// =============================================================================
// Maze Format (2D char array):
// =============================================================================
//
// This program assumes there is exactly one person ('@') and one exit ('e') per
// maze. The initial positions of those characters may vary from maze to maze.
//
// This program assumes all mazes are rectangular (all rows have the same
// length). There are no guarantees regarding the number of walls in the maze
// or the locations of those walls. It's possible that the outer edges of the
// maze might not be made up entirely of walls (i.e., the outer edge might
// contain spaces).
//
// While there is guaranteed to be a single person ('@') and a single exit ('e')
// in a well-formed maze, there is no guarantee that there exists a path from
// the starting position of the '@' character to the exit.
//
// =============================================================================
// Example:
// =============================================================================
//
// #############
// #@# # # #
// # # # # # #
// # ### # # # #
// # # # #
// # ##### #####
// # e#
// #############
//
// =============================================================================
// Legend:
// =============================================================================
//
// '#' - wall (not walkable)
// '@' - person
// 'e' - exit
// ' ' - space (walkable)
import java.io.*;
import java.util.*;
public class Pathogen
{
// Used to toggle "animated" output on and off (for debugging purposes).
private static boolean animationEnabled = false;
// "Animation" frame rate (frames per second).
private static double frameRate = 4.0;
// Setters. Note that for testing purposes you can call enableAnimation()
// from your backtracking method's wrapper method (i.e., the first line of
// your public findPaths() method) if you want to override the fact that the
// test cases are disabling animation. Just don't forget to remove that
// method call before submitting!
public static void enableAnimation() { Pathogen.animationEnabled = true; }
public static void disableAnimation() { Pathogen.animationEnabled = false; }
public static void setFrameRate(double fps) { Pathogen.frameRate = fps; }
// Maze constants.
private static final char WALL = '#';
private static final char PERSON = '@';
private static final char EXIT = 'e';
private static final char BREADCRUMB = '.'; // visited
private static final char SPACE = ' '; // unvisited
private static final char PATHOGEN = '*'; // another thing that needs to be avoided
// Takes a 2D char maze and returns true if it can find a path from the
// starting position to the exit. Assumes the maze is well-formed according
// to the restrictions above.
public static HashSet<String> findPaths(char [][] maze)
{
int height = maze.length;
int width = maze[0].length;
// Add a path to pass into the findPaths method that does the backtracking
HashSet<String> allPaths = new HashSet<>();
// Initilize an inital path that is empty
ArrayList<String> path = new ArrayList<>();
// The visited array keeps track of visited positions. It also keeps
// track of the exit, since the exit will be overwritten when the '@'
// symbol covers it up in the maze[][] variable. Each cell contains one
// of three values:
//
// '.' -- visited
// ' ' -- unvisited
// 'e' -- exit
char [][] visited = new char[height][width];
for (int i = 0; i < height; i++)
Arrays.fill(visited[i], SPACE);
// Find starting position (location of the '@' character).
int startRow = -1;
int startCol = -1;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (maze[i][j] == PERSON)
{
startRow = i;
startCol = j;
}
}
}
// Let's goooooooooo!!
// Adds the paths to the HashSet and returns from the function once all paths are found
findPaths(maze, visited, startRow, startCol, height, width, allPaths, path);
// change the return type of the find paths to return the hash of the paths
return allPaths;
}
private static boolean findPaths(char [][] maze, char [][] visited,
int currentRow, int currentCol,
int height, int width,
HashSet<String> allPaths, ArrayList<String> path)
{
// This conditional block prints the maze when a new move is made.
if (Pathogen.animationEnabled)
{
printAndWait(maze, height, width, "Searching...", Pathogen.frameRate);
}
// Hoorayyyy!
if (visited[currentRow][currentCol] == 'e')
{
if (Pathogen.animationEnabled)
{
char [] widgets = {'|', '/', '-', '\\', '|', '/', '-', '\\',
'|', '/', '-', '\\', '|', '/', '-', '\\', '|'};
for (int i = 0; i < widgets.length; i++)
{
maze[currentRow][currentCol] = widgets[i];
printAndWait(maze, height, width, "Hooray!", 12.0);
}
maze[currentRow][currentCol] = PERSON;
printAndWait(maze, height, width, "Hooray!", Pathogen.frameRate);
}
// puts the exit back into place once the person visits the node and backtracks away
maze[currentRow][currentCol] = EXIT;
return true;
}
// Moves: left, right, up, down
int [][] moves = new int[][] {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
for (int i = 0; i < moves.length; i++)
{
int newRow = currentRow + moves[i][0];
int newCol = currentCol + moves[i][1];
// Check move is in bounds, not a wall, and not marked as visited.
if (!isLegalMove(maze, visited, newRow, newCol, height, width))
continue;
// Change state. Before moving the person forward in the maze, we
// need to check whether we're overwriting the exit. If so, save the
// exit in the visited[][] array so we can actually detect that
// we've gotten there.
//
// NOTE: THIS IS OVERKILL. We could just track the exit position's
// row and column in two int variables. However, this approach makes
// it easier to extend our code in the event that we want to be able
// to handle multiple exits per maze.
if (maze[newRow][newCol] == EXIT)
visited[newRow][newCol] = EXIT;
maze[currentRow][currentCol] = BREADCRUMB;
visited[currentRow][currentCol] = BREADCRUMB;
maze[newRow][newCol] = PERSON;
// before we recursivly call we need to save the move that we just made
// left
if (i == 0)
{
path.add("l");
}
// right
if (i == 1)
{
path.add("r");
}
// up
if (i == 2)
{
path.add("u");
}
// down
if (i == 3)
{
path.add("d");
}
// adds a space after the direction to allow the next direction to be put in place
path.add(" ");
// Perform recursive descent.
if (findPaths(maze, visited, newRow, newCol, height, width, allPaths, path))
{
// remove the extra space at the end of the path that was created when finding the exit
path.remove(path.size() - 1);
// Converts the path array of multiple strings to a single string
StringBuilder builder = new StringBuilder(path.size());
for(String route:path)
builder.append(route);
// adds the path that was taken to the HashSet of available paths
allPaths.add(builder.toString());
// adds the space at the end to convert it back start
path.add(" ");
}
// Undo state change. Note that if we return from the previous call,
// we know visited[newRow][newCol] did not contain the exit, and
// therefore already contains a breadcrumb, so I haven't updated
// that here.
// Revise visited with a SPACE because when we go back their is a possibility that we can go
// on a similar position multiple times going through different paths
if (maze[newRow][newCol] != EXIT)
{
maze[newRow][newCol] = BREADCRUMB;
}
visited[newRow][newCol] = SPACE;
maze[currentRow][currentCol] = PERSON;
// removes the last move in the path when we are backtracking
path.remove(path.size() - 1);
path.remove(path.size() - 1);
// This conditional block prints the maze when a move gets undone
// (which is effectively another kind of move).
if (Pathogen.animationEnabled)
{
printAndWait(maze, height, width, "Backtracking...", frameRate);
}
}
return false;
}
// Returns true if moving to row and col is legal (i.e., we have not visited
// that position before, and it's not a wall).
private static boolean isLegalMove(char [][] maze, char [][] visited,
int row, int col, int height, int width)
{
// modify the original if statment with the first two conditionals to stop it from going
// out of bounds and if it hits the '*' pathogen
if (maze[row][col] == WALL || visited[row][col] == BREADCRUMB || maze[row][col] == PATHOGEN
|| row >= height - 1 || row <= 0|| col >= width - 1 || col <= 0)
return false;
return true;
}
// This effectively pauses the program for waitTimeInSeconds seconds.
private static void wait(double waitTimeInSeconds)
{
long startTime = System.nanoTime();
long endTime = startTime + (long)(waitTimeInSeconds * 1e9);
while (System.nanoTime() < endTime)
;
}
// Prints maze and waits. frameRate is given in frames per second.
private static void printAndWait(char [][] maze, int height, int width,
String header, double frameRate)
{
if (header != null && !header.equals(""))
System.out.println(header);
if (height < 1 || width < 1)
return;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
System.out.print(maze[i][j]);
}
System.out.println();
}
System.out.println();
wait(1.0 / frameRate);
}
// Read maze from file. This function dangerously assumes the input file
// exists and is well formatted according to the specification above.
private static char [][] readMaze(String filename) throws IOException
{
Scanner in = new Scanner(new File(filename));
int height = in.nextInt();
int width = in.nextInt();
char [][] maze = new char[height][];
// After reading the integers, there's still a new line character we
// need to do away with before we can continue.
in.nextLine();
for (int i = 0; i < height; i++)
{
// Explode out each line from the input file into a char array.
maze[i] = in.nextLine().toCharArray();
}
return maze;
}
public static double difficultyRating()
{
return 3.0;
}
public static double hoursSpent()
{
return 12.0;
}
}