diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 611e7c8a..02b3e5bf 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,6 +4,8 @@
-
-
-
-
-
-
-
-
-
+
@@ -25,27 +17,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -59,9 +123,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -74,10 +204,54 @@
1672210428730
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ad6e6861..bc0fdf3c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,57 @@
-# Java Lotto
+## 2주차 학습 계획
-- Last Update: 2022-12-28
+---
-## 코드 리뷰
+1. 사다리 리펙토링
+ - 일급 컬렉션 적용 (https://jojoldu.tistory.com/412)
+2. 5단계 구현(+ 게임 반복 기능)
+3. ~~TDD 학습, 적용~~
+4. 오브젝트 관심(?)
-* [텍스트와 이미지로 살펴보는 코드스쿼드의 온라인 코드 리뷰 과정](https://github.com/code-squad/codesquad-docs/blob/master/codereview/README.md)
-* [동영상으로 살펴보는 코드스쿼드의 온라인 코드 리뷰 과정](https://youtube.com/watch?v=lFinZfu3QO0&si=EnSIkaIECMiOmarE)
+### 3.13(월)
+
+---
+
+일급 컬렉션을 학습하고 Ladder, LadderLine 클래스가 List를 전달 받아 단일 필드를 가지도록 수정했습니다. 이전에는 플레이어 수, 사다리 높이 값을 전달 받아서 생성자 내부에서 private 메서드로 List를 초기화 했는데 다음과 같은 문제가 있었습니다.
+
+- 만약 LadderLine이 인터페이스고 구현체를 변경해야 한다면 Ladder 클래스 내부에서 코드를 수정해야 합니다.
+- Random 함수를 제어하기 위해 Mockito, Random 인터페이스 생성자 주입, 리플렉션 등의 방법을 써야 합니다.
+
+반면 List를 외부에서 생성하고 파라미터로 전달하는 경우에는 위와 같은 문제점이 발생하지 않습니다. LadderLine 구현체가 바뀌더라도 외부에서 교체해서 주입하면 되고 Random 함수도 테스트용 List를 만들면 되니까 조작할 필요가 없었습니다.
+
+
+
+### 3.14(화)
+
+---
+
+**학습 계획**
+
+입력값에 대한 예외 처리, 반복 입력 등의 구조를 리펙토링할 계획입니다. 지금은 비슷한 형태의 중복되는 코드가 많아서 비효율적인 것 같습니다. 그리고 오
+5단계를 끝내고 테스트 코드까지 작성하는 것이 목표입니다.
+
+**학습**
+
+~~5단계에서는 사다리 결과 입력을 추가하고 계속해서 플레이어 이름을 입력 받아서 사다리 결과를 받아오기 때문에 LadderGame 인스턴스가 살아있어야 합니다.
+기존에는 하나의 LadderGame 인스턴스에서 입력을 받을때마다 사다리를 생성하고 결과를 반환하는 방식으로 구현하려고 했지만
+LadderGame이 Ladder, Player의 정보를 가지고 있어야 하기 때문에 Ladder, Player 같은 필드를 final로 선언하고
+Controller에서 새로운 게임마다 LadderGame을 생성하도록 하는 것이 좋을 것 같습니다.~~
+
+-> LadderGame을 Controler 내부에서 인스턴스로 계속 생성하면 내부에서 new 생성자로 생성을 하기 때문에 결합력이 높아지고 유연하지 DIP도 깨지게 될 것 같습니다.
+그래서 LadderGame을 필드 변수로 주입 받고 InputDto를 전달해서 GameResultDto를 전달 받도록 해야될 것 같습니다.
+
+예외 처리는 Controller에서 LadderGame으로 Dto를 전달하면 LadderGame에서 Domain 객체를 생성하면서 예외 발생 시 throw를 합니다.
+그러면 Controller는 예외를 try-catch하고 다시 Dto를 전달하도록 하고 싶은데 이렇게 하면 처음부터 다시 입력을 받아야 하는 것이 문제입니다.
+
+
+
+### 3/16(목)
+
+---
+
+**학습**
+
+5단계 구현을 끝내고 테스트 코드를 작성하면서 리펙토링 작업을 했습니다. 아직 마음에 안 들고 리펙토링할 부분이 좀 남아있지만 개념 정리도 필요하다고 느껴서
+코드는 이정도에서 마무리 할 것 같습니다. 코드 리뷰때 아는 부분을 제대로 설명하지 못 하고 링크를 공유하게 되는 경우가 많은데 어설프게 알고 넘어가지 않도록 블로그에
+정리하는 습관을 들여야겠습니다.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index c5f7f1ac..87163ff9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,10 @@ repositories {
dependencies {
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
+ testCompile("org.junit.jupiter:junit-jupiter-params:5.9.2")
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+ //testImplementation 'org.mockito:mockito-core:4.8.0'
+ testImplementation 'org.mockito:mockito-inline:4.8.0'
}
test {
diff --git a/src/main/java/kr/codesquad/Main.java b/src/main/java/kr/codesquad/Main.java
index b5ec785b..25c20185 100644
--- a/src/main/java/kr/codesquad/Main.java
+++ b/src/main/java/kr/codesquad/Main.java
@@ -1,7 +1,14 @@
package kr.codesquad;
+import kr.codesquad.controller.LadderGameController;
+import kr.codesquad.service.LadderGame;
+import kr.codesquad.view.Screen;
+
+import java.util.Scanner;
+
public class Main {
public static void main(String[] args) {
- System.out.println("Hello world!");
+ final LadderGameController ladderGameController = new LadderGameController(new LadderGame(), new Screen(new Scanner(System.in)));
+ ladderGameController.run();
}
}
\ No newline at end of file
diff --git a/src/main/java/kr/codesquad/controller/LadderGameController.java b/src/main/java/kr/codesquad/controller/LadderGameController.java
new file mode 100644
index 00000000..2a0a1331
--- /dev/null
+++ b/src/main/java/kr/codesquad/controller/LadderGameController.java
@@ -0,0 +1,74 @@
+package kr.codesquad.controller;
+
+import kr.codesquad.controller.dto.LadderInputDto;
+import kr.codesquad.controller.dto.LadderOutputDto;
+import kr.codesquad.domain.LadderResult;
+import kr.codesquad.service.LadderGame;
+import kr.codesquad.view.Screen;
+
+import java.util.Collections;
+import java.util.List;
+
+public class LadderGameController {
+ private static final String EXIT_INPUT = "춘식이";
+
+ private final LadderGame ladderGame;
+ private final Screen screen;
+
+ public LadderGameController(LadderGame ladderGame, Screen screen) {
+ this.ladderGame = ladderGame;
+ this.screen = screen;
+ }
+
+ public void run() {
+ final List playerNames = readPlayerNames();
+ final int height = readLadderHeight();
+ final List goals = readGoals(playerNames.size());
+ final LadderOutputDto ladderOutputDto = ladderGame.play(new LadderInputDto(playerNames, height, goals));
+
+ screen.printLadder(playerNames, ladderOutputDto.getLadderShape(), goals);
+ printResult(ladderOutputDto.getLadderResult());
+ }
+
+ private void printResult(LadderResult ladderResult) {
+ String selectResult;
+
+ while (!EXIT_INPUT.equals(selectResult = screen.inputSelectResult())) {
+ screen.printResult(ladderResult, selectResult);
+ }
+
+ System.out.println("게임을 종료합니다.");
+ }
+
+ private List readGoals(int playerNumber) {
+ List goals = Collections.emptyList();
+ while (goals.isEmpty()) {
+ goals = screen.inputGoals();
+ }
+
+ checkMatchNumberOf(playerNumber, goals);
+
+ return goals;
+ }
+
+ private void checkMatchNumberOf(int playerNumber, List goals) {
+ if (playerNumber != goals.size()) {
+ System.out.println("플레이어 수만큼 결과를 입력하세요.");
+ readGoals(playerNumber);
+ }
+ }
+
+ private int readLadderHeight() {
+ return screen.inputLadderHeight().orElseGet(this::readLadderHeight);
+ }
+
+ private List readPlayerNames() {
+ List playerNames = Collections.emptyList();
+
+ while (playerNames.isEmpty()) {
+ playerNames = screen.inputPlayerNames();
+ }
+
+ return playerNames;
+ }
+}
diff --git a/src/main/java/kr/codesquad/controller/dto/LadderInputDto.java b/src/main/java/kr/codesquad/controller/dto/LadderInputDto.java
new file mode 100644
index 00000000..d83d1b7d
--- /dev/null
+++ b/src/main/java/kr/codesquad/controller/dto/LadderInputDto.java
@@ -0,0 +1,33 @@
+package kr.codesquad.controller.dto;
+
+import java.util.List;
+
+public class LadderInputDto {
+ private final List playerNames;
+ private final int playerNumber;
+ private final int height;
+ private final List goals;
+
+ public LadderInputDto(List playerNames, int height, List goals) {
+ this.playerNames = playerNames;
+ this.playerNumber = playerNames.size();
+ this.height = height;
+ this.goals = goals;
+ }
+
+ public int getPlayerNumber() {
+ return playerNumber;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public List getPlayerNames() {
+ return playerNames;
+ }
+
+ public List getGoals() {
+ return goals;
+ }
+}
diff --git a/src/main/java/kr/codesquad/controller/dto/LadderOutputDto.java b/src/main/java/kr/codesquad/controller/dto/LadderOutputDto.java
new file mode 100644
index 00000000..ac3258e2
--- /dev/null
+++ b/src/main/java/kr/codesquad/controller/dto/LadderOutputDto.java
@@ -0,0 +1,23 @@
+package kr.codesquad.controller.dto;
+
+import kr.codesquad.domain.LadderResult;
+
+import java.util.List;
+
+public class LadderOutputDto {
+ private final List outputLines;
+ private final LadderResult ladderResult;
+
+ public LadderOutputDto(List outputLines, LadderResult ladderResult) {
+ this.outputLines = outputLines;
+ this.ladderResult = ladderResult;
+ }
+
+ public String getLadderShape() {
+ return String.join("\n", outputLines);
+ }
+
+ public LadderResult getLadderResult() {
+ return ladderResult;
+ }
+}
diff --git a/src/main/java/kr/codesquad/domain/Direction.java b/src/main/java/kr/codesquad/domain/Direction.java
new file mode 100644
index 00000000..04edc23d
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/Direction.java
@@ -0,0 +1,17 @@
+package kr.codesquad.domain;
+
+public enum Direction {
+ LEFT(-2),
+ RIGHT(2),
+ NONE(0);
+
+ private final int unitVector;
+
+ Direction(int unitVector) {
+ this.unitVector = unitVector;
+ }
+
+ public int getUnitVector() {
+ return unitVector;
+ }
+}
diff --git a/src/main/java/kr/codesquad/domain/Ladder.java b/src/main/java/kr/codesquad/domain/Ladder.java
new file mode 100644
index 00000000..c6d79789
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/Ladder.java
@@ -0,0 +1,77 @@
+package kr.codesquad.domain;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.stream.Collectors;
+
+public class Ladder {
+ private final List ladderLines;
+
+ public Ladder(List ladderLines) {
+ validateLineWidth(ladderLines);
+ this.ladderLines = ladderLines;
+ }
+
+ private void validateLineWidth(List ladderLines) {
+ if (isSameWidth(ladderLines)) {
+ return;
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ private boolean isSameWidth(List ladderLines) {
+ if (ladderLines.size() < 1) {
+ return false;
+ }
+
+ final int width = ladderLines.get(0).getSumParts();
+
+ return ladderLines.stream()
+ .mapToInt(LadderLine::getSumParts)
+ .allMatch(sum -> sum == width);
+ }
+
+ public List createOutputLines() {
+ return ladderLines.stream()
+ .map(LadderLine::toString)
+ .collect(Collectors.toList());
+ }
+
+ public List makeResult() {
+ final Queue points = getStartPoints();
+ int maxHeight = ladderLines.size();
+ int height = 0;
+
+ while (height < maxHeight) {
+ goDownLadderLine(points, height);
+ ++height;
+ }
+
+ return new ArrayList<>(points);
+ }
+
+ private void goDownLadderLine(Queue points, int height) {
+ final int pointCount = points.size();
+ final LadderLine ladderLine = ladderLines.get(height);
+
+ for (int i = 0 ; i < pointCount; i++) {
+ Point point = points.poll();
+ final Direction direction = ladderLine.findCanMoveDirection(point);
+ point.move(direction.getUnitVector());
+ points.offer(point);
+ }
+ }
+
+ private Queue getStartPoints() {
+ final Queue points = new LinkedList<>();
+ final int maxWidth = ladderLines.get(0).getSumParts();
+
+ for (int width = 0 ; width < maxWidth; width += 2) {
+ points.offer(new Point(width));
+ }
+ return points;
+ }
+}
diff --git a/src/main/java/kr/codesquad/domain/LadderLine.java b/src/main/java/kr/codesquad/domain/LadderLine.java
new file mode 100644
index 00000000..5ec9eef2
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/LadderLine.java
@@ -0,0 +1,70 @@
+package kr.codesquad.domain;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LadderLine {
+ private final List ladderParts;
+
+ public LadderLine(List ladderParts) {
+ validateWidth(ladderParts);
+ validateLine(ladderParts);
+ this.ladderParts = ladderParts;
+ }
+
+ private void validateWidth(List ladderParts) {
+ if (ladderParts.size() % 2 == 0) {
+ throw new IllegalArgumentException("사다리 길이가 유효하지 않습니다.");
+ }
+ }
+
+ public Direction findCanMoveDirection(Point point) {
+ int currentWidth = point.getCurrentWidth();
+
+ if (currentWidth > 0 && ladderParts.get(currentWidth - 1) == LadderPart.BRIDGE) {
+ return Direction.LEFT;
+ }
+ if (currentWidth < ladderParts.size() - 1 && ladderParts.get(currentWidth + 1) == LadderPart.BRIDGE) {
+ return Direction.RIGHT;
+ }
+
+ return Direction.NONE;
+ }
+
+ public int getSumParts() {
+ return ladderParts.size();
+ }
+
+ private void validateLine(List ladderParts) {
+ final int maxWidth = ladderParts.size();
+
+ for (int width = 0; width < maxWidth; width++) {
+ validatePart(ladderParts, width);
+ }
+ }
+
+ private void validatePart(List ladderParts, int width) {
+ if (width % 2 == 0 && ladderParts.get(width) != LadderPart.BAR) {
+ throw new IllegalArgumentException("사다리 Bar가 생성될 위치입니다.");
+ }
+ if (width % 2 == 1 && ladderParts.get(width) == LadderPart.BAR) {
+ throw new IllegalArgumentException("사다리 Bar가 생성될 수 없는 위치입니다.");
+ }
+ if (isConnectedBridge(ladderParts, width)) {
+ throw new IllegalArgumentException("사다리 Bridge는 연속으로 생성될 수 없습니다.");
+ }
+ }
+
+ private boolean isConnectedBridge(List ladderParts, int width) {
+ return width > 2 &&
+ ladderParts.get(width - 2) == LadderPart.BRIDGE &&
+ ladderParts.get(width) == LadderPart.BRIDGE;
+ }
+
+ @Override
+ public String toString() {
+ return ladderParts.stream()
+ .map(LadderPart::toString)
+ .collect(Collectors.joining());
+ }
+}
diff --git a/src/main/java/kr/codesquad/domain/LadderPart.java b/src/main/java/kr/codesquad/domain/LadderPart.java
new file mode 100644
index 00000000..59f250c1
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/LadderPart.java
@@ -0,0 +1,31 @@
+package kr.codesquad.domain;
+
+public enum LadderPart {
+ BAR("|"),
+ BRIDGE("-----"),
+ EMPTY(" ");
+
+ private final String shape;
+
+ LadderPart(String shape) {
+ this.shape = shape;
+ }
+
+ public static LadderPart makeRandomBridge() {
+ if (isUpperThanPercent()) {
+ return BRIDGE;
+ }
+
+ return EMPTY;
+ }
+
+ private static boolean isUpperThanPercent() {
+
+ return Math.random() > 0.7;
+ }
+
+ @Override
+ public String toString() {
+ return shape;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/kr/codesquad/domain/LadderResult.java b/src/main/java/kr/codesquad/domain/LadderResult.java
new file mode 100644
index 00000000..4d879bd9
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/LadderResult.java
@@ -0,0 +1,51 @@
+package kr.codesquad.domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LadderResult {
+ private static final String ALL_RESULT = "all";
+
+ private final Map results; // String,String은 뭐가 뭔지 알기가 어렵다
+
+ private LadderResult(Map results) {
+ this.results = results;
+ }
+
+ public String getResult(String selectResult) {
+ if (ALL_RESULT.equals(selectResult)) {
+ return getAllResults();
+ }
+ if (results.containsKey(selectResult)) {
+ return results.get(selectResult);
+ }
+
+ throw new IllegalArgumentException("잘못된 입력입니다.");
+ }
+
+ private String getAllResults() {
+ final StringBuilder builder = new StringBuilder();
+
+ for (var result : results.entrySet()) {
+ builder.append(result.getKey())
+ .append(" : ")
+ .append(result.getValue())
+ .append("\n");
+ }
+
+ return builder.toString();
+ }
+
+ public static LadderResult of(List playerNames, List goals, List points) {
+ Map results = new HashMap<>();
+ for (Point point : points) {
+ final String playerName = playerNames.get(point.getStartLadderNumber());
+ final String goal = goals.get(point.getFinalLadderNumber());
+
+ results.put(playerName, goal);
+ }
+
+ return new LadderResult(results);
+ }
+}
diff --git a/src/main/java/kr/codesquad/domain/Point.java b/src/main/java/kr/codesquad/domain/Point.java
new file mode 100644
index 00000000..76e98aae
--- /dev/null
+++ b/src/main/java/kr/codesquad/domain/Point.java
@@ -0,0 +1,28 @@
+package kr.codesquad.domain;
+
+public class Point {
+ private final int startWidth;
+
+ private int currentWidth;
+
+ public Point(int startWidth) {
+ this.startWidth = startWidth;
+ this.currentWidth = startWidth;
+ }
+
+ public int getStartLadderNumber() {
+ return startWidth / 2;
+ }
+
+ public int getFinalLadderNumber() {
+ return currentWidth / 2;
+ }
+
+ public int getCurrentWidth() {
+ return currentWidth;
+ }
+
+ public void move(int width) {
+ currentWidth += width;
+ }
+}
diff --git a/src/main/java/kr/codesquad/service/LadderGame.java b/src/main/java/kr/codesquad/service/LadderGame.java
new file mode 100644
index 00000000..d514167f
--- /dev/null
+++ b/src/main/java/kr/codesquad/service/LadderGame.java
@@ -0,0 +1,57 @@
+package kr.codesquad.service;
+
+import kr.codesquad.controller.dto.LadderOutputDto;
+import kr.codesquad.domain.Ladder;
+import kr.codesquad.controller.dto.LadderInputDto;
+import kr.codesquad.domain.LadderLine;
+import kr.codesquad.domain.LadderPart;
+import kr.codesquad.domain.LadderResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LadderGame {
+ public LadderOutputDto play(LadderInputDto ladderInputDto) {
+ Ladder ladder = new Ladder(makeLadderMap(ladderInputDto.getPlayerNumber(), ladderInputDto.getHeight()));
+
+ final LadderResult ladderResult = LadderResult.of(ladderInputDto.getPlayerNames(), ladderInputDto.getGoals(), ladder.makeResult());
+
+ return new LadderOutputDto(ladder.createOutputLines(), ladderResult);
+ }
+
+ private List makeLadderMap(int playerNumber, int height) {
+ final List ladderLines = new ArrayList<>();
+
+ for (int i = 0; i < height; i++) {
+ ladderLines.add(makeLadderLine(playerNumber));
+ }
+
+ return ladderLines;
+ }
+
+ private LadderLine makeLadderLine(int playerNumber) {
+ final List ladderParts = new ArrayList<>();
+ int maxWidth = playerNumber * 2 - 1;
+
+ for (int width = 0; width < maxWidth; width++) {
+ ladderParts.add(makeLadderPart(ladderParts, width));
+ }
+
+ return new LadderLine(ladderParts);
+ }
+
+ private LadderPart makeLadderPart(List ladderParts, int width) {
+ if (width % 2 == 0) {
+ return LadderPart.BAR;
+ }
+ if (existBridgeOnLeft(ladderParts, width)) {
+ return LadderPart.EMPTY;
+ }
+
+ return LadderPart.makeRandomBridge();
+ }
+
+ private boolean existBridgeOnLeft(List ladderParts, int width) {
+ return width > 2 && ladderParts.get(width - 2) == LadderPart.BRIDGE;
+ }
+}
diff --git a/src/main/java/kr/codesquad/view/Screen.java b/src/main/java/kr/codesquad/view/Screen.java
new file mode 100644
index 00000000..b2abefc9
--- /dev/null
+++ b/src/main/java/kr/codesquad/view/Screen.java
@@ -0,0 +1,145 @@
+package kr.codesquad.view;
+
+import kr.codesquad.domain.LadderResult;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.stream.Collectors;
+
+public class Screen {
+ private final Scanner scanner;
+
+ public Screen(Scanner scanner) {
+ this.scanner = scanner;
+ }
+
+ public Optional inputLadderHeight() {
+ System.out.println("\n최대 사다리 높이는 몇 개인가요?");
+
+ try {
+ return Optional.of(readInt(scanner.nextLine()));
+ } catch (IllegalArgumentException ex) {
+ System.out.println(ex.getMessage());
+ }
+
+ return Optional.empty();
+ }
+
+ private int readInt(String input) {
+ try {
+ return toPositiveNumber(Integer.parseInt(input.trim()));
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException("숫자를 입력해주세요.");
+ }
+ }
+
+ private int toPositiveNumber(int number) {
+ if (number < 1) {
+ throw new IllegalArgumentException("0보다 큰 숫자를 입력해 주세요.");
+ }
+
+ return number;
+ }
+
+ public List inputPlayerNames() {
+ System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
+
+ try {
+ return readPlayerNames(scanner.nextLine());
+ } catch (IllegalArgumentException ex) {
+ System.out.println(ex.getMessage());
+ }
+
+ return Collections.emptyList();
+ }
+
+ public List inputGoals() {
+ System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)");
+
+ try {
+ return readGoals(scanner.nextLine());
+ } catch (IllegalArgumentException ex) {
+ System.out.println(ex.getMessage());
+ }
+
+ return Collections.emptyList();
+ }
+
+ private List readGoals(String input) {
+ return parseValidNames(input.split(","));
+ }
+
+ private List readPlayerNames(String input) {
+ return toPlayerNames(input.split(","));
+ }
+
+ private List toPlayerNames(String[] inputNames) {
+ final List validPlayerNames = parseValidNames(inputNames);
+
+ if (hasDuplicateName(validPlayerNames)) {
+ throw new IllegalArgumentException("중복된 이름이 있습니다.");
+ }
+
+ return new ArrayList<>(validPlayerNames);
+ }
+
+ private boolean hasDuplicateName(List validPlayerNames) {
+ return new HashSet<>(validPlayerNames).size() != validPlayerNames.size();
+ }
+
+ private List parseValidNames(String[] names) {
+ return Arrays.stream(names)
+ .map(this::parseValidName)
+ .collect(Collectors.toList());
+ }
+
+ private String parseValidName(String input) {
+ final String name = input.trim();
+ if (isInValidName(name)) {
+ throw new IllegalArgumentException("올바른 형식으로 입력해주세요.");
+ }
+
+ return name;
+ }
+
+ private boolean isInValidName(String name) {
+ final String playerNamePattern = "^[a-zA-Z0-9]{1,5}$";
+
+ return !name.matches(playerNamePattern);
+ }
+
+ public String toFormattedString(List lists) {
+ return lists.stream()
+ .map(str -> String.format("%-6s", str))
+ .collect(Collectors.joining());
+ }
+
+ public void printLadder(List playerNames, String ladderShape, List goals) {
+ System.out.println("\n사다리 결과\n");
+
+ System.out.println(toFormattedString(playerNames));
+ System.out.println(ladderShape);
+ System.out.println(toFormattedString(goals));
+ }
+
+ public void printResult(LadderResult ladderResult, String selectResult) {
+ try {
+ final String result = ladderResult.getResult(selectResult);
+ System.out.println("\n실행 결과");
+ System.out.println(result);
+ } catch (IllegalArgumentException ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public String inputSelectResult() {
+ System.out.println("\n결과를 보고 싶은 사람은?");
+
+ return scanner.nextLine();
+ }
+}
diff --git a/src/test/java/kr/codesquad/domain/LadderLineTest.java b/src/test/java/kr/codesquad/domain/LadderLineTest.java
new file mode 100644
index 00000000..f1a58876
--- /dev/null
+++ b/src/test/java/kr/codesquad/domain/LadderLineTest.java
@@ -0,0 +1,59 @@
+package kr.codesquad.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static kr.codesquad.domain.Direction.*;
+import static kr.codesquad.domain.LadderPart.*;
+import static org.assertj.core.api.Assertions.*;
+
+class LadderLineTest {
+ @DisplayName("잘못된 사다리 라인 생성 시 예외가 발생한다.")
+ @MethodSource("invalidLadderLineParts")
+ @ParameterizedTest()
+ void ladderLineConnectBridgeExceptionTest(List ladderParts) {
+ System.out.println("\"" + toShape(ladderParts) + "\"");
+
+ assertThatThrownBy(() -> new LadderLine(ladderParts)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @DisplayName("사다리 라인은 현재 위치 Point에서 이동 가능한 방향 Direction을 반환한다.")
+ @Test
+ void findDirectionTest() {
+ final LadderLine ladderLine = new LadderLine(List.of(BAR, EMPTY, BAR, BRIDGE, BAR));
+ final int parts = ladderLine.getSumParts();
+ List points = new ArrayList<>();
+
+ for (int i = 0; i < parts; i++) {
+ points.add(new Point(i));
+ }
+
+ assertThatStream(points.stream())
+ .map(ladderLine::findCanMoveDirection)
+ .containsExactly(NONE, NONE, RIGHT, NONE, LEFT);
+
+ }
+
+ static Stream invalidLadderLineParts() {
+ return Stream.of(
+ Arguments.of(List.of(BAR, BRIDGE, BAR, BRIDGE, BAR)),
+ Arguments.of(List.of(BRIDGE, BAR, BAR, EMPTY, BAR)),
+ Arguments.of(List.of(BAR, BRIDGE, EMPTY, EMPTY, BAR)),
+ Arguments.of(List.of(BAR, EMPTY, BAR, BRIDGE))
+ );
+ }
+
+ public String toShape(List ladderParts) {
+ return ladderParts.stream()
+ .map(LadderPart::toString)
+ .collect(Collectors.joining());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/kr/codesquad/domain/LadderPartTest.java b/src/test/java/kr/codesquad/domain/LadderPartTest.java
new file mode 100644
index 00000000..16d99a6d
--- /dev/null
+++ b/src/test/java/kr/codesquad/domain/LadderPartTest.java
@@ -0,0 +1,29 @@
+package kr.codesquad.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+class LadderPartTest {
+
+ @DisplayName("LadderPart 랜덤 생성 테스트")
+ @Test
+ void createRandomPartTest() {
+ int bridgeCount = 0;
+ int emptyCount = 0;
+
+ for (int i = 0; i < 100; i++) {
+ LadderPart ladderPart = LadderPart.makeRandomBridge();
+ if (ladderPart == LadderPart.BRIDGE) {
+ ++bridgeCount;
+ }
+ if (ladderPart == LadderPart.EMPTY) {
+ ++emptyCount;
+ }
+ }
+
+ assertThat(bridgeCount).isNotZero();
+ assertThat(emptyCount).isNotZero();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/kr/codesquad/domain/LadderTest.java b/src/test/java/kr/codesquad/domain/LadderTest.java
new file mode 100644
index 00000000..3a66e78c
--- /dev/null
+++ b/src/test/java/kr/codesquad/domain/LadderTest.java
@@ -0,0 +1,79 @@
+package kr.codesquad.domain;
+
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.data.MapEntry;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static kr.codesquad.domain.LadderPart.*;
+import static org.assertj.core.api.Assertions.*;
+
+class LadderTest {
+ private List ladderLines;
+
+ @BeforeEach
+ void setUp() {
+ ladderLines = List.of(
+ new LadderLine(List.of(BAR, BRIDGE, BAR, EMPTY, BAR)),
+ new LadderLine(List.of(BAR, EMPTY, BAR, BRIDGE, BAR)),
+ new LadderLine(List.of(BAR, BRIDGE, BAR, EMPTY, BAR))
+ );
+ }
+
+ @DisplayName("사다리 생성 시 라인의 길이가 일정하면 성공적으로 생성이 된다.")
+ @Test
+ void LadderCreateSuccessTest() {
+ assertThatNoException().isThrownBy(() -> new Ladder(ladderLines));
+ }
+
+ @DisplayName("사다리 생성 시 라인의 길이가 다르면 IllegalArgumentException 예외가 발생한다.")
+ @Test
+ void LadderCreateFailTest() {
+ final List ladderLines = List.of(
+ new LadderLine(List.of(BAR, BRIDGE, BAR, EMPTY, BAR)),
+ new LadderLine(List.of(BAR, BRIDGE, BAR))
+ );
+
+ assertThatThrownBy(() -> new Ladder(ladderLines))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @DisplayName("사다리 모양을 List로 한줄씩 담아서 반환할 수 있다.")
+ @Test
+ void LadderOutputLinesTest() {
+ final Ladder ladder = new Ladder(ladderLines);
+ final List expectOutputLines = toOutputLines(ladderLines);
+
+ final List outputLines = ladder.createOutputLines();
+
+
+ assertThat(outputLines).isEqualTo(expectOutputLines);
+ }
+
+ private List toOutputLines(List ladderLines) {
+ return ladderLines.stream()
+ .map(LadderLine::toString)
+ .collect(Collectors.toList());
+ }
+
+ @DisplayName("사다리 결과 생성시 올바르게 매칭이 된다.")
+ @Test
+ void makeResultTest() {
+ final Ladder ladder = new Ladder(ladderLines);
+ final Map results = ladder.makeResult().stream()
+ .collect(Collectors.toMap(Point::getStartLadderNumber, Point::getFinalLadderNumber));
+
+ assertThat(results).hasSize(3).contains(
+ entry(0, 2),
+ entry(1, 1),
+ entry(2, 0)
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/kr/codesquad/view/ScreenTest.java b/src/test/java/kr/codesquad/view/ScreenTest.java
new file mode 100644
index 00000000..fb36aa9c
--- /dev/null
+++ b/src/test/java/kr/codesquad/view/ScreenTest.java
@@ -0,0 +1,31 @@
+package kr.codesquad.view;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.ByteArrayInputStream;
+import java.util.Optional;
+import java.util.Scanner;
+
+import static org.assertj.core.api.Assertions.*;
+
+class ScreenTest {
+ @DisplayName("사다리 높이 입력 테스트")
+ @ValueSource(strings = {"1", "2"})
+ @ParameterizedTest
+ void inputLadderHeightTest(String inputLadderHeight) {
+ //given
+ int expectLadderHeight = Integer.parseInt(inputLadderHeight);
+ final ByteArrayInputStream testInputStream = new ByteArrayInputStream(inputLadderHeight.getBytes());
+
+ System.setIn(testInputStream);
+ final Screen screen = new Screen(new Scanner(System.in));
+
+ //when
+ final Optional ladderHeight = screen.inputLadderHeight();
+
+ //then
+ assertThat(ladderHeight).isPresent().get().isEqualTo(expectLadderHeight);
+ }
+}
\ No newline at end of file