diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java new file mode 100644 index 0000000..02b728c --- /dev/null +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java @@ -0,0 +1,120 @@ +package thb.jeanluc.adventure.io.text; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Renders a {@link MapView} as a styled ASCII grid: boxed rooms connected by + * corridor lines. Box-drawing uses Unicode or pure ASCII depending on {@code unicode}. + */ +public final class AsciiMap { + + private static final int INNER = 7; // room name field width + private static final int GAP = 3; // horizontal corridor width + + private AsciiMap() { + } + + public static StyledText render(MapView view, boolean unicode) { + if (view.cells().isEmpty()) { + return StyledText.of("(no map yet)"); + } + + int maxX = 0; + int maxY = 0; + Map at = new HashMap<>(); + for (RoomCell c : view.cells()) { + maxX = Math.max(maxX, c.x()); + maxY = Math.max(maxY, c.y()); + at.put(key(c.x(), c.y()), c); + } + + Set hEdge = new HashSet<>(); + Set vEdge = new HashSet<>(); + for (Connection cn : view.connections()) { + RoomCell a = cn.from(); + RoomCell b = cn.to(); + if (a.y() == b.y()) { + hEdge.add(key(Math.min(a.x(), b.x()), a.y())); + } else if (a.x() == b.x()) { + vEdge.add(key(a.x(), Math.min(a.y(), b.y()))); + } + } + + String tl = unicode ? "┌" : "+"; + String tr = unicode ? "┐" : "+"; + String bl = unicode ? "└" : "+"; + String br = unicode ? "┘" : "+"; + String hz = unicode ? "─" : "-"; + String vt = unicode ? "│" : "|"; + + StyledText.Builder b = StyledText.builder(); + for (int gy = 0; gy <= maxY; gy++) { + for (int line = 0; line < 3; line++) { + for (int gx = 0; gx <= maxX; gx++) { + RoomCell c = at.get(key(gx, gy)); + if (c == null) { + b.plain(" ".repeat(INNER + 2)); + } else if (line == 0) { + styled(b, tl + hz.repeat(INNER) + tr, c.state()); + } else if (line == 2) { + styled(b, bl + hz.repeat(INNER) + br, c.state()); + } else { + styled(b, vt + center(label(c), INNER) + vt, c.state()); + } + if (gx < maxX) { + if (line == 1 && hEdge.contains(key(gx, gy))) { + b.dim(hz.repeat(GAP)); + } else { + b.plain(" ".repeat(GAP)); + } + } + } + b.plain("\n"); + } + if (gy < maxY) { + for (int gx = 0; gx <= maxX; gx++) { + if (vEdge.contains(key(gx, gy))) { + b.plain(" ".repeat(4)).dim(vt).plain(" ".repeat(4)); + } else { + b.plain(" ".repeat(INNER + 2)); + } + if (gx < maxX) { + b.plain(" ".repeat(GAP)); + } + } + b.plain("\n"); + } + } + return b.build(); + } + + private static void styled(StyledText.Builder b, String s, CellState state) { + switch (state) { + case CURRENT -> b.heading(s); + case KNOWN -> b.dim(s); + default -> b.plain(s); + } + } + + private static String label(RoomCell c) { + String n = c.name(); + if (n.length() > INNER) { + n = n.substring(0, INNER); + } + return n; + } + + private static String center(String s, int width) { + int total = width - s.length(); + int left = total / 2; + int right = total - left; + return " ".repeat(left) + s + " ".repeat(right); + } + + private static long key(int x, int y) { + return (((long) x) << 32) ^ (y & 0xffffffffL); + } +} diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/io/text/AsciiMapTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/io/text/AsciiMapTest.java new file mode 100644 index 0000000..eada294 --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/io/text/AsciiMapTest.java @@ -0,0 +1,36 @@ +package thb.jeanluc.adventure.io.text; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + +class AsciiMapTest { + + @Test + void rendersVisitedNameCurrentAndKnownStub() { + RoomCell kitchen = new RoomCell("kitchen", "Kitchen", 0, 1, CellState.CURRENT); + RoomCell hallway = new RoomCell("hallway", "Hallway", 0, 0, CellState.VISITED); + RoomCell cellar = new RoomCell("cellar", "?", 1, 1, CellState.KNOWN); + MapView v = new MapView(List.of(kitchen, hallway, cellar), + List.of(new Connection(hallway, kitchen), new Connection(kitchen, cellar))); + + String out = AsciiMap.render(v, true).plainText(); + + assertThat(out).contains("Kitchen"); + assertThat(out).contains("Hallway"); + assertThat(out).contains("?"); + } + + @Test + void emptyMapRendersPlaceholder() { + String out = AsciiMap.render(new MapView(List.of(), List.of()), true).plainText(); + assertThat(out).containsIgnoringCase("no map"); + } + + @Test + void asciiModeUsesPlusFrames() { + RoomCell only = new RoomCell("kitchen", "Kitchen", 0, 0, CellState.CURRENT); + String out = AsciiMap.render(new MapView(List.of(only), List.of()), false).plainText(); + assertThat(out).contains("+").doesNotContain("┌"); + } +}