Implement a nicer text renderer.

This commit is contained in:
Manuel Friedli 2020-09-30 21:54:06 +02:00
parent 483e00d964
commit 0a27870fb8
7 changed files with 302 additions and 125 deletions

View file

@ -25,5 +25,10 @@
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -20,7 +20,11 @@ public class Labyrinth {
}
Tile getTileAt(@NonNull final Position position) {
return this.field[position.getX()][position.getY()];
return this.getTileAt(position.getX(), position.getY());
}
Tile getTileAt(final int x, final int y) {
return this.field[x][y];
}
private void initField() {
@ -41,9 +45,7 @@ public class Labyrinth {
tile.preventDiggingToOrFrom(Direction.RIGHT);
}
if (y == 0) {
if (x != 0) {
tile.preventDiggingToOrFrom(Direction.TOP);
}
tile.preventDiggingToOrFrom(Direction.TOP);
} else if (y == height - 1) {
tile.preventDiggingToOrFrom(Direction.BOTTOM);
}
@ -57,14 +59,16 @@ public class Labyrinth {
private final Deque<Position> positions = new LinkedList<>();
Generator() {
final Position topLeft = new Position(0, 0);
Labyrinth.this.getTileAt(topLeft).digFrom(Direction.TOP);
this.positions.push(topLeft);
this.dig();
final Position bottomRight = new Position(Labyrinth.this.width - 1, Labyrinth.this.height - 1);
final Tile bottomRightTile = Labyrinth.this.getTileAt(bottomRight);
bottomRightTile.enableDiggingToOrFrom(Direction.BOTTOM);
bottomRightTile.digTo(Direction.BOTTOM);
bottomRightTile.digFrom(Direction.BOTTOM);
this.positions.push(bottomRight);
this.dig();
final Position topLeft = new Position(0, 0);
final Tile topLeftTile = Labyrinth.this.getTileAt(topLeft);
topLeftTile.enableDiggingToOrFrom(Direction.TOP);
topLeftTile.digTo(Direction.TOP);
}
private void dig() {

View file

@ -4,9 +4,9 @@ import lombok.NonNull;
public class Main {
public static void main(@NonNull final String[] args) {
int width = 5;
int height = 8;
int width = 110;
int height = 15;
final Labyrinth labyrinth = new Labyrinth(width, height);
System.out.println(SimpleTextRenderer.render(labyrinth));
System.out.println(TextRenderer.render(labyrinth));
}
}

View file

@ -1,113 +0,0 @@
package labyrinth;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
@UtilityClass
public class SimpleTextRenderer {
private static final char TOP_LEFT = '\u250c';
private static final char TOP_RIGHT = '\u2510';
private static final char MIDDLE_LEFT = '\u251c';
private static final char MIDDLE_RIGHT = '\u2524';
private static final char BOTTOM_LEFT = '\u2514';
private static final char BOTTOM_RIGHT = '\u2518';
private static final char HORIZONTAL = '\u2500';
private static final char VERTICAL = '\u2502';
public String render(@NonNull final Labyrinth labyrinth) {
final int width;
final int height;
width = labyrinth.field.length;
if (width == 0) {
return "";
}
height = labyrinth.field[0].length;
StringBuilder sb = new StringBuilder();
for (int y = 0; y < height; y++) {
// TOP WALL
for (int x = 0; x < width; x++) {
final Tile tile = labyrinth.getTileAt(new Position(x, y));
final boolean top = tile.getWalls().isSet(Direction.TOP);
final boolean topHard = tile.getWalls().getHardened().contains(Direction.TOP);
sb.append(TOP_LEFT);
if (topHard) {
if (top) {
sb.append(HORIZONTAL);
} else {
sb.append("?");
}
} else {
if (top) {
sb.append("-");
} else {
sb.append(" ");
}
}
sb.append(TOP_RIGHT);
}
sb.append("\n");
// LEFT WALL, CENTER, RIGHT WALL
for (int x = 0; x < width; x++) {
final Tile tile = labyrinth.getTileAt(new Position(x, y));
// left
final boolean left = tile.getWalls().isSet(Direction.LEFT);
final boolean leftHard = tile.getWalls().getHardened().contains(Direction.LEFT);
if (leftHard) {
if (left) {
sb.append(VERTICAL);
} else {
sb.append("?");
}
} else {
if (left) {
sb.append(":");
} else {
sb.append(" ");
}
}
// center
sb.append(" ");
// right
final boolean right = tile.getWalls().isSet(Direction.RIGHT);
final boolean rightHard = tile.getWalls().getHardened().contains(Direction.RIGHT);
if (rightHard) {
if (right) {
sb.append(VERTICAL);
} else {
sb.append("?");
}
} else {
if (right) {
sb.append(":");
} else {
sb.append(" ");
}
}
}
sb.append("\n");
// BOTTOM WALL
for (int x = 0; x < width; x++) {
final Tile tile = labyrinth.getTileAt(new Position(x, y));
final boolean bottom = tile.getWalls().isSet(Direction.BOTTOM);
final boolean bottomHard = tile.getWalls().getHardened().contains(Direction.BOTTOM);
sb.append(BOTTOM_LEFT);
if (bottomHard) {
if (bottom) {
sb.append(HORIZONTAL);
} else {
sb.append("?");
}
} else {
if (bottom) {
sb.append("-");
} else {
sb.append(" ");
}
}
sb.append(BOTTOM_RIGHT);
}
sb.append("\n");
}
return sb.toString();
}
}

View file

@ -0,0 +1,250 @@
package labyrinth;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.With;
import org.jetbrains.annotations.Nullable;
public class TextRenderer {
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";
private final Labyrinth labyrinth;
private final int width;
private final int height;
private int x = 0;
private int y = 0;
private int border = 0;
private TextRenderer(@NonNull final Labyrinth labyrinth, final int width, final int height) {
this.labyrinth = labyrinth;
this.width = width;
this.height = height;
}
public static String render(@NonNull final Labyrinth labyrinth) {
final int width = labyrinth.field.length;
if (width == 0) {
return "";
}
final int height = labyrinth.field[0].length;
final TextRenderer renderer = new TextRenderer(labyrinth, width, height);
final StringBuilder sb = new StringBuilder();
while (renderer.hasNext()) {
sb.append(renderer.next());
}
return sb.toString();
}
private boolean hasNext() {
return this.y < this.height;
}
private String next() {
final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y);
final Tile leftTile = this.x > 0 ? this.labyrinth.getTileAt(this.x - 1, this.y) : null;
final Tile topTile = this.y > 0 ? this.labyrinth.getTileAt(this.x, this.y - 1) : null;
String s = "";
switch (this.border) {
case 0:
s = this.renderTop(currentTile, leftTile, topTile);
break;
case 1:
s = this.renderCenter(currentTile);
break;
case 2:
s = this.renderBottom(currentTile, leftTile);
break;
}
// do some magic ...
this.x++;
if (this.x == this.width) {
this.x = 0;
this.border++;
}
if (this.border == 2 && this.y < this.height - 1) {
this.border = 0;
this.y++;
}
if (this.border == 3) {
this.border = 0;
this.y++;
}
return s;
}
private String renderTop(@NonNull final Tile currentTile, @Nullable final Tile leftTile, @Nullable final Tile topTile) {
final CharDefinition.CharDefinitionBuilder builder1 = CharDefinition.builder();
final CharDefinition.CharDefinitionBuilder builder2 = CharDefinition.builder();
final CharDefinition.CharDefinitionBuilder builder3 = CharDefinition.builder();
String result;
if (currentTile.hasWallAt(Direction.LEFT)) {
builder1.down(true);
}
if (currentTile.hasWallAt(Direction.TOP)) {
builder1.right(true);
builder2.left(true).right(true);
builder3.left(true);
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
builder3.down(true);
}
if (leftTile != null && leftTile.hasWallAt(Direction.TOP)) {
builder1.left(true);
}
if (topTile != null) {
if (topTile.hasWallAt(Direction.LEFT)) {
builder1.up(true);
}
if (topTile.hasWallAt(Direction.RIGHT)) {
builder3.up(true);
}
}
result = builder1.build().toString() + builder2.build();
if (this.x == this.width - 1) {
result += builder3.build() + "\n";
}
return result;
}
private String renderCenter(@NonNull final Tile currentTile) {
final CharDefinition.CharDefinitionBuilder builder1 = CharDefinition.builder();
final CharDefinition.CharDefinitionBuilder builder2 = CharDefinition.builder();
String result;
if (currentTile.hasWallAt(Direction.LEFT)) {
builder1.up(true).down(true);
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
builder2.up(true).down(true);
}
result = builder1.build() + " ";
if (this.x == this.width - 1) {
result += builder2.build() + "\n";
}
return result;
}
private String renderBottom(@NonNull final Tile currentTile, @Nullable final Tile leftTile) {
String result;
final CharDefinition.CharDefinitionBuilder builder1 = CharDefinition.builder();
final CharDefinition.CharDefinitionBuilder builder2 = CharDefinition.builder();
final CharDefinition.CharDefinitionBuilder builder3 = CharDefinition.builder();
if (currentTile.hasWallAt(Direction.LEFT)) {
builder1.up(true);
}
if (currentTile.hasWallAt(Direction.BOTTOM)) {
builder1.right(true);
builder2.left(true).right(true);
builder3.left(true);
}
if (currentTile.hasWallAt(Direction.RIGHT)) {
builder3.up(true);
}
if (leftTile != null && leftTile.hasWallAt(Direction.BOTTOM)) {
builder1.left(true);
}
result = builder1.build().toString() + builder2.build();
if (this.x == this.width - 1) {
result += builder3.build();
}
return result;
}
@Value
@Builder
@With
static class CharDefinition {
@Builder.Default
boolean up = false;
@Builder.Default
boolean down = false;
@Builder.Default
boolean left = false;
@Builder.Default
boolean right = false;
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 UP_LEFT;
}
} else {
if (this.right) {
return UP_RIGHT;
} else {
return UP;
}
}
}
} else {
if (this.down) {
if (this.left) {
if (this.right) {
return HORIZONTAL_DOWN;
} else {
return DOWN_LEFT;
}
} else {
if (this.right) {
return 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 " ";
}
}
}
}
}
}
}

