Make the generation of a labyrinth reproducible and refactor the
PDFRenderer a bit.
This commit is contained in:
		
							parent
							
								
									fd38b141a1
								
							
						
					
					
						commit
						6060a08573
					
				
					 6 changed files with 114 additions and 74 deletions
				
			
		|  | @ -6,33 +6,6 @@ import lombok.NonNull; | |||
| import lombok.RequiredArgsConstructor; | ||||
| 
 | ||||
| public class HTMLRenderer implements Renderer<String> { | ||||
|     private static final String PREAMBLE = "<!DOCTYPE html><html lang=\"en\">" + | ||||
|             "<head>" + | ||||
|             "<title>Labyrinth</title>" + | ||||
|             "<meta charset=\"utf-8\">" + | ||||
|             "<style>" + | ||||
|             "table{border-collapse:collapse;}" + | ||||
|             "td{border:0 solid black;height:1em;width:1em;}" + | ||||
|             "td.top{border-top-width:1px;}" + | ||||
|             "td.right{border-right-width:1px;}" + | ||||
|             "td.bottom{border-bottom-width:1px;}" + | ||||
|             "td.left{border-left-width:1px;}" + | ||||
|             "</style>" + | ||||
|             "<script>" + | ||||
|             "let solution = false;" + | ||||
|             "function toggleSolution() {" + | ||||
|             "let stylesheet = document.styleSheets[0];" + | ||||
|             "if(solution){" + | ||||
|             "stylesheet.deleteRule(0);" + | ||||
|             "}else{" + | ||||
|             "stylesheet.insertRule(\"td.solution{background-color:lightgray;}\", 0);" + | ||||
|             "}" + | ||||
|             "solution = !solution;" + | ||||
|             "}" + | ||||
|             "</script>" + | ||||
|             "</head>" + | ||||
|             "<body>" + | ||||
|             "<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>"; | ||||
|     private static final String POSTAMBLE = "</body></html>"; | ||||
| 
 | ||||
|     private HTMLRenderer() { | ||||
|  | @ -46,10 +19,10 @@ public class HTMLRenderer implements Renderer<String> { | |||
|     @NonNull | ||||
|     public String render(@NonNull final Labyrinth labyrinth) { | ||||
|         if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) { | ||||
|             return PREAMBLE + POSTAMBLE; | ||||
|             return this.getPreamble(labyrinth) + POSTAMBLE; | ||||
|         } | ||||
|         final Generator generator = new Generator(labyrinth); | ||||
|         final StringBuilder sb = new StringBuilder(PREAMBLE); | ||||
|         final StringBuilder sb = new StringBuilder(this.getPreamble(labyrinth)); | ||||
|         sb.append("<table>"); | ||||
|         while (generator.hasNext()) { | ||||
|             sb.append(generator.next()); | ||||
|  | @ -59,6 +32,36 @@ public class HTMLRenderer implements Renderer<String> { | |||
|         return sb.toString(); | ||||
|     } | ||||
| 
 | ||||
|     private String getPreamble(@NonNull final Labyrinth labyrinth) { | ||||
|         return "<!DOCTYPE html><html lang=\"en\">" + | ||||
|                 "<head>" + | ||||
|                 "<title>Labyrinth " + labyrinth.getWidth() + "x" + labyrinth.getHeight() + ", ID " + labyrinth.getRandomSeed() + "</title>" + | ||||
|                 "<meta charset=\"utf-8\">" + | ||||
|                 "<style>" + | ||||
|                 "table{border-collapse:collapse;}" + | ||||
|                 "td{border:0 solid black;height:1em;width:1em;}" + | ||||
|                 "td.top{border-top-width:1px;}" + | ||||
|                 "td.right{border-right-width:1px;}" + | ||||
|                 "td.bottom{border-bottom-width:1px;}" + | ||||
|                 "td.left{border-left-width:1px;}" + | ||||
|                 "</style>" + | ||||
|                 "<script>" + | ||||
|                 "let solution = false;" + | ||||
|                 "function toggleSolution() {" + | ||||
|                 "let stylesheet = document.styleSheets[0];" + | ||||
|                 "if(solution){" + | ||||
|                 "stylesheet.deleteRule(0);" + | ||||
|                 "}else{" + | ||||
|                 "stylesheet.insertRule(\"td.solution{background-color:lightgray;}\", 0);" + | ||||
|                 "}" + | ||||
|                 "solution = !solution;" + | ||||
|                 "}" + | ||||
|                 "</script>" + | ||||
|                 "</head>" + | ||||
|                 "<body>" + | ||||
|                 "<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>"; | ||||
|     } | ||||
| 
 | ||||
|     @RequiredArgsConstructor | ||||
|     private static class Generator { | ||||
|         private final Labyrinth labyrinth; | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import lombok.NonNull; | |||
| 
 | ||||
| import java.util.Deque; | ||||
| import java.util.LinkedList; | ||||
| import java.util.Random; | ||||
| 
 | ||||
| public class Labyrinth { | ||||
|     private final Tile[][] field; | ||||
|  | @ -14,13 +15,22 @@ public class Labyrinth { | |||
|     @Getter | ||||
|     private final int height; | ||||
|     @Getter | ||||
|     private final long randomSeed; | ||||
|     private final Random random; | ||||
|     @Getter | ||||
|     private final Position start; | ||||
|     @Getter | ||||
|     private final Position end; | ||||
| 
 | ||||
|     public Labyrinth(final int width, final int height) { | ||||
|         this(width, height, System.nanoTime()); | ||||
|     } | ||||
| 
 | ||||
|     public Labyrinth(final int width, final int height, final long randomSeed) { | ||||
|         this.width = width; | ||||
|         this.height = height; | ||||
|         this.randomSeed = randomSeed; | ||||
|         this.random = new Random(this.randomSeed); | ||||
|         this.field = new Tile[width][height]; | ||||
|         this.start = new Position(0, 0); | ||||
|         this.end = new Position(this.width - 1, this.height - 1); | ||||
|  | @ -94,7 +104,7 @@ public class Labyrinth { | |||
|             while (!this.positions.isEmpty()) { | ||||
|                 final Position currentPosition = this.positions.peek(); | ||||
|                 final Tile currentTile = Labyrinth.this.getTileAt(currentPosition); | ||||
|                 final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(); | ||||
|                 final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(Labyrinth.this.random); | ||||
|                 if (directionToDigTo.isDefined()) { | ||||
|                     final Direction digTo = directionToDigTo.get(); | ||||
|                     final Direction digFrom = digTo.invert(); | ||||
|  |  | |||
|  | @ -9,17 +9,20 @@ public class Main { | |||
|     public static void main(@NonNull final String[] args) { | ||||
|         int width = 100; | ||||
|         int height = 100; | ||||
|         final Labyrinth labyrinth = new Labyrinth(width, height); | ||||
|         final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); | ||||
|         final TextRenderer textRenderer = TextRenderer.newInstance(); | ||||
|         final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); | ||||
|         final Path userHome = Paths.get(System.getProperty("user.home")); | ||||
|         final String baseFilename = getBaseFilename(labyrinth); | ||||
|         final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance() | ||||
|                 .setTargetLabyrinthFile(userHome.resolve("labyrinth.txt")) | ||||
|                 .setTargetSolutionFile(userHome.resolve("labyrinth-solution.txt")); | ||||
|                 .setTargetLabyrinthFile(userHome.resolve(baseFilename + ".txt")) | ||||
|                 .setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt")); | ||||
|         final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance() | ||||
|                 .setTargetFile(userHome.resolve("labyrinth.html")); | ||||
|         final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance().setTargetFile(userHome.resolve("labyrinth.pdf")); | ||||
|                 .setTargetFile(userHome.resolve(baseFilename + ".html")); | ||||
|         final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance() | ||||
|                 .setTargetFile(userHome.resolve(baseFilename + ".pdf")); | ||||
| 
 | ||||
|         System.out.println("Labyrinth-ID: " + labyrinth.getRandomSeed()); | ||||
|         // Render Labyrinth to stdout | ||||
|         System.out.println(textRenderer.render(labyrinth)); | ||||
|         // Render Labyrinth solution to stdout | ||||
|  | @ -33,4 +36,8 @@ public class Main { | |||
|         // Render PDF to file | ||||
|         System.out.println(pdfFileRenderer.render(labyrinth)); | ||||
|     } | ||||
| 
 | ||||
|     private static String getBaseFilename(@NonNull final Labyrinth labyrinth) { | ||||
|         return "labyrinth-" + labyrinth.getWidth() + "x" + labyrinth.getHeight() + "-" + labyrinth.getRandomSeed(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package ch.fritteli.labyrinth; | |||
| 
 | ||||
| import lombok.NonNull; | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDDocumentInformation; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.apache.pdfbox.pdmodel.PDPageContentStream; | ||||
| import org.apache.pdfbox.pdmodel.common.PDRectangle; | ||||
|  | @ -35,30 +36,22 @@ public class PDFRenderer implements Renderer<byte[]> { | |||
|         final float pageHeight = labyrinth.getHeight() * SCALE + 2 * MARGIN; | ||||
| 
 | ||||
|         final PDDocument pdDocument = new PDDocument(); | ||||
|         final PDDocumentInformation info = new PDDocumentInformation(); | ||||
|         info.setTitle("Labyrinth " + labyrinth.getWidth() + "x" + labyrinth.getHeight() + ", ID " + labyrinth.getRandomSeed()); | ||||
|         pdDocument.setDocumentInformation(info); | ||||
|         final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); | ||||
|         pdDocument.addPage(page); | ||||
|         try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, page)) { | ||||
|             pdPageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); | ||||
|             pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER); | ||||
|             pdPageContentStream.setLineWidth(1.0f); | ||||
|             pdPageContentStream.setStrokingColor(Color.BLACK); | ||||
|             pdPageContentStream.setNonStrokingColor(Color.BLACK); | ||||
|             this.drawHorizonzalLines(labyrinth, pdPageContentStream); | ||||
|             this.drawVerticalLines(labyrinth, pdPageContentStream); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); | ||||
|         pdDocument.addPage(page); | ||||
|         pdDocument.addPage(solution); | ||||
|         try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, solution)) { | ||||
|             pdPageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); | ||||
|             pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER); | ||||
|             pdPageContentStream.setLineWidth(1.0f); | ||||
|             pdPageContentStream.setStrokingColor(Color.BLACK); | ||||
|             pdPageContentStream.setNonStrokingColor(Color.BLACK); | ||||
|             this.drawHorizonzalLines(labyrinth, pdPageContentStream); | ||||
|             this.drawVerticalLines(labyrinth, pdPageContentStream); | ||||
|             this.drawSolution(labyrinth, pdPageContentStream); | ||||
|         try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); | ||||
|              final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { | ||||
|             setUpPageContentStream(labyrinthPageContentStream); | ||||
|             setUpPageContentStream(solutionPageContentStream); | ||||
|             this.drawHorizonzalLines(labyrinth, labyrinthPageContentStream); | ||||
|             this.drawVerticalLines(labyrinth, labyrinthPageContentStream); | ||||
|             this.drawHorizonzalLines(labyrinth, solutionPageContentStream); | ||||
|             this.drawVerticalLines(labyrinth, solutionPageContentStream); | ||||
|             this.drawSolution(labyrinth, solutionPageContentStream); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|  | @ -72,51 +65,65 @@ public class PDFRenderer implements Renderer<byte[]> { | |||
|         return output.toByteArray(); | ||||
|     } | ||||
| 
 | ||||
|     private void setUpPageContentStream(@NonNull final PDPageContentStream pageContentStream) throws IOException { | ||||
|         pageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); | ||||
|         pageContentStream.setLineJoinStyle(BasicStroke.JOIN_ROUND); | ||||
|         pageContentStream.setLineWidth(1.0f); | ||||
|         pageContentStream.setStrokingColor(Color.BLACK); | ||||
|         pageContentStream.setNonStrokingColor(Color.BLACK); | ||||
|     } | ||||
| 
 | ||||
|     private void drawHorizonzalLines(@NonNull final Labyrinth labyrinth, | ||||
|                                      @NonNull final PDPageContentStream pdPageContentStream) throws IOException { | ||||
|         // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. | ||||
|         final float labyrinthHeight = labyrinth.getHeight() * SCALE; | ||||
|         for (int y = 0; y < labyrinth.getHeight(); y++) { | ||||
|             boolean isPainting = false; | ||||
|             final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN; | ||||
|             for (int x = 0; x < labyrinth.getWidth(); x++) { | ||||
|                 final Tile currentTile = labyrinth.getTileAt(x, y); | ||||
|                 final float xCoordinate = x * SCALE + MARGIN; | ||||
|                 if (currentTile.hasWallAt(Direction.TOP)) { | ||||
|                     if (!isPainting) { | ||||
|                         pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                         pdPageContentStream.moveTo(xCoordinate, yCoordinate); | ||||
|                         isPainting = true; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (isPainting) { | ||||
|                         pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                         pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|                         pdPageContentStream.stroke(); | ||||
|                         isPainting = false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (isPainting) { | ||||
|                 pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                 final float xCoordinate = labyrinth.getWidth() * SCALE + MARGIN; | ||||
|                 pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|                 pdPageContentStream.stroke(); | ||||
|             } | ||||
|         } | ||||
|         boolean isPainting = false; | ||||
|         int y = labyrinth.getHeight(); | ||||
|         final float yCoordinate = /*labyrinthHeight - y * SCALE +*/ MARGIN; | ||||
|         for (int x = 0; x < labyrinth.getWidth(); x++) { | ||||
|             final Tile currentTile = labyrinth.getTileAt(x, y - 1); | ||||
|             final float xCoordinate = x * SCALE + MARGIN; | ||||
|             if (currentTile.hasWallAt(Direction.BOTTOM)) { | ||||
|                 if (!isPainting) { | ||||
|                     pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                     pdPageContentStream.moveTo(xCoordinate, yCoordinate); | ||||
|                     isPainting = true; | ||||
|                 } | ||||
|             } else { | ||||
|                 if (isPainting) { | ||||
|                     pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                     pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|                     pdPageContentStream.stroke(); | ||||
|                     isPainting = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         final float xCoordinate = labyrinth.getWidth() * SCALE + MARGIN; | ||||
|         if (isPainting) { | ||||
|             pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); | ||||
|             pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|             pdPageContentStream.stroke(); | ||||
|         } | ||||
|     } | ||||
|  | @ -127,45 +134,49 @@ public class PDFRenderer implements Renderer<byte[]> { | |||
|         final float labyrinthHeight = labyrinth.getHeight() * SCALE; | ||||
|         for (int x = 0; x < labyrinth.getWidth(); x++) { | ||||
|             boolean isPainting = false; | ||||
|             final float xCoordinate = x * SCALE + MARGIN; | ||||
|             for (int y = 0; y < labyrinth.getHeight(); y++) { | ||||
|                 final Tile currentTile = labyrinth.getTileAt(x, y); | ||||
|                 final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN; | ||||
|                 if (currentTile.hasWallAt(Direction.LEFT)) { | ||||
|                     if (!isPainting) { | ||||
|                         pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                         pdPageContentStream.moveTo(xCoordinate, yCoordinate); | ||||
|                         isPainting = true; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (isPainting) { | ||||
|                         pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                         pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|                         pdPageContentStream.stroke(); | ||||
|                         isPainting = false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (isPainting) { | ||||
|                 pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); | ||||
|                 pdPageContentStream.lineTo(xCoordinate, MARGIN); | ||||
|                 pdPageContentStream.stroke(); | ||||
|             } | ||||
|         } | ||||
|         boolean isPainting = false; | ||||
|         int x = labyrinth.getWidth(); | ||||
|         final float xCoordinate = x * SCALE + MARGIN; | ||||
|         for (int y = 0; y < labyrinth.getHeight(); y++) { | ||||
|             final Tile currentTile = labyrinth.getTileAt(x - 1, y); | ||||
|             final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN; | ||||
|             if (currentTile.hasWallAt(Direction.RIGHT)) { | ||||
|                 if (!isPainting) { | ||||
|                     pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                     pdPageContentStream.moveTo(xCoordinate, yCoordinate); | ||||
|                     isPainting = true; | ||||
|                 } | ||||
|             } else { | ||||
|                 if (isPainting) { | ||||
|                     pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); | ||||
|                     pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|                     pdPageContentStream.stroke(); | ||||
|                     isPainting = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (isPainting) { | ||||
|             pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); | ||||
|             pdPageContentStream.lineTo(xCoordinate, MARGIN); | ||||
|             pdPageContentStream.stroke(); | ||||
|         } | ||||
|     } | ||||
|  | @ -184,11 +195,14 @@ public class PDFRenderer implements Renderer<byte[]> { | |||
|             Position newCurrent = this.findNextSolutionPosition(labyrinth, previousPosition, currentPosition); | ||||
|             previousPosition = currentPosition; | ||||
|             currentPosition = newCurrent; | ||||
|             pdPageContentStream.lineTo(MARGIN + currentPosition.getX() * SCALE + SCALE / 2, MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2)); | ||||
|             final float xCoordinate = MARGIN + currentPosition.getX() * SCALE + SCALE / 2; | ||||
|             final float yCoordinate = MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2); | ||||
|             pdPageContentStream.lineTo(xCoordinate, yCoordinate); | ||||
|         } while (!currentPosition.equals(end)); | ||||
|         pdPageContentStream.stroke(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Position findNextSolutionPosition(@NonNull final Labyrinth labyrinth, @Nullable final Position previousPosition, @NonNull final Position currentPosition) { | ||||
|         final Tile currentTile = labyrinth.getTileAt(currentPosition); | ||||
|         for (final Direction direction : Direction.values()) { | ||||
|  | @ -202,7 +216,6 @@ public class PDFRenderer implements Renderer<byte[]> { | |||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // We *SHOULD* never get here. ... famous last words ... | ||||
|         return null; | ||||
|         throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ..."); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,8 @@ import lombok.Getter; | |||
| import lombok.NonNull; | ||||
| import lombok.experimental.FieldDefaults; | ||||
| 
 | ||||
| import java.util.Random; | ||||
| 
 | ||||
| @FieldDefaults(level = AccessLevel.PRIVATE) | ||||
| public class Tile { | ||||
|     final Walls walls = new Walls(); | ||||
|  | @ -42,8 +44,13 @@ public class Tile { | |||
|         this.walls.set(direction); | ||||
|     } | ||||
| 
 | ||||
|     public Option<Direction> getRandomAvailableDirection() { | ||||
|         return Stream.ofAll(this.walls.getUnhardenedSet()).shuffle().headOption(); | ||||
|     public Option<Direction> getRandomAvailableDirection(@NonNull final Random random) { | ||||
|         final Stream<Direction> availableDirections = this.walls.getUnhardenedSet(); | ||||
|         if (availableDirections.isEmpty()) { | ||||
|             return Option.none(); | ||||
|         } | ||||
|         final int index = random.nextInt(availableDirections.length()); | ||||
|         return Option.of(availableDirections.get(index)); | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasWallAt(@NonNull final Direction direction) { | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ public class Walls { | |||
|         return this.directions.contains(direction); | ||||
|     } | ||||
| 
 | ||||
|     public Iterable<Direction> getUnhardenedSet() { | ||||
|     public Stream<Direction> getUnhardenedSet() { | ||||
|         return Stream.ofAll(this.directions) | ||||
|                 .removeAll(this.hardened); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue