Code is below, but first some background:
I'm hoping to make a game via the Construct 2 engine. It intrigues my so I want to play with it. I have an idea for a game that involves dungeon/maze generation. I want it to be random for replayability. It's the first time I've done something like this though (still very much a newb) so I went to prototype the idea of the generation in Java because I can form the logic easier. I figured I could translate it to Construct's events later on. I sat down and thought about doing it and my mind drew a blank. I figured the easiest way would be to use some type of recursive methods, and after looking into the idea of maze generation algorithms, it seems I was right. The problem is though that I can't do anything recursively. It has to be done through loops. I'm also building the maze in a 2D array so I can build the map from that. That means I need empty spaces between paths. The algorithms I was looking at didn't do this. I understand the idea of running through a loop with a structure but structure are a bit non-existent in Construct from what I can tell. I can build a second 2D parallel array to track where the maze has been but I was hoping not to so I could save memory and reduce garbage collection and stuff. I got completely stuck at this point so I just started throwing ideas at the IDE and saw what stuck.
Anyway, by a really happy mistake, I came up with the code below. Now, it's not really good to develop mazes. The output results in exactly what I ultimately aim to achieve though. I wanted random tunnels and random large rooms and caverns. I wanted the path to the exit different every time. I was planning on doing some other silly stuff once a maze was created, but this creates the output right away. I ran through the algorithm a lot of times and adjusted the chokepoint to where it gives me a seemingly good result with a viable path every time (though I still have to write something to double check this each time. The code is very much a prototype but I wanted to get opinions on it, what looks crappy, what I could improve on. I'm still very much a newb so all the criticism I can get is good. Keep in mind I want to translate this to Construct 2 at some point though it doesn't bother me if you give me tips as if I was continuing this project in Java.
I thank anyone for their feedback. I really do appreciate it.
import java.util.Random;
public class Main {
//These variables define the size of the maze.
//Change only these to change the size of the maze
private static final int ROWSIZE = 40;
private static final int COLUMNSIZE = 40;
public static void main(String[] args) {
//variable declarations
int[][] maze;
int randX = 0;
int randY = 0;
int choke = 0;
int checkSurroundings = 0;
Random rand = new Random();
maze = new int[ROWSIZE][COLUMNSIZE];
//Initialize maze array to all zeros (make every space a non-moveable area)
for (int x = 0;x < ROWSIZE; x++){
for (int y = 0; y < COLUMNSIZE; y++){
maze[x][y] = 0;
}
}
//Define outside walls
//Construct 2 specific:
//We will use the number two to define all outside walls of maze so the game engine
//knows where to put these special tiles
for (int x = 0; x < ROWSIZE; x++){
maze[x][0] = 2;
maze[x][COLUMNSIZE - 1] = 2;
}
for (int x = 0; x < COLUMNSIZE; x++){
maze[0][x] = 2;
maze[ROWSIZE - 1][x] = 2;
}
//Maze Generation
randX = rand.nextInt(ROWSIZE - 10) + 5;
maze[randX][0] = 1; //Mark beginning
maze[randX][1] = 1;
randX = rand.nextInt(ROWSIZE - 10) + 5;
maze[randX][COLUMNSIZE - 1] = 1; //Mark ending
maze[randX][COLUMNSIZE - 2] = 1;
do {
randX = rand.nextInt(ROWSIZE - 2) + 1;
randY = rand.nextInt(COLUMNSIZE - 2) + 1;
checkSurroundings = maze[randX - 1][randY] + maze[randX + 1][randY] + maze[randX][randY - 1] + maze[randX][randY + 1];
if (checkSurroundings < 4){
maze[randX][randY] = 1;
choke = 0;
} else {
choke ++;
}
} while (choke < 4);
//Print results of the maze array
System.out.printf("\n");
for (int x = 0; x < ROWSIZE; x++){
for (int y = 0; y < COLUMNSIZE; y++){
if (maze[x][y] == 0 || maze[x][y] == 2)
System.out.printf("%d ", maze[x][y]);
if (maze[x][y] == 1)
System.out.printf("# ");
}
System.out.print("\n");
}
}
}
Nice code Mepis. The first thing I see where you could improve on is putting this code in a class, not in a main method.
You do not need to initialize everything with 0.
Java does that for you.
the mole is correct. But this is also more the reason why it should not be in main though. Their is something called the default constructor, which the JVM (Java Virtual Machine) creates for you when instantiating classes. It is used to assign all primitives with a 0, 0.0 or null value. However, if you needed to override or overload the default constructor, you can. An example would be like this:
File 1.
public class Main{
public static void main(String[] args){
ClassExample ce1; = new ClassExample(); // Uses default constructor.
int[][] maze = new int[40][40];
ClassExample ce2; = new ClassExample(0, 0, 0, 0, maze); // Overloads default constructor.
}
}
File 2
import java.util.Random;
public class ClassExample {
// Your variables should look like this.
private final int ROWSIZE = 40;
private final int COLUMNSIZE = 40;
private int[][] maze;
private int randX;
private int randY;
private int choke;
private int checkSurroundings;
/*If you want to define a default value on creation of the class,
you then want to override the default constructor like this.
*/
public ClassExample() {
randX = 0;
randY = 0;
choke = 0;
checkSurroundings = 0;
maze = new int[ROWSIZE][COLUMNSIZE]; // Will create instantiate on instantiate.
}
/*This will allow you to do the same thing as above but also allow
you to override the default values assigned in the default constructor
for reasons relating to making the code scalable. Remember, if you want
to overload the default constructor, you need to override the default
constructor first!
*/
public ClassExample(int x, int y, int choke, int cS, int[][] maze){
this.randX = x;
this.randY = y;
this.choke = choke;
this.checkSurroundings = cS;
this.maze = maze;
}
// <-- Put your logic methods here.
// Setters/Getters for all private class primatives.
public int[][] getMaze() {
return maze;
}
public void setMaze(int[][] maze) {
this.maze = maze;
}
public int getRandX() {
return randX;
}
public void setRandX(int randX) {
this.randX = randX;
}
public int getRandY() {
return randY;
}
public void setRandY(int randY) {
this.randY = randY;
}
public int getChoke() {
return choke;
}
public void setChoke(int choke) {
this.choke = choke;
}
public int getCheckSurroundings() {
return checkSurroundings;
}
public void setCheckSurroundings(int checkSurroundings) {
this.checkSurroundings = checkSurroundings;
}
// Getters only for final primatives
public int getROWSIZE() {
return ROWSIZE;
}
public int getCOLUMNSIZE() {
return COLUMNSIZE;
}
}
Also, remember that static means global. You must assign a static value to any variable called outside of a method in the main class because of runtime limitations. This is useful for some reasons, but not in the way you are using it. If you create those variables in a sub class to main, they do not need to be static as seen in the example above.
Additionally, remember that you can use getters for final (constant) variables too, you just can't use setters because they are final.
Another good reason for using a subclass like this for the sake of being able to instantiate it multiple times. For example, if you had a game with two players, and it was a race to see who could get through the maze faster, but one of the players was very experienced with your game, you may need to create a handicap for the more experienced. In this instance, you may want to overload the default constructor to make the maze size larger, thus creating two maze objects; one for each player with the larger maze going to the experienced player. Additionally, if you have to reuse the values stored in the instantiated objects, the values could remain the same since you could overload the default constructor. Or, if you needed to reset their values, you could also do this by instantiating with the default constructor. And finally, because the primitives are not static, their's less chance of exploitation. The above example are key points of overloading, overriding, encapsulation and polymorphism.
One more note: Try to get in the habit of using try/catch/finally. Here's an example:
public boolean Game(){
try {
// <-- Put your logic here.
// Example if only
if(true){
System.out.println(maze[500][500]); // Will create a null exception!
}
return true;
} catch (Exception e) { // Will attempt to catch exception and keep application running.
e.printStackTrace(); // Will tell you what happened.
return false;
}
}
The key reasons for using try/catch/finally is it keeps the application running even if their is an exception, and it also makes your life easier to track down the bug. This greatly improves on quality assurance. Hope this helps. Please feel free to add on to this in any way needed, or correct anywhere I am wrong. Thanks!