View file

@ -46,4 +46,8 @@ public class Tile {
public Option<Direction> getRandomAvailableDirection() {
return Stream.ofAll(this.walls.getSet(true)).shuffle().headOption();
}
public boolean hasWallAt(@NonNull final Direction direction) {
return this.walls.isSet(direction);
}
}

View file

@ -0,0 +1,27 @@
package labyrinth;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TextRendererTest {
@Test
void charDefTest() {
assertEquals(" ", new TextRenderer.CharDefinition(false, false, false, false).toString());
assertEquals("", new TextRenderer.CharDefinition(false, false, false, true).toString());
assertEquals("", new TextRenderer.CharDefinition(false, false, true, false).toString());
assertEquals("", new TextRenderer.CharDefinition(false, false, true, true).toString());
assertEquals("", new TextRenderer.CharDefinition(false, true, false, false).toString());
assertEquals("", new TextRenderer.CharDefinition(false, true, false, true).toString());
assertEquals("", new TextRenderer.CharDefinition(false, true, true, false).toString());
assertEquals("", new TextRenderer.CharDefinition(false, true, true, true).toString());
assertEquals("", new TextRenderer.CharDefinition(true, false, false, false).toString());
assertEquals("", new TextRenderer.CharDefinition(true, false, false, true).toString());
assertEquals("", new TextRenderer.CharDefinition(true, false, true, false).toString());
assertEquals("", new TextRenderer.CharDefinition(true, false, true, true).toString());
assertEquals("", new TextRenderer.CharDefinition(true, true, false, false).toString());
assertEquals("", new TextRenderer.CharDefinition(true, true, false, true).toString());
assertEquals("", new TextRenderer.CharDefinition(true, true, true, false).toString());
assertEquals("", new TextRenderer.CharDefinition(true, true, true, true).toString());
}
}