diff --git a/README.md b/README.md index 7ea051c7..43872696 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,53 @@ ## 요구사항 -- 사다리의 높이와 넓이를 입력받아 생성한다. - - 넓이와 높이는 2 이상 24 이하 여야한다. -- 사다리의 시작 지점과 도착 지점을 출력한다. +- 참여할 사람 이름 (쉼표 기준) + - 이름은 최대 5자까지 가능하다. +- 실행 결과 ex) 꽝, 5000, 꽝, 3000 +- 최대 사다리 높이 + - 높이는 2 이상 24 이하 여야한다. +- 결과를 볼 사람의 이름 + - 참여할 사람을 입력할 시 결과만 출력 + ```text + 결과를 보고 싶은 사람은? + neo + + 실행 결과 + 꽝 + ``` + - `all`을 입력할 시 전체 출력 후 종료 + ```text + 결과를 보고 싶은 사람은? + all + + 실행 결과 + neo : 꽝 + brown : 3000 + brie : 꽝 + tommy : 5000 + ``` ## 구현 기능 +### 플레이어(들) + +- 플레이어 이름 목록을 관리한다. +- 각 플레이어는 고유한 이름을 가진다. +- 이름 길이 제약 및 중복 방지를 검증한다. +- 사다리 결과 조회 시 이름으로 식별된다. + +### 실행 결과(들) + +- 실행 결과 값을 리스트로 관리한다. +- 플레이어 수와 결과 수가 일치하는지 검증한다. +- 결과는 순서가 보장되며, 인덱스를 기준으로 조회된다. + +### 사다리 결과 보드 + +- 사다리 구조와 실행 결과, 플레이어를 매핑하여 결과 보드를 생성한다. +- 플레이어 이름 → 결과값 을 빠르게 조회할 수 있도록 캐싱한다. +- 전체 결과 또는 특정 플레이어 결과를 제공한다. + ### 사다리 - 여러 개의 라인을 가지며 전체 사다리 구조를 나타낸다. @@ -44,23 +85,44 @@ ## 출력 예시 ```text -사다리의 넓이는 몇 인가요? -5 +참여할 플레이어의 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요) +neo,brown,brie,tommy -사다리의 높이는 몇 인가요? +실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요) +꽝,5000,꽝,3000 + +사다리의 최대 높이는 몇 인가요? 5 -실행결과 +사다리 결과 + + neo brown brie tommy + |-----| | | + |-----| |-----| + | | |-----| + | |-----| | + |-----| | | + 꽝 5000 꽝 3000 + +결과를 보고 싶은 사람은? +neo + +실행 결과 +5000 + +결과를 보고 싶은 사람은? +brie + +실행 결과 +꽝 + +결과를 보고 싶은 사람은? +all - |-----| |-----| | - | | | |-----| - |-----| | | | - | |-----| |-----| - |-----| |-----| | +실행 결과 +neo : 5000 +brown : 꽝 +brie : 꽝 +tommy : 3000 -0 -> 1 -1 -> 3 -2 -> 2 -3 -> 0 -4 -> 4 ``` diff --git a/src/main/java/constants/ReservedWord.java b/src/main/java/constants/ReservedWord.java new file mode 100644 index 00000000..e430aa1b --- /dev/null +++ b/src/main/java/constants/ReservedWord.java @@ -0,0 +1,9 @@ +package constants; + +public final class ReservedWord { + + public static final String FINISH_KEYWORD = "all"; + + private ReservedWord() { + } +} diff --git a/src/main/java/controller/LadderController.java b/src/main/java/controller/LadderController.java index 2b480778..cca4be3f 100644 --- a/src/main/java/controller/LadderController.java +++ b/src/main/java/controller/LadderController.java @@ -1,12 +1,17 @@ package controller; -import domain.Height; -import domain.Width; -import domain.dto.RequestLadder; +import static constants.ReservedWord.FINISH_KEYWORD; + +import domain.dto.RequestLadderGame; import domain.dto.ResponseLadder; -import domain.dto.ResponseLadderResult; +import domain.ladder.Height; import domain.ladder.Ladder; import domain.ladder.LadderFactory; +import domain.ladder.result.LadderResultBoard; +import domain.player.Players; +import domain.runningResult.Results; +import java.util.function.Function; +import java.util.function.Supplier; import strategy.LineGenerator; import strategy.PointGenerator; import strategy.RandomLineGenerator; @@ -16,22 +21,32 @@ public class LadderController { + private static final String NOT_FOUND_PLAYER_RETRY_MESSAGE = "존재하지 않는 플레이어입니다. 다시 입력해주세요."; + public void play() { - RequestLadder requestLadder = inputLadderSettings(); - Width width = requestLadder.toWidth(); - Height height = requestLadder.toHeight(); + RequestLadderGame request = inputLadderSettings(); + Players players = request.toPlayers(); + Height height = request.toHeight(); + Results results = request.toResults(players.size()); - LineGenerator lineGenerator = createLineGenerator(); - LadderFactory factory = new LadderFactory(); - Ladder ladder = factory.draw(width, height, lineGenerator); + Ladder ladder = drawLadder(players, height); + LadderResultBoard resultBoard = LadderResultBoard.of(players, ladder, results); - drawLadder(ladder, width); + showGameScreen(players, ladder, results); + showPlayerResult(resultBoard); } - private RequestLadder inputLadderSettings() { - String width = InputView.inputLadderWidth(); + private RequestLadderGame inputLadderSettings() { + String names = InputView.inputPlayerNames(); + String results = InputView.inputRunningResult(); String height = InputView.inputLadderHeight(); - return new RequestLadder(width, height); + return new RequestLadderGame(names, results, height); + } + + private Ladder drawLadder(final Players players, final Height height) { + LineGenerator generator = createLineGenerator(); + LadderFactory factory = new LadderFactory(); + return factory.draw(players, height, generator); } private LineGenerator createLineGenerator() { @@ -39,13 +54,39 @@ private LineGenerator createLineGenerator() { return new RandomLineGenerator(pointGenerator); } - private void drawLadder(final Ladder ladder, final Width width) { + private void showGameScreen(final Players players, final Ladder ladder, final Results results) { OutputView.printLadderResultTitle(); + OutputView.printPlayerNames(players); + OutputView.drawLadder(ResponseLadder.from(ladder)); + OutputView.printResults(results); + } + + private void showPlayerResult(final LadderResultBoard board) { + repeatUntilDone( + InputView::inputTargetPlayerName, + name -> { + if (name.equals(FINISH_KEYWORD)) { + OutputView.printAllLadderResult(board); + return true; + } - ResponseLadder responseLadder = ResponseLadder.from(ladder); - OutputView.drawLadder(responseLadder); + if (board.findResultOf(name).isEmpty()) { + System.out.println(); + System.out.println(NOT_FOUND_PLAYER_RETRY_MESSAGE); + return false; + } + + OutputView.printSingleLadderResult(board, name); + return false; + } + ); + } - ResponseLadderResult responseResult = ResponseLadderResult.of(ladder, width); - OutputView.printLadderResult(responseResult); + private void repeatUntilDone(final Supplier inputSupplier, final Function handler) { + boolean done = false; + while (!done) { + String input = inputSupplier.get(); + done = handler.apply(input); + } } } diff --git a/src/main/java/domain/Width.java b/src/main/java/domain/Width.java deleted file mode 100644 index 86b2a348..00000000 --- a/src/main/java/domain/Width.java +++ /dev/null @@ -1,24 +0,0 @@ -package domain; - -public record Width( - int value -) { - - static final int MIN_LADDER_WIDTH_SIZE = 2; - static final int MAX_LADDER_WIDTH_SIZE = 24; - - public Width { - validateWidthSize(value); - } - - public static Width from(final int value) { - return new Width(value); - } - - private void validateWidthSize(final int value) { - if (value < MIN_LADDER_WIDTH_SIZE || MAX_LADDER_WIDTH_SIZE < value) { - throw new IllegalArgumentException( - "사다리의 넓이는 %s 이상 %s 이하여야 합니다.".formatted(MIN_LADDER_WIDTH_SIZE, MAX_LADDER_WIDTH_SIZE)); - } - } -} diff --git a/src/main/java/domain/dto/RequestLadder.java b/src/main/java/domain/dto/RequestLadder.java deleted file mode 100644 index e054051d..00000000 --- a/src/main/java/domain/dto/RequestLadder.java +++ /dev/null @@ -1,37 +0,0 @@ -package domain.dto; - -import domain.Height; -import domain.Width; - -public record RequestLadder( - String width, - String height -) { - - public RequestLadder { - validateEmpty(width, height); - } - - private void validateEmpty(final String width, final String height) { - if (width == null || width.isBlank() || height == null || height.isBlank()) { - throw new IllegalArgumentException("사다리의 넓이와 높이를 입력해야 합니다."); - } - } - - public Width toWidth() { - try { - return new Width(Integer.parseInt(width.strip())); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("사다리의 넓이는 숫자여야 합니다."); - } - } - - public Height toHeight() { - try { - return new Height(Integer.parseInt(height.strip())); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("사다리의 높이는 숫자여야 합니다."); - } - } - -} diff --git a/src/main/java/domain/dto/RequestLadderGame.java b/src/main/java/domain/dto/RequestLadderGame.java new file mode 100644 index 00000000..d264dbb8 --- /dev/null +++ b/src/main/java/domain/dto/RequestLadderGame.java @@ -0,0 +1,70 @@ +package domain.dto; + +import domain.ladder.Height; +import domain.player.Players; +import domain.runningResult.Results; +import java.util.List; +import java.util.stream.Stream; + +public record RequestLadderGame( + String playerNames, + String runningResults, + String height +) { + + private static final String INPUT_DELIMITER = ","; + + public RequestLadderGame { + validateEmptyPlayerNames(playerNames); + validateEmptyResults(runningResults); + validateEmptyHeight(height); + } + + private static void validatePlayerCountEqualsResultsCount(final int playerCount, final List results) { + if (playerCount != results.size()) { + throw new IllegalArgumentException("실행 결과 수는 플레이어 수와 동일해야 합니다."); + } + } + + private void validateEmptyPlayerNames(final String playerNames) { + if (playerNames == null || playerNames.isBlank()) { + throw new IllegalArgumentException("플레이어들의 이름은 공백이 아니어야 합니다."); + } + } + + private void validateEmptyResults(final String results) { + if (results == null || results.isBlank()) { + throw new IllegalArgumentException("실행 결과는 공백이 아니어야 합니다."); + } + } + + private void validateEmptyHeight(final String height) { + if (height == null || height.isBlank()) { + throw new IllegalArgumentException("사다리의 높이는 공백이 아니어야 합니다."); + } + } + + public Players toPlayers() { + List names = Stream.of(playerNames.split(INPUT_DELIMITER)) + .map(String::strip) + .toList(); + return Players.from(names); + } + + public Height toHeight() { + try { + return new Height(Integer.parseInt(height.strip())); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("사다리의 높이는 숫자여야 합니다."); + } + } + + public Results toResults(final int playerCount) { + List results = Stream.of(runningResults.split(INPUT_DELIMITER)) + .map(String::strip) + .toList(); + + validatePlayerCountEqualsResultsCount(playerCount, results); + return Results.from(results); + } +} diff --git a/src/main/java/domain/dto/ResponseLadderResult.java b/src/main/java/domain/dto/ResponseLadderResult.java deleted file mode 100644 index 02a76253..00000000 --- a/src/main/java/domain/dto/ResponseLadderResult.java +++ /dev/null @@ -1,20 +0,0 @@ -package domain.dto; - -import domain.Width; -import domain.ladder.Ladder; -import java.util.List; -import java.util.stream.IntStream; - -public record ResponseLadderResult( - List results -) { - - private static final String LADDER_RESULT_ARROW = " -> "; - - public static ResponseLadderResult of(final Ladder ladder, final Width width) { - List result = IntStream.range(0, width.value()) - .mapToObj(start -> start + LADDER_RESULT_ARROW + ladder.move(start)) - .toList(); - return new ResponseLadderResult(result); - } -} diff --git a/src/main/java/domain/dto/ResponseLine.java b/src/main/java/domain/dto/ResponseLine.java index da04e413..0e287ffc 100644 --- a/src/main/java/domain/dto/ResponseLine.java +++ b/src/main/java/domain/dto/ResponseLine.java @@ -1,7 +1,7 @@ package domain.dto; -import domain.Line; -import domain.Point; +import domain.ladder.Line; +import domain.ladder.Point; import java.util.List; public record ResponseLine( @@ -10,8 +10,8 @@ public record ResponseLine( public static ResponseLine from(final Line line) { List connections = line.getPoints().stream() - .limit(line.getPoints().size() - 1) - .map(Point::right) + .limit(line.size() - 1) + .map(Point::isConnectRight) .toList(); return new ResponseLine(connections); } diff --git a/src/main/java/domain/Direction.java b/src/main/java/domain/ladder/Direction.java similarity index 91% rename from src/main/java/domain/Direction.java rename to src/main/java/domain/ladder/Direction.java index 198689b5..3311b311 100644 --- a/src/main/java/domain/Direction.java +++ b/src/main/java/domain/ladder/Direction.java @@ -1,4 +1,4 @@ -package domain; +package domain.ladder; public enum Direction { LEFT(-1), diff --git a/src/main/java/domain/Height.java b/src/main/java/domain/ladder/Height.java similarity index 79% rename from src/main/java/domain/Height.java rename to src/main/java/domain/ladder/Height.java index 28f9d5a0..6c8314ed 100644 --- a/src/main/java/domain/Height.java +++ b/src/main/java/domain/ladder/Height.java @@ -1,11 +1,11 @@ -package domain; +package domain.ladder; public record Height( int value ) { - static final int MIN_LADDER_HEIGHT_SIZE = 2; - static final int MAX_LADDER_HEIGHT_SIZE = 24; + private static final int MIN_LADDER_HEIGHT_SIZE = 2; + private static final int MAX_LADDER_HEIGHT_SIZE = 24; public Height { validateHeightSize(value); diff --git a/src/main/java/domain/ladder/Ladder.java b/src/main/java/domain/ladder/Ladder.java index 61176358..69828929 100644 --- a/src/main/java/domain/ladder/Ladder.java +++ b/src/main/java/domain/ladder/Ladder.java @@ -1,8 +1,6 @@ package domain.ladder; -import domain.Direction; -import domain.Line; -import domain.Width; +import domain.player.Players; import java.util.Collections; import java.util.List; import java.util.stream.IntStream; @@ -19,8 +17,8 @@ public static Ladder from(final List lines) { return new Ladder(lines); } - public boolean isFullyConnected(final Width width) { - return IntStream.range(0, width.value() - 1) + public boolean isFullyConnected(final Players players) { + return IntStream.range(0, players.values().size() - 1) .allMatch(this::isConnectedBetween); } diff --git a/src/main/java/domain/ladder/LadderFactory.java b/src/main/java/domain/ladder/LadderFactory.java index c8eb0875..c64c696f 100644 --- a/src/main/java/domain/ladder/LadderFactory.java +++ b/src/main/java/domain/ladder/LadderFactory.java @@ -1,8 +1,6 @@ package domain.ladder; -import domain.Height; -import domain.Line; -import domain.Width; +import domain.player.Players; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -13,22 +11,23 @@ public class LadderFactory { private static final int MAX_ATTEMPTS = 50; - public Ladder draw(final Width width, final Height height, final LineGenerator generator) { - return findDrawableLadder(width, height, generator) + public Ladder draw(final Players players, final Height height, final LineGenerator generator) { + return findDrawableLadder(players, height, generator) .orElseThrow(() -> new IllegalArgumentException("유효한 사다리를 생성할 수 없습니다.")); } - private Optional findDrawableLadder(final Width width, final Height height, final LineGenerator generator) { + private Optional findDrawableLadder(final Players players, final Height height, + final LineGenerator generator) { return IntStream.range(0, MAX_ATTEMPTS) - .mapToObj(attempt -> drawLadder(width, height, generator)) - .filter(ladder -> ladder.isFullyConnected(width)) + .mapToObj(attempt -> drawLadder(players, height, generator)) + .filter(ladder -> ladder.isFullyConnected(players)) .findFirst(); } - private Ladder drawLadder(final Width width, final Height height, final LineGenerator lineGenerator) { + private Ladder drawLadder(final Players players, final Height height, final LineGenerator lineGenerator) { List lines = new ArrayList<>(); for (int i = 0; i < height.value(); i++) { - lines.add(lineGenerator.generate(width.value())); + lines.add(lineGenerator.generate(players.size())); } return Ladder.from(lines); } diff --git a/src/main/java/domain/Line.java b/src/main/java/domain/ladder/Line.java similarity index 69% rename from src/main/java/domain/Line.java rename to src/main/java/domain/ladder/Line.java index 52427b52..a75cd1d4 100644 --- a/src/main/java/domain/Line.java +++ b/src/main/java/domain/ladder/Line.java @@ -1,10 +1,10 @@ -package domain; +package domain.ladder; import java.util.List; public class Line { - final List points; + private final List points; private Line(final List points) { this.points = List.copyOf(points); @@ -15,7 +15,7 @@ public static Line from(final List points) { } public boolean hasConnectionAt(final int index) { - return points.get(index).right(); + return points.get(index).isConnectRight(); } public Direction directionAt(final int index) { @@ -29,11 +29,15 @@ public Direction directionAt(final int index) { } private boolean canMoveRight(final int index) { - return index < points.size() - 1 && points.get(index).right(); + return index < points.size() - 1 && points.get(index).isConnectRight(); } private boolean canMoveLeft(final int index) { - return 0 < index && points.get(index - 1).right(); + return 0 < index && points.get(index - 1).isConnectRight(); + } + + public int size() { + return points.size(); } public List getPoints() { diff --git a/src/main/java/domain/Point.java b/src/main/java/domain/ladder/Point.java similarity index 50% rename from src/main/java/domain/Point.java rename to src/main/java/domain/ladder/Point.java index c2b244f5..c93d2bab 100644 --- a/src/main/java/domain/Point.java +++ b/src/main/java/domain/ladder/Point.java @@ -1,15 +1,15 @@ -package domain; +package domain.ladder; public record Point( - boolean right + boolean isConnectRight ) { - public static Point from(final boolean right) { - return new Point(right); + public static Point from(final boolean isConnectRight) { + return new Point(isConnectRight); } public Point connectNext(final boolean canConnectRight) { - if (this.right) { + if (this.isConnectRight) { return new Point(false); } return new Point(canConnectRight); diff --git a/src/main/java/domain/ladder/result/LadderResult.java b/src/main/java/domain/ladder/result/LadderResult.java new file mode 100644 index 00000000..eb52eaa7 --- /dev/null +++ b/src/main/java/domain/ladder/result/LadderResult.java @@ -0,0 +1,7 @@ +package domain.ladder.result; + +public record LadderResult( + String playerName, + String result +) { +} diff --git a/src/main/java/domain/ladder/result/LadderResultBoard.java b/src/main/java/domain/ladder/result/LadderResultBoard.java new file mode 100644 index 00000000..ce3e3ca2 --- /dev/null +++ b/src/main/java/domain/ladder/result/LadderResultBoard.java @@ -0,0 +1,42 @@ +package domain.ladder.result; + +import static java.util.stream.Collectors.toMap; + +import domain.ladder.Ladder; +import domain.player.Players; +import domain.runningResult.Results; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.IntStream; + +public class LadderResultBoard { + + private final Map playerResultMap; + + private LadderResultBoard(final Map playerResultMap) { + this.playerResultMap = Map.copyOf(playerResultMap); + } + + public static LadderResultBoard of(final Players players, final Ladder ladder, final Results results) { + Map playerResultMap = IntStream.range(0, players.size()) + .mapToObj(startIndex -> { + String playerName = players.name(startIndex); + int destinationIndex = ladder.move(startIndex); + String resultValue = results.value(destinationIndex); + return new LadderResult(playerName, resultValue); + }) + .collect(toMap(LadderResult::playerName, Function.identity())); + return new LadderResultBoard(playerResultMap); + } + + public Optional findResultOf(final String playerName) { + return Optional.ofNullable(playerResultMap.get(playerName)) + .map(LadderResult::result); + } + + public List getAllResults() { + return List.copyOf(playerResultMap.values()); + } +} diff --git a/src/main/java/domain/player/Name.java b/src/main/java/domain/player/Name.java new file mode 100644 index 00000000..a74c5138 --- /dev/null +++ b/src/main/java/domain/player/Name.java @@ -0,0 +1,34 @@ +package domain.player; + +import static constants.ReservedWord.FINISH_KEYWORD; + +public record Name( + String value +) { + + private static final int MAX_NAME_LENGTH = 5; + + public Name { + validateEmptyName(value); + validateNameLength(value); + validateInvalidName(value); + } + + private void validateEmptyName(final String value) { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("플레이어의 이름은 공백이 아니어야 합니다."); + } + } + + private void validateNameLength(final String value) { + if (MAX_NAME_LENGTH < value.length()) { + throw new IllegalArgumentException("플레이어의 이름은 %d자 이하여야 합니다.".formatted(MAX_NAME_LENGTH)); + } + } + + private void validateInvalidName(final String value) { + if (value.equals(FINISH_KEYWORD)) { + throw new IllegalArgumentException("'%s'은 예약어이므로 플레이어의 이름이 아니어야 합니다.".formatted(FINISH_KEYWORD)); + } + } +} diff --git a/src/main/java/domain/player/Player.java b/src/main/java/domain/player/Player.java new file mode 100644 index 00000000..a07b5db2 --- /dev/null +++ b/src/main/java/domain/player/Player.java @@ -0,0 +1,6 @@ +package domain.player; + +public record Player( + Name name +) { +} diff --git a/src/main/java/domain/player/Players.java b/src/main/java/domain/player/Players.java new file mode 100644 index 00000000..4f231a72 --- /dev/null +++ b/src/main/java/domain/player/Players.java @@ -0,0 +1,67 @@ +package domain.player; + +import static java.util.stream.Collectors.toSet; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class Players { + + private static final int MIN_PLAYER_SIZE = 2; + private static final int MAX_PLAYER_SIZE = 24; + + private final List players; + + private Players(final List players) { + validatePlayerSize(players); + validateDuplicateName(players); + this.players = players; + } + + public static Players from(final List names) { + return new Players( + names.stream() + .map(Name::new) + .map(Player::new) + .toList() + ); + } + + private void validatePlayerSize(final List players) { + int size = players.size(); + if (size < MIN_PLAYER_SIZE || MAX_PLAYER_SIZE < size) { + throw new IllegalArgumentException( + "플레이어 수는 %s 이상 %s 이하여야 합니다.".formatted(MIN_PLAYER_SIZE, MAX_PLAYER_SIZE)); + } + } + + private void validateDuplicateName(final List players) { + Set distinctNames = uniqueNamesFrom(players); + validateNameCountEqualsDistinctCount(players.size(), distinctNames.size()); + } + + private Set uniqueNamesFrom(final List players) { + return players.stream() + .map(player -> player.name().value()) + .collect(toSet()); + } + + private void validateNameCountEqualsDistinctCount(final int total, final int distinct) { + if (total != distinct) { + throw new IllegalArgumentException("플레이어의 이름은 중복이 아니어야 합니다."); + } + } + + public int size() { + return players.size(); + } + + public String name(final int index) { + return players.get(index).name().value(); + } + + public List values() { + return Collections.unmodifiableList(players); + } +} diff --git a/src/main/java/domain/runningResult/Result.java b/src/main/java/domain/runningResult/Result.java new file mode 100644 index 00000000..f21a4b82 --- /dev/null +++ b/src/main/java/domain/runningResult/Result.java @@ -0,0 +1,16 @@ +package domain.runningResult; + +public record Result( + String value +) { + + public Result { + validateEmptyResult(value); + } + + private void validateEmptyResult(final String value) { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("실행 결과는 공백이 아니어야 합니다."); + } + } +} diff --git a/src/main/java/domain/runningResult/Results.java b/src/main/java/domain/runningResult/Results.java new file mode 100644 index 00000000..f93e2328 --- /dev/null +++ b/src/main/java/domain/runningResult/Results.java @@ -0,0 +1,29 @@ +package domain.runningResult; + +import java.util.Collections; +import java.util.List; + +public class Results { + + private final List values; + + private Results(final List values) { + this.values = values; + } + + public static Results from(final List results) { + return new Results( + results.stream() + .map(Result::new) + .toList() + ); + } + + public String value(final int index) { + return values.get(index).value(); + } + + public List asList() { + return Collections.unmodifiableList(values); + } +} diff --git a/src/main/java/strategy/LineGenerator.java b/src/main/java/strategy/LineGenerator.java index 5c8d314e..42b15702 100644 --- a/src/main/java/strategy/LineGenerator.java +++ b/src/main/java/strategy/LineGenerator.java @@ -1,6 +1,6 @@ package strategy; -import domain.Line; +import domain.ladder.Line; public interface LineGenerator { Line generate(int width); diff --git a/src/main/java/strategy/RandomLineGenerator.java b/src/main/java/strategy/RandomLineGenerator.java index e4833820..f5b31f75 100644 --- a/src/main/java/strategy/RandomLineGenerator.java +++ b/src/main/java/strategy/RandomLineGenerator.java @@ -1,7 +1,7 @@ package strategy; -import domain.Line; -import domain.Point; +import domain.ladder.Line; +import domain.ladder.Point; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 79e8bb23..a7b45144 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -9,14 +9,26 @@ public final class InputView { private InputView() { } - public static String inputLadderWidth() { - System.out.println("사다리의 넓이는 몇 인가요?"); + public static String inputPlayerNames() { + System.out.println("참여할 플레이어의 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + return sc.nextLine(); + } + + public static String inputRunningResult() { + System.out.println(); + System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); return sc.nextLine(); } public static String inputLadderHeight() { System.out.println(); - System.out.println("사다리의 높이는 몇 인가요?"); + System.out.println("사다리의 최대 높이는 몇 인가요?"); + return sc.nextLine(); + } + + public static String inputTargetPlayerName() { + System.out.println(); + System.out.println("결과를 보고 싶은 사람은?"); return sc.nextLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index d8c02d4e..4292450e 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,12 +1,16 @@ package view; import domain.dto.ResponseLadder; -import domain.dto.ResponseLadderResult; import domain.dto.ResponseLine; +import domain.ladder.result.LadderResultBoard; +import domain.player.Players; +import domain.runningResult.Results; public final class OutputView { - private static final String LADDER_RESULT_TITLE = "실행결과"; + private static final String LADDER_RESULT_TITLE = "사다리 결과"; + private static final String RUNNING_RESULT_TITLE = "실행 결과"; + private static final int CELL_WIDTH = 6; private static final String BLANK = " "; private static final String VERTICAL = "|"; @@ -15,6 +19,14 @@ public final class OutputView { private OutputView() { } + public static void printPlayerNames(final Players players) { + System.out.print(BLANK); + players.values().forEach(player -> + System.out.print(padding(player.name().value())) + ); + System.out.println(); + } + public static void printLadderResultTitle() { System.out.println(); System.out.println(LADDER_RESULT_TITLE); @@ -47,10 +59,30 @@ private static String drawHorizontalIfConnected(final boolean connected) { return BLANK; } - public static void printLadderResult(final ResponseLadderResult result) { + public static void printResults(final Results results) { + System.out.print(BLANK); + results.asList().forEach(result -> + System.out.print(padding(result.value())) + ); System.out.println(); - for (String line : result.results()) { - System.out.println(line); - } + } + + public static void printSingleLadderResult(final LadderResultBoard resultBoard, final String playerName) { + System.out.println(); + System.out.println(RUNNING_RESULT_TITLE); + String result = resultBoard.findResultOf(playerName) + .orElse("존재하지 않는 플레이어입니다."); + System.out.println(result); + } + + public static void printAllLadderResult(final LadderResultBoard resultBoard) { + System.out.println(); + System.out.println(RUNNING_RESULT_TITLE); + resultBoard.getAllResults().forEach(ladderResult -> + System.out.println(ladderResult.playerName() + " : " + ladderResult.result())); + } + + private static String padding(final String text) { + return String.format("%-" + CELL_WIDTH + "s", text); } } diff --git a/src/test/java/domain/WidthTest.java b/src/test/java/domain/WidthTest.java deleted file mode 100644 index 43449786..00000000 --- a/src/test/java/domain/WidthTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package domain; - -import static domain.Width.MAX_LADDER_WIDTH_SIZE; -import static domain.Width.MIN_LADDER_WIDTH_SIZE; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class WidthTest { - - @ParameterizedTest - @ValueSource(ints = {1, 25}) - @DisplayName("사다리 넓이가 최소 기준을 준수하지 않았을 시 예외가 발생한다.") - void shouldThrowException_whenInvalidWidth(int width) { - // given & when & then - assertThatThrownBy(() -> Width.from(width)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 넓이는 %s 이상 %s 이하여야 합니다.".formatted(MIN_LADDER_WIDTH_SIZE, MAX_LADDER_WIDTH_SIZE)); - } -} diff --git a/src/test/java/domain/dto/RequestLadderGameTest.java b/src/test/java/domain/dto/RequestLadderGameTest.java new file mode 100644 index 00000000..25471454 --- /dev/null +++ b/src/test/java/domain/dto/RequestLadderGameTest.java @@ -0,0 +1,79 @@ +package domain.dto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import domain.ladder.Height; +import domain.player.Players; +import domain.runningResult.Results; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class RequestLadderGameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("플레이어들의 이름을 입력하지 않았을 경우 예외가 발생한다.") + void shouldThrowException_whenEmptyPlayerNames(String playerNames) { + // given & when & then + assertThatThrownBy(() -> new RequestLadderGame(playerNames, "꽝,5000,꽝,2000", "5")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어들의 이름은 공백이 아니어야 합니다."); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("실행 결과를 입력하지 않았을 경우 예외가 발생한다.") + void shouldThrowException_whenEmptyRunningResults(String runningResults) { + // given & when & then + assertThatThrownBy(() -> new RequestLadderGame("neo,brown,brie,tommy", runningResults, "5")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("실행 결과는 공백이 아니어야 합니다."); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("사다리의 높이를 입력하지 않았을 경우 예외가 발생한다.") + void shouldThrowException_whenEmptyHeight(String height) { + // given & when & then + assertThatThrownBy(() -> new RequestLadderGame("neo,brown,brie,tommy", "꽝,5000,꽝,2000", height)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("사다리의 높이는 공백이 아니어야 합니다."); + } + + @Test + @DisplayName("실행 결과 수와 플레이어 수가 동일하지 않을 경우 예외가 발생한다.") + void shouldThrowException_whenNotEqualsSize() { + // given + String playerNames = "neo,brown,brie,tommy"; + String runningResults = "꽝,5000,꽝"; + RequestLadderGame requestLadderGame = new RequestLadderGame(playerNames, runningResults, "7"); + + // when + Players players = requestLadderGame.toPlayers(); + + // then + assertThatThrownBy(() -> requestLadderGame.toResults(players.size())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("실행 결과 수는 플레이어 수와 동일해야 합니다."); + } + + @Test + @DisplayName("유효한 사다리 게임의 입력 값인 경우 객체가 생성된다.") + void shouldReturn_whenValidLadderGameValue() { + // given + RequestLadderGame requestLadderGame = new RequestLadderGame("neo,brown,brie,tommy", "꽝,5000,꽝,2000", "7"); + + // when + Players players = requestLadderGame.toPlayers(); + Results results = requestLadderGame.toResults(players.size()); + Height height = requestLadderGame.toHeight(); + + // then + assertThat(players.size()).isEqualTo(4); + assertThat(results.asList().size()).isEqualTo(4); + assertThat(height.value()).isEqualTo(7); + } +} diff --git a/src/test/java/domain/dto/RequestLadderTest.java b/src/test/java/domain/dto/RequestLadderTest.java deleted file mode 100644 index 0dfb3b7c..00000000 --- a/src/test/java/domain/dto/RequestLadderTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package domain.dto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import domain.Height; -import domain.Width; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; - -class RequestLadderTest { - - @ParameterizedTest - @NullAndEmptySource - @DisplayName("사다리의 넓이를 입력하지 않았을 경우 예외가 발생한다.") - void shouldThrowException_whenEmptyWidth(String width) { - // given & when & then - assertThatThrownBy(() -> new RequestLadder(width, "3")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 넓이와 높이를 입력해야 합니다."); - } - - @ParameterizedTest - @NullAndEmptySource - @DisplayName("사다리의 높이를 입력하지 않았을 경우 예외가 발생한다.") - void shouldThrowException_whenEmptyHeight(String height) { - // given & when & then - assertThatThrownBy(() -> new RequestLadder("3", height)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 넓이와 높이를 입력해야 합니다."); - } - - @ParameterizedTest - @ValueSource(strings = {"aa", "3ab"}) - @DisplayName("사다리의 넓이가 숫자가 아닌 경우 예외가 발생한다.") - void shouldThrowException_whenNoNumericalWidth(String width) { - // given - RequestLadder requestLadder = new RequestLadder(width, "3"); - - // when & then - assertThatThrownBy(requestLadder::toWidth) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 넓이는 숫자여야 합니다."); - } - - @ParameterizedTest - @ValueSource(strings = {"bb", "3cd"}) - @DisplayName("사다리의 높이가 숫자가 아닌 경우 예외가 발생한다.") - void shouldThrowException_whenNoNumericalHeight(String height) { - // given - RequestLadder requestLadder = new RequestLadder("3", height); - - // when & then - assertThatThrownBy(requestLadder::toHeight) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 높이는 숫자여야 합니다."); - } - - @Test - @DisplayName("유효한 넓이와 높이를 입력하면 올바르게 객체가 생성된다.") - void shouldReturnCorrectWidthAndHeight() { - // given - RequestLadder requestLadder = new RequestLadder("5", "7"); - - // when - Width width = requestLadder.toWidth(); - Height height = requestLadder.toHeight(); - - // then - assertThat(width.value()).isEqualTo(5); - assertThat(height.value()).isEqualTo(7); - } -} diff --git a/src/test/java/domain/HeightTest.java b/src/test/java/domain/ladder/HeightTest.java similarity index 69% rename from src/test/java/domain/HeightTest.java rename to src/test/java/domain/ladder/HeightTest.java index 902ceae4..5a750b3f 100644 --- a/src/test/java/domain/HeightTest.java +++ b/src/test/java/domain/ladder/HeightTest.java @@ -1,7 +1,5 @@ -package domain; +package domain.ladder; -import static domain.Height.MAX_LADDER_HEIGHT_SIZE; -import static domain.Height.MIN_LADDER_HEIGHT_SIZE; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.DisplayName; @@ -17,6 +15,6 @@ void shouldThrowException_whenInvalidHeight(int height) { // given & when & then assertThatThrownBy(() -> Height.from(height)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("사다리의 높이는 %s 이상 %s 이하여야 합니다.".formatted(MIN_LADDER_HEIGHT_SIZE, MAX_LADDER_HEIGHT_SIZE)); + .hasMessage("사다리의 높이는 2 이상 24 이하여야 합니다."); } } diff --git a/src/test/java/domain/ladder/LadderFactoryTest.java b/src/test/java/domain/ladder/LadderFactoryTest.java index cc342279..01a64587 100644 --- a/src/test/java/domain/ladder/LadderFactoryTest.java +++ b/src/test/java/domain/ladder/LadderFactoryTest.java @@ -3,8 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import domain.Height; -import domain.Width; +import domain.player.Players; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -25,13 +24,14 @@ void shouldReturnLadder_whenValid() { new boolean[]{true, false, true, false} )); LadderFactory factory = new LadderFactory(); + List names = List.of("dd", "dd2", "dd3"); // when - Ladder ladder = factory.draw(Width.from(4), Height.from(4), fixedGenerator); + Ladder ladder = factory.draw(Players.from(names), Height.from(4), fixedGenerator); // then assertThat(ladder).isNotNull(); - assertThat(ladder.isFullyConnected(Width.from(4))).isTrue(); + assertThat(ladder.isFullyConnected(Players.from(names))).isTrue(); } @Test @@ -44,9 +44,10 @@ void shouldThrowException_whenInvalidLadder() { new boolean[]{false, false, false, false}) ); LadderFactory factory = new LadderFactory(); + List names = List.of("dd", "dd2", "dd3"); // when & then - assertThatThrownBy(() -> factory.draw(Width.from(4), Height.from(4), fixedGenerator)) + assertThatThrownBy(() -> factory.draw(Players.from(names), Height.from(4), fixedGenerator)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("유효한 사다리를 생성할 수 없습니다."); } diff --git a/src/test/java/domain/ladder/LadderTest.java b/src/test/java/domain/ladder/LadderTest.java index 9af5de48..ad94a573 100644 --- a/src/test/java/domain/ladder/LadderTest.java +++ b/src/test/java/domain/ladder/LadderTest.java @@ -3,9 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import domain.Line; -import domain.Point; -import domain.Width; +import domain.player.Players; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -48,9 +46,10 @@ void shouldCheckIsFullyConnected() { ); Ladder ladder = Ladder.from(lines); + List names = List.of("dd", "dd2", "dd3"); // when & then - assertThat(ladder.isFullyConnected(Width.from(3))).isTrue(); + assertThat(ladder.isFullyConnected(Players.from(names))).isTrue(); } @Test @@ -63,9 +62,10 @@ void shouldCheckIsNotFullyConnected() { ); Ladder ladder = Ladder.from(lines); + List names = List.of("dd", "dd2", "dd3"); // when & then - assertThat(ladder.isFullyConnected(Width.from(3))).isFalse(); + assertThat(ladder.isFullyConnected(Players.from(names))).isFalse(); } @Test diff --git a/src/test/java/domain/LineTest.java b/src/test/java/domain/ladder/LineTest.java similarity index 97% rename from src/test/java/domain/LineTest.java rename to src/test/java/domain/ladder/LineTest.java index aaa00aa9..23cdab8e 100644 --- a/src/test/java/domain/LineTest.java +++ b/src/test/java/domain/ladder/LineTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.ladder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -24,7 +24,7 @@ void shouldGeneratePoint_whenGivenWidth() { // then assertThat(points) - .extracting(Point::right) + .extracting(Point::isConnectRight) .containsExactly(true, false, true, false); } diff --git a/src/test/java/domain/PointTest.java b/src/test/java/domain/ladder/PointTest.java similarity index 80% rename from src/test/java/domain/PointTest.java rename to src/test/java/domain/ladder/PointTest.java index 5f22da7c..453f5266 100644 --- a/src/test/java/domain/PointTest.java +++ b/src/test/java/domain/ladder/PointTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.ladder; import static org.assertj.core.api.Assertions.assertThat; @@ -19,8 +19,8 @@ void shouldCreatePoint_whenFromRightStatus() { Point falsePoint = Point.from(isRightFalse); // then - assertThat(truePoint.right()).isTrue(); - assertThat(falsePoint.right()).isFalse(); + assertThat(truePoint.isConnectRight()).isTrue(); + assertThat(falsePoint.isConnectRight()).isFalse(); } @Test @@ -33,7 +33,7 @@ void shouldReturnNextPointFalse_whenPointRightTrue() { Point next = point.connectNext(true); // then - assertThat(next.right()).isFalse(); + assertThat(next.isConnectRight()).isFalse(); } @Test @@ -47,7 +47,7 @@ void shouldReturnNextPointTrueOrFalse_whenPointRightFalse() { Point nextFalse = point.connectNext(false); // then - assertThat(nextTrue.right()).isTrue(); - assertThat(nextFalse.right()).isFalse(); + assertThat(nextTrue.isConnectRight()).isTrue(); + assertThat(nextFalse.isConnectRight()).isFalse(); } } diff --git a/src/test/java/domain/player/NameTest.java b/src/test/java/domain/player/NameTest.java new file mode 100644 index 00000000..542e18bd --- /dev/null +++ b/src/test/java/domain/player/NameTest.java @@ -0,0 +1,40 @@ +package domain.player; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class NameTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("이름이 빈 값이거나 공백일 경우 예외가 발생한다.") + void shouldThrowException_whenEmptyOrBlank(String value) { + // given & when & then + assertThatThrownBy(() -> new Name(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어의 이름은 공백이 아니어야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"jiyuni", "spiderman", "player2"}) + @DisplayName("이름의 최대 길이를 초과하였을 경우 예외가 발생한다.") + void shouldThrowException_whenOverMaxLength(String value) { + // given & when & then + assertThatThrownBy(() -> new Name(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어의 이름은 5자 이하여야 합니다."); + } + + @Test + @DisplayName("이름으로 예약어를 사용했을 경우 예외가 발생한다.") + void shouldThrowException_whenUsedReservedWord() { + // given & when & then + assertThatThrownBy(() -> new Name("all")) + .isInstanceOf(IllegalArgumentException.class).hasMessage("'all'은 예약어이므로 플레이어의 이름이 아니어야 합니다."); + } +} diff --git a/src/test/java/domain/player/PlayersTest.java b/src/test/java/domain/player/PlayersTest.java new file mode 100644 index 00000000..b526b873 --- /dev/null +++ b/src/test/java/domain/player/PlayersTest.java @@ -0,0 +1,40 @@ +package domain.player; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PlayersTest { + + @ParameterizedTest + @ValueSource(ints = {1, 25}) + @DisplayName("플레이어의 수가 기준을 준수하지 않았을 시 예외가 발생한다.") + void shouldThrowException_whenInvalidPlayerSize(int size) { + // given + List names = IntStream.range(0, size) + .mapToObj(i -> "dd" + i) + .toList(); + + // when & then + assertThatThrownBy(() -> Players.from(names)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어 수는 2 이상 24 이하여야 합니다."); + } + + @Test + @DisplayName("플레이어의 이름이 중복되었을 경우 예외가 발생한다.") + void shouldThrowException_whenDuplicatePlayerNames() { + // given + List duplicateNames = List.of("dd", "pobi", "dd"); + + // when & then + assertThatThrownBy(() -> Players.from(duplicateNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어의 이름은 중복이 아니어야 합니다."); + } +} diff --git a/src/test/java/domain/runningResult/ResultTest.java b/src/test/java/domain/runningResult/ResultTest.java new file mode 100644 index 00000000..3036e320 --- /dev/null +++ b/src/test/java/domain/runningResult/ResultTest.java @@ -0,0 +1,20 @@ +package domain.runningResult; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class ResultTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("실행 결과가 입력되지 않았을 경우 예외가 발생한다.") + void shouldThrowException_whenEmptyResult(String value) { + // given & when & then + assertThatThrownBy(() -> new Result(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("실행 결과는 공백이 아니어야 합니다."); + } +} diff --git a/src/test/java/domain/runningResult/ResultsTest.java b/src/test/java/domain/runningResult/ResultsTest.java new file mode 100644 index 00000000..f6ef605d --- /dev/null +++ b/src/test/java/domain/runningResult/ResultsTest.java @@ -0,0 +1,29 @@ +package domain.runningResult; + + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ResultsTest { + + @Test + @DisplayName("실행 결과가 입력되면 정상적으로 객체를 생성한다.") + void shouldCreateResults_whenValidInputValues() { + // given + List inputValues = List.of("4000", "꽝", "3000"); + + // when + Results results = Results.from(inputValues); + + // then + List actual = results.asList().stream() + .map(Result::value) + .toList(); + + assertThat(actual).isEqualTo(inputValues); + } + +} diff --git a/src/test/java/strategy/FixedLineGenerator.java b/src/test/java/strategy/FixedLineGenerator.java index 75e702de..cf8ef62e 100644 --- a/src/test/java/strategy/FixedLineGenerator.java +++ b/src/test/java/strategy/FixedLineGenerator.java @@ -1,7 +1,7 @@ package strategy; -import domain.Line; -import domain.Point; +import domain.ladder.Line; +import domain.ladder.Point; import java.util.ArrayList; import java.util.List;