initial commit.
This commit is contained in:
		
							parent
							
								
									817cfbe4f3
								
							
						
					
					
						commit
						f31821f100
					
				
					 8 changed files with 429 additions and 0 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue