diff --git a/docs/README.md b/docs/README.md index e69de29..ddf62b4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,108 @@ +## Feature + +- [X] 컴퓨터는 1..9 범위에서 서로 다른 임의의 수 3개 선택 +- [X] 유저는 1..9 범위에서 서로 다른 숫자 3개를 입력한다 + - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생 +- [X] 심판은 `컴퓨터 <-> 유저의 3가지 숫자`를 토대로 숫자 야구 게임 결과를 도출한다 + - 비교 후 `strikeCount & ballCount` 정보를 `Result`로 Wrapping한 후 결과를 발송한다 +- [X] 컴퓨터가 선택한 모든 숫자를 맞히면 게임은 클리어된다 + - [X] 사용자는 재시작[1], 종료[2] 중 하나를 입력한다 + - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생 + +
+
+ +## Model + +### `Baseballs` + +- 입력한 3개의 숫자들을 추상화시킨 `Baseballs` + - Baseballs에 속한 `List`은 + - [X] 각 원소가 `1..9` 범위 사이여야 한다 + - [X] 원소의 크기가 3이여야 한다 + - [X] 중복된 원소가 없어야 한다 + +
+ +### `Computer: 컴퓨터` + +- 1..9 범위에서 서로 다른 임의의 수 3개 선택 + - Computer는 중복 숫자를 선택하지 않도록 구현 +- User 선택을 통한 결과 산출의 기준 + +
+ +### `User: 사용자` + +- 숫자 3개 입력 + - 숫자를 입력하지 않을 경우 (IllegalArgumentException) +- 게임 재시작/종료와 관련된 커맨드 입력 + - 숫자를 입력하지 않을 경우 (IllegalArgumentException) + +
+ +### `Referee: 심판` + +- 플레이어 점수 채점 + - 숫자 겹침 + 동일 위치 = 스트라이크 + - 숫자 겹침 + 다른 위치 = 볼 + - 숫자 안겹침 = 낫싱 + +
+ +### `Result: 채점 결과` + +- 스트라이크 & 볼에 대한 count field 보유 + +
+ +### `GameStatus: 게임 상태` + +- 진행중인 게임에 대한 상태 + - `GAME_RUN` -> 게임 실행 + - `GAME_TERMINATE` -> 게임 종료 + +
+ +### `GameProcessDecider: 재시작 or 종료 결정자` + +- 게임 클리어 후 종료 or 재시작에 대한 커맨더 + +
+
+ +## Utils + +### `ExceptionConstants` + +- 전역 예외 메시지 통합 컴포넌트 + +### `BaseballConstants` + +- Baseball에 대한 상수 전용 컴포넌트 + - 숫자 범위 + - Baseball 개수 + +
+
+ +## View + +### `InputView` + +- 사용자 Input을 받기 위한 컴포넌트 + +### `OutputView` + +- 게임 진행과 관련된 출력 컴포넌트 + +
+
+ +## Controller + +### `GameController` + +- 게임 진행과 관련된 컨트롤러 + +
diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index dd95a34..119c1e3 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,9 @@ package baseball; +import baseball.controller.GameController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + new 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 0000000..b3f1db8 --- /dev/null +++ b/src/main/java/baseball/controller/GameController.java @@ -0,0 +1,67 @@ +package baseball.controller; + +import baseball.model.*; +import baseball.view.InputView; +import baseball.view.OutputView; + +import java.util.List; + +import static baseball.model.GameProcessDecider.GAME_RESTART; +import static baseball.model.GameStatus.GAME_RUNNING; +import static baseball.model.GameStatus.GAME_TERMINATE; + +public class GameController { + private static GameStatus gameStatus; + private Computer computer; + private User user; + + public GameController() { + computer = new Computer(); + gameStatus = GAME_RUNNING; + } + + public void run() { + // 게임 시작 + OutputView.printGameStart(); + + while (gameStatus.isGameNotTerminated()) { + // User - Baseball 입력 + readUserBaseballInput(); + + // 게임 결과 확인 + Result result = judgeGameByReferee(); + OutputView.printGameResult(result); + + // 게임 클리어 확인 + checkGameClear(result); + } + } + + private void readUserBaseballInput() { + List userBaseballs = InputView.readUserBaseballs(); + user = new User(userBaseballs); + } + + private Result judgeGameByReferee() { + return Referee.judge(computer.getBaseballs(), user.getBaseballs()); + } + + private void checkGameClear(final Result result) { + if (result.isGameClear()) { + OutputView.printGameClear(); + determineGameRestartOrEnd(); + } + } + + private void determineGameRestartOrEnd() { + int userCommand = InputView.readUserRestartCommand(); + GameProcessDecider decider = GameProcessDecider.getDecider(userCommand); + + if (decider == GAME_RESTART) { + computer = new Computer(); + gameStatus = GAME_RUNNING; + } else { + gameStatus = GAME_TERMINATE; + } + } +} diff --git a/src/main/java/baseball/model/Baseballs.java b/src/main/java/baseball/model/Baseballs.java new file mode 100644 index 0000000..c0df58e --- /dev/null +++ b/src/main/java/baseball/model/Baseballs.java @@ -0,0 +1,51 @@ +package baseball.model; + +import java.util.Collections; +import java.util.List; + +import static baseball.utils.BaseballConstants.*; +import static baseball.utils.ExceptionConstants.BaseballException.*; + +public class Baseballs { + private final List baseballs; + + public Baseballs(final List baseballs) { + validateEachBaseballElementIsInRange(baseballs); + validateTotalBaseballSize(baseballs); + validateBaseballHasDuplicateNumber(baseballs); + this.baseballs = baseballs; + } + + private void validateEachBaseballElementIsInRange(final List baseballs) { + if (hasOutOfRange(baseballs)) { + throw new IllegalArgumentException(BASEBALL_IS_NOT_IN_RANGE.message); + } + } + + private boolean hasOutOfRange(final List baseballs) { + return baseballs.stream() + .anyMatch(baseball -> baseball < MIN_BASEBALL || baseball > MAX_BASEBALL); + } + + private void validateTotalBaseballSize(final List baseballs) { + if (baseballs.size() != BASEBALL_SIZE) { + throw new IllegalArgumentException(BASEBALL_SIZE_IS_NOT_FULFILL.message); + } + } + + private void validateBaseballHasDuplicateNumber(final List baseballs) { + if (hasDuplicateNumber(baseballs)) { + throw new IllegalArgumentException(BASEBALL_MUST_BE_UNIQUE.message); + } + } + + private boolean hasDuplicateNumber(final List baseballs) { + return baseballs.stream() + .distinct() + .count() != BASEBALL_SIZE; + } + + public List getBaseballs() { + return Collections.unmodifiableList(baseballs); + } +} diff --git a/src/main/java/baseball/model/Computer.java b/src/main/java/baseball/model/Computer.java new file mode 100644 index 0000000..e0eaa74 --- /dev/null +++ b/src/main/java/baseball/model/Computer.java @@ -0,0 +1,48 @@ +package baseball.model; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.List; + +import static baseball.utils.BaseballConstants.*; + +public class Computer { + private final Baseballs baseballs; + + public Computer() { + this.baseballs = generateRandomBaseballs(); + } + + private Baseballs generateRandomBaseballs() { + List baseballs = new ArrayList<>(); + + while (isNotReachedLimitSize(baseballs)) { + int randomNumber = getRandomNumberInRange(); + if (isRandomNumberAbsentInBaseballs(baseballs, randomNumber)) { + baseballs.add(randomNumber); + } + } + + return new Baseballs(baseballs); + } + + private boolean isNotReachedLimitSize(final List baseballs) { + return baseballs.size() < BASEBALL_SIZE; + } + + private int getRandomNumberInRange() { + return Randoms.pickNumberInRange(MIN_BASEBALL, MAX_BASEBALL); + } + + private boolean isRandomNumberAbsentInBaseballs( + final List baseballs, + final int randomNumber + ) { + return !baseballs.contains(randomNumber); + } + + public List getBaseballs() { + return baseballs.getBaseballs(); + } +} diff --git a/src/main/java/baseball/model/GameProcessDecider.java b/src/main/java/baseball/model/GameProcessDecider.java new file mode 100644 index 0000000..fba54b4 --- /dev/null +++ b/src/main/java/baseball/model/GameProcessDecider.java @@ -0,0 +1,24 @@ +package baseball.model; + +import java.util.Arrays; + +import static baseball.utils.ExceptionConstants.GameProcessCommandException.INVALID_COMMAND; + +public enum GameProcessDecider { + GAME_RESTART(1), + GAME_END(2), + ; + + private final int command; + + GameProcessDecider(final int command) { + this.command = command; + } + + public static GameProcessDecider getDecider(final int userCommand) { + return Arrays.stream(values()) + .filter(restartDecider -> restartDecider.command == userCommand) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_COMMAND.message)); + } +} diff --git a/src/main/java/baseball/model/GameStatus.java b/src/main/java/baseball/model/GameStatus.java new file mode 100644 index 0000000..056b21d --- /dev/null +++ b/src/main/java/baseball/model/GameStatus.java @@ -0,0 +1,11 @@ +package baseball.model; + +public enum GameStatus { + GAME_RUNNING, + GAME_TERMINATE, + ; + + public boolean isGameNotTerminated() { + return this != GAME_TERMINATE; + } +} diff --git a/src/main/java/baseball/model/Referee.java b/src/main/java/baseball/model/Referee.java new file mode 100644 index 0000000..318f02b --- /dev/null +++ b/src/main/java/baseball/model/Referee.java @@ -0,0 +1,55 @@ +package baseball.model; + +import java.util.List; +import java.util.stream.IntStream; + +import static baseball.utils.BaseballConstants.BASEBALL_SIZE; + +public class Referee { + private Referee() { + } + + public static Result judge( + final List computerBaseballs, + final List userBaseballs + ) { + final int strikeCount = calculateStrikeCount(computerBaseballs, userBaseballs); + final int ballCount = calculateBallCount(computerBaseballs, userBaseballs); + + return new Result(strikeCount, ballCount); + } + + private static int calculateStrikeCount( + final List computerBaseballs, + final List userBaseballs + ) { + return (int) IntStream.range(0, BASEBALL_SIZE) + .filter(index -> isExactlyMatchAtIndex(computerBaseballs, userBaseballs, index)) + .count(); + } + + private static boolean isExactlyMatchAtIndex( + final List computerBaseballs, + final List userBaseballs, + final int index + ) { + return computerBaseballs.get(index).equals(userBaseballs.get(index)); + } + + private static int calculateBallCount( + final List computerBaseballs, + final List userBaseballs + ) { + return getContainedElementsCount(computerBaseballs, userBaseballs) + - calculateStrikeCount(computerBaseballs, userBaseballs); + } + + private static int getContainedElementsCount( + final List computerBaseballs, + final List userBaseballs + ) { + return (int) userBaseballs.stream() + .filter(computerBaseballs::contains) + .count(); + } +} diff --git a/src/main/java/baseball/model/Result.java b/src/main/java/baseball/model/Result.java new file mode 100644 index 0000000..f0ba3ff --- /dev/null +++ b/src/main/java/baseball/model/Result.java @@ -0,0 +1,25 @@ +package baseball.model; + +import static baseball.utils.BaseballConstants.BASEBALL_SIZE; + +public class Result { + private final int strikeCount; + private final int ballCount; + + public Result(final int strikeCount, final int ballCount) { + this.strikeCount = strikeCount; + this.ballCount = ballCount; + } + + public int getStrikeCount() { + return strikeCount; + } + + public int getBallCount() { + return ballCount; + } + + public boolean isGameClear() { + return strikeCount == BASEBALL_SIZE; + } +} diff --git a/src/main/java/baseball/model/User.java b/src/main/java/baseball/model/User.java new file mode 100644 index 0000000..b6d8379 --- /dev/null +++ b/src/main/java/baseball/model/User.java @@ -0,0 +1,15 @@ +package baseball.model; + +import java.util.List; + +public class User { + private final Baseballs baseballs; + + public User(final List baseballs) { + this.baseballs = new Baseballs(baseballs); + } + + public List getBaseballs() { + return baseballs.getBaseballs(); + } +} diff --git a/src/main/java/baseball/utils/BaseballConstants.java b/src/main/java/baseball/utils/BaseballConstants.java new file mode 100644 index 0000000..be546a8 --- /dev/null +++ b/src/main/java/baseball/utils/BaseballConstants.java @@ -0,0 +1,7 @@ +package baseball.utils; + +public interface BaseballConstants { + int MIN_BASEBALL = 1; + int MAX_BASEBALL = 9; + int BASEBALL_SIZE = 3; +} diff --git a/src/main/java/baseball/utils/ExceptionConstants.java b/src/main/java/baseball/utils/ExceptionConstants.java new file mode 100644 index 0000000..cc4985d --- /dev/null +++ b/src/main/java/baseball/utils/ExceptionConstants.java @@ -0,0 +1,39 @@ +package baseball.utils; + +public interface ExceptionConstants { + enum BaseballException { + BASEBALL_IS_NOT_IN_RANGE("숫자는 1..9 범위만 허용합니다."), + BASEBALL_SIZE_IS_NOT_FULFILL("숫자 야구 게임을 위해서 숫자 3개가 필요합니다."), + BASEBALL_MUST_BE_UNIQUE("중복된 숫자는 허용하지 않습니다."), + ; + + public final String message; + + BaseballException(final String message) { + this.message = message; + } + } + + enum GameProcessCommandException { + INVALID_COMMAND("재시작[1] / 종료[2] 중 하나를 입력해주세요."), + ; + + public final String message; + + GameProcessCommandException(final String message) { + this.message = message; + } + } + + enum InputException { + INPUT_MUST_BE_NUMERIC("숫자를 입력해주세요."), + INPUT_MUST_NOT_CONTAINS_SPACE("공백없이 숫자를 입력해주세요."), + ; + + public final String message; + + InputException(final String message) { + this.message = message; + } + } +} diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 0000000..388b084 --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,55 @@ +package baseball.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static baseball.utils.ExceptionConstants.InputException.INPUT_MUST_BE_NUMERIC; +import static baseball.utils.ExceptionConstants.InputException.INPUT_MUST_NOT_CONTAINS_SPACE; + +public class InputView { + public static List readUserBaseballs() { + System.out.print("숫자를 입력해주세요 : "); + + String userInput = Console.readLine(); + validateInputHasSpace(userInput); + validateInputIsNumeric(userInput); + + return convertUserInputToIntegerList(userInput); + } + + private static void validateInputHasSpace(final String userInput) { + if (hasSpace(userInput)) { + throw new IllegalArgumentException(INPUT_MUST_NOT_CONTAINS_SPACE.message); + } + } + + private static boolean hasSpace(String userInput) { + return userInput.chars() + .anyMatch(Character::isWhitespace); + } + + private static void validateInputIsNumeric(final String userInput) { + try { + Integer.parseInt(userInput); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException(INPUT_MUST_BE_NUMERIC.message); + } + } + + private static List convertUserInputToIntegerList(final String userInput) { + return Arrays.stream(userInput.split("")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public static int readUserRestartCommand() { + String userInput = Console.readLine(); + validateInputHasSpace(userInput); + validateInputIsNumeric(userInput); + + return Integer.parseInt(userInput); + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 0000000..22721d3 --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,59 @@ +package baseball.view; + +import baseball.model.Result; + +import static baseball.utils.BaseballConstants.BASEBALL_SIZE; + +public class OutputView { + private static final String ENTER = "\n"; + private static final String SEPARATOR = " "; + + private static final String NOTHING = "낫싱"; + private static final String STRIKE = "스트라이크"; + private static final String BALL = "볼"; + + public static void printGameStart() { + System.out.println("숫자 야구 게임을 시작합니다."); + } + + public static void printGameResult(final Result result) { + System.out.println(refineGameResult(result)); + } + + private static String refineGameResult(final Result result) { + if (!hasStrikeCount(result) && !hasBallCount(result)) { + return NOTHING; + } + + StringBuilder resultFormat = new StringBuilder(); + + if (hasBallCount(result)) { + resultFormat.append(result.getBallCount()) + .append(BALL) + .append(SEPARATOR); + } + + if (hasStrikeCount(result)) { + resultFormat.append(result.getStrikeCount()) + .append(STRIKE); + } + + return resultFormat.toString(); + } + + private static boolean hasBallCount(final Result result) { + return result.getBallCount() > 0; + } + + private static boolean hasStrikeCount(final Result result) { + return result.getStrikeCount() > 0; + } + + public static void printGameClear() { + System.out.printf( + "%d개의 숫자를 모두 맞히셨습니다! 게임 종료" + ENTER + + "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." + ENTER, + BASEBALL_SIZE + ); + } +} diff --git a/src/test/java/baseball/ApplicationTest.java b/src/test/java/baseball/ApplicationTest.java index 3fa29fa..dd218f6 100644 --- a/src/test/java/baseball/ApplicationTest.java +++ b/src/test/java/baseball/ApplicationTest.java @@ -13,13 +13,88 @@ class ApplicationTest extends NsTest { void 게임종료_후_재시작() { assertRandomNumberInRangeTest( () -> { - run("246", "135", "1", "597", "589", "2"); - assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료"); + run( + "246", + "135", + "1", + "597", + "589", + "2" + ); + assertThat(output()) + .contains( + "낫싱", + "3스트라이크", + "1볼 1스트라이크", + "3스트라이크", + "게임 종료" + ); }, 1, 3, 5, 5, 8, 9 ); } + @Test + void 게임종료_후_재시작2() { + assertRandomNumberInRangeTest( + () -> { + run( + "124", + "154", + "127", + "123", + "2" + ); + assertThat(output()) + .contains( + "2스트라이크", + "1스트라이크", + "2스트라이크", + "3스트라이크", + "게임 종료" + ); + }, + 1, 2, 3 + ); + } + + @Test + void 게임종료_후_재시작3() { + assertRandomNumberInRangeTest( + () -> { + run( + "123", + "128", + "813", + "782", + "287", + "987", + "1", + "316", + "312", + "213", + "123", + "2" + ); + assertThat(output()) + .contains( + "낫싱", + "1볼", + "1볼", + "1볼 1스트라이크", + "2스트라이크", + "3스트라이크", + "2볼", + "3볼", + "2볼 1스트라이크", + "3스트라이크", + "게임 종료" + ); + }, + 9, 8, 7, 1, 2, 3 + ); + } + @Test void 예외_테스트() { assertSimpleTest(() -> diff --git a/src/test/java/baseball/model/BaseballsTest.java b/src/test/java/baseball/model/BaseballsTest.java new file mode 100644 index 0000000..8282ed1 --- /dev/null +++ b/src/test/java/baseball/model/BaseballsTest.java @@ -0,0 +1,71 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import static baseball.utils.ExceptionConstants.BaseballException.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class BaseballsTest { + @ParameterizedTest + @MethodSource("invalidRange") + @DisplayName("1..9 범위가 아닌 숫자가 존재하면 Baseballs를 생성할 수 없다") + void throwExceptionByBaseballIsNotInRange(List baseballs) { + assertThatThrownBy(() -> new Baseballs(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_IS_NOT_IN_RANGE.message); + } + + private static Stream invalidRange() { + return Stream.of( + Arguments.of(List.of(0, 1, 9)), + Arguments.of(List.of(1, 9, 10)) + ); + } + + @ParameterizedTest + @MethodSource("invalidSize") + @DisplayName("숫자가 3개 미만이면 Baseballs를 생성할 수 없다") + void throwExceptionByBaseballSizeNotFulfill(List baseballs) { + assertThatThrownBy(() -> new Baseballs(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_SIZE_IS_NOT_FULFILL.message); + } + + private static Stream invalidSize() { + return Stream.of( + Arguments.of(List.of()), + Arguments.of(List.of(1)), + Arguments.of(List.of(1, 2)) + ); + } + + @ParameterizedTest + @MethodSource("duplicateNumber") + @DisplayName("중복된 숫자가 존재하면 Baseballs를 생성할 수 없다") + void throwExceptionByBaseballIsNotUnique(List baseballs) { + assertThatThrownBy(() -> new Baseballs(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_MUST_BE_UNIQUE.message); + } + + private static Stream duplicateNumber() { + return Stream.of( + Arguments.of(List.of(1, 2, 2)), + Arguments.of(List.of(3, 3, 3)) + ); + } + + @Test + @DisplayName("Baseballs를 생성한다") + void construct() { + assertDoesNotThrow(() -> new Baseballs(List.of(1, 2, 3))); + } +} diff --git a/src/test/java/baseball/model/ComputerTest.java b/src/test/java/baseball/model/ComputerTest.java new file mode 100644 index 0000000..ea771bb --- /dev/null +++ b/src/test/java/baseball/model/ComputerTest.java @@ -0,0 +1,27 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class ComputerTest { + @Test + @DisplayName("Baseballs 랜덤 제공을 통해서 Computer를 생성한다") + void construct() { + // when + final Computer computer = new Computer(); + + // then + assertAll( + () -> assertThat(computer.getBaseballs()).hasSize(3), // size + () -> assertThat( + computer.getBaseballs() + .stream() + .distinct() + .count() + ).isEqualTo(3) // has duplicate + ); + } +} diff --git a/src/test/java/baseball/model/GameProcessDeciderTest.java b/src/test/java/baseball/model/GameProcessDeciderTest.java new file mode 100644 index 0000000..1335be9 --- /dev/null +++ b/src/test/java/baseball/model/GameProcessDeciderTest.java @@ -0,0 +1,40 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static baseball.model.GameProcessDecider.GAME_END; +import static baseball.model.GameProcessDecider.GAME_RESTART; +import static baseball.utils.ExceptionConstants.GameProcessCommandException.INVALID_COMMAND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class GameProcessDeciderTest { + @Test + @DisplayName("재시작[1], 종료[2] 이외의 Command를 입력하면 예외가 발생한다") + void throwExceptionByInvalidCommand() { + assertThatThrownBy(() -> GameProcessDecider.getDecider(3)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_COMMAND.message); + } + + @Test + @DisplayName("게임 재시작[1] Decider를 선택한다") + void selectGameRestart() { + // when + GameProcessDecider decider = GameProcessDecider.getDecider(1); + + // then + assertThat(decider).isEqualTo(GAME_RESTART); + } + + @Test + @DisplayName("게임 종료[2] Decider를 선택한다") + void selectGameEnd() { + // when + GameProcessDecider decider = GameProcessDecider.getDecider(2); + + // then + assertThat(decider).isEqualTo(GAME_END); + } +} diff --git a/src/test/java/baseball/model/GameStatusTest.java b/src/test/java/baseball/model/GameStatusTest.java new file mode 100644 index 0000000..7bc593e --- /dev/null +++ b/src/test/java/baseball/model/GameStatusTest.java @@ -0,0 +1,20 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static baseball.model.GameStatus.GAME_RUNNING; +import static baseball.model.GameStatus.GAME_TERMINATE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class GameStatusTest { + @Test + @DisplayName("GameStatus가 TERMINATED가 아닌지 확인한다") + void isGameNotTerminated() { + assertAll( + () -> assertThat(GAME_RUNNING.isGameNotTerminated()).isTrue(), + () -> assertThat(GAME_TERMINATE.isGameNotTerminated()).isFalse() + ); + } +} diff --git a/src/test/java/baseball/model/RefereeTest.java b/src/test/java/baseball/model/RefereeTest.java new file mode 100644 index 0000000..49ac51c --- /dev/null +++ b/src/test/java/baseball/model/RefereeTest.java @@ -0,0 +1,112 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class RefereeTest { + @ParameterizedTest + @MethodSource("dummyBaseballs") + @DisplayName("Referee가 Computer & User 간 숫자 야구 게임 결과(Result)를 도출한다") + void judge( + List computerBaseballs, + List userBaseballs, + int strikeCount, + int ballCount + ) { + // when + Result result = Referee.judge(computerBaseballs, userBaseballs); + + // then + assertAll( + () -> assertThat(result.getStrikeCount()).isEqualTo(strikeCount), + () -> assertThat(result.getBallCount()).isEqualTo(ballCount) + ); + } + + private static Stream dummyBaseballs() { + return Stream.of( + Arguments.of( + List.of(1, 2, 3), + List.of(4, 5, 6), + 0, + 0 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(4, 5, 1), + 0, + 1 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(2, 1, 5), + 0, + 2 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(3, 1, 2), + 0, + 3 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(1, 4, 5), + 1, + 0 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(1, 2, 5), + 2, + 0 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(1, 2, 3), + 3, + 0 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(1, 3, 5), + 1, + 1 + ), + Arguments.of( + List.of(1, 2, 3), + List.of(1, 3, 2), + 1, + 2 + ) + ); + } + + @Test + @DisplayName("Game Clear(All Strike) 여부를 확인한다") + void isGameClear() { + // given + final List computerBaseballs = List.of(1, 2, 3); + final List userBaseballs1 = List.of(1, 2, 3); + final List userBaseballs2 = List.of(1, 2, 4); + + // when + Result result1 = Referee.judge(computerBaseballs, userBaseballs1); + Result result2 = Referee.judge(computerBaseballs, userBaseballs2); + + // then + assertAll( + () -> assertThat(result1.isGameClear()).isTrue(), + () -> assertThat(result2.isGameClear()).isFalse() + ); + } +} diff --git a/src/test/java/baseball/model/UserTest.java b/src/test/java/baseball/model/UserTest.java new file mode 100644 index 0000000..6e85b41 --- /dev/null +++ b/src/test/java/baseball/model/UserTest.java @@ -0,0 +1,61 @@ +package baseball.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static baseball.utils.ExceptionConstants.BaseballException.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class UserTest { + @Test + @DisplayName("입력한 원소중에 1..9 범위가 아닌 원소가 있으면 User는 Baseballs를 가질 수 없다") + void throwExceptionByBaseballIsNotInRange() { + // given + final List baseballs = List.of(1, 9, 10); + + // when - then + assertThatThrownBy(() -> new User(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_IS_NOT_IN_RANGE.message); + } + + @Test + @DisplayName("입력한 원소의 크기가 3이 아니면 User는 Baseballs를 가질 수 없다") + void throwExceptionByBaseballSizeNotFulfill() { + // given + final List baseballs = List.of(1, 2); + + // when - then + assertThatThrownBy(() -> new User(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_SIZE_IS_NOT_FULFILL.message); + } + + @Test + @DisplayName("입력한 원소중에 중복된 원소가 존재한다면 User는 Baseballs를 가질 수 없다") + void throwExceptionByBaseballIsNotUnique() { + // given + final List baseballs = List.of(1, 2, 2); + + // when - then + assertThatThrownBy(() -> new User(baseballs)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BASEBALL_MUST_BE_UNIQUE.message); + } + + @Test + @DisplayName("User를 생성한다") + void construct() { + // given + final List baseballs = List.of(1, 2, 3); + + // when + final User user = new User(baseballs); + + // then + assertThat(user.getBaseballs()).containsExactlyElementsOf(baseballs); + } +}