patternjavaModerate
World Generation for City Builder
Viewed 0 times
buildergenerationworldforcity
Problem
I decided that I wanted to add some spice to my bland looking city maps, so I created a couple of algorithms to add water to the map. I also added a "marshlands" setting that would just randomly sprinkle water throughout the map.
Here is an example of the river generation:
Here is the world generation method in its entirety:
Since the map is viewed from an isometric perspective, it's not as simple as just moving from 0 to max or max to 0. I needed these helper methods to find the exact positions to start the river (i.e. in the middle of the row), and the exact value of the row for each edge of the map:
```
IsoPoint edgePointForDirection(IsoDirection direction) {
switch (direction) {
case LEFT:
return new IsoPoint(0, CityWorld.WORLD_HEIGHT/2);
case RIGHT:
return new IsoPoint(CityWorld.WORLD_WIDTH - 1, CityWorld.WORLD_HEIGHT/2);
case UP:
return new IsoPoint(CityWorld.WORLD_WIDTH/2, CityWorld.WORLD_HEIGHT - 1);
case DOWN:
return new IsoPoint(CityWorld.WORLD_WIDTH/2, 0);
default:
return new IsoPoint(0, 0);
}
}
int edgeValue(IsoDirection direction) {
switch (direction) {
case LEFT:
return 0;
case RIGHT:
return
Here is an example of the river generation:
Here is the world generation method in its entirety:
private Tile[][] createWorld(boolean river, boolean ocean, boolean marsh) {
//use 6 instead of 5 to generate marshland
int lastGroundTile = marsh ? 6 : 5;
Tile[][] world = new Tile[CityWorld.WORLD_WIDTH][CityWorld.WORLD_HEIGHT];
for (int x = 0; x = 0; i--) {
Tile tile = world[sideEdgeValue + i][y];
tile.setBuildingType(IsoTileType.WATER01);
}
}
break;
case RIGHT:
for (int y = 0; y = 0; i--) {
Tile tile = world[x][sideEdgeValue + i];
tile.setBuildingType(IsoTileType.WATER01);
}
}
break;
default:
break;
}
}
return world;
}Since the map is viewed from an isometric perspective, it's not as simple as just moving from 0 to max or max to 0. I needed these helper methods to find the exact positions to start the river (i.e. in the middle of the row), and the exact value of the row for each edge of the map:
```
IsoPoint edgePointForDirection(IsoDirection direction) {
switch (direction) {
case LEFT:
return new IsoPoint(0, CityWorld.WORLD_HEIGHT/2);
case RIGHT:
return new IsoPoint(CityWorld.WORLD_WIDTH - 1, CityWorld.WORLD_HEIGHT/2);
case UP:
return new IsoPoint(CityWorld.WORLD_WIDTH/2, CityWorld.WORLD_HEIGHT - 1);
case DOWN:
return new IsoPoint(CityWorld.WORLD_WIDTH/2, 0);
default:
return new IsoPoint(0, 0);
}
}
int edgeValue(IsoDirection direction) {
switch (direction) {
case LEFT:
return 0;
case RIGHT:
return
Solution
createWorld
It's not
I hope that
I guess, you don't need to use the
You could use
and save yourself a line and a local variable.
In case you need multiple times, move it into the enum and use
This method is clearly too long and can be naturally split into main, "river", and "ocean" parts.
The "ocean" part is pretty long, but I can't see how to shorten it. Your loops are descending or ascending, which is not needed, but changing it doesn't help.
What about
? Shouldn't
I like printf-style debugging, but I never ever use
instead. When you remove the class, you can sure that no printing is left over (logging is a different thing).
Are there more than 4 directions? If no, then drop it (and get a warning when anything changes). You may drop the "break" in any case.
edgePointForDirection
Since the map is viewed from an isometric perspective, it's not as simple as just moving from 0 to max or max to 0.
Your coordinates can be 0 to max, independently of how they world get displayed. Usually, it's a bad idea to let the view influence the model too much. However, it's your choice, just be aware of it.
You're systematically using no blanks around
edgeValue
This could use a better name, but I have no idea.
randomMoveTowards
This is actually a "randomMovePerpendicularTo", isn't it?
Don't do it. It's much better to use a single random instance everywhere as this allows you to seed it and get deterministic behavior which is invaluable when debugging.
I could imagine shortening the method to something like
or even more using
I created these algorithms myself without really studying other approaches, so I would love to hear about other ways to do this.
It took me a while to understand your river algorithm, but it's actually simple and the result is nice.
I'm sure that the switch statement inside of the ocean generation section of the world generation method is really bad, but I can't think of a more concise way to do things.
Neither can I. I could simplify it somehow, but there are too many direction-specific parts and the result could get too hard to understand. I'd probably extract the ugly switch to a separate method and forget it.
Summary
The code is good and the game looks fantastic!
//use 6 instead of 5 to generate marshland
int lastGroundTile = marsh ? 6 : 5;It's not
lastGroundTile. It's not last and it does never get generated inworld[x][y] = new Tile(new IsoPoint(x, y), IsoTileType.getGroundType(this.game.random.nextInt(lastGroundTile)), ZoneType.NOZONE);I hope that
IsoTileType is an enum, so you could use a corresponding constant to make it clearer (marsh ? 6 : 5 even with a comment is not very good). Maybe you should move the functionality to IsoTileType and writeworld[x][y] = new Tile(new IsoPoint(x, y), IsoTileType.getGroundType(this.game.random, marsh), ZoneType.NOZONE);I guess, you don't need to use the
this qualifier above.boolean randomBool = this.game.random.nextBoolean();
int riverWidth = randomBool ? 2 : 1;You could use
int riverWidth = game.random.randomInt(2) + 1;and save yourself a line and a local variable.
IsoDirection sideToStart = IsoDirection.values()[this.game.random.nextInt(IsoDirection.values().length)];In case you need multiple times, move it into the enum and use
IsoDirection sideToStart = IsoDirection.get(game.random);This method is clearly too long and can be naturally split into main, "river", and "ocean" parts.
The "ocean" part is pretty long, but I can't see how to shorten it. Your loops are descending or ascending, which is not needed, but changing it doesn't help.
int randomWidth = this.game.random.nextInt(4); //max 3 deepWhat about
int randomWidth = this.game.random.nextInt(MAX_OCEAN_DEPTH + 1);? Shouldn't
WATER01 be called OCEAN?System.out.println("random # = " + String.valueOf(i));I like printf-style debugging, but I never ever use
System.out directly. Write your own trivial class and useDebug.print("random # = ", i);instead. When you remove the class, you can sure that no printing is left over (logging is a different thing).
default:
break;Are there more than 4 directions? If no, then drop it (and get a warning when anything changes). You may drop the "break" in any case.
edgePointForDirection
Since the map is viewed from an isometric perspective, it's not as simple as just moving from 0 to max or max to 0.
Your coordinates can be 0 to max, independently of how they world get displayed. Usually, it's a bad idea to let the view influence the model too much. However, it's your choice, just be aware of it.
return new IsoPoint(CityWorld.WORLD_WIDTH - 1, CityWorld.WORLD_HEIGHT/2);You're systematically using no blanks around
/.edgeValue
This could use a better name, but I have no idea.
randomMoveTowards
This is actually a "randomMovePerpendicularTo", isn't it?
Random random = new Random();Don't do it. It's much better to use a single random instance everywhere as this allows you to seed it and get deterministic behavior which is invaluable when debugging.
I could imagine shortening the method to something like
IsoDirection[] directions = {UP, RIGHT, DOWN, LEFT};
int index = directions.indexOf(direction);
index += 1; // to get perpendicular
index += 2 * random.nextInt(2); // to conditionally get the opposite direction
return directions(index % directions.length);or even more using
IsoDirection.values() and ordinal(), but this feels hacky.I created these algorithms myself without really studying other approaches, so I would love to hear about other ways to do this.
It took me a while to understand your river algorithm, but it's actually simple and the result is nice.
I'm sure that the switch statement inside of the ocean generation section of the world generation method is really bad, but I can't think of a more concise way to do things.
Neither can I. I could simplify it somehow, but there are too many direction-specific parts and the result could get too hard to understand. I'd probably extract the ugly switch to a separate method and forget it.
Summary
The code is good and the game looks fantastic!
Code Snippets
//use 6 instead of 5 to generate marshland
int lastGroundTile = marsh ? 6 : 5;world[x][y] = new Tile(new IsoPoint(x, y), IsoTileType.getGroundType(this.game.random.nextInt(lastGroundTile)), ZoneType.NOZONE);world[x][y] = new Tile(new IsoPoint(x, y), IsoTileType.getGroundType(this.game.random, marsh), ZoneType.NOZONE);boolean randomBool = this.game.random.nextBoolean();
int riverWidth = randomBool ? 2 : 1;int riverWidth = game.random.randomInt(2) + 1;Context
StackExchange Code Review Q#97865, answer score: 10
Revisions (0)
No revisions yet.