diff --git a/src/main/java/App.java b/src/main/java/App.java new file mode 100644 index 00000000..9866a205 --- /dev/null +++ b/src/main/java/App.java @@ -0,0 +1,125 @@ +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.Scanner; + +import domain.Columns; +import domain.Ladder; +import domain.LadderHeight; +import domain.Participants; +import domain.RungLength; +import service.LadderNavigator; +import service.RandomConnectionGenerator; +import view.LadderRenderer; + +public class App { + public static void main(String[] args) { + try (Scanner scanner = new Scanner(System.in)) { + Participants participants = readParticipants(scanner); + List labels = readLabels(scanner, participants.size()); + int heightCount = readHeight(scanner); + Ladder ladder = buildLadder(participants.size(), heightCount); + renderLadder(participants.names(), labels, ladder); + queryAndPrintResults(scanner, participants, labels, ladder); + } + } + + private static Participants readParticipants(Scanner scanner) { + while (true) { + List names = readCommaSeparated(scanner, "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + try { return Participants.of(names); } + catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } + } + } + + private static List readLabels(Scanner scanner, int count) { + while (true) { + List labels = readCommaSeparated(scanner, "실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); + if (labels.size() != count) { System.out.println("결과의 개수는 참여자 수(" + count + ")와 같아야 합니다"); continue; } + return labels; + } + } + + private static List readCommaSeparated(Scanner scanner, String prompt) { + System.out.println(prompt); + return Arrays.stream(scanner.nextLine().trim().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } + + private static int readHeight(Scanner scanner) { + System.out.println(); + System.out.println("최대 사다리 높이는 몇 개인가요?"); + return Integer.parseInt(scanner.nextLine().trim()); + } + + private static Ladder buildLadder(int numNames, int heightCount) { + Columns columns = Columns.of(numNames); + LadderHeight height = LadderHeight.of(heightCount); + RandomConnectionGenerator generator = new RandomConnectionGenerator(new Random()); + return generator.generateLadder(height, columns); + } + + private static void renderLadder(List names, List labels, Ladder ladder) { + Columns columns = Columns.of(names.size()); + RungLength rungLength = RungLength.defaultFive(); + LadderRenderer renderer = new LadderRenderer(); + System.out.println("\n사다리 결과\n"); + System.out.println(renderer.renderNamesHeader(names, rungLength)); + renderer.print(ladder, columns, rungLength); + System.out.println(renderer.renderBottomLabels(labels, rungLength)); + } + + private static void queryAndPrintResults(Scanner scanner, Participants participants, List labels, Ladder ladder) { + Columns columns = Columns.of(participants.size()); + LadderNavigator navigator = new LadderNavigator(); + while (true) { + String who = promptWho(scanner); + if (handleSelection(navigator, ladder, columns, participants, labels, who)) break; + } + } + + private static String promptWho(Scanner scanner) { + System.out.println(); + System.out.println("결과를 보고 싶은 사람은?"); + String who = scanner.nextLine().trim(); + System.out.println("\n실행 결과"); + return who; + } + + private static boolean handleSelection( + LadderNavigator navigator, + Ladder ladder, + Columns columns, + Participants participants, + List labels, + String who + ) { + if (who.equalsIgnoreCase("all")) { handleAllRequest(navigator, ladder, columns, participants, labels); return true; } + int index = participants.indexOf(who); + if (index < 0) { System.out.println("존재하지 않는 이름입니다"); return false; } + printSingleResult(navigator, ladder, columns, index, labels); + return false; + } + + private static void handleAllRequest(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List labels) { + printAllResults(navigator, ladder, columns, participants, labels); + } + + private static void printAllResults(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List labels) { + for (int i = 0; i < columns.count(); i++) { printSingleMapping(navigator, ladder, columns, participants, labels, i); } + } + + private static void printSingleResult(LadderNavigator navigator, Ladder ladder, Columns columns, int index, List labels) { + int dest = navigator.traverse(ladder, columns, index); + System.out.println(labels.get(dest)); + } + + private static void printSingleMapping(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List labels, int i) { + int dest = navigator.traverse(ladder, columns, i); + String name = participants.get(i); + String label = labels.get(dest); + System.out.println(name + " : " + label); + } +} diff --git a/src/main/java/domain/Columns.java b/src/main/java/domain/Columns.java new file mode 100644 index 00000000..4a26f0b9 --- /dev/null +++ b/src/main/java/domain/Columns.java @@ -0,0 +1,30 @@ +package domain; + +public final class Columns { + private final int count; + + private Columns(int count) { + this.count = count; + } + + public static Columns of(int count) { + if (count < 2) { + throw new IllegalArgumentException("열의 개수는 2 이상이어야 합니다"); + } + return new Columns(count); + } + + public static Columns fixedFour() { + return of(4); + } + + public int count() { + return count; + } + + public int numberOfSegments() { + return count - 1; + } +} + + diff --git a/src/main/java/domain/Connection.java b/src/main/java/domain/Connection.java new file mode 100644 index 00000000..6d2714b6 --- /dev/null +++ b/src/main/java/domain/Connection.java @@ -0,0 +1,8 @@ +package domain; + +public enum Connection { + CONNECTED, + EMPTY +} + + diff --git a/src/main/java/domain/Ladder.java b/src/main/java/domain/Ladder.java new file mode 100644 index 00000000..e5a61773 --- /dev/null +++ b/src/main/java/domain/Ladder.java @@ -0,0 +1,23 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class Ladder { + private final List rows; + + private Ladder(List rows) { + this.rows = Collections.unmodifiableList(new ArrayList<>(rows)); + } + + public static Ladder of(List rows) { + return new Ladder(rows); + } + + public List rows() { + return rows; + } +} + + diff --git a/src/main/java/domain/LadderHeight.java b/src/main/java/domain/LadderHeight.java new file mode 100644 index 00000000..2169ff1e --- /dev/null +++ b/src/main/java/domain/LadderHeight.java @@ -0,0 +1,22 @@ +package domain; + +public final class LadderHeight { + private final int count; + + private LadderHeight(int count) { + this.count = count; + } + + public static LadderHeight of(int count) { + if (count < 1) { + throw new IllegalArgumentException("행의 개수는 1 이상이어야 합니다"); + } + return new LadderHeight(count); + } + + public int count() { + return count; + } +} + + diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java new file mode 100644 index 00000000..43a209d7 --- /dev/null +++ b/src/main/java/domain/Participants.java @@ -0,0 +1,58 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class Participants { + private final List names; + + private Participants(List names) { + this.names = Collections.unmodifiableList(new ArrayList<>(names)); + } + + public static Participants of(List rawNames) { + List names = normalizeNames(rawNames); + requireNonEmpty(names); + requireMaxLength(names, 5); + return new Participants(names); + } + + private static List normalizeNames(List rawNames) { + List result = new ArrayList<>(); + for (String n : rawNames) { + String v = n == null ? "" : n.trim(); + if (!v.isEmpty()) { result.add(v); } + } + return result; + } + + private static void requireNonEmpty(List names) { + if (names.isEmpty()) { throw new IllegalArgumentException("이름을 한 개 이상 입력해 주세요"); } + } + + private static void requireMaxLength(List names, int max) { + boolean tooLongExists = names.stream().anyMatch(n -> n.length() > max); + if (tooLongExists) { + throw new IllegalArgumentException("각 이름은 최대 " + max + "글자까지 가능합니다"); + } + } + + public int size() { + return names.size(); + } + + public int indexOf(String name) { + return names.indexOf(name); + } + + public String get(int index) { + return names.get(index); + } + + public List names() { + return names; + } +} + + diff --git a/src/main/java/domain/Row.java b/src/main/java/domain/Row.java new file mode 100644 index 00000000..d1e870b1 --- /dev/null +++ b/src/main/java/domain/Row.java @@ -0,0 +1,34 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class Row { + private final List connections; + + private Row(List connections) { + this.connections = Collections.unmodifiableList(new ArrayList<>(connections)); + } + + public static Row of(List connections) { + validate(connections); + return new Row(connections); + } + + private static void validate(List connections) { + Connection previous = Connection.EMPTY; + for (Connection current : connections) { + if (previous == Connection.CONNECTED && current == Connection.CONNECTED) { + throw new IllegalArgumentException("인접한 연결은 허용되지 않습니다"); + } + previous = current; + } + } + + public List connections() { + return connections; + } +} + + diff --git a/src/main/java/domain/RungLength.java b/src/main/java/domain/RungLength.java new file mode 100644 index 00000000..43c9ebc2 --- /dev/null +++ b/src/main/java/domain/RungLength.java @@ -0,0 +1,34 @@ +package domain; + +public final class RungLength { + private final int length; + + private RungLength(int length) { + this.length = length; + } + + public static RungLength of(int length) { + if (length < 1) { + throw new IllegalArgumentException("길이는 1 이상이어야 합니다"); + } + return new RungLength(length); + } + + public static RungLength defaultFive() { + return of(5); + } + + public int length() { + return length; + } + + public String dashes() { + return "-".repeat(length); + } + + public String spaces() { + return " ".repeat(length); + } +} + + diff --git a/src/main/java/service/LadderNavigator.java b/src/main/java/service/LadderNavigator.java new file mode 100644 index 00000000..58d2fc07 --- /dev/null +++ b/src/main/java/service/LadderNavigator.java @@ -0,0 +1,37 @@ +package service; + +import java.util.ArrayList; +import java.util.List; + +import domain.Columns; +import domain.Connection; +import domain.Ladder; +import domain.Row; + +public final class LadderNavigator { + public int traverse(Ladder ladder, Columns columns, int startColumn) { + int currentColumn = startColumn; + for (Row row : ladder.rows()) { + if (currentColumn < columns.numberOfSegments() + && row.connections().get(currentColumn) == Connection.CONNECTED) { + currentColumn += 1; + continue; + } + if (currentColumn > 0 + && row.connections().get(currentColumn - 1) == Connection.CONNECTED) { + currentColumn -= 1; + } + } + return currentColumn; + } + + public List traverseAll(Ladder ladder, Columns columns) { + List results = new ArrayList<>(); + for (int start = 0; start < columns.count(); start++) { + results.add(traverse(ladder, columns, start)); + } + return results; + } +} + + diff --git a/src/main/java/service/RandomConnectionGenerator.java b/src/main/java/service/RandomConnectionGenerator.java new file mode 100644 index 00000000..300ba8c2 --- /dev/null +++ b/src/main/java/service/RandomConnectionGenerator.java @@ -0,0 +1,52 @@ +package service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import domain.Columns; +import domain.Connection; +import domain.Ladder; +import domain.LadderHeight; +import domain.Row; + +public final class RandomConnectionGenerator { + private final Random random; + + public RandomConnectionGenerator(Random random) { + this.random = random; + } + + public Row generateRow(Columns columns) { + List list = new ArrayList<>(); + Connection previous = Connection.EMPTY; + for (int i = 0; i < columns.numberOfSegments(); i++) { + Connection next = nextConnection(previous); + list.add(next); + previous = next; + } + return Row.of(list); + } + + private Connection nextConnection(Connection previous) { + if (previous == Connection.CONNECTED) { + return Connection.EMPTY; + } + boolean connect = random.nextBoolean(); + if (connect) { + return Connection.CONNECTED; + } + return Connection.EMPTY; + } + + public Ladder generateLadder(LadderHeight height, Columns columns) { + List rowList = new ArrayList<>(); + for (int r = 0; r < height.count(); r++) { + rowList.add(generateRow(columns)); + } + return Ladder.of(rowList); + } +} + + + diff --git a/src/main/java/view/LadderRenderer.java b/src/main/java/view/LadderRenderer.java new file mode 100644 index 00000000..c93caae8 --- /dev/null +++ b/src/main/java/view/LadderRenderer.java @@ -0,0 +1,85 @@ +package view; + +import java.util.ArrayList; +import java.util.List; + +import domain.Columns; +import domain.Connection; +import domain.Ladder; +import domain.Row; +import domain.RungLength; + +public final class LadderRenderer { + private static final String NAME_HEADER_INDENT = " "; + private static final String BODY_INDENT = " "; + + public List renderLines(Ladder ladder, Columns columns, RungLength rungLength) { + List lines = new ArrayList<>(); + for (Row row : ladder.rows()) { + lines.add(renderRow(row, columns, rungLength)); + } + return lines; + } + + private String renderRow(Row row, Columns columns, RungLength rungLength) { + StringBuilder builder = new StringBuilder(); + int segmentCount = columns.numberOfSegments(); + for (int i = 0; i < segmentCount; i++) { + builder.append('|'); + appendSpan(builder, row.connections().get(i), rungLength); + } + builder.append('|'); + return builder.toString(); + } + + private void appendSpan(StringBuilder builder, Connection connection, RungLength rungLength) { + if (connection == Connection.CONNECTED) { + builder.append(rungLength.dashes()); + return; + } + builder.append(rungLength.spaces()); + } + + public void print(Ladder ladder, Columns columns, RungLength rungLength) { + List lines = renderLines(ladder, columns, rungLength); + for (String line : lines) { + System.out.println(BODY_INDENT + line); + } + } + + public String renderNamesHeader(List names, RungLength rungLength) { + StringBuilder builder = new StringBuilder(); + builder.append(NAME_HEADER_INDENT); + for (String name : names) { + builder.append(padCell(name, rungLength)); + } + return builder.toString().stripTrailing(); + } + + public String renderBottomLabels(List labels, RungLength rungLength) { + StringBuilder builder = new StringBuilder(); + builder.append(BODY_INDENT); + for (String label : labels) { + builder.append(padCell(label, rungLength)); + } + return builder.toString().stripTrailing(); + } + + private String padCell(String text, RungLength rungLength) { + int width = rungLength.length(); + String value = text == null ? "" : text; + if (value.length() > width) { + value = value.substring(0, width); + } + int padding = width - value.length(); + StringBuilder builder = new StringBuilder(); + builder.append(value); + for (int i = 0; i < padding; i++) { + builder.append(' '); + } + builder.append(' '); + return builder.toString(); + } +} + +