maze-generator/src/main/java/ch/fritteli/maze/generator/renderer/text/Generator.java

208 lines
7.4 KiB
Java

package ch.fritteli.maze.generator.renderer.text;
import ch.fritteli.maze.generator.model.Direction;
import ch.fritteli.maze.generator.model.Maze;
import ch.fritteli.maze.generator.model.Tile;
import io.vavr.control.Option;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class Generator {
@NonNull
private final Maze maze;
private final boolean renderSolution;
private int x = 0;
private int y = 0;
// Each row has three lines:
// - The top border
// - The central pathway
// - The bottom border
// The bottom border of one row is identical to the top border of the following row. We use this variable here in
// order to be able to render the bottom border of the last row, which obviously does not have a following row with
// a top border.
private int line = 0;
boolean hasNext() {
return this.y < this.maze.getHeight();
}
String next() {
final Tile currentTile = this.maze.getTileAt(this.x, this.y).get();
final Option<Tile> topTile = this.maze.getTileAt(this.x, this.y - 1);
final Option<Tile> rightTile = this.maze.getTileAt(this.x + 1, this.y);
final Option<Tile> bottomTile = this.maze.getTileAt(this.x, this.y + 1);
final Option<Tile> leftTile = this.maze.getTileAt(this.x - 1, this.y);
final String s;
switch (this.line) {
case 0:
s = this.renderTopLine(currentTile, leftTile, topTile);
break;
case 1:
s = this.renderCenterLine(currentTile, topTile, rightTile, bottomTile, leftTile);
break;
case 2:
s = this.renderBottomLine(currentTile, leftTile);
break;
default:
s = "";
break;
}
this.prepareNextStep();
return s;
}
private void prepareNextStep() {
// do some magic ...
this.x++;
if (this.x == this.maze.getWidth()) {
// Reached the end of the row?
// On to the next line then!
this.x = 0;
this.line++;
}
if (this.line == 2 && this.y < this.maze.getHeight() - 1) {
// Finished rendering the center line, and more rows available?
// On to the next row then!
this.line = 0;
this.y++;
}
if (this.line == 3) {
// Finished rendering the bottom line (of the last row)?
// Increment row counter one more time => this is the exit condition.
this.line = 0;
this.y++;
}
}
private String renderTopLine(@NonNull final Tile currentTile, @NonNull final Option<Tile> leftTile, @NonNull final Option<Tile> topTile) {
final CharDefinition charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition();
final CharDefinition charDef3 = new CharDefinition();
String result;
if (currentTile.hasWallAt(Direction.LEFT)) {
charDef1.down();
}
if (currentTile.hasWallAt(Direction.TOP)) {
charDef1.right();
charDef2.horizontal();
charDef3.left();
} else {
if (this.isSolution(currentTile) && (this.isSolution(topTile) || topTile.isEmpty())) {
charDef2.solution().vertical();
}
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
charDef3.down();
}
if (this.hasWallAt(leftTile, Direction.TOP)) {
charDef1.left();
}
if (this.hasWallAt(topTile, Direction.LEFT)) {
charDef1.up();
}
if (this.hasWallAt(topTile, Direction.RIGHT)) {
charDef3.up();
}
result = charDef1.toString() + charDef2;
if (this.x == this.maze.getWidth() - 1) {
result += charDef3 + "\n";
}
return result;
}
private String renderCenterLine(@NonNull final Tile currentTile,
@NonNull final Option<Tile> topTile,
@NonNull final Option<Tile> rightTile,
@NonNull final Option<Tile> bottomTile,
@NonNull final Option<Tile> leftTile) {
final CharDefinition charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition();
final CharDefinition charDef3 = new CharDefinition();
String result;
if (currentTile.hasWallAt(Direction.LEFT)) {
charDef1.vertical();
} else {
if (this.isSolution(currentTile) && this.isSolution(leftTile)) {
charDef1.solution().horizontal();
}
}
if (this.isSolution(currentTile)) {
charDef2.solution();
if (!currentTile.hasWallAt(Direction.LEFT) && (this.isSolution(leftTile) || leftTile.isEmpty())) {
charDef2.left();
}
if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile.isEmpty())) {
charDef2.up();
}
if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile.isEmpty())) {
charDef2.right();
}
if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile.isEmpty())) {
charDef2.down();
}
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
charDef3.vertical();
} else {
if (this.isSolution(currentTile) && this.isSolution(rightTile)) {
charDef3.solution().horizontal();
}
}
result = charDef1.toString() + charDef2;
if (this.x == this.maze.getWidth() - 1) {
result += charDef3 + "\n";
}
return result;
}
private String renderBottomLine(@NonNull final Tile currentTile, @NonNull final Option<Tile> leftTile) {
String result;
final CharDefinition charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition();
final CharDefinition charDef3 = new CharDefinition();
if (currentTile.hasWallAt(Direction.LEFT)) {
charDef1.up();
}
if (currentTile.hasWallAt(Direction.BOTTOM)) {
charDef1.right();
charDef2.horizontal();
charDef3.left();
} else {
if (this.isSolution(currentTile)) {
charDef2.solution().vertical();
}
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
charDef3.up();
}
if (this.hasWallAt(leftTile, Direction.BOTTOM)) {
charDef1.left();
}
result = charDef1.toString() + charDef2;
if (this.x == this.maze.getWidth() - 1) {
result += charDef3;
}
return result;
}
private boolean hasWallAt(@NonNull final Option<Tile> tile, @NonNull final Direction direction) {
return tile.map(t -> t.hasWallAt(direction)).getOrElse(false);
}
private boolean isSolution(@NonNull final Tile tile) {
return this.renderSolution && tile != null && tile.isSolution();
}
private boolean isSolution(@NonNull final Option<Tile> tile) {
return this.renderSolution && tile.map(Tile::isSolution).getOrElse(false);
}
}