diff --git a/src/main/java/nextstep/ladder/LadderMain.java b/src/main/java/nextstep/ladder/LadderMain.java new file mode 100644 index 0000000000..bbbed32d4c --- /dev/null +++ b/src/main/java/nextstep/ladder/LadderMain.java @@ -0,0 +1,18 @@ +package nextstep.ladder; + +import nextstep.ladder.domain.Ladder; +import nextstep.ladder.domain.PersonNames; +import nextstep.ladder.view.InputView; +import nextstep.ladder.view.ReviewView; + +import java.util.List; + +public class LadderMain { + public static void main(String[] args) { + List personNameInput = InputView.getPersonNameInput("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)", ","); + int height = InputView.getPositiveNumberInput("최대 사다리 높이는 몇 개인가요?"); + + Ladder ladder = new Ladder(new PersonNames(personNameInput), height); + ReviewView.printLadder(ladder); + } +} diff --git a/src/main/java/nextstep/ladder/domain/Ladder.java b/src/main/java/nextstep/ladder/domain/Ladder.java new file mode 100644 index 0000000000..9cc0deb5db --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/Ladder.java @@ -0,0 +1,19 @@ +package nextstep.ladder.domain; + +public class Ladder { + private final PersonNames personNames; + private final LadderLines ladderLines; + + public Ladder(PersonNames personNames, int height) { + this.personNames = personNames; + this.ladderLines = LadderLineGenerator.generateLadderLines(height, personNames.size() - 1); + } + + public PersonNames getPersonNames() { + return personNames; + } + + public LadderLines getLadderLines() { + return ladderLines; + } +} diff --git a/src/main/java/nextstep/ladder/domain/LadderLine.java b/src/main/java/nextstep/ladder/domain/LadderLine.java new file mode 100644 index 0000000000..5b728dd842 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/LadderLine.java @@ -0,0 +1,46 @@ +package nextstep.ladder.domain; + +import java.util.Iterator; +import java.util.List; + +public class LadderLine { + private final List lines; + + public LadderLine(List lines) { + if (lines == null || lines.isEmpty()) { + throw new IllegalArgumentException("lines cannot be null or empty"); + } + + validateLines(lines); + + this.lines = lines; + } + + public int size() { + return lines.size(); + } + + private void validateLines(List lines) { + Iterator iterator = lines.iterator(); + boolean prev = iterator.next(); + + while (iterator.hasNext()) { + boolean cur = iterator.next(); + if (prev && cur) { + throw new IllegalArgumentException("Ladder lines cannot be connected continuously."); + } + prev = cur; + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (Boolean line : lines) { + sb.append("|"); + sb.append(line ? "--------": " "); + } + sb.append("|"); + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java b/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java new file mode 100644 index 0000000000..9594e9d1a0 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java @@ -0,0 +1,31 @@ +package nextstep.ladder.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class LadderLineGenerator { + private static final Random random = new Random(); + + public static LadderLines generateLadderLines(int height, int size) { + List ladderLines = new ArrayList<>(); + for (int i = 0; i < height; i++) { + ladderLines.add(generateLadderLine(size)); + } + + return new LadderLines(ladderLines); + } + + private static LadderLine generateLadderLine(int size) { + List lines = new ArrayList<>(); + + for (int i = 0; i < size; i++) { + boolean prev = i != 0 && lines.get(i - 1); + + boolean cur = !prev && random.nextBoolean(); + lines.add(cur); + } + + return new LadderLine(lines); + } +} diff --git a/src/main/java/nextstep/ladder/domain/LadderLines.java b/src/main/java/nextstep/ladder/domain/LadderLines.java new file mode 100644 index 0000000000..a5c9488487 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/LadderLines.java @@ -0,0 +1,28 @@ +package nextstep.ladder.domain; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class LadderLines { + private final List ladderLines; + + public LadderLines(List ladderLines) { + this.ladderLines = ladderLines; + } + + public int size() { + return ladderLines.size(); + } + + public List getLadderLines() { + return Collections.unmodifiableList(ladderLines); + } + + @Override + public String toString() { + return " " + ladderLines.stream() + .map(LadderLine::toString) + .collect(Collectors.joining("\n ")); + } +} diff --git a/src/main/java/nextstep/ladder/domain/PersonName.java b/src/main/java/nextstep/ladder/domain/PersonName.java new file mode 100644 index 0000000000..07e0349221 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/PersonName.java @@ -0,0 +1,48 @@ +package nextstep.ladder.domain; + +import java.util.Objects; + +public class PersonName { + public static final int MAX_NAME_LENGTH = 5; + + private String name; + + public PersonName(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be null or empty"); + } + + if (name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException("Name is too long"); + } + + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PersonName that = (PersonName) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(" ".repeat((9 - name.length()) / 2)); + sb.append(name); + sb.append(" ".repeat(9 - sb.length())); + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/ladder/domain/PersonNames.java b/src/main/java/nextstep/ladder/domain/PersonNames.java new file mode 100644 index 0000000000..d25a8c53b5 --- /dev/null +++ b/src/main/java/nextstep/ladder/domain/PersonNames.java @@ -0,0 +1,30 @@ +package nextstep.ladder.domain; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class PersonNames { + private final List personNames; + + public PersonNames(List personNames) { + this.personNames = personNames.stream() + .map(PersonName::new) + .collect(Collectors.toList()); + } + + public int size() { + return personNames.size(); + } + + public List getPersonNames() { + return Collections.unmodifiableList(personNames); + } + + @Override + public String toString() { + return personNames.stream() + .map(PersonName::toString) + .collect(Collectors.joining("")); + } +} diff --git a/src/main/java/nextstep/ladder/view/InputView.java b/src/main/java/nextstep/ladder/view/InputView.java new file mode 100644 index 0000000000..49a70377ae --- /dev/null +++ b/src/main/java/nextstep/ladder/view/InputView.java @@ -0,0 +1,72 @@ +package nextstep.ladder.view; + +import nextstep.ladder.domain.PersonName; + +import java.util.Arrays; +import java.util.InputMismatchException; +import java.util.List; +import java.util.Scanner; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + private InputView() { + } + + public static int getPositiveNumberInput(String prompt) { + return getNumberInput(prompt, "0 이상의 정수만 허용됩니다. 다시 입력해 주세요.", + input -> input >= 0); + } + + private static int getNumberInput(String prompt, String errorMessage, Predicate predicate) { + System.out.println(prompt); + + while (true) { + try { + int result = scanner.nextInt(); + + // scanner.nextInt() 는 개행 문자를 제거하지 못해 nextInt 이후 nextLine 으로 개행 제거 + scanner.nextLine(); + + if (predicate.test(result)) { + return result; + } + + System.out.println(errorMessage + " input: " + result); + } catch (InputMismatchException e) { + System.out.println(errorMessage + scanner.nextLine()); + } + } + } + + public static List getPersonNameInput(String prompt, String delimiter) { + System.out.println(prompt); + while (true) { + String line = scanner.nextLine(); + + String[] split = line.split(delimiter); + if (split.length == 0) { + System.out.println("이름은 하나 이상 입력되어야 합니다. 다시 입력해 주세요."); + continue; + } + + List result = Arrays.stream(split) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + if (split.length != result.size()) { + System.out.println("이름은 중복될 수 없습니다. 다시 입력해 주세요."); + continue; + } + + if (result.stream().allMatch(s -> s.length() <= PersonName.MAX_NAME_LENGTH)) { + return result; + } + + System.out.println("이름은 최대 5글자까지 사용 가능합니다. 다시 입력해 주세요."); + } + } +} diff --git a/src/main/java/nextstep/ladder/view/ReviewView.java b/src/main/java/nextstep/ladder/view/ReviewView.java new file mode 100644 index 0000000000..3507c31e46 --- /dev/null +++ b/src/main/java/nextstep/ladder/view/ReviewView.java @@ -0,0 +1,13 @@ +package nextstep.ladder.view; + +import nextstep.ladder.domain.Ladder; + +public class ReviewView { + private ReviewView() { + } + + public static void printLadder(Ladder ladder) { + System.out.println(ladder.getPersonNames()); + System.out.println(ladder.getLadderLines()); + } +} diff --git a/src/test/java/nextstep/ladder/domain/LadderLineTest.java b/src/test/java/nextstep/ladder/domain/LadderLineTest.java new file mode 100644 index 0000000000..398a43147b --- /dev/null +++ b/src/test/java/nextstep/ladder/domain/LadderLineTest.java @@ -0,0 +1,42 @@ +package nextstep.ladder.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LadderLineTest { + @DisplayName("사다리 라인 정상 연결 테스트") + @Test + public void connectLine() throws Exception { + assertThat(new LadderLine(List.of(true,false,true))) + .isNotNull(); + } + + @DisplayName("사다리 라인 null or empty") + @Test + public void invalidConnectLineNullOrEmpty() throws Exception { + assertThatThrownBy(() -> new LadderLine(List.of())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("lines cannot be null or empty"); + + assertThatThrownBy(() -> new LadderLine(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("lines cannot be null or empty"); + } + + @DisplayName("사다리 라인 연속해서 연결되어 생성 실패") + @Test + public void invalidConnectLine() throws Exception { + assertThatThrownBy(() -> new LadderLine(List.of(true,true,false))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Ladder lines cannot be connected continuously."); + + assertThatThrownBy(() -> new LadderLine(List.of(false,true,true))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Ladder lines cannot be connected continuously."); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/ladder/domain/LadderTest.java b/src/test/java/nextstep/ladder/domain/LadderTest.java new file mode 100644 index 0000000000..07a5fb95fb --- /dev/null +++ b/src/test/java/nextstep/ladder/domain/LadderTest.java @@ -0,0 +1,28 @@ +package nextstep.ladder.domain; + +import nextstep.ladder.view.ReviewView; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class LadderTest { + @DisplayName("사다리 생성 테스트") + @Test + public void createLadder() throws Exception { + Ladder ladder = new Ladder(new PersonNames(List.of("1", "2", "3")), 3); + ReviewView.printLadder(ladder); + + assertThat(ladder.getPersonNames().getPersonNames()) + .containsExactly(new PersonName("1"), new PersonName("2"), new PersonName("3")); + + assertThat(ladder.getLadderLines().size()) + .isEqualTo(3); + + assertThat(ladder.getLadderLines().getLadderLines().get(0).size()) + .isEqualTo(2); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/ladder/domain/PersonNameTest.java b/src/test/java/nextstep/ladder/domain/PersonNameTest.java new file mode 100644 index 0000000000..2ddb95d3d4 --- /dev/null +++ b/src/test/java/nextstep/ladder/domain/PersonNameTest.java @@ -0,0 +1,36 @@ +package nextstep.ladder.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PersonNameTest { + @DisplayName("사용자 이름 생성") + @Test + public void personName() throws Exception { + assertThat(new PersonName("12345").getName()) + .isEqualTo("12345"); + } + + @DisplayName("사용자 이름 생성 실패, null") + @Test + public void personNameNull() throws Exception { + assertThatThrownBy(() -> new PersonName(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Name cannot be null or empty"); + + assertThatThrownBy(() -> new PersonName("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Name cannot be null or empty"); + } + + @DisplayName("사용자 이름 생성 실패, 최대 길이 초과") + @Test + public void personNameMaxLength() throws Exception { + assertThatThrownBy(() -> new PersonName("123456")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Name is too long"); + } +} \ No newline at end of file