From f31821f10083552e8727617c268f3661f12b212d Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 30 Sep 2020 01:22:26 +0200 Subject: [PATCH] initial commit. --- .gitignore | 14 ++ pom.xml | 29 ++++ src/main/java/labyrinth/Direction.java | 57 +++++++ src/main/java/labyrinth/Directions.java | 63 ++++++++ src/main/java/labyrinth/Labyrinth.java | 194 ++++++++++++++++++++++++ src/main/java/labyrinth/Main.java | 12 ++ src/main/java/labyrinth/Position.java | 11 ++ src/main/java/labyrinth/Tile.java | 49 ++++++ 8 files changed, 429 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/labyrinth/Direction.java create mode 100644 src/main/java/labyrinth/Directions.java create mode 100644 src/main/java/labyrinth/Labyrinth.java create mode 100644 src/main/java/labyrinth/Main.java create mode 100644 src/main/java/labyrinth/Position.java create mode 100644 src/main/java/labyrinth/Tile.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd0f37d --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8afd225 --- /dev/null +++ b/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + ch.fritteli + fritteli-build-parent + 2.0.3 + + org.example + labyrinth + 0.0.1-SNAPSHOT + + + org.projectlombok + lombok + + + org.jetbrains + annotations + + + io.vavr + vavr + + + diff --git a/src/main/java/labyrinth/Direction.java b/src/main/java/labyrinth/Direction.java new file mode 100644 index 0000000..0ab2434 --- /dev/null +++ b/src/main/java/labyrinth/Direction.java @@ -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 translation; + + Direction(@NonNull final UnaryOperator translation) { + this.translation = translation; + } + + public static Direction getRandom() { + return values()[random.nextInt(4)]; + } + + public static Option getRandomExcluding(@NonNull final EnumSet directions) { + final EnumSet allowedDirections = EnumSet.complementOf(directions); + return Stream.ofAll(allowedDirections).shuffle().headOption(); + } + + public static Option 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); + } +} diff --git a/src/main/java/labyrinth/Directions.java b/src/main/java/labyrinth/Directions.java new file mode 100644 index 0000000..12e2ea6 --- /dev/null +++ b/src/main/java/labyrinth/Directions.java @@ -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 directions = new HashSet<>(); + // FIXME remove getter, only for debugging + @Getter + private final Set 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 getSet() { + return this.getSet(false); + } + + public Iterable 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); + } +} diff --git a/src/main/java/labyrinth/Labyrinth.java b/src/main/java/labyrinth/Labyrinth.java new file mode 100644 index 0000000..bb23734 --- /dev/null +++ b/src/main/java/labyrinth/Labyrinth.java @@ -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 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 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(); + } + } + } + } +} diff --git a/src/main/java/labyrinth/Main.java b/src/main/java/labyrinth/Main.java new file mode 100644 index 0000000..c478990 --- /dev/null +++ b/src/main/java/labyrinth/Main.java @@ -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); + } +} diff --git a/src/main/java/labyrinth/Position.java b/src/main/java/labyrinth/Position.java new file mode 100644 index 0000000..90f33e5 --- /dev/null +++ b/src/main/java/labyrinth/Position.java @@ -0,0 +1,11 @@ +package labyrinth; + +import lombok.Value; +import lombok.With; + +@Value +@With +public class Position { + int x; + int y; +} diff --git a/src/main/java/labyrinth/Tile.java b/src/main/java/labyrinth/Tile.java new file mode 100644 index 0000000..7ad5135 --- /dev/null +++ b/src/main/java/labyrinth/Tile.java @@ -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 getRandomAvailableDirection() { + return Stream.ofAll(this.walls.getSet(true)).shuffle().headOption(); + } +}