initial commit.
This commit is contained in:
parent
817cfbe4f3
commit
f31821f100
8 changed files with 429 additions and 0 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
.idea/
|
||||
### Maven template
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
29
pom.xml
Normal file
29
pom.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>ch.fritteli</groupId>
|
||||
<artifactId>fritteli-build-parent</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>labyrinth</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vavr</groupId>
|
||||
<artifactId>vavr</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
57
src/main/java/labyrinth/Direction.java
Normal file
57
src/main/java/labyrinth/Direction.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package labyrinth;
|
||||
|
||||
import io.vavr.collection.Stream;
|
||||
import io.vavr.control.Option;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Random;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public enum Direction {
|
||||
TOP(position -> position.withY(position.getY() - 1)),
|
||||
BOTTOM(position -> position.withY(position.getY() + 1)),
|
||||
LEFT(position -> position.withX(position.getX() - 1)),
|
||||
RIGHT(position -> position.withX(position.getX() + 1));
|
||||
private static final Random random = new Random();
|
||||
@NonNull
|
||||
private final UnaryOperator<Position> translation;
|
||||
|
||||
Direction(@NonNull final UnaryOperator<Position> translation) {
|
||||
this.translation = translation;
|
||||
}
|
||||
|
||||
public static Direction getRandom() {
|
||||
return values()[random.nextInt(4)];
|
||||
}
|
||||
|
||||
public static Option<Direction> getRandomExcluding(@NonNull final EnumSet<Direction> directions) {
|
||||
final EnumSet<Direction> allowedDirections = EnumSet.complementOf(directions);
|
||||
return Stream.ofAll(allowedDirections).shuffle().headOption();
|
||||
}
|
||||
|
||||
public static Option<Direction> getRandomExcluding(@NonNull final Direction... directions) {
|
||||
return Stream.of(values())
|
||||
.removeAll(Stream.of(directions))
|
||||
.shuffle()
|
||||
.headOption();
|
||||
}
|
||||
|
||||
public Direction getOpposite() {
|
||||
switch (this) {
|
||||
case TOP:
|
||||
return BOTTOM;
|
||||
case LEFT:
|
||||
return RIGHT;
|
||||
case RIGHT:
|
||||
return LEFT;
|
||||
case BOTTOM:
|
||||
return TOP;
|
||||
}
|
||||
throw new IllegalStateException("Programming error: Not all enum values covered in enum Direction#getOpposite()!");
|
||||
}
|
||||
|
||||
public Position translate(@NonNull final Position position) {
|
||||
return this.translation.apply(position);
|
||||
}
|
||||
}
|
63
src/main/java/labyrinth/Directions.java
Normal file
63
src/main/java/labyrinth/Directions.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
package labyrinth;
|
||||
|
||||
import io.vavr.collection.Stream;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Directions {
|
||||
private final Set<Direction> directions = new HashSet<>();
|
||||
// FIXME remove getter, only for debugging
|
||||
@Getter
|
||||
private final Set<Direction> hardened = new HashSet<>();
|
||||
|
||||
public boolean set(@NonNull final Direction direction) {
|
||||
return this.directions.add(direction);
|
||||
}
|
||||
|
||||
public void setAll() {
|
||||
this.directions.addAll(EnumSet.allOf(Direction.class));
|
||||
}
|
||||
|
||||
public boolean clear(@NonNull final Direction direction) {
|
||||
if (this.hardened.contains(direction)) {
|
||||
return false;
|
||||
}
|
||||
return this.directions.remove(direction);
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
this.directions.clear();
|
||||
this.directions.addAll(this.hardened);
|
||||
}
|
||||
|
||||
public boolean isSet(@NonNull final Direction direction) {
|
||||
return this.directions.contains(direction);
|
||||
}
|
||||
|
||||
public Iterable<Direction> getSet() {
|
||||
return this.getSet(false);
|
||||
}
|
||||
|
||||
public Iterable<Direction> getSet(final boolean onlyBrittleWalls) {
|
||||
if (onlyBrittleWalls) {
|
||||
return Stream.ofAll(this.directions)
|
||||
.removeAll(this.hardened);
|
||||
}
|
||||
return Stream.ofAll(this.directions);
|
||||
}
|
||||
|
||||
public void harden(@NonNull final Direction direction) {
|
||||
if (!this.directions.contains(direction)) {
|
||||
throw new IllegalStateException("Trying to harden cleared Direction: " + direction);
|
||||
}
|
||||
this.hardened.add(direction);
|
||||
}
|
||||
|
||||
public void unharden(@NonNull final Direction direction) {
|
||||
this.hardened.remove(direction);
|
||||
}
|
||||
}
|
194
src/main/java/labyrinth/Labyrinth.java
Normal file
194
src/main/java/labyrinth/Labyrinth.java
Normal file
|
@ -0,0 +1,194 @@
|
|||
package labyrinth;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class Labyrinth {
|
||||
private final Tile[][] field;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public Labyrinth(final int width, final int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.field = new Tile[width][height];
|
||||
this.initField();
|
||||
System.out.println(this);
|
||||
this.generate();
|
||||
}
|
||||
private static final char TOP_LEFT = '\u250c';
|
||||
private static final char TOP_RIGHT = '\u2510';
|
||||
private static final char MIDDLE_LEFT = '\u251c';
|
||||
private static final char MIDDLE_RIGHT = '\u2524';
|
||||
private static final char BOTTOM_LEFT = '\u2514';
|
||||
private static final char BOTTOM_RIGHT = '\u2518';
|
||||
private static final char HORIZONTAL = '\u2500';
|
||||
private static final char VERTICAL = '\u2502';
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int y = 0; y < this.height; y++) {
|
||||
// TOP WALL
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
final Tile tile = this.getTileAt(new Position(x, y));
|
||||
final boolean top = tile.getWalls().isSet(Direction.TOP);
|
||||
final boolean topHard = tile.getWalls().getHardened().contains(Direction.TOP);
|
||||
sb.append(TOP_LEFT);
|
||||
if (topHard) {
|
||||
if (top) {
|
||||
sb.append(HORIZONTAL);
|
||||
} else {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
if (top) {
|
||||
sb.append("-");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
sb.append(TOP_RIGHT);
|
||||
}
|
||||
sb.append("\n");
|
||||
// LEFT WALL, CENTER, RIGHT WALL
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
final Tile tile = this.getTileAt(new Position(x, y));
|
||||
// left
|
||||
final boolean left = tile.getWalls().isSet(Direction.LEFT);
|
||||
final boolean leftHard = tile.getWalls().getHardened().contains(Direction.LEFT);
|
||||
if (leftHard) {
|
||||
if (left) {
|
||||
sb.append(VERTICAL);
|
||||
} else {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
if (left) {
|
||||
sb.append(":");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
// center
|
||||
sb.append(" ");
|
||||
// right
|
||||
final boolean right = tile.getWalls().isSet(Direction.RIGHT);
|
||||
final boolean rightHard = tile.getWalls().getHardened().contains(Direction.RIGHT);
|
||||
if (rightHard) {
|
||||
if (right) {
|
||||
sb.append(VERTICAL);
|
||||
} else {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
if (right) {
|
||||
sb.append(":");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append("\n");
|
||||
// BOTTOM WALL
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
final Tile tile = this.getTileAt(new Position(x, y));
|
||||
final boolean bottom = tile.getWalls().isSet(Direction.BOTTOM);
|
||||
final boolean bottomHard = tile.getWalls().getHardened().contains(Direction.BOTTOM);
|
||||
sb.append(BOTTOM_LEFT);
|
||||
if (bottomHard) {
|
||||
if (bottom) {
|
||||
sb.append(HORIZONTAL);
|
||||
} else {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
if (bottom) {
|
||||
sb.append("-");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
sb.append(BOTTOM_RIGHT);
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private Tile getTileAt(@NonNull final Position position) {
|
||||
return this.field[position.getX()][position.getY()];
|
||||
}
|
||||
|
||||
private void initField() {
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
this.field[x] = new Tile[this.height];
|
||||
for (int y = 0; y < this.height; y++) {
|
||||
final Tile tile = new Tile();
|
||||
this.hardenWalls(tile, x, y);
|
||||
this.field[x][y] = tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hardenWalls(@NonNull final Tile tile, final int x, final int y) {
|
||||
if (x == 0) {
|
||||
tile.preventDiggingToOrFrom(Direction.LEFT);
|
||||
} else if (x == width - 1) {
|
||||
tile.preventDiggingToOrFrom(Direction.RIGHT);
|
||||
}
|
||||
if (y == 0) {
|
||||
if (x != 0) {
|
||||
tile.preventDiggingToOrFrom(Direction.TOP);
|
||||
}
|
||||
} else if (y == height - 1) {
|
||||
tile.preventDiggingToOrFrom(Direction.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
private void generate() {
|
||||
new Generator();
|
||||
}
|
||||
|
||||
public class Generator {
|
||||
private final Deque<Position> positions = new LinkedList<>();
|
||||
|
||||
Generator() {
|
||||
final Position topLeft = new Position(0, 0);
|
||||
Labyrinth.this.getTileAt(topLeft).digFrom(Direction.TOP);
|
||||
this.positions.push(topLeft);
|
||||
this.dig();
|
||||
final Position bottomRight = new Position(Labyrinth.this.width - 1, Labyrinth.this.height - 1);
|
||||
final Tile bottomRightTile = Labyrinth.this.getTileAt(bottomRight);
|
||||
bottomRightTile.enableDiggingToOrFrom(Direction.BOTTOM);
|
||||
bottomRightTile.digTo(Direction.BOTTOM);
|
||||
}
|
||||
|
||||
private void dig() {
|
||||
while (!this.positions.isEmpty()) {
|
||||
final Position currentPosition = this.positions.peek();
|
||||
final Tile currentTile = Labyrinth.this.getTileAt(currentPosition);
|
||||
final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection();
|
||||
if (directionToDigTo.isDefined()) {
|
||||
final Direction digTo = directionToDigTo.get();
|
||||
final Direction digFrom = digTo.getOpposite();
|
||||
final Position neighborPosition = digTo.translate(currentPosition);
|
||||
final Tile neighborTile = Labyrinth.this.getTileAt(neighborPosition);
|
||||
if (currentTile.digTo(digTo) && neighborTile.digFrom(digFrom)) {
|
||||
// all ok!
|
||||
this.positions.push(neighborPosition);
|
||||
} else {
|
||||
// Hm, didn't work.
|
||||
currentTile.undigTo(digTo);
|
||||
currentTile.preventDiggingToOrFrom(digTo);
|
||||
}
|
||||
} else {
|
||||
this.positions.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/main/java/labyrinth/Main.java
Normal file
12
src/main/java/labyrinth/Main.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package labyrinth;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public class Main {
|
||||
public static void main(@NonNull final String[] args) {
|
||||
int width = 5;
|
||||
int height = 8;
|
||||
final Labyrinth labyrinth = new Labyrinth(width, height);
|
||||
System.out.println(labyrinth);
|
||||
}
|
||||
}
|
11
src/main/java/labyrinth/Position.java
Normal file
11
src/main/java/labyrinth/Position.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
package labyrinth;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
@Value
|
||||
@With
|
||||
public class Position {
|
||||
int x;
|
||||
int y;
|
||||
}
|
49
src/main/java/labyrinth/Tile.java
Normal file
49
src/main/java/labyrinth/Tile.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package labyrinth;
|
||||
|
||||
import io.vavr.collection.Stream;
|
||||
import io.vavr.control.Option;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class Tile {
|
||||
// FIXME remove me; only for debugging
|
||||
@Getter
|
||||
final Directions walls = new Directions();
|
||||
@Getter
|
||||
boolean visited = false;
|
||||
|
||||
public Tile() {
|
||||
this.walls.setAll();
|
||||
}
|
||||
|
||||
public void preventDiggingToOrFrom(@NonNull final Direction direction) {
|
||||
this.walls.harden(direction);
|
||||
}
|
||||
|
||||
public void enableDiggingToOrFrom(@NonNull final Direction direction) {
|
||||
this.walls.unharden(direction);
|
||||
}
|
||||
|
||||
public boolean digFrom(@NonNull final Direction direction) {
|
||||
if (this.visited) {
|
||||
return false;
|
||||
}
|
||||
this.visited = true;
|
||||
return this.walls.clear(direction);
|
||||
}
|
||||
|
||||
public boolean digTo(@NonNull final Direction direction) {
|
||||
return this.walls.clear(direction);
|
||||
}
|
||||
|
||||
public void undigTo(@NonNull final Direction direction) {
|
||||
this.walls.set(direction);
|
||||
}
|
||||
|
||||
public Option<Direction> getRandomAvailableDirection() {
|
||||
return Stream.ofAll(this.walls.getSet(true)).shuffle().headOption();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue