Refactor package structure.
This commit is contained in:
		
							parent
							
								
									0bdbd7d0ef
								
							
						
					
					
						commit
						afd05f6871
					
				
					 17 changed files with 840 additions and 786 deletions
				
			
		|  | @ -3,6 +3,7 @@ package ch.fritteli.labyrinth; | ||||||
| import io.vavr.control.Option; | import io.vavr.control.Option; | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
|  | import org.jetbrains.annotations.Nullable; | ||||||
| 
 | 
 | ||||||
| import java.util.Deque; | import java.util.Deque; | ||||||
| import java.util.LinkedList; | import java.util.LinkedList; | ||||||
|  | @ -38,18 +39,35 @@ public class Labyrinth { | ||||||
|         this.generate(); |         this.generate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Tile getTileAt(@NonNull final Position position) { |     @NonNull | ||||||
|  |     public Tile getTileAt(@NonNull final Position position) { | ||||||
|         return this.getTileAt(position.getX(), position.getY()); |         return this.getTileAt(position.getX(), position.getY()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Tile getTileAt(final int x, final int y) { |     @NonNull | ||||||
|  |     public Tile getTileAtOrNull(@NonNull final Position position) { | ||||||
|  |         return this.getTileAtOrNull(position.getX(), position.getY()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public Tile getTileAt(final int x, final int y) { | ||||||
|         return this.field[x][y]; |         return this.field[x][y]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Nullable | ||||||
|  |     public Tile getTileAtOrNull(final int x, final int y) { | ||||||
|  |         if (x < 0 || y < 0 || x >= this.width || y >= this.height) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return this.getTileAt(x, y); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|     Tile getStartTile() { |     Tile getStartTile() { | ||||||
|         return this.getTileAt(this.start); |         return this.getTileAt(this.start); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @NonNull | ||||||
|     Tile getEndTile() { |     Tile getEndTile() { | ||||||
|         return this.getTileAt(this.end); |         return this.getTileAt(this.end); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,10 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth; | ||||||
| 
 | 
 | ||||||
|  | import ch.fritteli.labyrinth.renderer.html.HTMLRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.htmlfile.HTMLFileRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.pdffile.PDFFileRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.text.TextRenderer; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
| import java.nio.file.Path; | import java.nio.file.Path; | ||||||
|  |  | ||||||
|  | @ -1,301 +0,0 @@ | ||||||
| package ch.fritteli.labyrinth; |  | ||||||
| 
 |  | ||||||
| import lombok.NonNull; |  | ||||||
| import lombok.RequiredArgsConstructor; |  | ||||||
| import lombok.Value; |  | ||||||
| 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; |  | ||||||
| import org.jetbrains.annotations.Nullable; |  | ||||||
| 
 |  | ||||||
| import java.awt.*; |  | ||||||
| import java.io.ByteArrayOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| 
 |  | ||||||
| public class PDFRenderer implements Renderer<byte[]> { |  | ||||||
|     private static final float MARGIN = 10; |  | ||||||
|     private static final float SCALE = 10; |  | ||||||
| 
 |  | ||||||
|     private PDFRenderer() { |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public static PDFRenderer newInstance() { |  | ||||||
|         return new PDFRenderer(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     @NonNull |  | ||||||
|     public byte[] render(@NonNull final Labyrinth labyrinth) { |  | ||||||
|         final Generator generator = new Generator(labyrinth); |  | ||||||
|         return generator.generate(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     @RequiredArgsConstructor |  | ||||||
|     private static class Generator { |  | ||||||
|         @NonNull |  | ||||||
|         private final Labyrinth labyrinth; |  | ||||||
| 
 |  | ||||||
|         private static boolean isValid(@NonNull final Position position) { |  | ||||||
|             return position.getX() >= 0 && position.getY() >= 0; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public byte[] generate() { |  | ||||||
|             final float pageWidth = this.labyrinth.getWidth() * SCALE + 2 * MARGIN; |  | ||||||
|             final float pageHeight = this.labyrinth.getHeight() * SCALE + 2 * MARGIN; |  | ||||||
| 
 |  | ||||||
|             final PDDocument pdDocument = new PDDocument(); |  | ||||||
|             final PDDocumentInformation info = new PDDocumentInformation(); |  | ||||||
|             info.setTitle("Labyrinth " + this.labyrinth.getWidth() + "x" + this.labyrinth.getHeight() + ", ID " + this.labyrinth.getRandomSeed()); |  | ||||||
|             pdDocument.setDocumentInformation(info); |  | ||||||
|             final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); |  | ||||||
|             final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); |  | ||||||
|             pdDocument.addPage(page); |  | ||||||
|             pdDocument.addPage(solution); |  | ||||||
|             try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); |  | ||||||
|                  final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { |  | ||||||
|                 setUpPageContentStream(labyrinthPageContentStream); |  | ||||||
|                 setUpPageContentStream(solutionPageContentStream); |  | ||||||
|                 this.drawHorizonzalLines(labyrinthPageContentStream, solutionPageContentStream); |  | ||||||
|                 this.drawVerticalLines(labyrinthPageContentStream, solutionPageContentStream); |  | ||||||
|                 this.drawSolution(solutionPageContentStream); |  | ||||||
|             } catch (IOException e) { |  | ||||||
|                 e.printStackTrace(); |  | ||||||
|             } |  | ||||||
|             final ByteArrayOutputStream output = new ByteArrayOutputStream(); |  | ||||||
|             try { |  | ||||||
|                 pdDocument.save(output); |  | ||||||
|                 pdDocument.close(); |  | ||||||
|             } catch (IOException e) { |  | ||||||
|                 e.printStackTrace(); |  | ||||||
|             } |  | ||||||
|             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 PDPageContentStream... contentStreams) throws IOException { |  | ||||||
|             // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. |  | ||||||
|             Coordinate coordinate = new Coordinate(0f, 0f); |  | ||||||
|             for (int y = 0; y < this.labyrinth.getHeight(); y++) { |  | ||||||
|                 boolean isPainting = false; |  | ||||||
|                 coordinate = coordinate.withY(y); |  | ||||||
|                 for (int x = 0; x < this.labyrinth.getWidth(); x++) { |  | ||||||
|                     final Tile currentTile = this.labyrinth.getTileAt(x, y); |  | ||||||
|                     coordinate = coordinate.withX(x); |  | ||||||
|                     if (currentTile.hasWallAt(Direction.TOP)) { |  | ||||||
|                         if (!isPainting) { |  | ||||||
|                             for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                                 contentStream.moveTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                             } |  | ||||||
|                             isPainting = true; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         if (isPainting) { |  | ||||||
|                             for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                                 contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                                 contentStream.stroke(); |  | ||||||
|                             } |  | ||||||
|                             isPainting = false; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (isPainting) { |  | ||||||
|                     coordinate = coordinate.withX(this.labyrinth.getWidth()); |  | ||||||
|                     for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                         contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                         contentStream.stroke(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             boolean isPainting = false; |  | ||||||
|             int y = this.labyrinth.getHeight(); |  | ||||||
|             coordinate = coordinate.withY(this.labyrinth.getHeight()); |  | ||||||
|             for (int x = 0; x < this.labyrinth.getWidth(); x++) { |  | ||||||
|                 final Tile currentTile = this.labyrinth.getTileAt(x, y - 1); |  | ||||||
|                 coordinate = coordinate.withX(x); |  | ||||||
|                 if (currentTile.hasWallAt(Direction.BOTTOM)) { |  | ||||||
|                     if (!isPainting) { |  | ||||||
|                         for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                             contentStream.moveTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                         } |  | ||||||
|                         isPainting = true; |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (isPainting) { |  | ||||||
|                         for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                             contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                             contentStream.stroke(); |  | ||||||
|                         } |  | ||||||
|                         isPainting = false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if (isPainting) { |  | ||||||
|                 coordinate = coordinate.withX(this.labyrinth.getWidth()); |  | ||||||
|                 for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                     contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                     contentStream.stroke(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private void drawVerticalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { |  | ||||||
|             // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. |  | ||||||
|             Coordinate coordinate = new Coordinate(0f, 0f); |  | ||||||
|             for (int x = 0; x < this.labyrinth.getWidth(); x++) { |  | ||||||
|                 boolean isPainting = false; |  | ||||||
|                 coordinate = coordinate.withX(x); |  | ||||||
|                 for (int y = 0; y < this.labyrinth.getHeight(); y++) { |  | ||||||
|                     final Tile currentTile = this.labyrinth.getTileAt(x, y); |  | ||||||
|                     coordinate = coordinate.withY(y); |  | ||||||
|                     if (currentTile.hasWallAt(Direction.LEFT)) { |  | ||||||
|                         if (!isPainting) { |  | ||||||
|                             for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                                 contentStream.moveTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                             } |  | ||||||
|                             isPainting = true; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         if (isPainting) { |  | ||||||
|                             for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                                 contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                                 contentStream.stroke(); |  | ||||||
|                             } |  | ||||||
|                             isPainting = false; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (isPainting) { |  | ||||||
|                     coordinate = coordinate.withY(this.labyrinth.getHeight()); |  | ||||||
|                     for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                         contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                         contentStream.stroke(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             boolean isPainting = false; |  | ||||||
|             int x = this.labyrinth.getWidth(); |  | ||||||
|             coordinate = coordinate.withX(this.labyrinth.getWidth()); |  | ||||||
|             for (int y = 0; y < this.labyrinth.getHeight(); y++) { |  | ||||||
|                 final Tile currentTile = this.labyrinth.getTileAt(x - 1, y); |  | ||||||
|                 coordinate = coordinate.withY(y); |  | ||||||
|                 if (currentTile.hasWallAt(Direction.RIGHT)) { |  | ||||||
|                     if (!isPainting) { |  | ||||||
|                         for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                             contentStream.moveTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                         } |  | ||||||
|                         isPainting = true; |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (isPainting) { |  | ||||||
|                         for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                             contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                             contentStream.stroke(); |  | ||||||
|                         } |  | ||||||
|                         isPainting = false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if (isPainting) { |  | ||||||
|                 coordinate = coordinate.withY(this.labyrinth.getHeight()); |  | ||||||
|                 for (final PDPageContentStream contentStream : contentStreams) { |  | ||||||
|                     contentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|                     contentStream.stroke(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private void drawSolution(@NonNull final PDPageContentStream pageContentStream) throws IOException { |  | ||||||
|             // Draw the solution in red |  | ||||||
|             pageContentStream.setStrokingColor(Color.RED); |  | ||||||
|             // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. |  | ||||||
|             final Position end = this.labyrinth.getEnd(); |  | ||||||
|             Position currentPosition = this.labyrinth.getStart(); |  | ||||||
|             Position previousPosition = null; |  | ||||||
|             SolutionCoordinate coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); |  | ||||||
|             pageContentStream.moveTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|             do { |  | ||||||
|                 Position newCurrent = this.findNextSolutionPosition(previousPosition, currentPosition); |  | ||||||
|                 previousPosition = currentPosition; |  | ||||||
|                 currentPosition = newCurrent; |  | ||||||
|                 coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); |  | ||||||
|                 pageContentStream.lineTo(coordinate.getX(), coordinate.getY()); |  | ||||||
|             } while (!currentPosition.equals(end)); |  | ||||||
|             pageContentStream.stroke(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @NonNull |  | ||||||
|         private Position findNextSolutionPosition(@Nullable final Position previousPosition, @NonNull final Position currentPosition) { |  | ||||||
|             final Tile currentTile = this.labyrinth.getTileAt(currentPosition); |  | ||||||
|             for (final Direction direction : Direction.values()) { |  | ||||||
|                 if (!currentTile.hasWallAt(direction)) { |  | ||||||
|                     final Position position = direction.translate(currentPosition); |  | ||||||
|                     if (position.equals(previousPosition) || !isValid(position)) { |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
|                     if (this.labyrinth.getTileAt(position).isSolution()) { |  | ||||||
|                         return position; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ..."); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @Value |  | ||||||
|         private class Coordinate { |  | ||||||
|             float x; |  | ||||||
|             float y; |  | ||||||
| 
 |  | ||||||
|             public Coordinate(final float x, final float y) { |  | ||||||
|                 this.x = x; |  | ||||||
|                 this.y = y; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             private float calcX(final int x) { |  | ||||||
|                 return x * SCALE + MARGIN; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             private float calcY(final int y) { |  | ||||||
|                 return (Generator.this.labyrinth.getHeight() - y) * SCALE + MARGIN; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             public Coordinate withX(final int x) { |  | ||||||
|                 return new Coordinate(calcX(x), this.y); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             public Coordinate withY(final int y) { |  | ||||||
|                 return new Coordinate(this.x, calcY(y)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @Value |  | ||||||
|         private class SolutionCoordinate { |  | ||||||
|             float x; |  | ||||||
|             float y; |  | ||||||
| 
 |  | ||||||
|             public SolutionCoordinate(final int x, final int y) { |  | ||||||
|                 this.x = calcX(x); |  | ||||||
|                 this.y = calcY(y); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             private float calcX(final int x) { |  | ||||||
|                 return x * SCALE + SCALE / 2 + MARGIN; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             private float calcY(final int y) { |  | ||||||
|                 return (Generator.this.labyrinth.getHeight() - y) * SCALE - SCALE / 2 + MARGIN; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,381 +0,0 @@ | ||||||
| package ch.fritteli.labyrinth; |  | ||||||
| 
 |  | ||||||
| import lombok.AccessLevel; |  | ||||||
| import lombok.AllArgsConstructor; |  | ||||||
| import lombok.NoArgsConstructor; |  | ||||||
| import lombok.NonNull; |  | ||||||
| import lombok.RequiredArgsConstructor; |  | ||||||
| import lombok.experimental.FieldDefaults; |  | ||||||
| import org.jetbrains.annotations.Nullable; |  | ||||||
| 
 |  | ||||||
| public class TextRenderer implements Renderer<String> { |  | ||||||
|     private boolean renderSolution; |  | ||||||
| 
 |  | ||||||
|     private TextRenderer() { |  | ||||||
|         this.renderSolution = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public static TextRenderer newInstance() { |  | ||||||
|         return new TextRenderer(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public TextRenderer setRenderSolution(final boolean renderSolution) { |  | ||||||
|         this.renderSolution = renderSolution; |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public String render(@NonNull final Labyrinth labyrinth) { |  | ||||||
|         if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) { |  | ||||||
|             return ""; |  | ||||||
|         } |  | ||||||
|         final Generator generator = new Generator(labyrinth, this.renderSolution); |  | ||||||
|         final StringBuilder sb = new StringBuilder(); |  | ||||||
|         while (generator.hasNext()) { |  | ||||||
|             sb.append(generator.next()); |  | ||||||
|         } |  | ||||||
|         return sb.toString(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @RequiredArgsConstructor(access = AccessLevel.PRIVATE) |  | ||||||
|     static class Generator { |  | ||||||
|         @NonNull |  | ||||||
|         private final Labyrinth labyrinth; |  | ||||||
|         private final boolean renderSolution; |  | ||||||
|         private int x = 0; |  | ||||||
|         private int y = 0; |  | ||||||
|         private int line = 0; |  | ||||||
| 
 |  | ||||||
|         private boolean hasNext() { |  | ||||||
|             return this.y < this.labyrinth.getHeight(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private String next() { |  | ||||||
|             final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y); |  | ||||||
|             final Tile topTile = this.getTileOrNull(this.x, this.y - 1); |  | ||||||
|             final Tile rightTile = this.getTileOrNull(this.x + 1, this.y); |  | ||||||
|             final Tile bottomTile = this.getTileOrNull(this.x, this.y + 1); |  | ||||||
|             final Tile leftTile = this.getTileOrNull(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.labyrinth.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.labyrinth.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 Tile getTileOrNull(final int x, final int y) { |  | ||||||
|             if (x < 0 || y < 0 || x >= this.labyrinth.getWidth() || y >= this.labyrinth.getHeight()) { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|             return this.labyrinth.getTileAt(x, y); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private String renderTopLine(@NonNull final Tile currentTile, @Nullable final Tile leftTile, @Nullable final 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 == null)) { |  | ||||||
|                     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.labyrinth.getWidth() - 1) { |  | ||||||
|                 result += charDef3 + "\n"; |  | ||||||
|             } |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private String renderCenterLine(@NonNull final Tile currentTile, |  | ||||||
|                                         @Nullable final Tile topTile, |  | ||||||
|                                         @Nullable final Tile rightTile, |  | ||||||
|                                         @Nullable final Tile bottomTile, |  | ||||||
|                                         @Nullable final 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 == null)) { |  | ||||||
|                     charDef2.left(); |  | ||||||
|                 } |  | ||||||
|                 if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile == null)) { |  | ||||||
|                     charDef2.up(); |  | ||||||
|                 } |  | ||||||
|                 if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile == null)) { |  | ||||||
|                     charDef2.right(); |  | ||||||
|                 } |  | ||||||
|                 if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile == null)) { |  | ||||||
|                     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.labyrinth.getWidth() - 1) { |  | ||||||
|                 result += charDef3 + "\n"; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private String renderBottomLine(@NonNull final Tile currentTile, @Nullable final 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.labyrinth.getWidth() - 1) { |  | ||||||
|                 result += charDef3; |  | ||||||
|             } |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private boolean hasWallAt(@Nullable final Tile tile, @NonNull final Direction direction) { |  | ||||||
|             return tile != null && tile.hasWallAt(direction); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private boolean isSolution(@Nullable final Tile tile) { |  | ||||||
|             return this.renderSolution && tile != null && tile.isSolution(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @FieldDefaults(level = AccessLevel.PRIVATE) |  | ||||||
|         @NoArgsConstructor |  | ||||||
|         @AllArgsConstructor |  | ||||||
|         static class CharDefinition { |  | ||||||
|             // ─ |  | ||||||
|             static final String HORIZONTAL = "\u2500"; |  | ||||||
|             // │ |  | ||||||
|             static final String VERTICAL = "\u2502"; |  | ||||||
|             // ╴ |  | ||||||
|             static final String LEFT = "\u2574"; |  | ||||||
|             // ╵ |  | ||||||
|             static final String UP = "\u2575"; |  | ||||||
|             // ╶ |  | ||||||
|             static final String RIGHT = "\u2576"; |  | ||||||
|             // ╷ |  | ||||||
|             static final String DOWN = "\u2577"; |  | ||||||
|             // ┌ |  | ||||||
|             static final String DOWN_RIGHT = "\u250c"; |  | ||||||
|             // ┐ |  | ||||||
|             static final String DOWN_LEFT = "\u2510"; |  | ||||||
|             // └ |  | ||||||
|             static final String UP_RIGHT = "\u2514"; |  | ||||||
|             // ┘ |  | ||||||
|             static final String UP_LEFT = "\u2518"; |  | ||||||
|             // ├ |  | ||||||
|             static final String VERTICAL_RIGHT = "\u251c"; |  | ||||||
|             // ┤ |  | ||||||
|             static final String VERTICAL_LEFT = "\u2524"; |  | ||||||
|             // ┬ |  | ||||||
|             static final String HORIZONTAL_DOWN = "\u252c"; |  | ||||||
|             // ┴ |  | ||||||
|             static final String HORIZONTAL_UP = "\u2534"; |  | ||||||
|             // ┼ |  | ||||||
|             static final String CROSS = "\u253c"; |  | ||||||
|             // ╭ |  | ||||||
|             static final String SOLUTION_DOWN_RIGHT = "\u256d"; |  | ||||||
|             // ╮ |  | ||||||
|             static final String SOLUTION_DOWN_LEFT = "\u256e"; |  | ||||||
|             // ╯ |  | ||||||
|             static final String SOLUTION_UP_LEFT = "\u256f"; |  | ||||||
|             // ╰ |  | ||||||
|             static final String SOLUTION_UP_RIGHT = "\u2570"; |  | ||||||
|             boolean up = false; |  | ||||||
|             boolean down = false; |  | ||||||
|             boolean left = false; |  | ||||||
|             boolean right = false; |  | ||||||
|             boolean solution = false; |  | ||||||
| 
 |  | ||||||
|             CharDefinition solution() { |  | ||||||
|                 this.solution = true; |  | ||||||
|                 return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition up() { |  | ||||||
|                 this.up = true; |  | ||||||
|                 return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition down() { |  | ||||||
|                 this.down = true; |  | ||||||
|                 return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition vertical() { |  | ||||||
|                 return this.up().down(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition left() { |  | ||||||
|                 this.left = true; |  | ||||||
|                 return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition right() { |  | ||||||
|                 this.right = true; |  | ||||||
|                 return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             CharDefinition horizontal() { |  | ||||||
|                 return this.left().right(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             public String toString() { |  | ||||||
|                 if (this.up) { |  | ||||||
|                     if (this.down) { |  | ||||||
|                         if (this.left) { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return CROSS; |  | ||||||
|                             } else { |  | ||||||
|                                 return VERTICAL_LEFT; |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return VERTICAL_RIGHT; |  | ||||||
|                             } else { |  | ||||||
|                                 return VERTICAL; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         if (this.left) { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return HORIZONTAL_UP; |  | ||||||
|                             } else { |  | ||||||
|                                 return this.solution ? SOLUTION_UP_LEFT : UP_LEFT; |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return this.solution ? SOLUTION_UP_RIGHT : UP_RIGHT; |  | ||||||
|                             } else { |  | ||||||
|                                 return UP; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (this.down) { |  | ||||||
|                         if (this.left) { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return HORIZONTAL_DOWN; |  | ||||||
|                             } else { |  | ||||||
|                                 return this.solution ? SOLUTION_DOWN_LEFT : DOWN_LEFT; |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return this.solution ? SOLUTION_DOWN_RIGHT : DOWN_RIGHT; |  | ||||||
|                             } else { |  | ||||||
|                                 return DOWN; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         if (this.left) { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return HORIZONTAL; |  | ||||||
|                             } else { |  | ||||||
|                                 return LEFT; |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             if (this.right) { |  | ||||||
|                                 return RIGHT; |  | ||||||
|                             } else { |  | ||||||
|                                 return " "; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth.renderer; | ||||||
| 
 | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
| public interface Renderer<T> { | public interface Renderer<T> { | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.html; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.Direction; | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.Tile; | ||||||
|  | import io.vavr.collection.HashSet; | ||||||
|  | import io.vavr.collection.Set; | ||||||
|  | import lombok.AccessLevel; | ||||||
|  | import lombok.NonNull; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  | 
 | ||||||
|  | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) | ||||||
|  | class Generator { | ||||||
|  |     private final Labyrinth labyrinth; | ||||||
|  |     private int y = 0; | ||||||
|  | 
 | ||||||
|  |     boolean hasNext() { | ||||||
|  |         return this.y < this.labyrinth.getHeight(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     String next() { | ||||||
|  |         StringBuilder sb = new StringBuilder("<tr>"); | ||||||
|  |         for (int x = 0; x < this.labyrinth.getWidth(); x++) { | ||||||
|  |             final Tile currentTile = this.labyrinth.getTileAt(x, this.y); | ||||||
|  |             sb.append("<td class=\""); | ||||||
|  |             sb.append(this.getClasses(currentTile).mkString(" ")); | ||||||
|  |             sb.append("\"> </td>"); | ||||||
|  |         } | ||||||
|  |         sb.append("</tr>"); | ||||||
|  |         this.y++; | ||||||
|  |         return sb.toString(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Set<String> getClasses(@NonNull final Tile tile) { | ||||||
|  |         Set<String> result = HashSet.empty(); | ||||||
|  |         if (tile.hasWallAt(Direction.TOP)) { | ||||||
|  |             result = result.add("top"); | ||||||
|  |         } | ||||||
|  |         if (tile.hasWallAt(Direction.RIGHT)) { | ||||||
|  |             result = result.add("right"); | ||||||
|  |         } | ||||||
|  |         if (tile.hasWallAt(Direction.BOTTOM)) { | ||||||
|  |             result = result.add("bottom"); | ||||||
|  |         } | ||||||
|  |         if (tile.hasWallAt(Direction.LEFT)) { | ||||||
|  |             result = result.add("left"); | ||||||
|  |         } | ||||||
|  |         if (tile.isSolution()) { | ||||||
|  |             result = result.add("solution"); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,9 +1,8 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth.renderer.html; | ||||||
| 
 | 
 | ||||||
| import io.vavr.collection.HashSet; | import ch.fritteli.labyrinth.Labyrinth; | ||||||
| import io.vavr.collection.Set; | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| import lombok.RequiredArgsConstructor; |  | ||||||
| 
 | 
 | ||||||
| public class HTMLRenderer implements Renderer<String> { | public class HTMLRenderer implements Renderer<String> { | ||||||
|     private static final String POSTAMBLE = "</body></html>"; |     private static final String POSTAMBLE = "</body></html>"; | ||||||
|  | @ -62,46 +61,4 @@ public class HTMLRenderer implements Renderer<String> { | ||||||
|                 "<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>"; |                 "<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequiredArgsConstructor |  | ||||||
|     private static class Generator { |  | ||||||
|         private final Labyrinth labyrinth; |  | ||||||
|         private int y = 0; |  | ||||||
| 
 |  | ||||||
|         private boolean hasNext() { |  | ||||||
|             return this.y < this.labyrinth.getHeight(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private String next() { |  | ||||||
|             StringBuilder sb = new StringBuilder("<tr>"); |  | ||||||
|             for (int x = 0; x < this.labyrinth.getWidth(); x++) { |  | ||||||
|                 final Tile currentTile = this.labyrinth.getTileAt(x, this.y); |  | ||||||
|                 sb.append("<td class=\""); |  | ||||||
|                 sb.append(this.getClasses(currentTile).mkString(" ")); |  | ||||||
|                 sb.append("\"> </td>"); |  | ||||||
|             } |  | ||||||
|             sb.append("</tr>"); |  | ||||||
|             this.y++; |  | ||||||
|             return sb.toString(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private Set<String> getClasses(@NonNull final Tile tile) { |  | ||||||
|             Set<String> result = HashSet.empty(); |  | ||||||
|             if (tile.hasWallAt(Direction.TOP)) { |  | ||||||
|                 result = result.add("top"); |  | ||||||
|             } |  | ||||||
|             if (tile.hasWallAt(Direction.RIGHT)) { |  | ||||||
|                 result = result.add("right"); |  | ||||||
|             } |  | ||||||
|             if (tile.hasWallAt(Direction.BOTTOM)) { |  | ||||||
|                 result = result.add("bottom"); |  | ||||||
|             } |  | ||||||
|             if (tile.hasWallAt(Direction.LEFT)) { |  | ||||||
|                 result = result.add("left"); |  | ||||||
|             } |  | ||||||
|             if (tile.isSolution()) { |  | ||||||
|                 result = result.add("solution"); |  | ||||||
|             } |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -1,5 +1,8 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth.renderer.htmlfile; | ||||||
| 
 | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.html.HTMLRenderer; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
							
								
								
									
										284
									
								
								src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,284 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.pdf; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.Direction; | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.Position; | ||||||
|  | import ch.fritteli.labyrinth.Tile; | ||||||
|  | import lombok.NonNull; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  | import lombok.Value; | ||||||
|  | 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; | ||||||
|  | import org.jetbrains.annotations.Nullable; | ||||||
|  | 
 | ||||||
|  | import java.awt.*; | ||||||
|  | import java.io.ByteArrayOutputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | 
 | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | class Generator { | ||||||
|  |     @NonNull | ||||||
|  |     private final Labyrinth labyrinth; | ||||||
|  | 
 | ||||||
|  |     private static boolean isValid(@NonNull final Position position) { | ||||||
|  |         return position.getX() >= 0 && position.getY() >= 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public byte[] generate() { | ||||||
|  |         final float pageWidth = this.labyrinth.getWidth() * PDFRenderer.SCALE + 2 * PDFRenderer.MARGIN; | ||||||
|  |         final float pageHeight = this.labyrinth.getHeight() * PDFRenderer.SCALE + 2 * PDFRenderer.MARGIN; | ||||||
|  | 
 | ||||||
|  |         final PDDocument pdDocument = new PDDocument(); | ||||||
|  |         final PDDocumentInformation info = new PDDocumentInformation(); | ||||||
|  |         info.setTitle("Labyrinth " + this.labyrinth.getWidth() + "x" + this.labyrinth.getHeight() + ", ID " + this.labyrinth.getRandomSeed()); | ||||||
|  |         pdDocument.setDocumentInformation(info); | ||||||
|  |         final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); | ||||||
|  |         final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); | ||||||
|  |         pdDocument.addPage(page); | ||||||
|  |         pdDocument.addPage(solution); | ||||||
|  |         try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); | ||||||
|  |              final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { | ||||||
|  |             setUpPageContentStream(labyrinthPageContentStream); | ||||||
|  |             setUpPageContentStream(solutionPageContentStream); | ||||||
|  |             this.drawHorizonzalLines(labyrinthPageContentStream, solutionPageContentStream); | ||||||
|  |             this.drawVerticalLines(labyrinthPageContentStream, solutionPageContentStream); | ||||||
|  |             this.drawSolution(solutionPageContentStream); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |         final ByteArrayOutputStream output = new ByteArrayOutputStream(); | ||||||
|  |         try { | ||||||
|  |             pdDocument.save(output); | ||||||
|  |             pdDocument.close(); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |         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 PDPageContentStream... contentStreams) throws IOException { | ||||||
|  |         // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. | ||||||
|  |         Coordinate coordinate = new Coordinate(0f, 0f); | ||||||
|  |         for (int y = 0; y < this.labyrinth.getHeight(); y++) { | ||||||
|  |             boolean isPainting = false; | ||||||
|  |             coordinate = coordinate.withY(y); | ||||||
|  |             for (int x = 0; x < this.labyrinth.getWidth(); x++) { | ||||||
|  |                 final Tile currentTile = this.labyrinth.getTileAt(x, y); | ||||||
|  |                 coordinate = coordinate.withX(x); | ||||||
|  |                 if (currentTile.hasWallAt(Direction.TOP)) { | ||||||
|  |                     if (!isPainting) { | ||||||
|  |                         for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                             contentStream.moveTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                         } | ||||||
|  |                         isPainting = true; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (isPainting) { | ||||||
|  |                         for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                             contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                             contentStream.stroke(); | ||||||
|  |                         } | ||||||
|  |                         isPainting = false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (isPainting) { | ||||||
|  |                 coordinate = coordinate.withX(this.labyrinth.getWidth()); | ||||||
|  |                 for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                     contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                     contentStream.stroke(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         boolean isPainting = false; | ||||||
|  |         int y = this.labyrinth.getHeight(); | ||||||
|  |         coordinate = coordinate.withY(this.labyrinth.getHeight()); | ||||||
|  |         for (int x = 0; x < this.labyrinth.getWidth(); x++) { | ||||||
|  |             final Tile currentTile = this.labyrinth.getTileAt(x, y - 1); | ||||||
|  |             coordinate = coordinate.withX(x); | ||||||
|  |             if (currentTile.hasWallAt(Direction.BOTTOM)) { | ||||||
|  |                 if (!isPainting) { | ||||||
|  |                     for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                         contentStream.moveTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                     } | ||||||
|  |                     isPainting = true; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (isPainting) { | ||||||
|  |                     for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                         contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                         contentStream.stroke(); | ||||||
|  |                     } | ||||||
|  |                     isPainting = false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (isPainting) { | ||||||
|  |             coordinate = coordinate.withX(this.labyrinth.getWidth()); | ||||||
|  |             for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                 contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                 contentStream.stroke(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void drawVerticalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { | ||||||
|  |         // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. | ||||||
|  |         Coordinate coordinate = new Coordinate(0f, 0f); | ||||||
|  |         for (int x = 0; x < this.labyrinth.getWidth(); x++) { | ||||||
|  |             boolean isPainting = false; | ||||||
|  |             coordinate = coordinate.withX(x); | ||||||
|  |             for (int y = 0; y < this.labyrinth.getHeight(); y++) { | ||||||
|  |                 final Tile currentTile = this.labyrinth.getTileAt(x, y); | ||||||
|  |                 coordinate = coordinate.withY(y); | ||||||
|  |                 if (currentTile.hasWallAt(Direction.LEFT)) { | ||||||
|  |                     if (!isPainting) { | ||||||
|  |                         for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                             contentStream.moveTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                         } | ||||||
|  |                         isPainting = true; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (isPainting) { | ||||||
|  |                         for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                             contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                             contentStream.stroke(); | ||||||
|  |                         } | ||||||
|  |                         isPainting = false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (isPainting) { | ||||||
|  |                 coordinate = coordinate.withY(this.labyrinth.getHeight()); | ||||||
|  |                 for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                     contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                     contentStream.stroke(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         boolean isPainting = false; | ||||||
|  |         int x = this.labyrinth.getWidth(); | ||||||
|  |         coordinate = coordinate.withX(this.labyrinth.getWidth()); | ||||||
|  |         for (int y = 0; y < this.labyrinth.getHeight(); y++) { | ||||||
|  |             final Tile currentTile = this.labyrinth.getTileAt(x - 1, y); | ||||||
|  |             coordinate = coordinate.withY(y); | ||||||
|  |             if (currentTile.hasWallAt(Direction.RIGHT)) { | ||||||
|  |                 if (!isPainting) { | ||||||
|  |                     for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                         contentStream.moveTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                     } | ||||||
|  |                     isPainting = true; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (isPainting) { | ||||||
|  |                     for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                         contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                         contentStream.stroke(); | ||||||
|  |                     } | ||||||
|  |                     isPainting = false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (isPainting) { | ||||||
|  |             coordinate = coordinate.withY(this.labyrinth.getHeight()); | ||||||
|  |             for (final PDPageContentStream contentStream : contentStreams) { | ||||||
|  |                 contentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |                 contentStream.stroke(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void drawSolution(@NonNull final PDPageContentStream pageContentStream) throws IOException { | ||||||
|  |         // Draw the solution in red | ||||||
|  |         pageContentStream.setStrokingColor(Color.RED); | ||||||
|  |         // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. | ||||||
|  |         final Position end = this.labyrinth.getEnd(); | ||||||
|  |         Position currentPosition = this.labyrinth.getStart(); | ||||||
|  |         Position previousPosition = null; | ||||||
|  |         SolutionCoordinate coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); | ||||||
|  |         pageContentStream.moveTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |         do { | ||||||
|  |             Position newCurrent = this.findNextSolutionPosition(previousPosition, currentPosition); | ||||||
|  |             previousPosition = currentPosition; | ||||||
|  |             currentPosition = newCurrent; | ||||||
|  |             coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); | ||||||
|  |             pageContentStream.lineTo(coordinate.getX(), coordinate.getY()); | ||||||
|  |         } while (!currentPosition.equals(end)); | ||||||
|  |         pageContentStream.stroke(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     private Position findNextSolutionPosition(@Nullable final Position previousPosition, @NonNull final Position currentPosition) { | ||||||
|  |         final Tile currentTile = this.labyrinth.getTileAt(currentPosition); | ||||||
|  |         for (final Direction direction : Direction.values()) { | ||||||
|  |             if (!currentTile.hasWallAt(direction)) { | ||||||
|  |                 final Position position = direction.translate(currentPosition); | ||||||
|  |                 final Tile tileAtPosition = this.labyrinth.getTileAtOrNull(position); | ||||||
|  |                 if (position.equals(previousPosition) || tileAtPosition == null) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 if (tileAtPosition.isSolution()) { | ||||||
|  |                     return position; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ..."); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Value | ||||||
|  |     private class Coordinate { | ||||||
|  |         float x; | ||||||
|  |         float y; | ||||||
|  | 
 | ||||||
|  |         public Coordinate(final float x, final float y) { | ||||||
|  |             this.x = x; | ||||||
|  |             this.y = y; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private float calcX(final int x) { | ||||||
|  |             return x * PDFRenderer.SCALE + PDFRenderer.MARGIN; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private float calcY(final int y) { | ||||||
|  |             return (Generator.this.labyrinth.getHeight() - y) * PDFRenderer.SCALE + PDFRenderer.MARGIN; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Coordinate withX(final int x) { | ||||||
|  |             return new Coordinate(calcX(x), this.y); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Coordinate withY(final int y) { | ||||||
|  |             return new Coordinate(this.x, calcY(y)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Value | ||||||
|  |     private class SolutionCoordinate { | ||||||
|  |         float x; | ||||||
|  |         float y; | ||||||
|  | 
 | ||||||
|  |         public SolutionCoordinate(final int x, final int y) { | ||||||
|  |             this.x = calcX(x); | ||||||
|  |             this.y = calcY(y); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private float calcX(final int x) { | ||||||
|  |             return x * PDFRenderer.SCALE + PDFRenderer.SCALE / 2 + PDFRenderer.MARGIN; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private float calcY(final int y) { | ||||||
|  |             return (Generator.this.labyrinth.getHeight() - y) * PDFRenderer.SCALE - PDFRenderer.SCALE / 2 + PDFRenderer.MARGIN; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.pdf; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
|  | import lombok.NonNull; | ||||||
|  | 
 | ||||||
|  | public class PDFRenderer implements Renderer<byte[]> { | ||||||
|  |     static final float MARGIN = 10; | ||||||
|  |     static final float SCALE = 10; | ||||||
|  | 
 | ||||||
|  |     private PDFRenderer() { | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public static PDFRenderer newInstance() { | ||||||
|  |         return new PDFRenderer(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     @NonNull | ||||||
|  |     public byte[] render(@NonNull final Labyrinth labyrinth) { | ||||||
|  |         final Generator generator = new Generator(labyrinth); | ||||||
|  |         return generator.generate(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -1,5 +1,8 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth.renderer.pdffile; | ||||||
| 
 | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.pdf.PDFRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | @ -0,0 +1,152 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.text; | ||||||
|  | 
 | ||||||
|  | import lombok.AccessLevel; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import lombok.experimental.FieldDefaults; | ||||||
|  | 
 | ||||||
|  | @FieldDefaults(level = AccessLevel.PRIVATE) | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | class CharDefinition { | ||||||
|  |     // ─ | ||||||
|  |     static final String HORIZONTAL = "\u2500"; | ||||||
|  |     // │ | ||||||
|  |     static final String VERTICAL = "\u2502"; | ||||||
|  |     // ╴ | ||||||
|  |     static final String LEFT = "\u2574"; | ||||||
|  |     // ╵ | ||||||
|  |     static final String UP = "\u2575"; | ||||||
|  |     // ╶ | ||||||
|  |     static final String RIGHT = "\u2576"; | ||||||
|  |     // ╷ | ||||||
|  |     static final String DOWN = "\u2577"; | ||||||
|  |     // ┌ | ||||||
|  |     static final String DOWN_RIGHT = "\u250c"; | ||||||
|  |     // ┐ | ||||||
|  |     static final String DOWN_LEFT = "\u2510"; | ||||||
|  |     // └ | ||||||
|  |     static final String UP_RIGHT = "\u2514"; | ||||||
|  |     // ┘ | ||||||
|  |     static final String UP_LEFT = "\u2518"; | ||||||
|  |     // ├ | ||||||
|  |     static final String VERTICAL_RIGHT = "\u251c"; | ||||||
|  |     // ┤ | ||||||
|  |     static final String VERTICAL_LEFT = "\u2524"; | ||||||
|  |     // ┬ | ||||||
|  |     static final String HORIZONTAL_DOWN = "\u252c"; | ||||||
|  |     // ┴ | ||||||
|  |     static final String HORIZONTAL_UP = "\u2534"; | ||||||
|  |     // ┼ | ||||||
|  |     static final String CROSS = "\u253c"; | ||||||
|  |     // ╭ | ||||||
|  |     static final String SOLUTION_DOWN_RIGHT = "\u256d"; | ||||||
|  |     // ╮ | ||||||
|  |     static final String SOLUTION_DOWN_LEFT = "\u256e"; | ||||||
|  |     // ╯ | ||||||
|  |     static final String SOLUTION_UP_LEFT = "\u256f"; | ||||||
|  |     // ╰ | ||||||
|  |     static final String SOLUTION_UP_RIGHT = "\u2570"; | ||||||
|  |     boolean up = false; | ||||||
|  |     boolean down = false; | ||||||
|  |     boolean left = false; | ||||||
|  |     boolean right = false; | ||||||
|  |     boolean solution = false; | ||||||
|  | 
 | ||||||
|  |     CharDefinition solution() { | ||||||
|  |         this.solution = true; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition up() { | ||||||
|  |         this.up = true; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition down() { | ||||||
|  |         this.down = true; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition vertical() { | ||||||
|  |         return this.up().down(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition left() { | ||||||
|  |         this.left = true; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition right() { | ||||||
|  |         this.right = true; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CharDefinition horizontal() { | ||||||
|  |         return this.left().right(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String toString() { | ||||||
|  |         if (this.up) { | ||||||
|  |             if (this.down) { | ||||||
|  |                 if (this.left) { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return CROSS; | ||||||
|  |                     } else { | ||||||
|  |                         return VERTICAL_LEFT; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return VERTICAL_RIGHT; | ||||||
|  |                     } else { | ||||||
|  |                         return VERTICAL; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (this.left) { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return HORIZONTAL_UP; | ||||||
|  |                     } else { | ||||||
|  |                         return this.solution ? SOLUTION_UP_LEFT : UP_LEFT; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return this.solution ? SOLUTION_UP_RIGHT : UP_RIGHT; | ||||||
|  |                     } else { | ||||||
|  |                         return UP; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             if (this.down) { | ||||||
|  |                 if (this.left) { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return HORIZONTAL_DOWN; | ||||||
|  |                     } else { | ||||||
|  |                         return this.solution ? SOLUTION_DOWN_LEFT : DOWN_LEFT; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return this.solution ? SOLUTION_DOWN_RIGHT : DOWN_RIGHT; | ||||||
|  |                     } else { | ||||||
|  |                         return DOWN; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (this.left) { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return HORIZONTAL; | ||||||
|  |                     } else { | ||||||
|  |                         return LEFT; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.right) { | ||||||
|  |                         return RIGHT; | ||||||
|  |                     } else { | ||||||
|  |                         return " "; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										196
									
								
								src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,196 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.text; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.Direction; | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.Tile; | ||||||
|  | import lombok.AccessLevel; | ||||||
|  | import lombok.NonNull; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  | import org.jetbrains.annotations.Nullable; | ||||||
|  | 
 | ||||||
|  | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) | ||||||
|  | class Generator { | ||||||
|  |     @NonNull | ||||||
|  |     private final Labyrinth labyrinth; | ||||||
|  |     private final boolean renderSolution; | ||||||
|  |     private int x = 0; | ||||||
|  |     private int y = 0; | ||||||
|  |     private int line = 0; | ||||||
|  | 
 | ||||||
|  |     boolean hasNext() { | ||||||
|  |         return this.y < this.labyrinth.getHeight(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     String next() { | ||||||
|  |         final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y); | ||||||
|  |         final Tile topTile = this.labyrinth.getTileAtOrNull(this.x, this.y - 1); | ||||||
|  |         final Tile rightTile = this.labyrinth.getTileAtOrNull(this.x + 1, this.y); | ||||||
|  |         final Tile bottomTile = this.labyrinth.getTileAtOrNull(this.x, this.y + 1); | ||||||
|  |         final Tile leftTile = this.labyrinth.getTileAtOrNull(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.labyrinth.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.labyrinth.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, @Nullable final Tile leftTile, @Nullable final 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 == null)) { | ||||||
|  |                 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.labyrinth.getWidth() - 1) { | ||||||
|  |             result += charDef3 + "\n"; | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private String renderCenterLine(@NonNull final Tile currentTile, | ||||||
|  |                                     @Nullable final Tile topTile, | ||||||
|  |                                     @Nullable final Tile rightTile, | ||||||
|  |                                     @Nullable final Tile bottomTile, | ||||||
|  |                                     @Nullable final 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 == null)) { | ||||||
|  |                 charDef2.left(); | ||||||
|  |             } | ||||||
|  |             if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile == null)) { | ||||||
|  |                 charDef2.up(); | ||||||
|  |             } | ||||||
|  |             if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile == null)) { | ||||||
|  |                 charDef2.right(); | ||||||
|  |             } | ||||||
|  |             if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile == null)) { | ||||||
|  |                 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.labyrinth.getWidth() - 1) { | ||||||
|  |             result += charDef3 + "\n"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private String renderBottomLine(@NonNull final Tile currentTile, @Nullable final 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.labyrinth.getWidth() - 1) { | ||||||
|  |             result += charDef3; | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean hasWallAt(@Nullable final Tile tile, @NonNull final Direction direction) { | ||||||
|  |         return tile != null && tile.hasWallAt(direction); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isSolution(@Nullable final Tile tile) { | ||||||
|  |         return this.renderSolution && tile != null && tile.isSolution(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.text; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
|  | import lombok.NonNull; | ||||||
|  | 
 | ||||||
|  | public class TextRenderer implements Renderer<String> { | ||||||
|  |     private boolean renderSolution; | ||||||
|  | 
 | ||||||
|  |     private TextRenderer() { | ||||||
|  |         this.renderSolution = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public static TextRenderer newInstance() { | ||||||
|  |         return new TextRenderer(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public TextRenderer setRenderSolution(final boolean renderSolution) { | ||||||
|  |         this.renderSolution = renderSolution; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public String render(@NonNull final Labyrinth labyrinth) { | ||||||
|  |         if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |         final Generator generator = new Generator(labyrinth, this.renderSolution); | ||||||
|  |         final StringBuilder sb = new StringBuilder(); | ||||||
|  |         while (generator.hasNext()) { | ||||||
|  |             sb.append(generator.next()); | ||||||
|  |         } | ||||||
|  |         return sb.toString(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -1,5 +1,8 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth.renderer.textfile; | ||||||
| 
 | 
 | ||||||
|  | import ch.fritteli.labyrinth.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.Renderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.text.TextRenderer; | ||||||
| import io.vavr.collection.List; | import io.vavr.collection.List; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
|  | @ -1,52 +0,0 @@ | ||||||
| package ch.fritteli.labyrinth; |  | ||||||
| 
 |  | ||||||
| import ch.fritteli.labyrinth.TextRenderer.Generator.CharDefinition; |  | ||||||
| import org.junit.jupiter.api.Nested; |  | ||||||
| import org.junit.jupiter.api.Test; |  | ||||||
| 
 |  | ||||||
| import static org.junit.jupiter.api.Assertions.assertEquals; |  | ||||||
| 
 |  | ||||||
| class TextRendererTest { |  | ||||||
|     @Nested |  | ||||||
|     class CharDefinitionTest { |  | ||||||
|         @Test |  | ||||||
|         void testRenderingWall() { |  | ||||||
|             assertEquals(" ", new CharDefinition(false, false, false, false, false).toString()); |  | ||||||
|             assertEquals("╶", new CharDefinition(false, false, false, true, false).toString()); |  | ||||||
|             assertEquals("╴", new CharDefinition(false, false, true, false, false).toString()); |  | ||||||
|             assertEquals("─", new CharDefinition(false, false, true, true, false).toString()); |  | ||||||
|             assertEquals("╷", new CharDefinition(false, true, false, false, false).toString()); |  | ||||||
|             assertEquals("┌", new CharDefinition(false, true, false, true, false).toString()); |  | ||||||
|             assertEquals("┐", new CharDefinition(false, true, true, false, false).toString()); |  | ||||||
|             assertEquals("┬", new CharDefinition(false, true, true, true, false).toString()); |  | ||||||
|             assertEquals("╵", new CharDefinition(true, false, false, false, false).toString()); |  | ||||||
|             assertEquals("└", new CharDefinition(true, false, false, true, false).toString()); |  | ||||||
|             assertEquals("┘", new CharDefinition(true, false, true, false, false).toString()); |  | ||||||
|             assertEquals("┴", new CharDefinition(true, false, true, true, false).toString()); |  | ||||||
|             assertEquals("│", new CharDefinition(true, true, false, false, false).toString()); |  | ||||||
|             assertEquals("├", new CharDefinition(true, true, false, true, false).toString()); |  | ||||||
|             assertEquals("┤", new CharDefinition(true, true, true, false, false).toString()); |  | ||||||
|             assertEquals("┼", new CharDefinition(true, true, true, true, false).toString()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @Test |  | ||||||
|         void testRenderingSolution() { |  | ||||||
|             assertEquals(" ", new CharDefinition(false, false, false, false, true).toString()); |  | ||||||
|             assertEquals("╶", new CharDefinition(false, false, false, true, true).toString()); |  | ||||||
|             assertEquals("╴", new CharDefinition(false, false, true, false, true).toString()); |  | ||||||
|             assertEquals("─", new CharDefinition(false, false, true, true, true).toString()); |  | ||||||
|             assertEquals("╷", new CharDefinition(false, true, false, false, true).toString()); |  | ||||||
|             assertEquals("╭", new CharDefinition(false, true, false, true, true).toString()); |  | ||||||
|             assertEquals("╮", new CharDefinition(false, true, true, false, true).toString()); |  | ||||||
|             assertEquals("┬", new CharDefinition(false, true, true, true, true).toString()); |  | ||||||
|             assertEquals("╵", new CharDefinition(true, false, false, false, true).toString()); |  | ||||||
|             assertEquals("╰", new CharDefinition(true, false, false, true, true).toString()); |  | ||||||
|             assertEquals("╯", new CharDefinition(true, false, true, false, true).toString()); |  | ||||||
|             assertEquals("┴", new CharDefinition(true, false, true, true, true).toString()); |  | ||||||
|             assertEquals("│", new CharDefinition(true, true, false, false, true).toString()); |  | ||||||
|             assertEquals("├", new CharDefinition(true, true, false, true, true).toString()); |  | ||||||
|             assertEquals("┤", new CharDefinition(true, true, true, false, true).toString()); |  | ||||||
|             assertEquals("┼", new CharDefinition(true, true, true, true, true).toString()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | package ch.fritteli.labyrinth.renderer.text; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  | 
 | ||||||
|  | class CharDefinitionTest { | ||||||
|  |     @Test | ||||||
|  |     void testRenderingWall() { | ||||||
|  |         assertEquals(" ", new CharDefinition(false, false, false, false, false).toString()); | ||||||
|  |         assertEquals("╶", new CharDefinition(false, false, false, true, false).toString()); | ||||||
|  |         assertEquals("╴", new CharDefinition(false, false, true, false, false).toString()); | ||||||
|  |         assertEquals("─", new CharDefinition(false, false, true, true, false).toString()); | ||||||
|  |         assertEquals("╷", new CharDefinition(false, true, false, false, false).toString()); | ||||||
|  |         assertEquals("┌", new CharDefinition(false, true, false, true, false).toString()); | ||||||
|  |         assertEquals("┐", new CharDefinition(false, true, true, false, false).toString()); | ||||||
|  |         assertEquals("┬", new CharDefinition(false, true, true, true, false).toString()); | ||||||
|  |         assertEquals("╵", new CharDefinition(true, false, false, false, false).toString()); | ||||||
|  |         assertEquals("└", new CharDefinition(true, false, false, true, false).toString()); | ||||||
|  |         assertEquals("┘", new CharDefinition(true, false, true, false, false).toString()); | ||||||
|  |         assertEquals("┴", new CharDefinition(true, false, true, true, false).toString()); | ||||||
|  |         assertEquals("│", new CharDefinition(true, true, false, false, false).toString()); | ||||||
|  |         assertEquals("├", new CharDefinition(true, true, false, true, false).toString()); | ||||||
|  |         assertEquals("┤", new CharDefinition(true, true, true, false, false).toString()); | ||||||
|  |         assertEquals("┼", new CharDefinition(true, true, true, true, false).toString()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void testRenderingSolution() { | ||||||
|  |         assertEquals(" ", new CharDefinition(false, false, false, false, true).toString()); | ||||||
|  |         assertEquals("╶", new CharDefinition(false, false, false, true, true).toString()); | ||||||
|  |         assertEquals("╴", new CharDefinition(false, false, true, false, true).toString()); | ||||||
|  |         assertEquals("─", new CharDefinition(false, false, true, true, true).toString()); | ||||||
|  |         assertEquals("╷", new CharDefinition(false, true, false, false, true).toString()); | ||||||
|  |         assertEquals("╭", new CharDefinition(false, true, false, true, true).toString()); | ||||||
|  |         assertEquals("╮", new CharDefinition(false, true, true, false, true).toString()); | ||||||
|  |         assertEquals("┬", new CharDefinition(false, true, true, true, true).toString()); | ||||||
|  |         assertEquals("╵", new CharDefinition(true, false, false, false, true).toString()); | ||||||
|  |         assertEquals("╰", new CharDefinition(true, false, false, true, true).toString()); | ||||||
|  |         assertEquals("╯", new CharDefinition(true, false, true, false, true).toString()); | ||||||
|  |         assertEquals("┴", new CharDefinition(true, false, true, true, true).toString()); | ||||||
|  |         assertEquals("│", new CharDefinition(true, true, false, false, true).toString()); | ||||||
|  |         assertEquals("├", new CharDefinition(true, true, false, true, true).toString()); | ||||||
|  |         assertEquals("┤", new CharDefinition(true, true, true, false, true).toString()); | ||||||
|  |         assertEquals("┼", new CharDefinition(true, true, true, true, true).toString()); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue