diff --git a/README.md b/README.md index d045937e4b..299e3f602c 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,59 @@ -# java-ladder - -사다리 타기 미션 저장소 - -## 우아한테크코스 코드리뷰 - -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) - -## 기능 요구사항 해석 - -1. 사다리 게임에 참여하는 사람에 이름을 최대5글자까지 부여할 수 있다. 사다리를 출력할 때 사람 이름도 같이 출력한다. - - 사람의 이름은 중복이 되어선 안된다. - - 이름은 최소 1글자 최대 5글자 까지 부여되어야 한다. - - 알파뱃 대 소문자로 이루어 진다. - - 사람 이름은 "all"일 수 없다. - -2. 사람 이름은 쉼표(,)를 기준으로 구분한다. - - 쉼표로 시작하거나 쉼표로 끝내면 예외 발생 - - 쉼표로 나뉘는데, 알파벳 대소문자가 아닌 경우 예외 발생 - - 사람은 최대 10명까지 받을 수 있다. - -3. 사다리 높이를 입력할 수 있다. - - 사다리 높이는 5 이상 10 이하의 정수로 입력해야 한다. - - 사다리의 폭은 사람들의 수이다. - -4. 실행 결과를 입력할 수 있다. 실행 결과란, 사다리 게임에서 사다리 아래의 항목을 말한다. - - 실행 결과는 쉼표(,)를 기준으로 구분한다. - - 각 실행 결과는 최소 1글자 최대 5글자 까지 부여되어야 한다. - - 각 실행 결과는 공백 문자만으로 이루어질 수 없다. - -5. 사람 이름을 5자 기준으로 출력하기 때문에 사다리 폭도 넓어져야 한다. - - 사다리의 가로(`-`)는 `최대 이름 길이`로 고정한다. - - 사람의 이름은 사다리의 세로(`|`) 에 맞춰 정렬한다. - - 사람의 이름이 `최대 이름 길이`보다 작을경우 다음과 같은 규칙으로 공백을 추가한다. - - ``` - `a` -> ` a ` - `aa` -> ` aa ` - `aaa` -> ` aaa ` - `aaaa` -> `aaaa ` - `aaaaa` -> `aaaaa` - ``` - -6. 사다리 타기가 정상적으로 동작하려면 라인이 겹치지 않도록 해야 한다. - - |-----|-----| 모양과 같이 가로 라인이 겹치는 경우 어느 방향으로 이동할지 결정할 수 없다. -7. 사용자 입력 중 예외가 발생할 경우, 예외 발생 원인을 출력한 뒤, 처음부터 다시 입력받는다. -8. 사다리 게임 실행 결과를 알 수 있다. - - all 을 입력한 경우 전체 결과를 알 수 있다. - - 앞서 입력한 사람 이름 중 하나를 입력하면 그 사람의 결과를 알 수 있다. - - 잘못된 입력을 할 경우, 예외 메세지를 출력한 뒤 결과를 보고 싶은 사람 이름부터 다시 입력받는다. - -## 기능 목록 - -- [x] 사람 이름 입력 기능 - - [x] 사람 이름 길이 검증 기능 - - [x] 사람 이름 문자 검증 가능 -- [x] 사람 생성 기능 - - [x] 사람 이름 중복 검증 기능 - - [x] ","로 구분된 사람 이름 입력 기능 - - [x] 사람 이름 개수 검증 기능 +# 요구사항 해석 + +> 다시 구현하는 것인 만큼, 조금 난이도를 높이기 위해 생성되는 사다리에 제약 사항을 추가했습니다. + +- 참가자 관련 요구 사항 + 1. 참가자는 최대 10명까지 입력 가능하다 + 2. 참가자 이름의 구분은 ","를 기준으로 수행된다. +- 참가자 이름 관련 요구 사항 + 1. 참가자 이름은 최소 한글자 최대 5글자까지 부여할 수 있다. + 2. 참가자 이름은 중복될 수 없다. + 3. 참가자 이름에는 알파벳 대소문자, 숫자 만 입력할 수 있다. + 4. 참가자 이름은 all이 될 수 없다. +- 사다리 관련 요구 사항 + 1. 어느 한 위치에서 왼쪽과 오른쪽에 동시에 발판이 있을 수 없다. + - 즉, 다음과 같은 사다리는 생성되면 안된다. + ``` + |-----| | + |-----|-----| + ``` + 2. 사다리의 높이는 최소 2, 최대 20이다. + 3. 두 참여자 사이에는 최소한 한개 이상의 발판이 있어야 한다. + - 즉, 다음과 같은 사다리는 생성되면 안된다. + ``` + |-----| | + |-----| | + |-----| | + ``` +- 상품 관련 요구 사항 + 1. 상품은 사다리 타기의 결과로 참여자와 1대 1 매칭 될 어떤 것을 말한다. + 2. 상품은 최소 한글자 최대 5글자까지 부여할 수 있다. +- 안정적인 프로그램 실행을 위한 요구사항 + 1. 잘못된 입력이 발생한 경우, 적절한 에러 메세지를 출력한 뒤 해당 위치부터 다시 입력을 수행한다. + 2. 재입력은 무한히 시도될 수 있어야 한다. +- 출력 요구 사항 + 1. 사다리의 가로(-)는 최대 이름 길이로 고정한다. + 2. 사람의 이름은 사다리의 세로(|) 에 맞춰 정렬한다. + 3. 사람의 이름이 최대 이름 길이보다 작을경우 다음과 같은 규칙으로 공백을 추가한다. + 1. 이름 맨 뒤에 공백 문자를 추가한다. + 2. 앞에서 추가된 공백 문자를 포함한 이름의 길이가 최대 이름 길이 보다 작을 경우, 공백을 포함한 이름의 길이가 최대 이름 길이가 되도록 공백 문자를 이름 앞에 추가한다. + +# 기능 목록 + +- [x] 참가자 이름 입력 기능 +- [x] 참가자 생성 기능 - [x] 사다리 높이 입력 기능 - - [x] 사다리 높이 검증 기능 +- [x] 상품 이름 입력 기능 +- [x] 상품 생성 기능 - [x] 사다리 생성 기능 - - [x] 사다리 높이 지정 기능 - - [x] 사다리 전체 폭 지정 기능 - - [x] 세로 라인 생성 기능 - - [x] 가로 라인 생성 기능 -- [x] 사람 이름, 사다리 출력 기능 - - [x] 사람 이름 출력 기능 - - [x] 사다리 출력 기능 -- [x] 입력 예외 처리 기능 -- [x] 실행 결과 입력 기능 -- [x] 실행 결과 판독 기능 -- [x] 실행 결과를 보고 싶은 이름 입력 기능 -- [x] 실행 결과 출력 기능 - -## 1단계 피드백 반영 사항 - -- [x] Pattern 객체 재사용하도록 수정 -- [x] 도메인 로직에서 UI 로직 분리 - - [x] ~ String 클래스 이름 변경 - - [x] ~ String 클래스 view 패키지로 이동 - - [x] 이름 관련 정책 구현 위치 view로 이동 +- [x] 사다리 출력 기능 +- [x] 사다리 타기 실행 기능 +- [x] 사다리 타기 결과 출력 기능 -## 2단계 피드백 반영 사항 +# 피드백 반영 예정 목록 -- [x] 모호한 변수 및 메서드 명 변경 -- [x] 적절하지 않은 예외 사용 코드 삭제 +- [x] List와 가변 배열 사이의 불필요한 변환 작업 삭제 +- [x] Ladder 의 정적 팩토리 메서드 추가 +- [x] 표준 함수형 인터페이스 대신 적절한 커스텀 인터페이스 정의 +- [x] 불필요한 매개변수 삭제 +- [x] 생성자와 정적 팩토리 메서드에 매개변수 차이 부여 diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 24af80ad45..ef419ed81f 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,99 +1,86 @@ import domain.LadderGame; +import domain.LadderGame.LadderGameBuilder; +import domain.LineGenerateStrategy; +import domain.RandomLineGenerateStrategy; +import dto.LadderGameResults; import java.util.List; -import java.util.function.Supplier; -import view.ClimbResultPrinter; +import util.RetryHelper; +import view.GiftsInputView; import view.InputView; import view.LadderGameOperatorInputView; -import view.LadderPrinter; -import view.NameInputView; -import view.NamesPrinter; +import view.LadderHeightInputView; import view.OutputView; -import view.ResultInputView; +import view.PlayersInputView; public class Main { public static void main(String[] args) { - LadderGame ladderGame = generateLadderGame(); + RetryHelper retryHelper = new RetryHelper(10); + List playerNames = getPlayerNames(retryHelper); + List giftNames = getGiftNames(retryHelper, playerNames); + Integer ladderHeight = getLadderHeight(retryHelper); - printName(ladderGame); - printLadder(ladderGame); - printResults(ladderGame); + LadderGame ladderGame = makeLadderGame(playerNames, giftNames, ladderHeight); + printLadderGame(playerNames, giftNames, ladderGame); - List rawNames = ladderGame.getRawNames(); - printClimbResult(ladderGame, rawNames); + printLadderGameResults(playerNames, ladderGame); } - private static LadderGame generateLadderGame() { - return RetryHelper.retry(() -> { + private static List getPlayerNames(RetryHelper retryHelper) { + return retryHelper.retry(() -> { OutputView.print("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); - List names = NameInputView.getNames(InputView::getInput); - OutputView.print("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); - List rawResults = ResultInputView.getResults(InputView::getInput, names.size()); - OutputView.print("최대 사다리 높이는 몇 개인가요?"); - int ladderHeight = Integer.parseInt(InputView.getInput()); - return new LadderGame(names, ladderHeight, rawResults); + return PlayersInputView.getPlayerNames(InputView.getInput()); }); } - private static void printName(LadderGame ladderGame) { - OutputView.print(NamesPrinter.from(ladderGame.getRawNames())); + private static List getGiftNames(RetryHelper retryHelper, List playerNames) { + return retryHelper.retry(() -> { + OutputView.print("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); + return GiftsInputView.getGiftNames(InputView.getInput(), playerNames.size()); + }); } - private static void printLadder(LadderGame ladderGame) { - OutputView.print(LadderPrinter.from(ladderGame.getRawLadder())); + private static Integer getLadderHeight(RetryHelper retryHelper) { + return retryHelper.retry(() -> { + OutputView.print("최대 사다리 높이는 몇 개인가요?"); + return LadderHeightInputView.getLadderHeight(InputView.getInput()); + }); } - private static void printResults(LadderGame ladderGame) { - OutputView.print(NamesPrinter.from(ladderGame.getRawResults())); + private static LadderGame makeLadderGame(List playerNames, List giftNames, Integer ladderHeight) { + LineGenerateStrategy lineGenerateStrategy = new RandomLineGenerateStrategy(); + return makeLadderGame(playerNames, giftNames, ladderHeight, lineGenerateStrategy); } - private static void printClimbResult(LadderGame ladderGame, List rawNames) { - String gameOperator = getGameOperator(ladderGame); - - gameOperator = printClimbResultsUntilOperatorIsAll(ladderGame, gameOperator); - - List climbResults = ClimbResultPrinter.of(rawNames, ladderGame.getClimbResults(gameOperator)); - climbResults.forEach(OutputView::print); + private static LadderGame makeLadderGame(List playerNames, List giftNames, Integer ladderHeight, + LineGenerateStrategy randomLineMakeStrategy) { + return LadderGameBuilder.builder() + .players(playerNames) + .gifts(giftNames) + .ladderHeight(ladderHeight) + .lineGenerateStrategy(randomLineMakeStrategy) + .build(); } - private static String getGameOperator(LadderGame ladderGame) { - OutputView.print("결과를 보고 싶은 사람은?"); - String gameOperator = RetryHelper.retry( - () -> LadderGameOperatorInputView.getOperator(InputView::getInput, ladderGame.getRawNames()) - ); - OutputView.print("실행 결과"); - return gameOperator; + private static void printLadderGame(List playerNames, List giftNames, LadderGame ladderGame) { + OutputView.printPlayers(playerNames); + OutputView.printLadder(ladderGame.rawLadder()); + OutputView.printGifts(giftNames); } - private static String printClimbResultsUntilOperatorIsAll(LadderGame ladderGame, String gameOperator) { - while (!gameOperator.equals("all")) { - List climbResults = ClimbResultPrinter.of( - List.of(gameOperator), - ladderGame.getClimbResults(gameOperator) - ); - climbResults.forEach(OutputView::print); - - gameOperator = getGameOperator(ladderGame); + private static void printLadderGameResults(List playerNames, LadderGame ladderGame) { + String ladderGameResultOwner = showLadderGameResult(playerNames, ladderGame); + while (!ladderGameResultOwner.equals("all")) { + ladderGameResultOwner = showLadderGameResult(playerNames, ladderGame); } - return gameOperator; } - static final class RetryHelper { - - public static E retry(Supplier supplier) { - E result = null; - while (result == null) { - result = useSupplier(supplier); - } - return result; - } - - private static E useSupplier(Supplier supplier) { - try { - return supplier.get(); - } catch (Exception e) { - System.out.println(e.getMessage()); - return null; - } - } + private static String showLadderGameResult(List playerNames, LadderGame ladderGame) { + RetryHelper retryHelper = new RetryHelper(10); + return retryHelper.retry(() -> { + String operator = LadderGameOperatorInputView.getOperator(InputView.getInput(), playerNames); + LadderGameResults ladderGameResults = ladderGame.start(operator); + OutputView.printLadderGameResults(ladderGameResults); + return operator; + }); } } diff --git a/src/main/java/domain/AbstractLineGenerateStrategy.java b/src/main/java/domain/AbstractLineGenerateStrategy.java new file mode 100644 index 0000000000..4ef0b49bfc --- /dev/null +++ b/src/main/java/domain/AbstractLineGenerateStrategy.java @@ -0,0 +1,33 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +abstract non-sealed class AbstractLineGenerateStrategy implements LineGenerateStrategy { + @Override + public final List generate(int lineSize) { + List generate = new ArrayList<>(generateStrategy(lineSize)); + fixInvalidBridges(generate); + return Collections.unmodifiableList(generate); + } + + public abstract List generateStrategy(int lineSize); + + private void fixInvalidBridges(List rawBridges) { + rawBridges.set(rawBridges.size() - 1, false); + for (int index = 1; index < rawBridges.size() - 1; index++) { + fixIfNeed(rawBridges, index); + } + } + + private void fixIfNeed(List rawBridges, int index) { + if (isBridgeInARow(rawBridges, index)) { + rawBridges.set(index, false); + } + } + + private boolean isBridgeInARow(List rawBridges, int index) { + return rawBridges.get(index) && rawBridges.get(index - 1); + } +} diff --git a/src/main/java/domain/BridgeGenerator.java b/src/main/java/domain/BridgeGenerator.java deleted file mode 100644 index 3ae2e55d04..0000000000 --- a/src/main/java/domain/BridgeGenerator.java +++ /dev/null @@ -1,7 +0,0 @@ -package domain; - -import java.util.List; - -interface BridgeGenerator { - List generate(int width); -} diff --git a/src/main/java/domain/Direction.java b/src/main/java/domain/Direction.java new file mode 100644 index 0000000000..5b7e5a9166 --- /dev/null +++ b/src/main/java/domain/Direction.java @@ -0,0 +1,24 @@ +package domain; + +import java.util.function.Function; + +enum Direction { + + RIGHT(index -> index + 1), LEFT(index -> index - 1), STRAIGHT(index -> index); + private final Function nextIndexFunction; + + Direction(Function nextIndexFunction) { + this.nextIndexFunction = nextIndexFunction; + } + + Point nextPoint(int nowIndex) { + Integer nextIndex = nextIndexFunction.apply(nowIndex); + if (this == LEFT) { + return new Point(RIGHT, nextIndex); + } + if (this == RIGHT) { + return new Point(LEFT, nextIndex); + } + return new Point(STRAIGHT, nextIndex); + } +} diff --git a/src/main/java/domain/ExceptionType.java b/src/main/java/domain/ExceptionType.java deleted file mode 100644 index 4b54288a0b..0000000000 --- a/src/main/java/domain/ExceptionType.java +++ /dev/null @@ -1,30 +0,0 @@ -package domain; - -public enum ExceptionType { - HEIGHT_RANGE("높이는 5이상 10 이하여야 합니다."), - WIDTH_RANGE("폭은 2이상 10 이하여야 합니다."), - NAME_CHARACTER("이름은 알파벳 대소문자로만 이루어져있어야 합니다."), - NAME_BLACK_LIST("사용할 수 없는 이름입니다."), - NAME_LENGTH_RANGE("이름의 길이는 1자 이상 5자 이하여야 합니다."), - NAMES_COUNT("사람은 최대 10명까지 받을 수 있습니다."), - NAMES_DUPLICATE("이름은 중복될 수 없습니다."), - NAMES_SEPARATOR("구분자가 맨 앞이나 맨 뒤에 있으면 안됩니다."), - ROW_COUNT("가로 라인 개수는 1이상 9 이하여야 합니다."), - ROW_NEAR("연속해서 가로 라인이 등장할 수 없습니다."), - RESULTS_COUNT_RANGE("실행 결과는 최대 10개까지 받을 수 있습니다."), - RESULT_COUNT("실행 결과의 개수와 이름의 개수가 다릅니다."), - RESULTS_SEPARATOR("구분자가 맨 앞이나 맨 뒤에 있으면 안됩니다."), - RESULT_LENGTH("실행 결과의 길이는 1자 이상 5자 이하여야 합니다."), - NAME_NOT_FOUND("없는 이름입니다."), - POSITION_OVERFLOW("위치는 0 이상 이름 개수 미만이어야 합니다."); - - private final String message; - - ExceptionType(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } -} diff --git a/src/main/java/domain/Gift.java b/src/main/java/domain/Gift.java new file mode 100644 index 0000000000..1539e08013 --- /dev/null +++ b/src/main/java/domain/Gift.java @@ -0,0 +1,14 @@ +package domain; + +record Gift(String name) { + + Gift { + validateNameLength(name); + } + + private void validateNameLength(String name) { + if (name == null || name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("상품 이름은 1글자 이상 5글자 이하여야 합니다."); + } + } +} diff --git a/src/main/java/domain/Gifts.java b/src/main/java/domain/Gifts.java new file mode 100644 index 0000000000..6762376c42 --- /dev/null +++ b/src/main/java/domain/Gifts.java @@ -0,0 +1,28 @@ +package domain; + +import java.util.List; + +class Gifts { + private final List gifts; + + private Gifts(List gifts) { + validateGiftsCount(gifts); + this.gifts = gifts; + } + + private void validateGiftsCount(List players) { + if (players == null || players.size() < 2 || players.size() > 10) { + throw new IllegalStateException("상품은 2개 이상 10개 이하여야 합니다."); + } + } + + String getGiftName(int index) { + Gift gift = gifts.get(index); + return gift.name(); + } + + static Gifts of(List giftsName) { + List gifts = giftsName.stream().map(Gift::new).toList(); + return new Gifts(gifts); + } +} diff --git a/src/main/java/domain/Height.java b/src/main/java/domain/Height.java deleted file mode 100644 index aa1555802f..0000000000 --- a/src/main/java/domain/Height.java +++ /dev/null @@ -1,23 +0,0 @@ -package domain; - -class Height { - static final int MIN = 5; - static final int MAX = 10; - - private final int length; - - Height(int length) { - validate(length); - this.length = length; - } - - private void validate(int length) { - if (length < MIN || length > MAX) { - throw new LadderGameException(ExceptionType.HEIGHT_RANGE); - } - } - - int getLength() { - return length; - } -} diff --git a/src/main/java/domain/Ladder.java b/src/main/java/domain/Ladder.java index 0ab787028f..f0a2dbc7b3 100644 --- a/src/main/java/domain/Ladder.java +++ b/src/main/java/domain/Ladder.java @@ -2,30 +2,51 @@ import java.util.List; import java.util.stream.IntStream; +import util.RetryHelper; -class Ladder { - private final List rows; +public class Ladder { + private final List lines; - Ladder(int rawHeight, int rawWidth, BridgeGenerator bridgeGenerator) { - Height height = new Height(rawHeight); - Width width = new Width(rawWidth); - rows = IntStream.range(0, height.getLength()) - .mapToObj(value -> bridgeGenerator.generate(width.getLength() - 1)) - .map(Row::new) - .toList(); + Ladder(List lines) { + validateConnectedLadder(lines); + this.lines = lines; + } + + private void validateConnectedLadder(List lines) { + int lastIndex = lines.get(0).length() - 1; + boolean isConnectedLadder = IntStream.range(0, lastIndex) + .allMatch(index -> isConnectedAtIndex(lines, index)); + if (!isConnectedLadder) { + throw new IllegalStateException("두 지점 사이에는 반드시 한개 이상의 발판이 있어야 합니다."); + } + } + + private boolean isConnectedAtIndex(List lines, int index) { + return lines.stream() + .anyMatch(line -> line.isConnectWithNextPoint(index)); + } + + public int climb(int startIndex) { + int endIndex = startIndex; + for (Line line : lines) { + endIndex = line.move(endIndex); + } + return endIndex; } List> getRawLadder() { - return rows.stream() - .map(Row::getBridges) + return lines.stream() + .map(Line::getRawLine) .toList(); } - Position climb(Position startPosition) { - Position endPosition = startPosition; - for (Row row : rows) { - endPosition = row.move(endPosition); - } - return endPosition; + public static Ladder of(LineGenerateStrategy lineGenerateStrategy, int ladderHeight, int lineSize) { + RetryHelper retryHelper = new RetryHelper(Integer.MAX_VALUE); + return retryHelper.retry(() -> { + List lines = IntStream.range(0, ladderHeight) + .mapToObj(value -> Line.of(lineGenerateStrategy, lineSize)) + .toList(); + return new Ladder(lines); + }); } } diff --git a/src/main/java/domain/LadderGame.java b/src/main/java/domain/LadderGame.java index 910bbb48b6..b887e7313e 100644 --- a/src/main/java/domain/LadderGame.java +++ b/src/main/java/domain/LadderGame.java @@ -1,55 +1,100 @@ package domain; +import dto.LadderGameResult; +import dto.LadderGameResults; import java.util.List; public class LadderGame { + private final Players players; + private final Gifts gifts; private final Ladder ladder; - private final Names names; - private final Results results; - public LadderGame(List userNames, int ladderHeight, List rawResults) { - names = new Names(userNames); - int nameCount = names.getNameCount(); - ladder = new Ladder(ladderHeight, nameCount, new BridgeRandomGenerator()); - this.results = new Results(rawResults); + private LadderGame(Players players, Gifts gifts, LineGenerateStrategy lineMakeStrategy, int ladderHeight) { + validateRequiredValues(players, gifts, lineMakeStrategy, ladderHeight); + this.players = players; + this.gifts = gifts; + this.ladder = Ladder.of(lineMakeStrategy, ladderHeight, players.getPlayerNames().size()); } - LadderGame(List userNames, int ladderHeight, List rawResults, BridgeGenerator bridgeGenerator) { - names = new Names(userNames); - int nameCount = names.getNameCount(); - ladder = new Ladder(ladderHeight, nameCount, bridgeGenerator); - this.results = new Results(rawResults); + private void validateRequiredValues(Players players, Gifts gifts, LineGenerateStrategy lineGenerateStrategy, + Integer ladderHeight) { + if (players == null || gifts == null || lineGenerateStrategy == null || ladderHeight == null) { + throw new IllegalStateException("필수 값이 설정되지 않았습니다."); + } } - public List getRawNames() { - return names.getRawNames(); + public LadderGameResults start(String operator) { + if (operator.equals("all")) { + return startAllPlayer(); + } + return startSinglePlayer(operator); } - public List> getRawLadder() { - return ladder.getRawLadder(); + private LadderGameResults startSinglePlayer(String operator) { + validateOperator(operator); + LadderGameResult ladderGameResult = generateLadderGameResult(operator); + return LadderGameResults.of(ladderGameResult); } - public List getRawResults() { - return results.getRawResults(); + private void validateOperator(String operator) { + if (players.notContains(operator)) { + throw new IllegalArgumentException("없는 참가자 입니다."); + } } - public List getClimbResults(String rawOperator) { - LadderGameOperator operator = new LadderGameOperator(rawOperator); - if (operator.isAll()) { - return climbAll(); - } - return List.of(climb(rawOperator)); + private LadderGameResult generateLadderGameResult(String operator) { + int startIndex = players.indexOf(operator); + int endIndex = ladder.climb(startIndex); + String giftName = gifts.getGiftName(endIndex); + return new LadderGameResult(operator, giftName); } - private List climbAll() { - return names.getRawNames().stream() - .map(this::climb) + private LadderGameResults startAllPlayer() { + List ladderGameResults = players.getPlayerNames().stream() + .map(this::generateLadderGameResult) .toList(); + return new LadderGameResults(ladderGameResults); } - private String climb(String rawName) { - Position startPosition = names.position(rawName); - Position endPosition = ladder.climb(startPosition); - return results.getRawResult(endPosition.getRawPosition()); + public List> rawLadder() { + return ladder.getRawLadder(); + } + + public static final class LadderGameBuilder { + private Players players; + private Gifts gifts; + private LineGenerateStrategy lineGenerateStrategy; + private int ladderHeight; + + private LadderGameBuilder() { + } + + public static LadderGameBuilder builder() { + return new LadderGameBuilder(); + } + + public LadderGameBuilder players(List playerNames) { + this.players = Players.of(playerNames); + return this; + } + + public LadderGameBuilder gifts(List giftNames) { + this.gifts = Gifts.of(giftNames); + return this; + } + + public LadderGameBuilder lineGenerateStrategy(LineGenerateStrategy lineGenerateStrategy) { + this.lineGenerateStrategy = lineGenerateStrategy; + return this; + } + + public LadderGameBuilder ladderHeight(int ladderHeight) { + this.ladderHeight = ladderHeight; + return this; + } + + public LadderGame build() { + return new LadderGame(players, gifts, lineGenerateStrategy, ladderHeight); + } } } diff --git a/src/main/java/domain/LadderGameException.java b/src/main/java/domain/LadderGameException.java deleted file mode 100644 index e1ba3ce68b..0000000000 --- a/src/main/java/domain/LadderGameException.java +++ /dev/null @@ -1,8 +0,0 @@ -package domain; - -public class LadderGameException extends RuntimeException { - - public LadderGameException(ExceptionType exceptionType) { - super(exceptionType.getMessage()); - } -} diff --git a/src/main/java/domain/LadderGameOperator.java b/src/main/java/domain/LadderGameOperator.java deleted file mode 100644 index 6a45e8cbc2..0000000000 --- a/src/main/java/domain/LadderGameOperator.java +++ /dev/null @@ -1,7 +0,0 @@ -package domain; - -record LadderGameOperator(String rawOperator) { - boolean isAll() { - return rawOperator.equals("all"); - } -} diff --git a/src/main/java/domain/Line.java b/src/main/java/domain/Line.java new file mode 100644 index 0000000000..4dcc2bed61 --- /dev/null +++ b/src/main/java/domain/Line.java @@ -0,0 +1,98 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class Line { + private final List points; + private final List canGoRights; + + private Line(List canGoRights) { + validateLineSize(canGoRights); + validateRightAfterEnd(canGoRights); + List points = new ArrayList<>(); + addFirstPoint(points, canGoRights); + addRemainPoints(points, canGoRights); + this.points = Collections.unmodifiableList(points); + this.canGoRights = Collections.unmodifiableList(canGoRights); + } + + private void validateLineSize(List canGoRights2) { + if (canGoRights2.size() == 0) { + throw new IllegalArgumentException(); + } + } + + private void validateRightAfterEnd(List canGoRights2) { + if (canGoRights2.get(canGoRights2.size() - 1)) { + throw new IllegalArgumentException("오른쪽 끝에선 오른쪽으로 갈 수 없습니다."); + } + } + + private void addFirstPoint(List points, List canGoRights) { + if (canGoRights.get(0)) { + points.add(new Point(Direction.RIGHT, 0)); + } + if (!canGoRights.get(0)) { + points.add(new Point(Direction.STRAIGHT, 0)); + } + } + + private void addRemainPoints(List points, List canGoRights) { + for (int index = 1; index < canGoRights.size(); index++) { + validateContinuousRight(index, canGoRights); + addPoint(points, index, canGoRights); + } + } + + private void validateContinuousRight(int index, List canGoRights) { + if (canGoRights.get(index - 1) && canGoRights.get(index)) { + throw new IllegalArgumentException("|-----|-----| 연결 감지!"); + } + } + + private void addPoint(List points, int index, List canGoRights) { + if (canGoRights.get(index)) { + points.add(new Point(Direction.RIGHT, index)); + } + if (canGoRights.get(index - 1) && !canGoRights.get(index)) { + points.add(new Point(Direction.LEFT, index)); + } + if (!canGoRights.get(index - 1) && !canGoRights.get(index)) { + points.add(new Point(Direction.STRAIGHT, index)); + } + } + + int length() { + return points.size(); + } + + boolean isConnectWithNextPoint(int index) { + if (index == points.size() - 1) { + return false; + } + Point point = points.get(index); + Point nextPoint = points.get(index + 1); + return point.next().equals(nextPoint); + } + + int move(int startIndex) { + Point start = points.get(startIndex); + Point next = start.next(); + return next.index(); + } + + List getRawLine() { + return canGoRights; + } + + public static Line of(LineGenerateStrategy lineGenerateStrategy, int lineSize) { + List generate = lineGenerateStrategy.generate(lineSize); + return Line.from(generate.toArray(Boolean[]::new)); + } + + static Line from(Boolean... canGoRights) { + return new Line(List.of(canGoRights)); + } +} diff --git a/src/main/java/domain/LineGenerateStrategy.java b/src/main/java/domain/LineGenerateStrategy.java new file mode 100644 index 0000000000..e55978af60 --- /dev/null +++ b/src/main/java/domain/LineGenerateStrategy.java @@ -0,0 +1,7 @@ +package domain; + +import java.util.List; + +public sealed interface LineGenerateStrategy permits AbstractLineGenerateStrategy { + List generate(int lineSize); +} diff --git a/src/main/java/domain/Name.java b/src/main/java/domain/Name.java deleted file mode 100644 index 183d034886..0000000000 --- a/src/main/java/domain/Name.java +++ /dev/null @@ -1,32 +0,0 @@ -package domain; - -import java.util.Objects; - -public class Name { - private final String name; - - Name(String name) { - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Name other = (Name) o; - return Objects.equals(name, other.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - String getName() { - return name; - } -} diff --git a/src/main/java/domain/Names.java b/src/main/java/domain/Names.java deleted file mode 100644 index 3b7c62d99a..0000000000 --- a/src/main/java/domain/Names.java +++ /dev/null @@ -1,39 +0,0 @@ -package domain; - -import java.util.List; - -public class Names { - private final List names; - - Names(List names) { - validateDuplicateName(names); - this.names = names.stream().map(Name::new).toList(); - } - - private void validateDuplicateName(List splitNames) { - long distinctCount = splitNames.stream().distinct().count(); - if (distinctCount != splitNames.size()) { - throw new LadderGameException(ExceptionType.NAMES_DUPLICATE); - } - } - - List getRawNames() { - return names.stream() - .map(Name::getName) - .toList(); - } - - Position position(String rawName) { - Name needToFind = new Name(rawName); - try { - int rawPosition = names.indexOf(needToFind); - return Position.getCachedPosition(rawPosition, names.size() - 1); - } catch (NullPointerException e) { - throw new LadderGameException(ExceptionType.NAME_NOT_FOUND); - } - } - - int getNameCount() { - return names.size(); - } -} diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java new file mode 100644 index 0000000000..33e391f805 --- /dev/null +++ b/src/main/java/domain/Player.java @@ -0,0 +1,34 @@ +package domain; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +record Player(String name) { + private static final Pattern pattern = Pattern.compile(".*[^a-zA-Z0-9].*"); + + Player { + validateNameLength(name); + validateNameCharacter(name); + validateNameBlackList(name); + } + + private void validateNameLength(String name) { + if (name == null || name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("참가자 이름은 1글자 이상 5글자 이하여야 합니다."); + } + } + + private void validateNameCharacter(String name) { + Matcher matcher = pattern.matcher(name); + if (matcher.matches()) { + throw new IllegalArgumentException("참가자 이름은 알파벳 대소문자와 숫자만으로 이루어져야 합니다."); + } + } + + private void validateNameBlackList(String name) { + if (name.equals("all")) { + throw new IllegalArgumentException("참가자 이름은 all이 될 수 없습니다."); + } + } + +} diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java new file mode 100644 index 0000000000..1ea44774be --- /dev/null +++ b/src/main/java/domain/Players.java @@ -0,0 +1,48 @@ +package domain; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class Players { + private final List players; + + Players(List players) { + validateDuplicateName(players); + validatePlayersCount(players); + this.players = Collections.unmodifiableList(players); + } + + private void validateDuplicateName(List players) { + Set distinctPlayers = new HashSet<>(players); + if (distinctPlayers.size() != players.size()) { + throw new IllegalStateException("이름이 같은 참가자는 있을 수 없습니다."); + } + } + + private void validatePlayersCount(List players) { + if (players == null || players.size() < 2 || players.size() > 10) { + throw new IllegalStateException("참가자는 2명 이상 10명 이하여야 합니다."); + } + } + + boolean notContains(String playerName) { + return players.stream() + .noneMatch(player -> player.name().equals(playerName)); + } + + int indexOf(String playerName) { + List playerNames = players.stream().map(Player::name).toList(); + return playerNames.indexOf(playerName); + } + + List getPlayerNames() { + return players.stream().map(Player::name).toList(); + } + + static Players of(List playerNames) { + List players = playerNames.stream().map(Player::new).toList(); + return new Players(players); + } +} diff --git a/src/main/java/domain/Point.java b/src/main/java/domain/Point.java new file mode 100644 index 0000000000..e1f24f37b8 --- /dev/null +++ b/src/main/java/domain/Point.java @@ -0,0 +1,11 @@ +package domain; + +record Point(Direction direction, int index) { + Point(Direction direction) { + this(direction, 0); + } + + Point next() { + return direction.nextPoint(index); + } +} diff --git a/src/main/java/domain/Position.java b/src/main/java/domain/Position.java deleted file mode 100644 index ba3b71ab67..0000000000 --- a/src/main/java/domain/Position.java +++ /dev/null @@ -1,88 +0,0 @@ -package domain; - -import static domain.ExceptionType.POSITION_OVERFLOW; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -class Position { - - private static final Map> cache = new ConcurrentHashMap<>(); - - static Position getCachedPosition(int rawPosition, int maxPosition) { - if (cache.containsKey(maxPosition)) { - Map cacheThatSameMaxPosition = cache.get(maxPosition); - return cacheThatSameMaxPosition.computeIfAbsent(rawPosition, key -> new Position(key, maxPosition)); - } - cache.computeIfAbsent(maxPosition, key -> new ConcurrentHashMap<>()) - .put(rawPosition, new Position(rawPosition, maxPosition)); - return getCachedPosition(rawPosition, maxPosition); - } - - private final int rawPosition; - private final int maxPosition; - - - private Position(int rawPosition, int maxPosition) { - validateRange(rawPosition, maxPosition); - this.rawPosition = rawPosition; - this.maxPosition = maxPosition; - } - - private void validateRange(int rawPosition, int maxPosition) { - if (rawPosition < 0 || rawPosition > maxPosition) { - throw new LadderGameException(POSITION_OVERFLOW); - } - } - - Position move(List bridges) { - Boolean canGoRight = getCanGo(bridges, rawPosition); - Boolean canGoLeft = getCanGo(bridges, rawPosition - 1); - if (canGoRight) { - return moveRight(); - } - if (canGoLeft) { - return moveLeft(); - } - return this; - } - - private Boolean getCanGo(List bridges, int nextRawPosition) { - if (nextRawPosition < 0 || nextRawPosition >= maxPosition) { - return false; - } - return bridges.get(nextRawPosition); - } - - private Position moveRight() { - return Position.getCachedPosition(rawPosition + 1, maxPosition); - } - - private Position moveLeft() { - return Position.getCachedPosition(rawPosition - 1, maxPosition); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - var that = (Position) obj; - return this.rawPosition == that.rawPosition && - this.maxPosition == that.maxPosition; - } - - @Override - public int hashCode() { - return Objects.hash(rawPosition, maxPosition); - } - - int getRawPosition() { - return rawPosition; - } -} diff --git a/src/main/java/domain/RandomLineGenerateStrategy.java b/src/main/java/domain/RandomLineGenerateStrategy.java new file mode 100644 index 0000000000..2573ba453e --- /dev/null +++ b/src/main/java/domain/RandomLineGenerateStrategy.java @@ -0,0 +1,15 @@ +package domain; + +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +public final class RandomLineGenerateStrategy extends AbstractLineGenerateStrategy { + @Override + public List generateStrategy(int lineSize) { + Random random = new Random(); + return IntStream.range(0, lineSize) + .mapToObj(value -> random.nextBoolean()) + .toList(); + } +} diff --git a/src/main/java/domain/BridgeRandomGenerator.java b/src/main/java/domain/RandomLineMakeStrategy.java similarity index 50% rename from src/main/java/domain/BridgeRandomGenerator.java rename to src/main/java/domain/RandomLineMakeStrategy.java index 0779a6ba73..934f28cce7 100644 --- a/src/main/java/domain/BridgeRandomGenerator.java +++ b/src/main/java/domain/RandomLineMakeStrategy.java @@ -5,22 +5,28 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -class BridgeRandomGenerator implements BridgeGenerator { - public List generate(int width) { - Random random = new Random(); - List rawBridges = generateBridges(width, random); - fixInvalidBridges(width, rawBridges); - return rawBridges; +public class RandomLineMakeStrategy { + private final int lineSize; + + public RandomLineMakeStrategy(int lineSize) { + this.lineSize = lineSize; + } + + public Boolean[] makeLine() { + return generateBridges(new Random()); } - private List generateBridges(int width, Random random) { - return IntStream.range(0, width) + private Boolean[] generateBridges(Random random) { + List randomBooleans = IntStream.range(0, lineSize - 1) .mapToObj(value -> random.nextBoolean()) .collect(Collectors.toList()); + fixInvalidBridges(randomBooleans); + randomBooleans.add(false); + return randomBooleans.toArray(Boolean[]::new); } - private void fixInvalidBridges(int width, List rawBridges) { - for (int index = 1; index < width; index++) { + private void fixInvalidBridges(List rawBridges) { + for (int index = 1; index < lineSize - 1; index++) { fixIfNeed(rawBridges, index); } } diff --git a/src/main/java/domain/Result.java b/src/main/java/domain/Result.java deleted file mode 100644 index b1652e31d1..0000000000 --- a/src/main/java/domain/Result.java +++ /dev/null @@ -1,4 +0,0 @@ -package domain; - -record Result(String rawResult) { -} diff --git a/src/main/java/domain/Results.java b/src/main/java/domain/Results.java deleted file mode 100644 index c80ce6763a..0000000000 --- a/src/main/java/domain/Results.java +++ /dev/null @@ -1,24 +0,0 @@ -package domain; - -import java.util.List; - -public class Results { - private final List results; - - public Results(List results) { - this.results = results.stream() - .map(Result::new) - .toList(); - } - - public List getRawResults() { - return results.stream() - .map(Result::rawResult) - .toList(); - } - - public String getRawResult(int position) { - List rawResults = getRawResults(); - return rawResults.get(position); - } -} diff --git a/src/main/java/domain/Row.java b/src/main/java/domain/Row.java deleted file mode 100644 index 9149e21a3d..0000000000 --- a/src/main/java/domain/Row.java +++ /dev/null @@ -1,29 +0,0 @@ -package domain; - -import java.util.Collections; -import java.util.List; -import java.util.stream.IntStream; - -class Row { - private final List bridges; - - Row(List bridges) { - IntStream.range(1, bridges.size()) - .forEach(index -> validateNearInfo(bridges, index)); - this.bridges = Collections.unmodifiableList(bridges); - } - - private void validateNearInfo(List rowInfos, int index) { - if (rowInfos.get(index) && rowInfos.get(index - 1)) { - throw new LadderGameException(ExceptionType.ROW_NEAR); - } - } - - List getBridges() { - return bridges; - } - - Position move(Position startPosition) { - return startPosition.move(bridges); - } -} diff --git a/src/main/java/domain/Width.java b/src/main/java/domain/Width.java deleted file mode 100644 index 8cadbef622..0000000000 --- a/src/main/java/domain/Width.java +++ /dev/null @@ -1,23 +0,0 @@ -package domain; - -class Width { - static final int MIN = 2; - static final int MAX = 10; - - private final int length; - - Width(int length) { - validate(length); - this.length = length; - } - - private void validate(int width) { - if (width < MIN || width > MAX) { - throw new LadderGameException(ExceptionType.WIDTH_RANGE); - } - } - - int getLength() { - return length; - } -} diff --git a/src/main/java/dto/LadderGameResult.java b/src/main/java/dto/LadderGameResult.java new file mode 100644 index 0000000000..653ea9922a --- /dev/null +++ b/src/main/java/dto/LadderGameResult.java @@ -0,0 +1,4 @@ +package dto; + +public record LadderGameResult(String playerName, String giftName) { +} diff --git a/src/main/java/dto/LadderGameResults.java b/src/main/java/dto/LadderGameResults.java new file mode 100644 index 0000000000..71429aad49 --- /dev/null +++ b/src/main/java/dto/LadderGameResults.java @@ -0,0 +1,27 @@ +package dto; + +import java.util.HashSet; +import java.util.List; + +public record LadderGameResults(List ladderGameResults) { + + public static LadderGameResults of(LadderGameResult... ladderGameResults) { + return new LadderGameResults(List.of(ladderGameResults)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LadderGameResults that = (LadderGameResults) o; + boolean thatContainsThis = new HashSet<>(that.ladderGameResults).containsAll(ladderGameResults); + boolean thisContainsThat = new HashSet<>(ladderGameResults).containsAll(that.ladderGameResults); + return thatContainsThis && thisContainsThat; + } + +} diff --git a/src/main/java/util/RetryHelper.java b/src/main/java/util/RetryHelper.java new file mode 100644 index 0000000000..7ea1c36219 --- /dev/null +++ b/src/main/java/util/RetryHelper.java @@ -0,0 +1,32 @@ +package util; + +import java.util.function.Supplier; + +public class RetryHelper { + + private final int maxRetryCount; + private int retryCount = -1; + + public RetryHelper(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public E retry(Supplier supplier) { + E result = null; + while (result == null && retryCount < maxRetryCount) { + result = useSupplier(supplier); + } + retryCount = -1; + return result; + } + + private E useSupplier(Supplier supplier) { + retryCount++; + try { + return supplier.get(); + } catch (Exception e) { + System.out.println(e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/view/ClimbResultPrinter.java b/src/main/java/view/ClimbResultPrinter.java deleted file mode 100644 index b5714fe504..0000000000 --- a/src/main/java/view/ClimbResultPrinter.java +++ /dev/null @@ -1,17 +0,0 @@ -package view; - -import java.util.ArrayList; -import java.util.List; - -public class ClimbResultPrinter { - public static List of(List rawNames, List rawResults) { - List climbResults = new ArrayList<>(); - for (int index = 0; index < rawNames.size(); index++) { - String rawName = rawNames.get(index); - String rawResult = rawResults.get(index); - String result = "%s : %s".formatted(rawName, rawResult); - climbResults.add(result); - } - return climbResults; - } -} diff --git a/src/main/java/view/NamePrinter.java b/src/main/java/view/GiftPrinter.java similarity index 91% rename from src/main/java/view/NamePrinter.java rename to src/main/java/view/GiftPrinter.java index 4f39c20a5b..04d0e4a7d3 100644 --- a/src/main/java/view/NamePrinter.java +++ b/src/main/java/view/GiftPrinter.java @@ -1,6 +1,6 @@ package view; -class NamePrinter { +public class GiftPrinter { static final int MAX_NAME_LENGTH = 5; static String from(String name) { diff --git a/src/main/java/view/GiftsInputView.java b/src/main/java/view/GiftsInputView.java new file mode 100644 index 0000000000..dcab54a4d1 --- /dev/null +++ b/src/main/java/view/GiftsInputView.java @@ -0,0 +1,29 @@ +package view; + +import java.util.List; + +public class GiftsInputView { + private static final String SEPARATOR = ","; + + public static List getGiftNames(String rawString, int count) { + StringSeparator separator = new StringSeparator(SEPARATOR); + List splitNames = separator.splitName(rawString); + validateSplitNamesCount(count, splitNames); + for (String splitName : splitNames) { + validateNameLength(splitName); + } + return splitNames; + } + + private static void validateSplitNamesCount(int count, List splitNames) { + if (splitNames.size() != count) { + throw new IllegalArgumentException("상품 이름이 너무 많거나 너무 적습니다."); + } + } + + private static void validateNameLength(String name) { + if (name == null || name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("상품 이름은 1글자 이상 5글자 이하여야 합니다."); + } + } +} diff --git a/src/main/java/view/GiftsPrinter.java b/src/main/java/view/GiftsPrinter.java new file mode 100644 index 0000000000..5853c522bd --- /dev/null +++ b/src/main/java/view/GiftsPrinter.java @@ -0,0 +1,12 @@ +package view; + +import java.util.List; +import java.util.stream.Collectors; + +public class GiftsPrinter { + public static String from(List rawGiftNames) { + return rawGiftNames.stream() + .map(GiftPrinter::from) + .collect(Collectors.joining(" ")); + } +} diff --git a/src/main/java/view/LadderGameOperatorInputView.java b/src/main/java/view/LadderGameOperatorInputView.java index 1a0875c0f4..80a9fd0f49 100644 --- a/src/main/java/view/LadderGameOperatorInputView.java +++ b/src/main/java/view/LadderGameOperatorInputView.java @@ -1,22 +1,16 @@ package view; -import domain.ExceptionType; -import domain.LadderGameException; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.function.Supplier; public class LadderGameOperatorInputView { - public static String getOperator(Supplier supplier, List names) { - String operator = supplier.get(); - validateOperator(new HashSet<>(names), operator); - return operator; + public static String getOperator(String rawString, List playerNames) { + validateOperator(rawString, playerNames); + return rawString; } - private static void validateOperator(Set names, String operator) { - if (!names.contains(operator) && !operator.equals("all")) { - throw new LadderGameException(ExceptionType.NAME_NOT_FOUND); + private static void validateOperator(String rawString, List playerNames) { + if (!playerNames.contains(rawString) && !rawString.equals("all")) { + throw new IllegalArgumentException("없는 참가자 입니다."); } } } diff --git a/src/main/java/view/LadderGameResultPrinter.java b/src/main/java/view/LadderGameResultPrinter.java new file mode 100644 index 0000000000..e57ac1f70a --- /dev/null +++ b/src/main/java/view/LadderGameResultPrinter.java @@ -0,0 +1,9 @@ +package view; + +import dto.LadderGameResult; + +public class LadderGameResultPrinter { + static String from(LadderGameResult ladderGameResult) { + return "%s : %s".formatted(ladderGameResult.playerName(), ladderGameResult.giftName()); + } +} diff --git a/src/main/java/view/LadderGameResultsPrinter.java b/src/main/java/view/LadderGameResultsPrinter.java new file mode 100644 index 0000000000..0d554763f3 --- /dev/null +++ b/src/main/java/view/LadderGameResultsPrinter.java @@ -0,0 +1,12 @@ +package view; + +import dto.LadderGameResults; +import java.util.stream.Collectors; + +public class LadderGameResultsPrinter { + static String from(LadderGameResults ladderGameResults) { + return ladderGameResults.ladderGameResults().stream() + .map(LadderGameResultPrinter::from) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/main/java/view/LadderHeightInputView.java b/src/main/java/view/LadderHeightInputView.java new file mode 100644 index 0000000000..10a884f492 --- /dev/null +++ b/src/main/java/view/LadderHeightInputView.java @@ -0,0 +1,19 @@ +package view; + +public class LadderHeightInputView { + public static int getLadderHeight(String rawString) { + int ladderHeight = parseInteger(rawString); + if (ladderHeight < 2 || ladderHeight > 20) { + throw new IllegalArgumentException("사다리 높이는 2 이상 20 이하여야 합니다."); + } + return ladderHeight; + } + + private static int parseInteger(String rawString) { + try { + return Integer.parseInt(rawString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자가 아닙니다."); + } + } +} diff --git a/src/main/java/view/LadderPrinter.java b/src/main/java/view/LadderPrinter.java index d697192a3f..6e9b4a1373 100644 --- a/src/main/java/view/LadderPrinter.java +++ b/src/main/java/view/LadderPrinter.java @@ -6,7 +6,7 @@ public class LadderPrinter { public static String from(List> rawLadder) { return rawLadder.stream() - .map(RowPrinter::from) + .map(LinePrinter::from) .collect(Collectors.joining("\n")); } } diff --git a/src/main/java/view/RowPrinter.java b/src/main/java/view/LinePrinter.java similarity index 63% rename from src/main/java/view/RowPrinter.java rename to src/main/java/view/LinePrinter.java index 42317093f1..4878a1a736 100644 --- a/src/main/java/view/RowPrinter.java +++ b/src/main/java/view/LinePrinter.java @@ -3,9 +3,9 @@ import java.util.List; import java.util.stream.Collectors; -class RowPrinter { - static String from(List bridges) { - String rawRowString = bridges.stream().map(RowPrinter::makeBridge) +public class LinePrinter { + static String from(List canGoRights) { + String rawRowString = canGoRights.stream().limit(canGoRights.size() - 1).map(LinePrinter::makeBridge) .collect(Collectors.joining("|")); return " |%s|".formatted(rawRowString); } diff --git a/src/main/java/view/NameInputView.java b/src/main/java/view/NameInputView.java deleted file mode 100644 index 422799722f..0000000000 --- a/src/main/java/view/NameInputView.java +++ /dev/null @@ -1,69 +0,0 @@ -package view; - -import domain.ExceptionType; -import domain.LadderGameException; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class NameInputView { - static final int MAX_NAMES_COUNT = 10; - static final int MAX_NAME_LENGTH = 5; - - private static final String SEPARATOR = ","; - - private static final Pattern NAME_CHARACTER_REGEX = Pattern.compile("^[^a-zA-Z]+$"); - - - public static List getNames(Supplier supplier) { - String names = supplier.get(); - validateSeparator(names); - List splitNames = splitName(names); - validateNameCount(splitNames); - splitNames.forEach(NameInputView::validateNameCharacters); - splitNames.forEach(NameInputView::validateNameBlackList); - splitNames.forEach(NameInputView::validateNameLength); - return splitNames; - } - - private static void validateSeparator(String names) { - boolean startsWith = names.startsWith(SEPARATOR); - boolean endsWith = names.endsWith(SEPARATOR); - if (startsWith || endsWith) { - throw new LadderGameException(ExceptionType.NAMES_SEPARATOR); - } - } - - private static List splitName(String names) { - return Arrays.stream(names.split(SEPARATOR)).toList(); - } - - private static void validateNameCount(List splitNames) { - if (splitNames.size() > MAX_NAMES_COUNT) { - throw new LadderGameException(ExceptionType.NAMES_COUNT); - } - } - - private static void validateNameCharacters(String name) { - Matcher matcher = NAME_CHARACTER_REGEX.matcher(name); - if (matcher.matches()) { - throw new LadderGameException(ExceptionType.NAME_CHARACTER); - } - } - - private static void validateNameLength(String name) { - if (name.isEmpty() || name.length() > MAX_NAME_LENGTH) { - throw new LadderGameException(ExceptionType.NAME_LENGTH_RANGE); - } - } - - private static void validateNameBlackList(String name) { - Set blackList = Set.of("all"); - if (blackList.contains(name)) { - throw new LadderGameException(ExceptionType.NAME_BLACK_LIST); - } - } -} diff --git a/src/main/java/view/NamesPrinter.java b/src/main/java/view/NamesPrinter.java deleted file mode 100644 index f06e6fdbab..0000000000 --- a/src/main/java/view/NamesPrinter.java +++ /dev/null @@ -1,12 +0,0 @@ -package view; - -import java.util.List; -import java.util.stream.Collectors; - -public class NamesPrinter { - public static String from(List names) { - return names.stream() - .map(NamePrinter::from) - .collect(Collectors.joining(" ")); - } -} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index a4fe96b263..f752b5790a 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,7 +1,26 @@ package view; +import dto.LadderGameResults; +import java.util.List; + public class OutputView { - public static void print(String needToPrint) { - System.out.println(needToPrint); + public static void print(String string) { + System.out.println(string); + } + + public static void printPlayers(List playerNames) { + print(PlayersPrinter.from(playerNames)); + } + + public static void printLadder(List> rawLadder) { + print(LadderPrinter.from(rawLadder)); + } + + public static void printGifts(List giftNames) { + print(GiftsPrinter.from(giftNames)); + } + + public static void printLadderGameResults(LadderGameResults ladderGameResults) { + print(LadderGameResultsPrinter.from(ladderGameResults)); } } diff --git a/src/main/java/view/PlayerPrinter.java b/src/main/java/view/PlayerPrinter.java new file mode 100644 index 0000000000..a6799d1d55 --- /dev/null +++ b/src/main/java/view/PlayerPrinter.java @@ -0,0 +1,13 @@ +package view; + +public class PlayerPrinter { + static final int MAX_NAME_LENGTH = 5; + + static String from(String name) { + if (name.length() < MAX_NAME_LENGTH) { + name = name + " "; + } + int nameStringLength = name.length(); + return " ".repeat(MAX_NAME_LENGTH - nameStringLength) + name; + } +} diff --git a/src/main/java/view/PlayersInputView.java b/src/main/java/view/PlayersInputView.java new file mode 100644 index 0000000000..52089304e1 --- /dev/null +++ b/src/main/java/view/PlayersInputView.java @@ -0,0 +1,58 @@ +package view; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PlayersInputView { + private static final String SEPARATOR = ","; + private static final Pattern pattern = Pattern.compile(".*[^a-zA-Z0-9].*"); + + + public static List getPlayerNames(String rawString) { + StringSeparator separator = new StringSeparator(SEPARATOR); + List splitName = separator.splitName(rawString); + validateDuplicateName(splitName); + validatePlayersCount(splitName); + for (String name : splitName) { + validateNameLength(name); + validateNameCharacter(name); + validateNameBlackList(name); + } + return splitName; + } + + private static void validateDuplicateName(List playersNames) { + Set distinctPlayersNames = new HashSet<>(playersNames); + if (distinctPlayersNames.size() != playersNames.size()) { + throw new IllegalStateException("이름이 같은 참가자는 있을 수 없습니다."); + } + } + + private static void validatePlayersCount(List playersNames) { + if (playersNames == null || playersNames.size() < 2 || playersNames.size() > 10) { + throw new IllegalStateException("참가자는 2명 이상 10명 이하여야 합니다."); + } + } + + private static void validateNameLength(String name) { + if (name == null || name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("참가자 이름은 1글자 이상 5글자 이하여야 합니다."); + } + } + + private static void validateNameCharacter(String name) { + Matcher matcher = pattern.matcher(name); + if (matcher.matches()) { + throw new IllegalArgumentException("참가자 이름은 알파벳 대소문자와 숫자만으로 이루어져야 합니다."); + } + } + + private static void validateNameBlackList(String name) { + if (name.equals("all")) { + throw new IllegalArgumentException("참가자 이름은 all이 될 수 없습니다."); + } + } +} diff --git a/src/main/java/view/PlayersPrinter.java b/src/main/java/view/PlayersPrinter.java new file mode 100644 index 0000000000..0821c0818a --- /dev/null +++ b/src/main/java/view/PlayersPrinter.java @@ -0,0 +1,12 @@ +package view; + +import java.util.List; +import java.util.stream.Collectors; + +public class PlayersPrinter { + public static String from(List rawPlayerNames) { + return rawPlayerNames.stream() + .map(PlayerPrinter::from) + .collect(Collectors.joining(" ")); + } +} diff --git a/src/main/java/view/ResultInputView.java b/src/main/java/view/ResultInputView.java deleted file mode 100644 index d403a16939..0000000000 --- a/src/main/java/view/ResultInputView.java +++ /dev/null @@ -1,51 +0,0 @@ -package view; - -import domain.ExceptionType; -import domain.LadderGameException; -import java.util.Arrays; -import java.util.List; -import java.util.function.Supplier; - -public class ResultInputView { - static final int MAX_RESULTS_COUNT = 10; - static final int MAX_RESULT_LENGTH = 5; - private static final String SEPARATOR = ","; - - public static List getResults(Supplier supplier, int resultCount) { - String results = supplier.get(); - validateSeparator(results); - List splitResults = splitResult(results); - splitResults.forEach(ResultInputView::validateResultLength); - validateResultsCount(splitResults, resultCount); - return splitResults; - } - - private static List splitResult(String results) { - return Arrays.stream(results.split(SEPARATOR)).toList(); - } - - - private static void validateSeparator(String results) { - boolean startsWith = results.startsWith(SEPARATOR); - boolean endsWith = results.endsWith(SEPARATOR); - if (startsWith || endsWith) { - throw new LadderGameException(ExceptionType.RESULTS_SEPARATOR); - } - } - - private static void validateResultsCount(List splitNames, int expectedResultCount) { - if (splitNames.size() > MAX_RESULTS_COUNT) { - throw new LadderGameException(ExceptionType.RESULTS_COUNT_RANGE); - } - if (splitNames.size() != expectedResultCount) { - throw new LadderGameException(ExceptionType.RESULT_COUNT); - } - } - - private static void validateResultLength(String name) { - if (name.isEmpty() || name.length() > MAX_RESULT_LENGTH) { - throw new LadderGameException(ExceptionType.RESULT_LENGTH); - } - - } -} diff --git a/src/main/java/view/StringSeparator.java b/src/main/java/view/StringSeparator.java new file mode 100644 index 0000000000..57c3c292c9 --- /dev/null +++ b/src/main/java/view/StringSeparator.java @@ -0,0 +1,36 @@ +package view; + +import java.util.Arrays; +import java.util.List; + +class StringSeparator { + private final String separator; + + StringSeparator(String separator) { + this.separator = separator; + } + + List splitName(String separatedString) { + validateSeparator(separatedString); + return Arrays.stream(separatedString.split(separator)).toList(); + } + + private void validateSeparator(String separatedString) { + validateStarOrEndWithSeparator(separatedString); + validateDuplicateSeparator(separatedString); + } + + private void validateStarOrEndWithSeparator(String separatedString) { + boolean startsWith = separatedString.startsWith(separator); + boolean endsWith = separatedString.endsWith(separator); + if (startsWith || endsWith) { + throw new IllegalArgumentException("입력이 구분자로 시작하거나 끝나면 안됩니다."); + } + } + + private void validateDuplicateSeparator(String separatedString) { + if (separatedString.contains(separator + separator)) { + throw new IllegalArgumentException("입력에 구분자가 연속으로 등장하면 안됩니다."); + } + } +} diff --git a/src/test/java/domain/GiftTest.java b/src/test/java/domain/GiftTest.java new file mode 100644 index 0000000000..caa99822a8 --- /dev/null +++ b/src/test/java/domain/GiftTest.java @@ -0,0 +1,17 @@ +package domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class GiftTest { + @ParameterizedTest + @ValueSource(strings = {"123456", ""}) + @DisplayName("상품 이름 길이 검증") + void validateGiftNameLength(String name) { + Assertions.assertThatThrownBy(() -> new Gift(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 이름은 1글자 이상 5글자 이하여야 합니다."); + } +} diff --git a/src/test/java/domain/GiftsTest.java b/src/test/java/domain/GiftsTest.java new file mode 100644 index 0000000000..d37cb25e8a --- /dev/null +++ b/src/test/java/domain/GiftsTest.java @@ -0,0 +1,24 @@ +package domain; + +import java.util.List; +import java.util.stream.IntStream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class GiftsTest { + @ParameterizedTest + @ValueSource(ints = {1, 11}) + @DisplayName("상품 수가 너무 적거나 너무 많은지 확인") + void validateGiftsCount(int giftsCount) { + List giftNames = IntStream.rangeClosed(1, giftsCount) + .mapToObj("a%d"::formatted) + .toList(); + + Assertions.assertThatThrownBy(() -> Gifts.of(giftNames)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("상품은 2개 이상 10개 이하여야 합니다."); + } + +} diff --git a/src/test/java/domain/HeightTest.java b/src/test/java/domain/HeightTest.java deleted file mode 100644 index 05b0905f0e..0000000000 --- a/src/test/java/domain/HeightTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package domain; - -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 HeightTest { - @ParameterizedTest - @ValueSource(ints = {Height.MIN - 1, Height.MAX + 1}) - @DisplayName("높이 검증") - void validateHeight(int length) { - assertThatThrownBy(() -> new Height(length)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.HEIGHT_RANGE.getMessage()); - } -} diff --git a/src/test/java/domain/LadderGameOperatorTest.java b/src/test/java/domain/LadderGameOperatorTest.java deleted file mode 100644 index f528191746..0000000000 --- a/src/test/java/domain/LadderGameOperatorTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package domain; - - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class LadderGameOperatorTest { - - @ParameterizedTest - @CsvSource(value = {"'a',false", "'all',true"}) - @DisplayName("isAll 메서드 검증") - void isAll(String rawOperator, boolean expected) { - LadderGameOperator all = new LadderGameOperator(rawOperator); - Assertions.assertThat(all.isAll()) - .isEqualTo(expected); - } -} diff --git a/src/test/java/domain/LadderGameTest.java b/src/test/java/domain/LadderGameTest.java index 2fc9a1ea5a..b65a1670b9 100644 --- a/src/test/java/domain/LadderGameTest.java +++ b/src/test/java/domain/LadderGameTest.java @@ -1,73 +1,42 @@ package domain; +import domain.LadderGame.LadderGameBuilder; +import dto.LadderGameResult; +import dto.LadderGameResults; import java.util.List; -import java.util.stream.Stream; import org.assertj.core.api.Assertions; 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 org.junit.jupiter.params.provider.CsvSource; class LadderGameTest { - static Stream climbParameter() { - return Stream.of( - Arguments.of("a", List.of("2")), - Arguments.of("b", List.of("1")), - Arguments.of("c", List.of("3")), - Arguments.of("all", List.of("2", "1", "3")) - ); - } - - @Test - @DisplayName("사다리 타기 게임의 이름이 제대로 저장되는지 검증") - void getRawNames() { - List expectedRawNames = List.of("a", "b"); - List rawResults = List.of("1,2"); - LadderGame ladderGame = new LadderGame(expectedRawNames, 5, rawResults); - List actualRawNames = ladderGame.getRawNames(); - Assertions.assertThat(actualRawNames) - .containsExactlyElementsOf(expectedRawNames); + @ParameterizedTest + @CsvSource(value = {"robin,2등", "phobi,1등"}) + @DisplayName("게임 실행 결과 검증") + void start(String playerName, String giftName) { + LadderGame ladderGame = makeTestLadderGame(); + LadderGameResults actualGameResults = ladderGame.start(playerName); + Assertions.assertThat(actualGameResults) + .isEqualTo(new LadderGameResults(List.of(new LadderGameResult(playerName, giftName)))); } - @Test - @DisplayName("사다리 타기 게임의 사다리가 제대로 생성되는지 검증") - void getRawLadder() { - List expectedRawNames = List.of("a", "b"); - List rawResults = List.of("1,2"); - int ladderHeight = 5; - LadderGame ladderGame = new LadderGame(expectedRawNames, ladderHeight, rawResults); - List> actualRawLadder = ladderGame.getRawLadder(); - Assertions.assertThat(actualRawLadder.size()) - .isEqualTo(ladderHeight); - Assertions.assertThat(actualRawLadder.get(0).size()) - .isEqualTo(expectedRawNames.size() - 1); + private LadderGame makeTestLadderGame() { + return LadderGameBuilder.builder() + .players(List.of("robin", "phobi")) + .gifts(List.of("1등", "2등")) + .lineGenerateStrategy(new TestLineGenerateStrategy()) + .ladderHeight(5) + .build(); } @Test - @DisplayName("사다리 타기 게임의 실행 결과가 제대로 저장되는지 검증") - void getRawResults() { - List rawNames = List.of("a", "b"); - List expectedRawResults = List.of("1,2"); - int ladderHeight = 5; - LadderGame ladderGame = new LadderGame(rawNames, ladderHeight, expectedRawResults); - List actualRawResults = ladderGame.getRawResults(); - Assertions.assertThat(actualRawResults) - .containsExactlyElementsOf(expectedRawResults); - } - - @ParameterizedTest - @MethodSource("climbParameter") - @DisplayName("사다리 타기 게임을 실행했을 때, 제대로 사다리가 타지는지 검증") - void climb(String nameThatWantToShoResult, List expectedResult) { - List expectedRawNames = List.of("a", "b", "c"); - List rawResults = List.of("1", "2", "3"); - int ladderHeight = 5; - LadderGame ladderGame = new LadderGame(expectedRawNames, ladderHeight, rawResults, - width -> List.of(true, false)); - List climbResults = ladderGame.getClimbResults(nameThatWantToShoResult); - Assertions.assertThat(climbResults) - .isEqualTo(expectedResult); + @DisplayName("게임 실행 시 없는 참여자 결과 조회 안되는지 검증") + void validateOperator() { + LadderGame ladderGame = makeTestLadderGame(); + Assertions.assertThatThrownBy(() -> ladderGame.start("none")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("없는 참가자 입니다."); } } diff --git a/src/test/java/domain/LadderTest.java b/src/test/java/domain/LadderTest.java index baec9b3bd5..9c58a292ff 100644 --- a/src/test/java/domain/LadderTest.java +++ b/src/test/java/domain/LadderTest.java @@ -9,38 +9,52 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class LadderTest { +public class LadderTest { - static Stream climbParameters() { - return Stream.of( - Arguments.of(0, 1), - Arguments.of(1, 0), - Arguments.of(2, 2) - ); + @Test + @DisplayName("정상적인 사다리가 생성되는지 검증") + void validLadder() { + Assertions.assertThatNoException() + .isThrownBy(() -> new Ladder( + List.of(Line.from(false, true, false), Line.from(true, false, false), + Line.from(false, true, false), Line.from(false, true, false), + Line.from(false, true, false), Line.from(false, true, false)))); } @Test - @DisplayName("사다리 전체 폭 검증") - void validateRowCount() { - Ladder ladder = new Ladder(5, 5, new BridgeRandomGenerator()); - Assertions.assertThat(ladder.getRawLadder().size()) - .isEqualTo(5); + @DisplayName("두 지점 사이에 연결이 없는 사다리가 생성되지 않는지 검증") + void noConnectedLadder() { + Assertions.assertThatThrownBy(() -> new Ladder( + List.of(Line.from(false, true, false), Line.from(false, true, false), Line.from(false, true, false), + Line.from(false, true, false), Line.from(false, true, false), Line.from(false, true, false)))) + .isInstanceOf(IllegalStateException.class) + .hasMessage("두 지점 사이에는 반드시 한개 이상의 발판이 있어야 합니다."); } @ParameterizedTest - @MethodSource("climbParameters") - @DisplayName("사다리 타기") - void climb(int startPosition, int endPosition) { - int rawWidth = 3; - Ladder ladder = new Ladder(5, rawWidth, width -> List.of(true, false)); + @MethodSource("climbParameter") + @DisplayName("사다리 타기 결과가 잘 나오는지 검증") + void climb(int startIndex, int endIndex) { /* - * |-----| | - * |-----| | - * |-----| | - * |-----| | - * |-----| | */ - Position actual = ladder.climb(Position.getCachedPosition(startPosition, rawWidth - 1)); + * | |-----| + * |-----| | + * | |-----| + * | |-----| + * | |-----| + * | |-----| + * */ + Ladder ladder = new Ladder( + List.of(Line.from(false, true, false), Line.from(true, false, false), + Line.from(false, true, false), Line.from(false, true, false), + Line.from(false, true, false), Line.from(false, true, false))); + int actual = ladder.climb(startIndex); Assertions.assertThat(actual) - .isEqualTo(Position.getCachedPosition(endPosition, rawWidth - 1)); + .isEqualTo(endIndex); + } + + static Stream climbParameter() { + return Stream.of(Arguments.of(0, 1), + Arguments.of(1, 2), + Arguments.of(2, 0)); } } diff --git a/src/test/java/domain/LineTest.java b/src/test/java/domain/LineTest.java new file mode 100644 index 0000000000..58e228fcdd --- /dev/null +++ b/src/test/java/domain/LineTest.java @@ -0,0 +1,40 @@ +package domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LineTest { + @Test + @DisplayName("올바른 Point로 Line이 잘 생성되는지 확인") + void normal() { + // |-----| | |-----| + Line line = Line.from(true, false, false, true, false); + int length = line.length(); + Assertions.assertThat(length) + .isEqualTo(5); + // | | | |-----| + Line line2 = Line.from(false, false, false, true, false); + int length2 = line2.length(); + Assertions.assertThat(length2) + .isEqualTo(5); + } + + @Test + @DisplayName("|-----|-----| 인 Point로 Line이 생성되지 않는지 확인") + void leftContinueOrRightContinue() { + Assertions.assertThatThrownBy( + () -> Line.from(true, true, false)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("|-----|-----| 연결 감지!"); + } + + @Test + @DisplayName("|-----|----- 인 Point로 Line이 생성되지 않는지 확인") + void rightAfterEnd() { + Assertions.assertThatThrownBy( + () -> Line.from(true, false, true)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("오른쪽 끝에선 오른쪽으로 갈 수 없습니다."); + } +} diff --git a/src/test/java/domain/NamesTest.java b/src/test/java/domain/NamesTest.java deleted file mode 100644 index 55acb0cf4c..0000000000 --- a/src/test/java/domain/NamesTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package domain; - -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class NamesTest { - @Test - @DisplayName("사람 이름 중복 검사") - void validateDuplicateName() { - Assertions.assertThatThrownBy(() -> new Names(List.of("abcde", "abcde", "abc"))) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAMES_DUPLICATE.getMessage()); - } -} diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java new file mode 100644 index 0000000000..905f39ff1f --- /dev/null +++ b/src/test/java/domain/PlayerTest.java @@ -0,0 +1,44 @@ +package domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PlayerTest { + @ParameterizedTest + @ValueSource(strings = {"123456", ""}) + @DisplayName("참가자 이름 길이 검증") + void validatePlayerNameLength(String name) { + Assertions.assertThatThrownBy(() -> new Player(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 1글자 이상 5글자 이하여야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"!", " ", "가나다", "abc "}) + @DisplayName("참가자 이름 구성 문자 검증") + void validatePlayerNameCharacter(String name) { + Assertions.assertThatThrownBy(() -> new Player(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 알파벳 대소문자와 숫자만으로 이루어져야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"all"}) + @DisplayName("참가자 이름 구성 문자 검증") + void validateNameBlackList(String name) { + Assertions.assertThatThrownBy(() -> new Player(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 all이 될 수 없습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"robin", "12345", "abc12", "12Abc"}) + @DisplayName("정상적인 이름의 참여자 생성") + void normalName(String name) { + Assertions.assertThatNoException() + .isThrownBy(() -> new Player(name)); + } + +} diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java new file mode 100644 index 0000000000..0b4429eb81 --- /dev/null +++ b/src/test/java/domain/PlayersTest.java @@ -0,0 +1,34 @@ +package domain; + +import java.util.List; +import java.util.stream.IntStream; +import org.assertj.core.api.Assertions; +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 { + + @Test + @DisplayName("중복된 이름의 참가자가 생성되지 않는지 확인") + void validateDuplicateName() { + Assertions.assertThatThrownBy(() -> Players.of(List.of("robin", "robin"))) + .isInstanceOf(IllegalStateException.class) + .hasMessage("이름이 같은 참가자는 있을 수 없습니다."); + } + + @ParameterizedTest + @ValueSource(ints = {1, 11}) + @DisplayName("참가자 인원 수가 너무 적거나 너무 많은지 확인") + void validatePlayersCount(int playersCount) { + List playerNames = IntStream.rangeClosed(1, playersCount) + .mapToObj("a%d"::formatted) + .toList(); + + Assertions.assertThatThrownBy(() -> Players.of(playerNames)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("참가자는 2명 이상 10명 이하여야 합니다."); + } + +} diff --git a/src/test/java/domain/PointTest.java b/src/test/java/domain/PointTest.java new file mode 100644 index 0000000000..93f83fdc5f --- /dev/null +++ b/src/test/java/domain/PointTest.java @@ -0,0 +1,25 @@ +package domain; + +import static domain.Direction.LEFT; +import static domain.Direction.RIGHT; +import static domain.Direction.STRAIGHT; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PointTest { + @Test + @DisplayName("각 지점에서 이동 시 적절한 지점으로 이동하는지 확인") + void next() { + // |-----| |-----| | + List points = List.of(new Point(RIGHT), new Point(LEFT, 1), new Point(RIGHT, 2), new Point(LEFT, 3), + new Point(STRAIGHT, 4)); + Assertions.assertThat(points.get(0).next()).isEqualTo(points.get(1)); + Assertions.assertThat(points.get(1).next()).isEqualTo(points.get(0)); + Assertions.assertThat(points.get(2).next()).isEqualTo(points.get(3)); + Assertions.assertThat(points.get(3).next()).isEqualTo(points.get(2)); + Assertions.assertThat(points.get(4).next()).isEqualTo(points.get(4)); + } +} diff --git a/src/test/java/domain/PositionTest.java b/src/test/java/domain/PositionTest.java deleted file mode 100644 index f8c5cc33f1..0000000000 --- a/src/test/java/domain/PositionTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package domain; - -import static domain.ExceptionType.POSITION_OVERFLOW; - -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; -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; - -class PositionTest { - - static Stream nextParameter() { - return Stream.of( - Arguments.of(0, 1), - Arguments.of(1, 0) - ); - } - - @Test - @DisplayName("최대 위치 값 이하만 생성되는지 검증") - void validateMax() { - Assertions.assertThatThrownBy(() -> Position.getCachedPosition(3, 2)) - .isInstanceOf(LadderGameException.class) - .hasMessage(POSITION_OVERFLOW.getMessage()); - } - - @Test - @DisplayName("최소 위치 값 이상만 생성되는지 검증") - void validateMin() { - Assertions.assertThatThrownBy(() -> Position.getCachedPosition(-1, 2)) - .isInstanceOf(LadderGameException.class) - .hasMessage(POSITION_OVERFLOW.getMessage()); - } - - @ParameterizedTest - @MethodSource("nextParameter") - @DisplayName("다음 위치 검증") - void next(int rawPosition, int rawExpectedNextPosition) { - Position position = Position.getCachedPosition(rawPosition, 1); - Position actual = position.move(List.of(true)); - Position expected = Position.getCachedPosition(rawExpectedNextPosition, 1); - Assertions.assertThat(actual) - .isEqualTo(expected); - } -} diff --git a/src/test/java/domain/RandomLineGenerateStrategyTest.java b/src/test/java/domain/RandomLineGenerateStrategyTest.java new file mode 100644 index 0000000000..42370db37f --- /dev/null +++ b/src/test/java/domain/RandomLineGenerateStrategyTest.java @@ -0,0 +1,20 @@ +package domain; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RandomLineGenerateStrategyTest { + + @Test + @DisplayName("|-----|----- 인 Point로 Line이 생성되지 않는지 확인") + void makeLine() { + LineGenerateStrategy randomLineMakeStrategy = new RandomLineGenerateStrategy(); + List generate = randomLineMakeStrategy.generate(5); + Assertions.assertThat(generate.size()) + .isEqualTo(5); + Assertions.assertThat(generate.get(generate.size() - 1)) + .isFalse(); + } +} diff --git a/src/test/java/domain/RowTest.java b/src/test/java/domain/RowTest.java deleted file mode 100644 index b521987499..0000000000 --- a/src/test/java/domain/RowTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package domain; - -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class RowTest { - @Test - @DisplayName("연속해서 가로 라인이 등장하는지 확인") - void validateNearInfo() { - Assertions.assertThatThrownBy(() -> new Row(List.of(true, true))) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.ROW_NEAR.getMessage()); - } -} diff --git a/src/test/java/domain/TestLineGenerateStrategy.java b/src/test/java/domain/TestLineGenerateStrategy.java new file mode 100644 index 0000000000..4ca8f92a70 --- /dev/null +++ b/src/test/java/domain/TestLineGenerateStrategy.java @@ -0,0 +1,13 @@ +package domain; + +import java.util.List; +import java.util.stream.IntStream; + +public class TestLineGenerateStrategy extends AbstractLineGenerateStrategy { + @Override + public List generateStrategy(int lineSize) { + return IntStream.range(0, lineSize) + .mapToObj(value -> value % 2 == 0) + .toList(); + } +} diff --git a/src/test/java/domain/WidthTest.java b/src/test/java/domain/WidthTest.java deleted file mode 100644 index eb7a1957bd..0000000000 --- a/src/test/java/domain/WidthTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package domain; - -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 = {Width.MIN - 1, Width.MAX + 1}) - @DisplayName("폭 검증") - void validateWidth(int length) { - assertThatThrownBy(() -> new Width(length)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.WIDTH_RANGE.getMessage()); - } -} diff --git a/src/test/java/util/RetryHelperTest.java b/src/test/java/util/RetryHelperTest.java new file mode 100644 index 0000000000..393f09872b --- /dev/null +++ b/src/test/java/util/RetryHelperTest.java @@ -0,0 +1,30 @@ +package util; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RetryHelperTest { + + @Test + @DisplayName("최대 재시도 횟수까지만 재시도 하는지 검증") + void retry() { + int maxRetryCount = 10; + int retryCount = retrySomeLogic(maxRetryCount); + Assertions.assertThat(retryCount) + .isEqualTo(maxRetryCount); + } + + private static int retrySomeLogic(int maxRetryCount) { + RetryHelper retryHelper = new RetryHelper(maxRetryCount); + int[] count = new int[]{-1}; + retryHelper.retry(() -> { + if (count[0] <= maxRetryCount * 2) { + count[0]++; + throw new RuntimeException(); + } + return 1; + }); + return count[0]; + } +} diff --git a/src/test/java/view/ClimbResultPrinterTest.java b/src/test/java/view/ClimbResultPrinterTest.java deleted file mode 100644 index 261db20e70..0000000000 --- a/src/test/java/view/ClimbResultPrinterTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package view; - -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class ClimbResultPrinterTest { - - @Test - @DisplayName("사다리 타기 출력 문자열 생성") - void of() { - List actualResults = ClimbResultPrinter.of(List.of("nameA", "nameB"), List.of("A", "B")); - List expectedResults = List.of("nameA : A", "nameB : B"); - Assertions.assertThat(actualResults) - .containsExactlyElementsOf(expectedResults); - } -} diff --git a/src/test/java/view/GiftsInputViewTest.java b/src/test/java/view/GiftsInputViewTest.java new file mode 100644 index 0000000000..4e56b24c24 --- /dev/null +++ b/src/test/java/view/GiftsInputViewTest.java @@ -0,0 +1,53 @@ +package view; + +import java.util.List; +import org.assertj.core.api.Assertions; +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 GiftsInputViewTest { + + @Test + @DisplayName("상품 이름을 구분자로 잘 자르는지 검증") + void getGiftNames() { + List giftNames = GiftsInputView.getGiftNames("robin,phobi,12345,test1", 4); + Assertions.assertThat(giftNames) + .containsExactlyElementsOf(List.of("robin", "phobi", "12345", "test1")); + } + + @Test + @DisplayName("연속된 구분자가 입력된 경우 실행 안되는지 검증") + void validateDuplicateSeparator() { + Assertions.assertThatThrownBy(() -> GiftsInputView.getGiftNames("robin,,a", 3)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("입력에 구분자가 연속으로 등장하면 안됩니다."); + } + + @ParameterizedTest + @ValueSource(strings = {",robin,phobi", "robin,phobi,"}) + @DisplayName("구분자로 시작하거나 끝나도록 입력된 경우 실행 안되는지 검증") + void validateStartOrEndWithSeparator(String input) { + Assertions.assertThatThrownBy(() -> GiftsInputView.getGiftNames(input, 3)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("입력이 구분자로 시작하거나 끝나면 안됩니다."); + } + + @Test + @DisplayName("상품 이름 길이 검증") + void validateGiftNameLength() { + Assertions.assertThatThrownBy(() -> GiftsInputView.getGiftNames("123456,abc", 2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 이름은 1글자 이상 5글자 이하여야 합니다."); + } + + @ParameterizedTest + @ValueSource(ints = {2, 10}) + @DisplayName("상품 이름 개수 검증") + void validateGiftNameLength(int count) { + Assertions.assertThatThrownBy(() -> GiftsInputView.getGiftNames("12345,abc,10", count)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상품 이름이 너무 많거나 너무 적습니다."); + } +} diff --git a/src/test/java/view/GiftsPrinterTest.java b/src/test/java/view/GiftsPrinterTest.java new file mode 100644 index 0000000000..34a56c9c95 --- /dev/null +++ b/src/test/java/view/GiftsPrinterTest.java @@ -0,0 +1,18 @@ +package view; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class GiftsPrinterTest { + @Test + @DisplayName("상품 문자열 생성") + void namesToString() { + List rawNames = List.of("a", "aa", "aaa", "aaaa", "aaaaa"); + String actual = GiftsPrinter.from(rawNames); + String expected = " a aa aaa aaaa aaaaa"; + Assertions.assertThat(actual).isEqualTo(expected); + } + +} diff --git a/src/test/java/view/LadderGameOperatorInputViewTest.java b/src/test/java/view/LadderGameOperatorInputViewTest.java index 8476af150e..b31bdf4659 100644 --- a/src/test/java/view/LadderGameOperatorInputViewTest.java +++ b/src/test/java/view/LadderGameOperatorInputViewTest.java @@ -1,9 +1,5 @@ package view; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import domain.ExceptionType; -import domain.LadderGameException; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -12,22 +8,21 @@ import org.junit.jupiter.params.provider.ValueSource; class LadderGameOperatorInputViewTest { - @Test - @DisplayName("결과를 확인하고 싶은 이름이 입력되지 않은 이름인 경우 검증") - void validateNameNotFound() { - assertThatThrownBy( - () -> LadderGameOperatorInputView.getOperator(() -> "abc", List.of("abcd,ab"))) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAME_NOT_FOUND.getMessage()); - } @ParameterizedTest - @ValueSource(strings = {"a", "b", "c", "all"}) - @DisplayName("결과를 확인하고 싶은 이름이 입력되지 않은 이름인 경우 검증") - void getNameThatNeedToShowResult(String name) { - String nameThatNeedToShowResult = LadderGameOperatorInputView.getOperator(() -> name, - List.of("a", "b", "c")); - Assertions.assertThat(nameThatNeedToShowResult) - .isEqualTo(name); + @ValueSource(strings = {"robin", "phobi", "all"}) + @DisplayName("참가자 이름이거나 all 이 입력된 경우만 제대로 처리되는지 검증") + void getOperator(String input) { + String operator = LadderGameOperatorInputView.getOperator(input, List.of("robin", "phobi")); + Assertions.assertThat(operator) + .isEqualTo(input); + } + + @Test + @DisplayName("잘못된 명령어인지 검증") + void invalidOperator() { + Assertions.assertThatThrownBy(() -> LadderGameOperatorInputView.getOperator("aa", List.of("robin", "phobi"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("없는 참가자 입니다."); } } diff --git a/src/test/java/view/LadderHeightInputViewTest.java b/src/test/java/view/LadderHeightInputViewTest.java new file mode 100644 index 0000000000..00757525ec --- /dev/null +++ b/src/test/java/view/LadderHeightInputViewTest.java @@ -0,0 +1,36 @@ +package view; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class LadderHeightInputViewTest { + + @ParameterizedTest + @ValueSource(strings = {"2", "20"}) + @DisplayName("정상적인 사다리 높이가 입력된 경우") + void getLadderHeight(String input) { + int ladderHeight = LadderHeightInputView.getLadderHeight(input); + Assertions.assertThat(ladderHeight) + .isEqualTo(Integer.valueOf(input)); + } + + @ParameterizedTest + @ValueSource(strings = {"ㅁ2", "a", " 2", "2 ", "a", "A"}) + @DisplayName("숫자가 아닌게 입력된 경우") + void validateLadderHeightIsNotNumber(String input) { + Assertions.assertThatThrownBy(() -> LadderHeightInputView.getLadderHeight(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("숫자가 아닙니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"1", "21"}) + @DisplayName("너무 길거나 짧은 사다리 높이가 입력된 경우") + void validateLadderHeight(String input) { + Assertions.assertThatThrownBy(() -> LadderHeightInputView.getLadderHeight(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("사다리 높이는 2 이상 20 이하여야 합니다."); + } +} diff --git a/src/test/java/view/LadderPrinterTest.java b/src/test/java/view/LadderPrinterTest.java index e0e0f1edd0..44c8fb8b4f 100644 --- a/src/test/java/view/LadderPrinterTest.java +++ b/src/test/java/view/LadderPrinterTest.java @@ -19,11 +19,11 @@ void ladderToString() { private static List> makeRawLadder() { return List.of( - List.of(false, true), - List.of(false, true), - List.of(false, true), - List.of(false, true), - List.of(false, true) + List.of(false, true, false), + List.of(false, true, false), + List.of(false, true, false), + List.of(false, true, false), + List.of(false, true, false) ); } } diff --git a/src/test/java/view/NameInputViewTest.java b/src/test/java/view/NameInputViewTest.java deleted file mode 100644 index a12e07ca8b..0000000000 --- a/src/test/java/view/NameInputViewTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package view; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import domain.ExceptionType; -import domain.LadderGameException; -import org.assertj.core.api.Assertions; -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 NameInputViewTest { - @ParameterizedTest - @ValueSource(strings = {",abc,ab", "abc,ab,", ",abc,ab,"}) - @DisplayName("구분자가 맨 앞이나 맨 뒤에 있는지 확인") - void validateSeparator(String names) { - Assertions.assertThatThrownBy(() -> NameInputView.getNames(() -> names)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAMES_SEPARATOR.getMessage()); - } - - @Test - @DisplayName("사람이름 개수 검사") - void validateNameCount() { - Assertions.assertThatThrownBy(() -> NameInputView.getNames(() -> "a,b,c,d,e,f,g,h,i,j,k")) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAMES_COUNT.getMessage()); - } - - @ParameterizedTest - @ValueSource(strings = {"", "abcdef,a"}) - @DisplayName("사람 이름 길이 검증") - void validateNameLength(String name) { - assertThatThrownBy(() -> NameInputView.getNames(() -> name)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAME_LENGTH_RANGE.getMessage()); - } - - @ParameterizedTest - @ValueSource(strings = {"12345,a", "!@#$%,a", "가나다라마,a"}) - @DisplayName("사람 이름 구성 문자 검증") - void validateNameCharacters(String name) { - assertThatThrownBy(() -> NameInputView.getNames(() -> name)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAME_CHARACTER.getMessage()); - } - - @Test - @DisplayName("사람 이름 구성 문자 검증") - void validateBlackList() { - assertThatThrownBy(() -> NameInputView.getNames(() -> "all,abc")) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.NAME_BLACK_LIST.getMessage()); - } -} diff --git a/src/test/java/view/PlayersInputViewTest.java b/src/test/java/view/PlayersInputViewTest.java new file mode 100644 index 0000000000..9334581e50 --- /dev/null +++ b/src/test/java/view/PlayersInputViewTest.java @@ -0,0 +1,84 @@ +package view; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.assertj.core.api.Assertions; +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 PlayersInputViewTest { + + @Test + @DisplayName("참여자 이름을 구분자로 잘 자르는지 검증") + void getPlayerNames() { + List playerNames = PlayersInputView.getPlayerNames("robin,phobi,12345,test1"); + Assertions.assertThat(playerNames) + .containsExactlyElementsOf(List.of("robin", "phobi", "12345", "test1")); + } + + @Test + @DisplayName("연속된 구분자가 입력된 경우 실행 안되는지 검증") + void validateDuplicateSeparator() { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames("robin,,a")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("입력에 구분자가 연속으로 등장하면 안됩니다."); + } + + @ParameterizedTest + @ValueSource(strings = {",robin,phobi", "robin,phobi,"}) + @DisplayName("구분자로 시작하거나 끝나도록 입력된 경우 실행 안되는지 검증") + void validateStartOrEndWithSeparator(String input) { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("입력이 구분자로 시작하거나 끝나면 안됩니다."); + } + + @Test + @DisplayName("참가자 이름 길이 검증") + void validatePlayerNameLength() { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames("123456,aa")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 1글자 이상 5글자 이하여야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"!,zbc", "zbc, ", "zvb,가나다", "zbv,abc "}) + @DisplayName("참가자 이름 구성 문자 검증") + void validatePlayerNameCharacter(String input) { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 알파벳 대소문자와 숫자만으로 이루어져야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"all,aa"}) + @DisplayName("참가자 이름 블랙리스트 검증") + void validateNameBlackList(String input) { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("참가자 이름은 all이 될 수 없습니다."); + } + + @Test + @DisplayName("중복된 이름의 참가자가 생성되지 않는지 확인") + void validateDuplicateName() { + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames("aa,aa")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("이름이 같은 참가자는 있을 수 없습니다."); + } + + @ParameterizedTest + @ValueSource(ints = {1, 11}) + @DisplayName("참가자 인원 수가 너무 적거나 너무 많은지 확인") + void validatePlayersCount(int playersCount) { + String input = IntStream.rangeClosed(1, playersCount) + .mapToObj("a%d"::formatted) + .collect(Collectors.joining(",")); + Assertions.assertThatThrownBy(() -> PlayersInputView.getPlayerNames(input)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("참가자는 2명 이상 10명 이하여야 합니다."); + } +} diff --git a/src/test/java/view/NamesPrinterTest.java b/src/test/java/view/PlayersPrinterTest.java similarity index 82% rename from src/test/java/view/NamesPrinterTest.java rename to src/test/java/view/PlayersPrinterTest.java index 94b9173240..aecbfab954 100644 --- a/src/test/java/view/NamesPrinterTest.java +++ b/src/test/java/view/PlayersPrinterTest.java @@ -5,13 +5,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class NamesPrinterTest { +public class PlayersPrinterTest { @Test @DisplayName("이름 문자열 생성") void namesToString() { List rawNames = List.of("a", "aa", "aaa", "aaaa", "aaaaa"); - String actual = NamesPrinter.from(rawNames); + String actual = PlayersPrinter.from(rawNames); String expected = " a aa aaa aaaa aaaaa"; Assertions.assertThat(actual).isEqualTo(expected); } + } diff --git a/src/test/java/view/ResultInputViewTest.java b/src/test/java/view/ResultInputViewTest.java deleted file mode 100644 index 97df8e675f..0000000000 --- a/src/test/java/view/ResultInputViewTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package view; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import domain.ExceptionType; -import domain.LadderGameException; -import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; -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 org.junit.jupiter.params.provider.ValueSource; - -class ResultInputViewTest { - - static Stream validateResultLengthParameter() { - return Stream.of( - Arguments.of("", 0), - Arguments.of("abcdef,a", 2) - ); - } - - @ParameterizedTest - @ValueSource(strings = {",abc,ab", "abc,ab,", ",abc,ab,"}) - @DisplayName("구분자가 맨 앞이나 맨 뒤에 있는지 확인") - void validateSeparator(String results) { - Assertions.assertThatThrownBy(() -> ResultInputView.getResults(() -> results, 3)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.RESULTS_SEPARATOR.getMessage()); - } - - @Test - @DisplayName("실행 결과 최대 개수 검사") - void validateResultsCount() { - Assertions.assertThatThrownBy(() -> ResultInputView.getResults(() -> "a,b,c,d,e,f,g,h,i,j,k", 11)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.RESULTS_COUNT_RANGE.getMessage()); - } - - @Test - @DisplayName("실행 결과 개수가 이름의 개수와 같은지 검사") - void validateResultsCountSameAsNamesCount() { - Assertions.assertThatThrownBy(() -> ResultInputView.getResults(() -> "a,b,c", 4)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.RESULT_COUNT.getMessage()); - } - - @ParameterizedTest - @MethodSource("validateResultLengthParameter") - @DisplayName("실행 결과 길이 검증") - void validateResultLength(String result, int resultCount) { - assertThatThrownBy(() -> ResultInputView.getResults(() -> result, 2)) - .isInstanceOf(LadderGameException.class) - .hasMessage(ExceptionType.RESULT_LENGTH.getMessage()); - } - - @Test - @DisplayName("정상 동작 테스트") - void getResults() { - List results = ResultInputView.getResults(() -> "당첨,꽝,꽝", 3); - List expected = List.of("당첨", "꽝", "꽝"); - Assertions.assertThat(results) - .containsExactlyElementsOf(expected); - } -}