diff --git a/README.md b/README.md index 8d7e8aee..b9fdb5a8 100644 --- a/README.md +++ b/README.md @@ -1 +1,60 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +# ⚾ 숫자 야구 게임 + +## 기능 구현 목록 + +### 1. 게임 시작 및 초기화 + +- [ ] **컴퓨터의 수 생성 기능**: 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. + +### 2. 사용자 입력 + +- [ ] **숫자 입력 기능**: 플레이어에게 서로 다른 3자리의 숫자를 입력받는다. +- [ ] **재시작/종료 입력 기능**: 게임 종료 후 재시작(1) 또는 종료(2)를 구분하는 값을 입력받는다. + +### 3. 입력 값 유효성 검사 (예외 처리) + +- [ ] **숫자 입력 유효성 검사**: 사용자가 입력한 값이 유효한지 검증한다. + - [ ] 3자리 숫자인가? + - [ ] 1~9 사이의 숫자로만 이루어져 있는가? + - [ ] 중복된 숫자가 없는가? +- [ ] **재시작 입력 유효성 검사**: 입력 값이 1 또는 2인지 검증한다. +- [ ] **에러 처리**: 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, `[ERROR]`로 시작하는 에러 메시지를 출력한다. + - 에러 발생 후 게임을 종료하지 않고 다시 입력을 받도록 처리한다. + +### 4. 게임 로직 (판정) + +- [ ] **볼/스트라이크 판정 기능**: 컴퓨터의 수와 플레이어의 수를 비교하여 결과를 계산한다. + - 같은 수가 같은 자리에 있으면 스트라이크 + - 같은 수가 다른 자리에 있으면 볼 + - 같은 수가 전혀 없으면 낫싱 + +### 5. 결과 출력 + +- [ ] **게임 결과 출력 기능**: 판정된 결과를 요구사항에 맞는 형식으로 출력한다. + - 예: `1스트라이크 1볼`, `3스트라이크`, `낫싱` +- [ ] **게임 종료 문구 출력 기능**: 3개의 숫자를 모두 맞혔을 때 종료 문구를 출력한다. + - 예: `3개의 숫자를 모두 맞히셨습니다! 게임 끝` + +### 6. 게임 흐름 제어 + +- [ ] **게임 루프 구현**: 정답을 맞출 때까지 입력 -> 판정 -> 출력을 반복한다. +- [ ] **전체 게임 제어**: 게임 종료 후 사용자의 선택(1, 2)에 따라 게임을 재시작하거나 완전히 종료한다. + +--- + +## 🔍 기능적 요구사항 (참고) + +기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. + +- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. + - [예] 상대방(컴퓨터)의 수가 425일 때 + - 123을 제시한 경우 : 1스트라이크 + - 456을 제시한 경우 : 1스트라이크 1볼 + - 789를 제시한 경우 : 낫싱 +- 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 + 결과를 출력한다. +- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. +- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. +- 사용자가 잘못된 값을 입력할 경우 `[ERROR]`로 시작하는 에러 메시지를 출력하고 게임을 계속 진행할 수 있어야 한다. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 20a92c9e..75684882 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'checkstyle' } group = 'camp.nextstep.edu' @@ -11,6 +12,14 @@ java { } } +// Checkstyle config +checkstyle { + maxWarnings = 0 // 규칙이 어긋나는 코드가 하나라도 있을 경우 빌드 fail을 내고 싶다면 이 선언을 추가한다. + configFile = file("${rootDir}/naver-checkstyle-rules.xml") + configProperties = ["suppressionFile": "${rootDir}/naver-checkstyle-suppressions.xml"] + toolVersion = "8.24" // checkstyle 버전 8.24 이상 선언 +} + repositories { mavenCentral() } @@ -23,3 +32,6 @@ dependencies { test { useJUnitPlatform() } + + + diff --git a/naver-checkstyle-rules.xml b/naver-checkstyle-rules.xml new file mode 100644 index 00000000..dafbb4d1 --- /dev/null +++ b/naver-checkstyle-rules.xml @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 00000000..d1c0ad85 --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,10 @@ +package baseball; + +import baseball.controller.GameController; + +public class Application { + public static void main(String[] args) { + GameController gameController = new GameController(); + gameController.run(); + } +} diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java new file mode 100644 index 00000000..6d856ef0 --- /dev/null +++ b/src/main/java/baseball/controller/GameController.java @@ -0,0 +1,67 @@ +package baseball.controller; + +import java.util.List; + +import baseball.model.Computer; +import baseball.model.GameResult; +import baseball.model.Player; +import baseball.model.Referee; +import baseball.model.RestartCommand; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class GameController { + + private final InputView inputView; + private final Referee referee; + private final Player player; + private final Computer computer; + + public GameController() { + this.inputView = new InputView(); + this.referee = new Referee(); + this.player = new Player(); + this.computer = new Computer(); + } + + public void run() { + RestartCommand restartCommand; + do { + playGame(); + restartCommand = getPlayerRestartCommand(); + } while (RestartCommand.RESTART.equals(restartCommand)); + } + + private void playGame() { + computer.setRandomNumbers(); + + GameResult gameResult; + do { + getPlayerValidInput(); + gameResult = referee.compare(computer.getNumbers(), player.getNumbers()); + OutputView.printGameResult(gameResult); + } while (!gameResult.isThreeStrikes()); + } + + private void getPlayerValidInput() { + while (true) { + try { + List numbers = inputView.readPlayerNumbers(); + player.setNumbers(numbers); + break; + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } + + private RestartCommand getPlayerRestartCommand() { + while (true) { + try { + return inputView.readRestartCommand(); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } +} diff --git a/src/main/java/baseball/model/Computer.java b/src/main/java/baseball/model/Computer.java new file mode 100644 index 00000000..e374ebae --- /dev/null +++ b/src/main/java/baseball/model/Computer.java @@ -0,0 +1,37 @@ +package baseball.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class Computer { + private List numbers; + + public Computer() { + this.setRandomNumbers(); + } + + public void setRandomNumbers() { + List generatedNumbers = new ArrayList<>(); + Random random = new Random(); + + while (generatedNumbers.size() < 3) { + int randomNumber = random.nextInt(9) + 1; + + if (!generatedNumbers.contains(randomNumber)) { + generatedNumbers.add(randomNumber); + } + } + + numbers = List.copyOf(generatedNumbers); + } + + public List getNumbers() { + if (numbers == null) { + return Collections.emptyList(); + } + + return Collections.unmodifiableList(numbers); + } +} diff --git a/src/main/java/baseball/model/GameResult.java b/src/main/java/baseball/model/GameResult.java new file mode 100644 index 00000000..35718c7a --- /dev/null +++ b/src/main/java/baseball/model/GameResult.java @@ -0,0 +1,27 @@ +package baseball.model; + +public class GameResult { + private int strikes; + private int balls; + + public GameResult(int strikes, int balls) { + this.strikes = strikes; + this.balls = balls; + } + + public boolean isThreeStrikes() { + return strikes == 3; + } + + public boolean isNothing() { + return strikes == 0 && balls == 0; + } + + public int getStrikes() { + return this.strikes; + } + + public int getBalls() { + return this.balls; + } +} diff --git a/src/main/java/baseball/model/Player.java b/src/main/java/baseball/model/Player.java new file mode 100644 index 00000000..d008dece --- /dev/null +++ b/src/main/java/baseball/model/Player.java @@ -0,0 +1,15 @@ +package baseball.model; + +import java.util.List; + +public class Player { + private List numbers; + + public void setNumbers(List numbers) { + this.numbers = numbers; + } + + public List getNumbers() { + return numbers; + } +} diff --git a/src/main/java/baseball/model/Referee.java b/src/main/java/baseball/model/Referee.java new file mode 100644 index 00000000..21a6970a --- /dev/null +++ b/src/main/java/baseball/model/Referee.java @@ -0,0 +1,41 @@ +package baseball.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Referee { + public GameResult compare(List computerNumbers, List playerNumbers) { + int strikeCnt = calculateStrikeCnt(computerNumbers, playerNumbers); + int strikeAndBallCnt = calculateStrikeAndBallCnt(computerNumbers, playerNumbers); + int ballCnt = strikeAndBallCnt - strikeCnt; + + return new GameResult(strikeCnt, ballCnt); + } + + private int calculateStrikeCnt(List computerNumbers, List playerNumbers) { + int strikeCnt = 0; + + for (int i = 0; i < playerNumbers.size(); ++i) { + if (computerNumbers.get(i).equals(playerNumbers.get(i))) { + strikeCnt++; + } + } + + return strikeCnt; + } + + private int calculateStrikeAndBallCnt(List computerNumbers, List playerNumbers) { + int strikeAndBallCnt = 0; + + Set computerNumberSet = new HashSet<>(computerNumbers); + + for (Integer number : playerNumbers) { + if (computerNumberSet.contains(number)) { + strikeAndBallCnt++; + } + } + + return strikeAndBallCnt; + } +} diff --git a/src/main/java/baseball/model/RestartCommand.java b/src/main/java/baseball/model/RestartCommand.java new file mode 100644 index 00000000..a558d63f --- /dev/null +++ b/src/main/java/baseball/model/RestartCommand.java @@ -0,0 +1,23 @@ +package baseball.model; + +public enum RestartCommand { + RESTART("1"), + EXIT("2"); + + private final String command; + + RestartCommand(String command) { + this.command = command; + } + + public static RestartCommand of(String input) { + if ("1".equals(input)) { + return RestartCommand.RESTART; + } + if ("2".equals(input)) { + return RestartCommand.EXIT; + } + + throw new IllegalArgumentException("유효하지 않은 실행 명렵입니다. (유효값: 1 or 2)"); + } +} diff --git a/src/main/java/baseball/util/InputValidator.java b/src/main/java/baseball/util/InputValidator.java new file mode 100644 index 00000000..a1c7d994 --- /dev/null +++ b/src/main/java/baseball/util/InputValidator.java @@ -0,0 +1,25 @@ +package baseball.util; + +import java.util.HashSet; +import java.util.Set; + +public class InputValidator { + + private static final int NUMBER_LENGTH = 3; + + public void validatePlayerNumbers(String input) { + if (input == null || input.length() != NUMBER_LENGTH) { + throw new IllegalArgumentException("3자리의 서로 다른 숫자를 입력해야 합니다."); + } + + Set uniqueChars = new HashSet<>(); + for (char c : input.toCharArray()) { + if (c < '1' || c > '9') { + throw new IllegalArgumentException("1에서 9까지의 숫자로만 입력해야 합니다."); + } + if (!uniqueChars.add(c)) { + throw new IllegalArgumentException("중복되지 않은 숫자를 입력해야 합니다."); + } + } + } +} diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 00000000..3924c7cb --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,36 @@ +package baseball.view; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import baseball.model.RestartCommand; +import baseball.util.InputValidator; + +public class InputView { + + private final InputValidator validator; + private final Scanner scanner; + + public InputView() { + this.scanner = new Scanner(System.in); + this.validator = new InputValidator(); + } + + public List readPlayerNumbers() { + System.out.print("숫자를 입력해주세요 : "); + String input = scanner.nextLine(); + validator.validatePlayerNumbers(input); + + List numbers = new ArrayList<>(); + for (char c : input.toCharArray()) { + numbers.add(c - '0'); + } + return numbers; + } + + public RestartCommand readRestartCommand() { + String input = scanner.nextLine(); + return RestartCommand.of(input); + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 00000000..072f4ccd --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,50 @@ +package baseball.view; + +import baseball.model.GameResult; + +public class OutputView { + private static final String STRIKE_SUFFIX = "스트라이크"; + private static final String BALL_SUFFIX = "볼"; + private static final String NOTHING_MESSAGE = "낫싱"; + + public static void printGameResult(GameResult result) { + if (isGameFinished(result)) { + printEndMessage(); + return; + } + + if (isNothingResult(result)) { + System.out.println(NOTHING_MESSAGE); + return; + } + + printNormalResult(result); + } + + private static boolean isGameFinished(GameResult result) { + return result.isThreeStrikes(); + } + + private static boolean isNothingResult(GameResult result) { + return result.isNothing(); + } + + private static void printNormalResult(GameResult result) { + printStrikeAndBallMessage(result.getStrikes(), result.getBalls()); + } + + private static void printStrikeAndBallMessage(int strikes, int balls) { + if (strikes > 0) { + System.out.print(strikes + STRIKE_SUFFIX + " "); + } + if (balls > 0) { + System.out.print(balls + BALL_SUFFIX); + } + System.out.println(); + } + + public static void printEndMessage() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + } +} diff --git a/src/test/java/baseball/model/ComputerTest.java b/src/test/java/baseball/model/ComputerTest.java new file mode 100644 index 00000000..1e6008e9 --- /dev/null +++ b/src/test/java/baseball/model/ComputerTest.java @@ -0,0 +1,53 @@ +package baseball.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ComputerTest { + + @Test + @DisplayName("컴퓨터는 3개의 난수를 갖는다") + void generatesThreeDistinctNumbersBetweenOneAndNine() { + Computer computer = new Computer(); + computer.setRandomNumbers(); + + List numbers = computer.getNumbers(); + + assertThat(numbers).hasSize(3); + + Set unique = new HashSet<>(); + for (Integer number : numbers) { + assertThat(number).isBetween(1, 9); + unique.add(number); + } + assertThat(unique).hasSize(3); + } + + @Test + @DisplayName("생성 직후에도 3개의 숫자를 반환한다") + void constructorInitializesNumbers() { + Computer computer = new Computer(); + + List numbers = computer.getNumbers(); + + assertThat(numbers).hasSize(3); + } + + @Test + @DisplayName("반환된 숫자 리스트는 수정할 수 없다") + void getNumbersReturnsUnmodifiableList() { + Computer computer = new Computer(); + + List numbers = computer.getNumbers(); + + assertThatThrownBy(() -> numbers.add(1)) + .isInstanceOf(UnsupportedOperationException.class); + } +} diff --git a/src/test/java/baseball/model/GameResultTest.java b/src/test/java/baseball/model/GameResultTest.java new file mode 100644 index 00000000..2e630325 --- /dev/null +++ b/src/test/java/baseball/model/GameResultTest.java @@ -0,0 +1,33 @@ +package baseball.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameResultTest { + + @Test + @DisplayName("getter가 스트라이크와 볼 값을 반환한다") + void gettersReturnValues() { + GameResult result = new GameResult(2, 1); + + assertThat(result.getStrikes()).isEqualTo(2); + assertThat(result.getBalls()).isEqualTo(1); + } + + @Test + @DisplayName("isThreeStrikes는 스트라이크가 3일 때만 true이다") + void isThreeStrikesIsTrueOnlyWhenStrikesAreThree() { + assertThat(new GameResult(3, 0).isThreeStrikes()).isTrue(); + assertThat(new GameResult(2, 1).isThreeStrikes()).isFalse(); + } + + @Test + @DisplayName("isNothing은 스트라이크와 볼이 모두 0일 때만 true이다") + void isNothingIsTrueOnlyWhenNoMatches() { + assertThat(new GameResult(0, 0).isNothing()).isTrue(); + assertThat(new GameResult(1, 0).isNothing()).isFalse(); + assertThat(new GameResult(0, 1).isNothing()).isFalse(); + } +} diff --git a/src/test/java/baseball/model/RefereeTest.java b/src/test/java/baseball/model/RefereeTest.java new file mode 100644 index 00000000..aad06c18 --- /dev/null +++ b/src/test/java/baseball/model/RefereeTest.java @@ -0,0 +1,87 @@ +package baseball.model; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RefereeTest { + + private final Referee referee = new Referee(); + + @Test + @DisplayName("스트라이크와 볼을 정확히 계산한다") + void compareCalculatesStrikesAndBalls() { + List computer = Arrays.asList(4, 2, 5); + List player = Arrays.asList(4, 5, 6); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(1); + assertThat(result.getBalls()).isEqualTo(1); + } + + @Test + @DisplayName("일치하는 숫자가 없으면 낫싱이다") + void compareReturnsNothingWhenNoMatches() { + List computer = Arrays.asList(4, 2, 5); + List player = Arrays.asList(1, 7, 8); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(0); + assertThat(result.getBalls()).isEqualTo(0); + } + + @Test + @DisplayName("모두 일치하면 3스트라이크이다") + void compareReturnsThreeStrikesWhenAllMatch() { + List computer = Arrays.asList(4, 2, 5); + List player = Arrays.asList(4, 2, 5); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(3); + assertThat(result.getBalls()).isEqualTo(0); + assertThat(result.isThreeStrikes()).isTrue(); + } + + @Test + @DisplayName("볼만 있는 경우를 계산한다") + void compareReturnsOnlyBallsWhenAllDigitsMatchDifferentPositions() { + List computer = Arrays.asList(1, 2, 3); + List player = Arrays.asList(3, 1, 2); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(0); + assertThat(result.getBalls()).isEqualTo(3); + } + + @Test + @DisplayName("스트라이크만 있는 경우를 계산한다") + void compareReturnsOnlyStrikesWhenPositionsMatch() { + List computer = Arrays.asList(7, 8, 9); + List player = Arrays.asList(7, 8, 9); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(3); + assertThat(result.getBalls()).isEqualTo(0); + } + + @Test + @DisplayName("스트라이크 1개, 볼 0개를 계산한다") + void compareReturnsSingleStrikeNoBalls() { + List computer = Arrays.asList(1, 2, 3); + List player = Arrays.asList(1, 4, 5); + + GameResult result = referee.compare(computer, player); + + assertThat(result.getStrikes()).isEqualTo(1); + assertThat(result.getBalls()).isEqualTo(0); + } +} diff --git a/src/test/java/baseball/model/RestartCommandTest.java b/src/test/java/baseball/model/RestartCommandTest.java new file mode 100644 index 00000000..4d03e1b8 --- /dev/null +++ b/src/test/java/baseball/model/RestartCommandTest.java @@ -0,0 +1,35 @@ +package baseball.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RestartCommandTest { + + @Test + @DisplayName("입력값 1은 RESTART를 반환한다") + void ofReturnsRestartWhenInputIsOne() { + assertThat(RestartCommand.of("1")).isEqualTo(RestartCommand.RESTART); + } + + @Test + @DisplayName("입력값 2는 EXIT를 반환한다") + void ofReturnsExitWhenInputIsTwo() { + assertThat(RestartCommand.of("2")).isEqualTo(RestartCommand.EXIT); + } + + @Test + @DisplayName("유효하지 않은 입력은 예외를 발생시킨다") + void ofThrowsWhenInputIsInvalid() { + assertThatThrownBy(() -> RestartCommand.of("0")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> RestartCommand.of("3")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> RestartCommand.of("")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> RestartCommand.of(" 1")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/baseball/util/InputValidatorTest.java b/src/test/java/baseball/util/InputValidatorTest.java new file mode 100644 index 00000000..7cb5fe30 --- /dev/null +++ b/src/test/java/baseball/util/InputValidatorTest.java @@ -0,0 +1,54 @@ +package baseball.util; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InputValidatorTest { + + private final InputValidator validator = new InputValidator(); + + @Test + @DisplayName("정상 입력은 예외를 발생시키지 않는다") + void validInputDoesNotThrow() { + validator.validatePlayerNumbers("123"); + validator.validatePlayerNumbers("987"); + } + + @Test + @DisplayName("입력 길이가 3이 아니면 예외를 발생시킨다") + void throwsWhenLengthIsNotThree() { + assertThatThrownBy(() -> validator.validatePlayerNumbers("12")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.validatePlayerNumbers("1234")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("입력이 null이면 예외를 발생시킨다") + void throwsWhenInputIsNull() { + assertThatThrownBy(() -> validator.validatePlayerNumbers(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("숫자가 아니거나 0이 포함되면 예외를 발생시킨다") + void throwsWhenContainsNonDigitOrZero() { + assertThatThrownBy(() -> validator.validatePlayerNumbers("10a")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.validatePlayerNumbers("023")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("중복된 숫자가 있으면 예외를 발생시킨다") + void throwsWhenHasDuplicateDigits() { + assertThatThrownBy(() -> validator.validatePlayerNumbers("112")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.validatePlayerNumbers("121")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.validatePlayerNumbers("211")) + .isInstanceOf(IllegalArgumentException.class); + } +}