Repository navigation
Expand file tree
/
Copy pathMaze.java
More file actions
612 lines (538 loc) · 18.7 KB
/
Copy pathMaze.java
File metadata and controls
612 lines (538 loc) · 18.7 KB
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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;
/**
* This class is responsible for everything to do with mazes generated by the program. It stores all relevant information about itself but interactions with it are handled by other classes.
*
*/
/**
* @author Thomas
*
*/
/**
* @author Thomas
*
*/
/**
* @author Thomas
*
*/
public class Maze {
//class constants
//Tile status definitions
private static final int EMPTY = 0;
private static final int FLOOR = 1;
private static final int WALL = 2;
private static final int START = 3;
private static final int EXIT = 4;
private static final int SENTRY = 5;
private static final int KEY = 6;
// Maze generator constants
private static final float CYCLECHANCE = (float) 0.75; //determines the chance cycles are allowed to possibly exist. The lower this number is the less cycles should be (probabilistically speaking) in the maze
private static final int SENTRYSPACE = 3; // Space in between Sentries so they don't clump too close together and make impossible puzzles
// Maze generator pseudo-constants -- these are constant after the class has been initialized.
private int DIFFICULTY = 15; //these are constant once the game starts-- they are changed in settings.java
private int NUMSEN = 3;//number of sentries
//class variables
private Tile[][] maze;
private ArrayList<Sentry> sentries;
private boolean keyStatus,exitStatus;
private int size;
private Tile exit; //holds the exit tile while the key has yet to be found
/**
* Constructor for the maze. Actually generates multiple mazes until it finds one that it thinks is good enough.
*
* @param size Maze size
*/
public Maze(int size, int numSen, int diff){
this.size = size;
this.maze = new Tile[size][size];
this.sentries = new ArrayList<Sentry>();
NUMSEN = numSen;
DIFFICULTY = diff;//this sets how hard the maze is -- a better explanation is found in goodMaze()
//I SWEAR THIS IS THE ONE TIME DO...WHILE IS ACTAULLY WORTH
//Make a maze using generate maze, but if it's not good enough just try again until it is.
do { // Initialize maze grid
for (int n = 0; n < size; n++){
for (int m = 0; m < size; m++){
this.maze[n][m] = new Tile(n,m);
}
}
generateMaze(size);
} while (!goodMaze()); // Check if maze is good
addSentries(NUMSEN);
addKey(maze, size);
keyStatus = false;
exitStatus = false;
findSprites();
exit = getTileType(EXIT);
getTileType(EXIT).setType(FLOOR);
}
//this method figures out which sprite should be displayed for each tile
//the sprites themselves are in a type BufferedImage [][] in Renderer
//but the logic should be done in maze.java while the images stored
//in renderer because I don't want to store images outside of
//renderer. It also saves space
/**
* Calculates which sprite should be displayed for each tile on the maze depending on what type of tile it is and also what type of tile its neighbors are
*/
private void findSprites() {
Random x = new Random();
int vert, avert, ahorz, horz = 0;//these are just to help me out with logic
int triggers = 0;//has two uses -- explained below
for (int n = 0; n < size; n++){
for (int m = 0; m < size; m++){
vert = horz = avert = ahorz = 0;
triggers = 0;
if (maze[n][m].getType() == FLOOR || maze[n][m].getType() == START || maze[n][m].getType() == EXIT ){//sets sprites for walkable areas
maze[n][m].setImgRow(5);
maze[n][m].setImgCol(x.nextInt(3));
}else if (maze[n][m].getType() == WALL ){//sets sprites for walls
//looks up, down, left and right to find walls and picks the appropriate wall sprite
if(getN(maze[n][m]) == null ||(getN(maze[n][m]) != null && getN(maze[n][m]).getType() != WALL)){
vert++;
avert++;
triggers++;//triggers here is the number of adjacent walls to the current tile
}
if(getS(maze[n][m]) == null ||(getS(maze[n][m]) != null && getS(maze[n][m]).getType() != WALL)){
vert--;
avert++;
triggers++;
}
if(getE(maze[n][m]) == null ||(getE(maze[n][m]) != null && getE(maze[n][m]).getType() != WALL)){
horz++;
ahorz++;
triggers++;
}
if(getW(maze[n][m]) == null ||(getW(maze[n][m]) != null && getW(maze[n][m]).getType() != WALL)){
horz--;
ahorz++;
triggers++;
}
maze[n][m].setImgRow(triggers % 4);//just because when triggers = 4 I want imgrow = 0
switch (triggers){
case 0: maze[n][m].setImgCol(0);
break;
case 1: maze[n][m].setImgCol(2*horz + vert + 2);//so these are +-1 for up/down and left/right
break;
case 2: maze[n][m].setImgCol(2*horz + vert + avert - ahorz + 3);//so these are +1 for up&down and left&right
break;
case 3: maze[n][m].setImgCol(2*horz + vert + 2);
break;
case 4: maze[n][m].setImgCol(1);
break;
}
}else if (maze[n][m].getType() == EMPTY){//we have special logic for empty tiles so it's not just walls! yay for visual candy
if(getNW(maze[n][m]) == null || getNW(maze[n][m]) != null && (getNW(maze[n][m]).getType() != WALL && getNW(maze[n][m]).getType() != SENTRY && getNW(maze[n][m]).getType() != EMPTY)){
triggers++;
}if(getNE(maze[n][m]) == null ||getNE(maze[n][m]) != null && (getNE(maze[n][m]).getType() != WALL && getNE(maze[n][m]).getType() != SENTRY && getNE(maze[n][m]).getType() != EMPTY)){
triggers++;
}if(getSW(maze[n][m]) == null ||getSW(maze[n][m]) != null && (getSW(maze[n][m]).getType() != WALL && getSW(maze[n][m]).getType() != SENTRY && getSW(maze[n][m]).getType() != EMPTY)){
triggers++;
}if(getSE(maze[n][m]) == null ||getSE(maze[n][m]) != null && (getSE(maze[n][m]).getType() != WALL && getSE(maze[n][m]).getType() != SENTRY && getSE(maze[n][m]).getType() != EMPTY)){
triggers++;
}
maze[n][m].setImgRow(4);
maze[n][m].setImgCol((triggers)%4);
}else if (maze[n][m].getType() == KEY){//it's actaully not a key
maze[n][m].setImgRow(0);
maze[n][m].setImgCol(2);
}else if (maze[n][m].getType() == SENTRY){//this is the only sprite that I drew from scratch and it looks horrible lol
maze[n][m].setImgRow(0);
maze[n][m].setImgCol(5);
}
}
}
}
/**
* Sets the exit tile (and performs related sprite operations) when the key is collected.
*/
public void keyOff(){
Tile temp = getTile(exit.getCol(), exit.getRow());
temp.setType(EXIT);
temp.setImgRow(0);
temp.setImgCol(3);
temp = getTileType(KEY);
temp.setType(FLOOR);
temp.setImgRow(5);
temp.setImgCol(3);
keyStatus = true;
}
/**
* Called when the player makes it to the end tile.
* Displays the key on the pedestal.
*/
public void activateShrine(){
exit.setImgRow(0);
exit.setImgCol(4);
exitStatus = true;
}
/**
* Set exit status.
*
* @param exitStatus boolean
*/
public void setExitStatus(boolean exitStatus) {
this.exitStatus = exitStatus;
}
/**
* Gets the exit status.
*
* @return exit status as boolean
*/
public boolean isExitStatus() {
return exitStatus;
}
/**
* Sets the keyStatus
* @param keyStatus whether or not the key has been taken
*/
public void setKeyStatus(boolean keyStatus) {
this.keyStatus = keyStatus;
}
/**
* Adds instances of the Sentry class to a maze. Sentries cannot be less than SENTRYSPACE tiles close to each other or the start and exit tiles.
*
* @param num is the number of sentries to create
*/
private void addSentries(int num) {
Random x = new Random();
Random y = new Random();
int r = 0;
int c = 0;
ArrayList<Tile> avoid = new ArrayList<Tile>();
boolean tooClose = false;
while (num > 0) {
r = x.nextInt(size);
c = y.nextInt(size);
tooClose = false;
for (Sentry sentry: this.sentries ){
avoid.add(getTile(sentry.getColumn(),sentry.getRow()));
}
avoid.add(getTileType(EXIT));
avoid.add(getTileType(START));
for (Tile tile: avoid){
if(Math.sqrt(Math.pow(tile.getRow()-r,2) + Math.pow(tile.getCol()-c,2)) < SENTRYSPACE){
tooClose = true;
}
}
if (this.getTile(c, r).getType() == WALL && tooClose == false) {
Sentry s = new Sentry(c, r);
this.sentries.add(s);
num--;
}
}
}
/**
* Function to add the key to the maze
*
* @param maze is this maze
* @param size is the size of this maze
*/
private void addKey(Tile[][] maze, int size) {
int row = size;
int col = 0;
do {
Random x = new Random();
Random y = new Random();
row = x.nextInt(size);
col = y.nextInt(size);
if(maze[row][col].getType() == FLOOR && isCorner(maze[row][col])) { // if the tile is a floor and a corner, add a key to it
maze[row][col].setType(KEY);
}
}while(maze[row][col].getType() != KEY); // continue the loop if the tile is not a floor or a corner
}
/**
* Function to check if a tile is a corner (one pathway only) - Andy
*
* @param tile is the square/tile to check
* @return true if the checking square/tile is a corner
* false if it is not
*/
private boolean isCorner(Tile tile) {
int count = 0;
if (getN(tile) != null && getN(tile).getType() == FLOOR) {// need to include null check because getN returns null if you check outside the maze -- tom
count++;
}
if (getS(tile) != null && getS(tile).getType() == FLOOR) {
count++;
}
if (getE(tile) != null && getE(tile).getType() == FLOOR) {
count++;
}
if (getW(tile) != null && getW(tile).getType() == FLOOR) {
count++;
}
if (count == 1) {
return true;
}
return false;
}
/**
* Rotates sentries in the Maze.
*/
public void updateMaze(){
for (Sentry sentry : this.sentries){
sentry.updateDegree();
}
}
/**
* Get a list of the sentries in the maze.
*
* @return ArrayList of Sentries
*/
public ArrayList<Sentry> getSentries() {
return this.sentries;
}
/**
* Maze generation function using pseudo BFS (random walk algorithm?) on Tile grid.
*
* @param size Maze size
*/
private void generateMaze(int size) { // generates maze using random walk (at least that's what I think I'm doing)
Random rand = new Random();
Tile start = new Tile(rand.nextInt(size),rand.nextInt(size)); // Choose starting Tile at random
start.setType(START);
this.maze[start.getCol()][start.getRow()] = start;
LinkedList<Tile> ends = new LinkedList<Tile>(); // Queue of Tiles representing paths that have not been closed as a List
LinkedList<Tile> choices = new LinkedList<Tile>(); // Holds the Tiles that can be traveled to from the current Tile
int branches = 0;
int temp = 0;
Tile cur = null;
Tile process = null;
ends.add(start); // Add starting Tile to ends Queue
while (!ends.isEmpty()){ // While there are still open paths
cur = ends.remove(); // Take an open path from the Queue
if (this.getN(cur)!= null){ // Add adjacent undiscovered Tiles to the choices list
if(this.getN(cur).getType() == EMPTY) choices.add(getN(cur));
}
if (this.getS(cur)!= null){
if(this.getS(cur).getType() == EMPTY) choices.add(getS(cur));
}
if (this.getE(cur)!= null){
if(this.getE(cur).getType() == EMPTY) choices.add(getE(cur));
}
if (this.getW(cur)!= null){
if(this.getW(cur).getType() == EMPTY) choices.add(getW(cur));
}
if (!choices.isEmpty()){
// Choose a number from 0 to the number of choices, skewed towards 0 by taking the uniform distribution to the power of 1.5
branches = (int)Math.round(Math.sqrt(rand.nextFloat()*rand.nextFloat()*rand.nextFloat()))*(choices.size()-1);
// Branches is the number of possible options that WILL be explored (not all available options may be explored)
while (branches >= 0 && !choices.isEmpty()){ // For each remaining branch
if (choices.size() == 1){ // Pick one of the choices randomly
temp = 0;
} else {
temp = Math.round(rand.nextFloat())*(choices.size()-1);
}
process = choices.remove(temp); // Take it off the choices list
// Regulate the number of cycles by randomly checking the status of each direction from the current Tile
if (rand.nextFloat() > CYCLECHANCE && getN(process)!= null && getN(process) != cur && getN(process).getType() == FLOOR){
process.setType(WALL);
} else if (rand.nextFloat() > CYCLECHANCE && getS(process)!= null && getS(process) != cur && getS(process).getType() == FLOOR){
process.setType(WALL);
} else if (rand.nextFloat() > CYCLECHANCE && getW(process)!= null && getW(process) != cur && getW(process).getType() == FLOOR){
process.setType(WALL);
} else if (rand.nextFloat() > CYCLECHANCE && getE(process)!= null && getE(process) != cur && getE(process).getType() == FLOOR){
process.setType(WALL);
} else {
process.setType(FLOOR); // Set Tile to FLOOR if cycle test is passed or ignored
ends.add(process); // Add Tile to the list of unfinished paths
}
branches--; // reduces the number of branches
}
while (choices.size() > 0){ // Set remaining choices as wall after all the branches have been accounted
process = choices.remove();
process.setType(WALL);
}
// These finish off diagonal walling logic for 2,3 or 4 way forks.
if (getN(cur) != null && getN(cur).getType() == FLOOR){
if (getW(cur) != null &&getW(cur).getType() == FLOOR && getNW(cur).getType()!=FLOOR){
getNW(cur).setType(WALL);//needed because these are essentially corners
}
if (getE(cur) != null &&getE(cur).getType() == FLOOR && getNE(cur).getType()!=FLOOR){
getNE(cur).setType(WALL);
}
}
if (getS(cur) != null && getS(cur).getType() == FLOOR){
if (getW(cur) != null &&getW(cur).getType() == FLOOR && getSW(cur).getType()!=FLOOR){
getSW(cur).setType(WALL);
}
if (getE(cur) != null &&getE(cur).getType() == FLOOR && getSE(cur).getType()!=FLOOR){
getSE(cur).setType(WALL);
}
}
}
}
cur.setType(EXIT); // Select the last visited node as the exit Tile
}
/**
* Check the number of unreachable Tiles in the maze. If there are too many, the maze is deemed too easy and thrown away
*
* @return Boolean result
*/
private boolean goodMaze(){
int count = 0;
for (int n = 0;n < size;n++){
for (int m = 0;m < size;m++){
if (maze[n][m].getType() == EMPTY){
count++;
}
}
}
//so the more unreachable tiles there are in the maze, the more likely that they're in larger clumps. Even if they aren't, they create large zones of walls, which make the maze easier to traverse while still looking organic
if (count > DIFFICULTY){
return false;
} else {
return true;
}
}
/**
* Get relative Tile above some current Tile.
* NOTE: WILL RETURN NULL IF YOU ACCESS A TILE OUTSIDE THE ARRAY
*
* @param tile Current Tile
* @return Tile above current Tile
*/
public Tile getN(Tile tile){
if(tile == null) return null;
if (tile.getRow() > 0){
return maze[tile.getCol()][tile.getRow()-1];
} else {
return null;
}
}
/**
* Get relative Tile below some current Tile.
* NOTE: WILL RETURN NULL IF YOU ACCESS A TILE OUTSIDE THE ARRAY
*
* @param tile Current Tile
* @return Tile below current Tile
*/
public Tile getS(Tile tile){
if(tile == null) return null;
if (tile.getRow() < size-1){
return maze[tile.getCol()][tile.getRow()+1];
} else {
return null;
}
}
/**
* Get relative Tile to the right of some current Tile.
* NOTE: WILL RETURN NULL IF YOU ACCESS A TILE OUTSIDE THE ARRAY
*
* @param tile Current Tile
* @return Tile to the right of current Tile
*/
public Tile getE(Tile tile){
if(tile == null) return null;
if (tile.getCol() < size-1){
return maze[tile.getCol()+1][tile.getRow()];
} else {
return null;
}
}
/**
* Get relative Tile to the left some current Tile.
* NOTE: WILL RETURN NULL IF YOU ACCESS A TILE OUTSIDE THE ARRAY
*
* @param tile Current Tile
* @return Tile to the left current Tile
*/
public Tile getW(Tile tile){
if(tile == null) return null;
if (tile.getCol() > 0){
return maze[tile.getCol()-1][tile.getRow()];
} else {
return null;
}
}
/**
* Get relative Tile to the upper-left some current Tile.
*
* @param tile
* @return
*/
public Tile getNW(Tile tile){
return getW(getN(tile));
}
/**
* Get relative Tile to the upper-right some current Tile.
*
* @param tile
* @return
*/
public Tile getNE(Tile tile){
return getE(getN(tile));
}
/**
* Get relative Tile to the lower-left some current Tile.
*
* @param tile
* @return
*/
public Tile getSW(Tile tile){
return getW(getS(tile));
}
/**
* Get relative Tile to the lower-right some current Tile.
*
* @param tile
* @return
*/
public Tile getSE(Tile tile){
return getE(getS(tile));
}
/**
* Finds the key status of the maze
*
* @return the key status of the maze. True if the key has been picked up, false otherwise.
*/
public boolean isKeyStatus() {
return keyStatus;
}
/**
* Finds the size of the maze (actually the number of rows/columns)
*
* @return the number of rows or columns in the maze
*/
public int getSize() {
return size;
}
/**
* Returns a specific tile at the specified row and column number
*
* @param col the column in the maze that you are looking for
* @param row the row in the maze that you are looking for
* @return the specific tile at the specified row and column number
*/
public Tile getTile(int col, int row){
return maze[col][row];
}
/**
* Gets the first instance of a Tile of the given type. Usually used to find EXIT, START and KEY tiles, hence why it only finds the first instance
* Returns null if it can't find anything (it really shouldn't)
*
* @param type the type of the tile you're looking for
* @return Tile the first instance of a tile that you're looking for
*/
public Tile getTileType(int type){
for(int n = 0;n < size;n++){
for(int m = 0;m < size;m++){
if(maze[n][m].getType() == type)return maze[n][m];
}
}
return null;
}
//Irfan -renamed function for User.java
/**
* Get the type of a Tile.
*
* @param row the row in the maze that you are looking for
* @param col the column in the maze that you are looking for
* @return
*/
public int tileType(int row, int col){
return maze[col][row].getType();
}
}