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…
Reference in a new issue