diff --git a/README.md b/README.md index e69de29..b92c48f 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,30 @@ +# 숫자 야구 게임 + +## 프로그램 흐름 + +1. 컴퓨터는 세자리의 중복되지 않는 임의의 3자리 숫자를 생성한다. +2. 플레이어는 숫자를 수동으로 입력해 컴퓨터가 생성한 숫자를 맞힌다. +3. 컴퓨터는 플레이어가 입력한 값을 받아 게임 결과를 반환한다. + - 3스트라이크가 나오는 경우: 게임이 종료된다. + - 그 외의 경우: 2번부터 다시 수행한다. +4. 게임 종료 후, 게임 재시작 여부를 입력 한다. + - 재시작(1)을 요청하는 경우 + - 1번부터 다시 시작한다. + - 게임 종료(2)를 요청하는 경우 + - 프로그램을 종료한다. + +--- + +## 기능 구현 목록 +- 숫자목록은 3자리의 값을 자동/수동으로 생성한다. + - 수동 생성하는 경우, 콘솔에서 값을 직접 입력 받는다. + - 자동 생성하는 경우, 임의로 값을 생성한다. +- 생성되는 숫자는 1~9의 범위 값만 가질 수 있다. + - 1~9 외의 값을 포함하고 있으면 예외 발생 + - 값이 3자리가 아니면 예외 발생 + - 값들 중 중복 값이 포함되어 있으면 예외 발생 +- 숫자 목록은 다른 숫자 목록을 입력 받아 스트라이크, 볼등의 게임 결과를 계산 할 수 있다. +- 계산한 결과는 게임의 결과(2스트라이크, 1스트라이크 1볼, 낫싱 등)을 알고 있다. +- 계산한 결과는 게임 종료 여부를 반환할 수 있다. (3스트라이크) + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c7156de..4240e7b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { group 'org.example' version '1.0-SNAPSHOT' +sourceCompatibility = '8' repositories { mavenCentral() @@ -11,5 +12,6 @@ repositories { dependencies { testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.0' + testCompile("org.junit.jupiter:junit-jupiter-params:5.4.2") testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c0e3016..0176e2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Sat Jun 20 13:56:54 KST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/main/java/baseball/BaseBallMainApplication.java b/src/main/java/baseball/BaseBallMainApplication.java new file mode 100644 index 0000000..da8d41c --- /dev/null +++ b/src/main/java/baseball/BaseBallMainApplication.java @@ -0,0 +1,54 @@ +package baseball; + +import baseball.domain.AutoNumberBallsGenerator; +import baseball.domain.ManualNumberBallsGenerator; +import baseball.domain.NumberBalls; +import baseball.domain.NumberBallsGenerator; +import baseball.domain.WhetherReplay; +import baseball.domain.Result; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class BaseBallMainApplication { + public static void main(String[] args) { + final NumberBallsGenerator autoGenerator = new AutoNumberBallsGenerator(); + do { + final NumberBalls computerBalls = autoGenerator.generateBalls(); + inputNumberWhileAllStrike(computerBalls); + OutputView.printWinningMessage(); + } while (inputWhetherReplay().isReplay()); + } + + private static void inputNumberWhileAllStrike(final NumberBalls computerBalls) { + while (true) { + final NumberBalls playerBalls = inputPlayerBalls(); + final Result computeResult = computerBalls.compute(playerBalls); + OutputView.printCompareResult(computeResult); + if (computeResult.isAllStrikes()) { + break; + } + } + } + + private static NumberBalls inputPlayerBalls() { + while (true) { + try { + final NumberBallsGenerator manualBallsGenerator = new ManualNumberBallsGenerator( + InputView.inputBaseBallNumbers()); + return manualBallsGenerator.generateBalls(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } + + private static WhetherReplay inputWhetherReplay() { + while (true) { + try { + return WhetherReplay.of(InputView.inputWhetherReplay()); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } +} diff --git a/src/main/java/baseball/domain/AutoNumberBallsGenerator.java b/src/main/java/baseball/domain/AutoNumberBallsGenerator.java new file mode 100644 index 0000000..22d0923 --- /dev/null +++ b/src/main/java/baseball/domain/AutoNumberBallsGenerator.java @@ -0,0 +1,27 @@ +package baseball.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AutoNumberBallsGenerator implements NumberBallsGenerator { + private static final int BALLS_FIRST_INDEX = 0; + + private final List allDistinctNumbers = new ArrayList<>(); + + public AutoNumberBallsGenerator() { + init(); + } + + private void init() { + for (int i = NumberBall.MIN_VALUE; i < NumberBall.MAX_VALUE; i++) { + allDistinctNumbers.add(NumberBall.of(i)); + } + } + + @Override + public NumberBalls generateBalls() { + Collections.shuffle(allDistinctNumbers); + return new NumberBalls(allDistinctNumbers.subList(BALLS_FIRST_INDEX, NumberBalls.BALL_NUMBER_LENGTH)); + } +} diff --git a/src/main/java/baseball/domain/Count.java b/src/main/java/baseball/domain/Count.java new file mode 100644 index 0000000..b73f03a --- /dev/null +++ b/src/main/java/baseball/domain/Count.java @@ -0,0 +1,67 @@ +package baseball.domain; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public final class Count { + private static final int MIN_COUNT_VALUE = 0; + private static final int MAX_COUNT_VALUE = NumberBalls.BALL_NUMBER_LENGTH; + private static final Map COUNT_CACHE; + + private static final String INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE = "볼, 스트라이크수를 계산할 수 있는 유효한 값의 범위가 아닙니다."; + + static { + COUNT_CACHE = new HashMap<>(); + for (int i = MIN_COUNT_VALUE; i <= MAX_COUNT_VALUE; i++) { + COUNT_CACHE.put(i, new Count(i)); + } + } + + private final int value; + + private Count(final int value) { + verifyCountValue(value); + this.value = value; + } + + private void verifyCountValue(final int value) { + if (value < MIN_COUNT_VALUE || value > MAX_COUNT_VALUE) { + throw new IllegalArgumentException(INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE); + } + } + + public static Count of(final int value) { + if (COUNT_CACHE.containsKey(value)) { + return COUNT_CACHE.get(value); + } + throw new IllegalArgumentException(INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE); + } + + public boolean isSameValue(final int countValue) { + return this.value == countValue; + } + + public boolean isSumOfValueGreaterThanMax(final Count otherCount) { + return (this.value + otherCount.value) > NumberBalls.BALL_NUMBER_LENGTH; + } + + public int getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Count count1 = (Count)o; + return value == count1.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/baseball/domain/ManualNumberBallsGenerator.java b/src/main/java/baseball/domain/ManualNumberBallsGenerator.java new file mode 100644 index 0000000..42c96a9 --- /dev/null +++ b/src/main/java/baseball/domain/ManualNumberBallsGenerator.java @@ -0,0 +1,45 @@ +package baseball.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +public class ManualNumberBallsGenerator implements NumberBallsGenerator { + private static final int EXTRACT_COMPLETE_CRITERIA = 0; + private static final int EXTRACT_BALL_NUMBER_DIVIDER = 10; + + private static final String NEGATIVE_NUMBER_EXCEPTION_MESSAGE = "음수는 허용되지 않습니다."; + + private final int number; + + public ManualNumberBallsGenerator(final int number) { + validateNumber(number); + this.number = number; + } + + private void validateNumber(final int number) { + if (number < EXTRACT_COMPLETE_CRITERIA) { + throw new IllegalArgumentException(NEGATIVE_NUMBER_EXCEPTION_MESSAGE); + } + } + + @Override + public NumberBalls generateBalls() { + final List result = new ArrayList<>(); + final Stack numberBalls = extractNumberBalls(); + while (!numberBalls.empty()) { + result.add(numberBalls.pop()); + } + return new NumberBalls(result); + } + + private Stack extractNumberBalls() { + final Stack numberBallStack = new Stack<>(); + int num = number; + while (num > EXTRACT_COMPLETE_CRITERIA) { + numberBallStack.push(NumberBall.of(num % EXTRACT_BALL_NUMBER_DIVIDER)); + num /= EXTRACT_BALL_NUMBER_DIVIDER; + } + return numberBallStack; + } +} diff --git a/src/main/java/baseball/domain/NumberBall.java b/src/main/java/baseball/domain/NumberBall.java new file mode 100644 index 0000000..185b056 --- /dev/null +++ b/src/main/java/baseball/domain/NumberBall.java @@ -0,0 +1,54 @@ +package baseball.domain; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public final class NumberBall { + static final int MIN_VALUE = 1; + static final int MAX_VALUE = 9; + private static final Map BALL_CACHE = new HashMap<>(); + + private static final String INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE = "주어진 범위 바깥의 볼은 사용할 수 없어요."; + + static { + for (int i = MIN_VALUE; i <= MAX_VALUE; i++) { + BALL_CACHE.put(i, new NumberBall(i)); + } + } + + private final int value; + + private NumberBall(final int value) { + verifyNumberBall(value); + this.value = value; + } + + private void verifyNumberBall(final int value) { + if (value < MIN_VALUE || value > MAX_VALUE) { + throw new IllegalArgumentException(INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE); + } + } + + public static NumberBall of(final int value) { + if (BALL_CACHE.containsKey(value)) { + return BALL_CACHE.get(value); + } + throw new IllegalArgumentException(INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + NumberBall that = (NumberBall)o; + return value == that.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/baseball/domain/NumberBalls.java b/src/main/java/baseball/domain/NumberBalls.java new file mode 100644 index 0000000..e0b1fd6 --- /dev/null +++ b/src/main/java/baseball/domain/NumberBalls.java @@ -0,0 +1,92 @@ +package baseball.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public final class NumberBalls { + static final int BALL_NUMBER_LENGTH = 3; + private static final int DEFAULT_STRIKE_COUNT_NUMBER = 0; + + private static final String INVALID_BALLS_ARGUMENT_EXCEPTION_MESSAGE = "입력 값이 유효하지 않습니다."; + private static final String DUPLICATE_NUMBER_CONTAINS_EXCEPTION_MESSAGE = "중복된 값은 들어갈 수 없습니다."; + private static final String NULL_ARGUMENT_EXCEPTION_MESSAGE = "Null객체는 허용되지 않습니다."; + + private final List balls; + + public NumberBalls(final List balls) { + verifyBalls(balls); + this.balls = Collections.unmodifiableList(new ArrayList<>(balls)); + } + + private void verifyBalls(final List balls) { + verifyNonNull(balls); + verifyNoContainsDuplicateBall(balls); + verifyLength(balls); + } + + private void verifyNonNull(final List balls) { + if (Objects.isNull(balls)) { + throw new IllegalArgumentException(NULL_ARGUMENT_EXCEPTION_MESSAGE); + } + } + + private void verifyNoContainsDuplicateBall(final List balls) { + final Set numberBalls = new HashSet<>(balls); + if (balls.size() != numberBalls.size()) { + throw new IllegalArgumentException(DUPLICATE_NUMBER_CONTAINS_EXCEPTION_MESSAGE); + } + } + + private void verifyLength(final List balls) { + if (balls.size() != BALL_NUMBER_LENGTH) { + throw new IllegalArgumentException(INVALID_BALLS_ARGUMENT_EXCEPTION_MESSAGE); + } + } + + public Result compute(final NumberBalls other) { + final int strike = computeStrike(other); + final int ball = computeBall(other, strike); + return Result.of(strike, ball); + } + + private int computeStrike(final NumberBalls other) { + int strike = DEFAULT_STRIKE_COUNT_NUMBER; + for (int i = 0; i < this.balls.size(); i++) { + if (this.balls.get(i).equals(other.balls.get(i))) { + strike++; + } + } + return strike; + } + + private int computeBall(final NumberBalls other, final int strike) { + final int ballCandidateCount = computeCountOfBallCandidate(other); + return ballCandidateCount - strike; + } + + private int computeCountOfBallCandidate(final NumberBalls other) { + final List otherBalls = other.balls; + final Set distinctNumbers = new HashSet<>(this.balls); + distinctNumbers.addAll(otherBalls); + return this.balls.size() + otherBalls.size() - distinctNumbers.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + NumberBalls that = (NumberBalls)o; + return Objects.equals(balls, that.balls); + } + + @Override + public int hashCode() { + return Objects.hash(balls); + } +} diff --git a/src/main/java/baseball/domain/NumberBallsGenerator.java b/src/main/java/baseball/domain/NumberBallsGenerator.java new file mode 100644 index 0000000..465cfbd --- /dev/null +++ b/src/main/java/baseball/domain/NumberBallsGenerator.java @@ -0,0 +1,5 @@ +package baseball.domain; + +public interface NumberBallsGenerator { + NumberBalls generateBalls(); +} diff --git a/src/main/java/baseball/domain/Result.java b/src/main/java/baseball/domain/Result.java new file mode 100644 index 0000000..74a2db1 --- /dev/null +++ b/src/main/java/baseball/domain/Result.java @@ -0,0 +1,59 @@ +package baseball.domain; + +import java.util.Objects; + +public final class Result { + private static final String INVALID_TOTAL_COUNT_OF_STRIKE_BALL_EXCEPTION_MESSAGE = + "스트라이크와 볼의 값의 합이 현재 가능한 값을 넘었습니다."; + private static final String NULL_COUNT_EXCEPTION_MESSAGE = "NULL값이 들어갈 수 없습니다."; + + private final Count strike; + private final Count ball; + + private Result(final Count strike, final Count ball) { + verifyCounts(strike, ball); + this.strike = strike; + this.ball = ball; + } + + private void verifyCounts(final Count strikeCount, final Count ballCount) { + if (Objects.isNull(strikeCount) || Objects.isNull(ballCount)) { + throw new IllegalArgumentException(NULL_COUNT_EXCEPTION_MESSAGE); + } + if (strikeCount.isSumOfValueGreaterThanMax(ballCount)) { + throw new IllegalArgumentException(INVALID_TOTAL_COUNT_OF_STRIKE_BALL_EXCEPTION_MESSAGE); + } + } + + public static Result of(final int strikeCount, final int ballCount) { + return new Result(Count.of(strikeCount), Count.of(ballCount)); + } + + public boolean isAllStrikes() { + return strike.isSameValue(NumberBalls.BALL_NUMBER_LENGTH); + } + + public Count getStrike() { + return strike; + } + + public Count getBall() { + return ball; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Result result = (Result)o; + return strike == result.strike && + ball == result.ball; + } + + @Override + public int hashCode() { + return Objects.hash(strike, ball); + } +} diff --git a/src/main/java/baseball/domain/WhetherReplay.java b/src/main/java/baseball/domain/WhetherReplay.java new file mode 100644 index 0000000..d1e4213 --- /dev/null +++ b/src/main/java/baseball/domain/WhetherReplay.java @@ -0,0 +1,26 @@ +package baseball.domain; + +import java.util.Arrays; + +public enum WhetherReplay { + YES("1"), NO("2"); + + private static final String INVALID_PLAYER_CHOICE_CODE_EXCEPTION_MESSAGE = "유효한 값이 아닙니다."; + + private final String chooseCode; + + WhetherReplay(final String chooseCode) { + this.chooseCode = chooseCode; + } + + public static WhetherReplay of(final String value) { + return Arrays.stream(values()) + .filter(whetherReplayValue -> whetherReplayValue.chooseCode.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_PLAYER_CHOICE_CODE_EXCEPTION_MESSAGE)); + } + + public boolean isReplay() { + return this == YES; + } +} diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 0000000..486bb40 --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,28 @@ +package baseball.view; + +import java.util.Scanner; + +public class InputView { + private static final Scanner SCANNER = new Scanner(System.in); + private static final String NON_NUMBER_INPUT_EXCEPTION_MESSAGE = "숫자 외의 값은 입력 받을 수 없습니다."; + private static final String INPUT_PLAY_AGAIN_INTRO_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.: "; + private static final String INPUT_BASEBALL_NUMBER_INTRO_MESSAGE = "숫자를 입력해주세요 : "; + + public static Integer inputBaseBallNumbers() { + System.out.print(INPUT_BASEBALL_NUMBER_INTRO_MESSAGE); + return parseInt(SCANNER.next()); + } + + private static Integer parseInt(final String inputValue) { + try { + return Integer.parseInt(inputValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(NON_NUMBER_INPUT_EXCEPTION_MESSAGE); + } + } + + public static String inputWhetherReplay() { + System.out.print(INPUT_PLAY_AGAIN_INTRO_MESSAGE); + return SCANNER.next(); + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 0000000..3feef6c --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,60 @@ +package baseball.view; + +import baseball.domain.Count; +import baseball.domain.Result; + +public class OutputView { + private static final int NOT_EXIST_VALUE = 0; + + private static final String STRIKE_MESSAGE = "스트라이크"; + private static final String BALL_MESSAGE = "볼"; + private static final String NOTHING_MESSAGE = "낫싱"; + private static final String GAME_END_MESSAGE = "3개의 숫자를 모두 맞혔습니다. 게임종료."; + + private static final String SPLITTER = ", "; + private static final String LINE_SEPARATOR = System.lineSeparator(); + + public static void printCompareResult(final Result compute) { + final Count strike = compute.getStrike(); + final Count ball = compute.getBall(); + if (isNothing(strike, ball)) { + System.out.println(NOTHING_MESSAGE); + return; + } + printResultWithAnyMatchingInfo(strike, ball); + } + + private static boolean isNothing(final Count strike, final Count ball) { + return strike.getValue() == NOT_EXIST_VALUE && ball.getValue() == NOT_EXIST_VALUE; + } + + private static void printResultWithAnyMatchingInfo(final Count strike, final Count ball) { + final StringBuilder result = new StringBuilder(); + appendMessageIfValueExists(result, strike.getValue(), STRIKE_MESSAGE); + appendCommaIfAllResultExists(result, strike.getValue(), ball.getValue()); + appendMessageIfValueExists(result, ball.getValue(), BALL_MESSAGE); + result.append(LINE_SEPARATOR); + System.out.print(result); + } + + private static void appendCommaIfAllResultExists(final StringBuilder result, final int strike, final int ball) { + if (strike * ball != NOT_EXIST_VALUE) { + result.append(SPLITTER); + } + } + + private static void appendMessageIfValueExists(final StringBuilder result, final int value, final String message) { + if (value != NOT_EXIST_VALUE) { + result.append(value); + result.append(message); + } + } + + public static void printWinningMessage() { + System.out.println(GAME_END_MESSAGE); + } + + public static void printErrorMessage(final Exception e) { + System.out.println(e.getMessage()); + } +} diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/baseball/domain/AutoNumberBallsGeneratorTest.java b/src/test/java/baseball/domain/AutoNumberBallsGeneratorTest.java new file mode 100644 index 0000000..d2b8617 --- /dev/null +++ b/src/test/java/baseball/domain/AutoNumberBallsGeneratorTest.java @@ -0,0 +1,21 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AutoNumberBallsGeneratorTest { + @DisplayName("숫자목록 자동 생성시 예외 발생 없이 숫자목록 객체가 정상 생성 된다.") + @Test + void generatingTest() { + NumberBallsGenerator numberBallsGenerator = new AutoNumberBallsGenerator(); + assertThatCode(() -> generateNumberByAuto(numberBallsGenerator, 500)).doesNotThrowAnyException(); + } + + private void generateNumberByAuto(NumberBallsGenerator numberBallsGenerator, int times) { + for (int i = 0; i < times; i++) { + numberBallsGenerator.generateBalls(); + } + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/CountTest.java b/src/test/java/baseball/domain/CountTest.java new file mode 100644 index 0000000..794466e --- /dev/null +++ b/src/test/java/baseball/domain/CountTest.java @@ -0,0 +1,41 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class CountTest { + @DisplayName("스트라이크 혹은 볼의 최소, 최대 가능 갯수를 인자로 count 객체 생성시 정상적으로 객체 생성") + @ParameterizedTest + @ValueSource(ints = {0, 3}) + void create_test(int count) { + assertThatCode(() -> Count.of(count)).doesNotThrowAnyException(); + } + + @DisplayName("스트라이크 혹은 볼의 갯수로 가능하지 않는 값을 인자로 count 객체 생성시 IllegalArgumentException 발생") + @ParameterizedTest + @ValueSource(ints = {-1, 4}) + void create_test_fail(int count) { + assertThatThrownBy(() -> Count.of(count)).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("count 객체가 인자값에 해당하는 값을 가지고 있으면 true, 그렇지 않으면 false 반환") + @ParameterizedTest + @CsvSource(value = {"3, 3, true", "2, 3, false"}) + void isSameValueTest(int countValue, int comparer, boolean expected) { + Count count = Count.of(countValue); + assertThat(count.isSameValue(comparer)).isEqualTo(expected); + } + + @DisplayName("두 count객체의 value의 값의 합이 숫자의 자릿수를 넘으면 true, 넘지 않으면 false 반환") + @ParameterizedTest + @CsvSource(value = {"3, 3, true", "0, 3, false", "1, 2, false", "0, 0, false"}) + void isSumOfValueGreaterThanMax(int firstValue, int secondValue, boolean expected) { + Count firstCount = Count.of(firstValue); + Count secondCount = Count.of(secondValue); + assertThat(firstCount.isSumOfValueGreaterThanMax(secondCount)).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/ManualNumberBallsGeneratorTest.java b/src/test/java/baseball/domain/ManualNumberBallsGeneratorTest.java new file mode 100644 index 0000000..bf214f4 --- /dev/null +++ b/src/test/java/baseball/domain/ManualNumberBallsGeneratorTest.java @@ -0,0 +1,29 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.assertj.core.util.Lists; +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 ManualNumberBallsGeneratorTest { + @DisplayName("중복이 없고 0이 포함되지 않은 세자리의 자연수를 숫자 목록 객체로 생성한다.") + @Test + void generateBalls_test() { + NumberBallsGenerator numberBallsGenerator = new ManualNumberBallsGenerator(321); + assertThat(numberBallsGenerator.generateBalls()) + .isEqualTo(new NumberBalls(Lists.list(NumberBall.of(3), NumberBall.of(2), NumberBall.of(1)))); + } + + @DisplayName("중복값 포함 혹은 세자리가 아니거나 음수인경우 생성도중 IllegalArgumentException 발생") + @ParameterizedTest + @ValueSource(ints = {333, 1234, 75, -152, -10}) + void generateBalls_test_fail(int value) { + assertThatThrownBy(() -> { + NumberBallsGenerator numberBallsGenerator = new ManualNumberBallsGenerator(value); + numberBallsGenerator.generateBalls(); + }).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/NumberBallTest.java b/src/test/java/baseball/domain/NumberBallTest.java new file mode 100644 index 0000000..cc0eea7 --- /dev/null +++ b/src/test/java/baseball/domain/NumberBallTest.java @@ -0,0 +1,24 @@ +package baseball.domain; + +import static 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 NumberBallTest { + @DisplayName("1-9 범위의 숫자를 입력받아 캐싱된 Ball객체를 반환한다.") + @ParameterizedTest + @ValueSource(ints = {1, 9}) + void create_test(int value) { + assertThatCode(() -> NumberBall.of(value)).doesNotThrowAnyException(); + } + + @DisplayName("1-9 바깥 범위의 Ball 객체 획득 시도시, IllegalArgumentException 발생") + @ParameterizedTest + @ValueSource(ints = {0, 10}) + void create_test_out_of_bound_exception(int invalidValue) { + assertThatThrownBy(() -> NumberBall.of(invalidValue)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/NumberBallsTest.java b/src/test/java/baseball/domain/NumberBallsTest.java new file mode 100644 index 0000000..c612ba5 --- /dev/null +++ b/src/test/java/baseball/domain/NumberBallsTest.java @@ -0,0 +1,79 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.util.Lists.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +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 NumberBallsTest { + private NumberBall numberOne; + private NumberBall numberTwo; + private NumberBall numberThree; + + @BeforeEach + void setUp() { + numberOne = NumberBall.of(1); + numberTwo = NumberBall.of(2); + numberThree = NumberBall.of(3); + } + + @DisplayName("중복이 없고, NumberBall를 3개 입력 받는 경우 NumberBalls 생성이 된다.") + @Test + void create_test() { + assertThatCode(() -> new NumberBalls(list(numberOne, numberTwo, numberThree))) + .doesNotThrowAnyException(); + } + + @DisplayName("숫자 목록 생성시 중복된 숫자객체가 포함되어 있는 경우 IllegalArgumentException 발생") + @Test + void create_exception_when_contains_duplicate_number() { + NumberBall sameBallWithFirstBall = numberOne; + assertThatThrownBy(() -> new NumberBalls(list(numberOne, numberTwo, sameBallWithFirstBall))) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("숫자 목록 생성시 숫자객체의 size가 유효하지 않는 경우 (3이 아닌경우) IllegalArgumentException 발생") + @Test + void create_exception_when_balls_size_is_invalid() { + NumberBall firstNumber = NumberBall.of(1); + NumberBall secondNumber = NumberBall.of(2); + assertThatThrownBy(() -> new NumberBalls(list(firstNumber, secondNumber))) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("두 숫자 목록을 비교후, 게임 룰에 따른 결과 객체를 반환.") + @ParameterizedTest + @MethodSource("getPlayerBallsAndResultSet") + void compute_baseball_result(NumberBalls playerBalls, Result gameResult) { + NumberBalls computerBalls = new NumberBalls(list(numberOne, numberTwo, numberThree)); + assertThat(computerBalls.compute(playerBalls)).isEqualTo(gameResult); + } + + private static Stream getPlayerBallsAndResultSet() { + NumberBall one = NumberBall.of(1); + NumberBall two = NumberBall.of(2); + NumberBall three = NumberBall.of(3); + NumberBall four = NumberBall.of(4); + NumberBall five = NumberBall.of(5); + NumberBall six = NumberBall.of(6); + + return Stream.of( + Arguments.of(new NumberBalls(list(one, two, three)), Result.of(3, 0)), + Arguments.of(new NumberBalls(list(one, four, three)), Result.of(2, 0)), + Arguments.of(new NumberBalls(list(five, two, six)), Result.of(1, 0)), + Arguments.of(new NumberBalls(list(five, four, six)), Result.of(0, 0)), + Arguments.of(new NumberBalls(list(one, five, two)), Result.of(1, 1)), + Arguments.of(new NumberBalls(list(five, three, six)), Result.of(0, 1)), + Arguments.of(new NumberBalls(list(two, one, three)), Result.of(1, 2)), + Arguments.of(new NumberBalls(list(three, four, two)), Result.of(0, 2)), + Arguments.of(new NumberBalls(list(three, one, two)), Result.of(0, 3)) + ); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/ResultTest.java b/src/test/java/baseball/domain/ResultTest.java new file mode 100644 index 0000000..8414e19 --- /dev/null +++ b/src/test/java/baseball/domain/ResultTest.java @@ -0,0 +1,33 @@ +package baseball.domain; + +import static 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 ResultTest { + @DisplayName("스트라이크, 볼 카운트가 0~3 범위이고 두 카운트의 합이 3을 넘지 않는 경우 결과 객체 생성.") + @ParameterizedTest + @CsvSource(value = {"0, 0", "1, 2", "2, 1", "3, 0", "0, 3"}) + void createTest(int strikeCount, int ballCount) { + assertThatCode(() -> Result.of(strikeCount, ballCount)) + .doesNotThrowAnyException(); + } + + @DisplayName("스트라이크, 볼 카운트가 0~3 범위 밖이거나 두 카운트의 합이 3을 넘는 경우 결과 생성시 IllegalArgumentException 발생") + @ParameterizedTest + @CsvSource(value = {"-1, 0", "0, 4", "-1, 4", "1, 3"}) + void create_fail_when_invalid_count_include(int strikeCount, int ballCount) { + assertThatThrownBy(() -> Result.of(strikeCount, ballCount)) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("결과 객체가 풀 스트라이크 여부를 반환할 수 있다.") + @ParameterizedTest + @CsvSource(value = {"3, true", "2, false"}) + void isAllStrikeTest(int strikeCount, boolean isAllStrike) { + Result result = Result.of(strikeCount, 0); + assertThat(result.isAllStrikes()).isEqualTo(isAllStrike); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/WhetherReplayTest.java b/src/test/java/baseball/domain/WhetherReplayTest.java new file mode 100644 index 0000000..68327cf --- /dev/null +++ b/src/test/java/baseball/domain/WhetherReplayTest.java @@ -0,0 +1,24 @@ +package baseball.domain; + +import static 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.CsvSource; + +class WhetherReplayTest { + @DisplayName("1, 2 값 이외의 값을 입력받은 경우, IllegalArgumentException 발생") + @Test + void create_fail() { + assertThatThrownBy(() -> WhetherReplay.of("3")).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("1을 넣는 경우, 게임 재시작 여부 true, 2를 넣는 경우, 게임 재시작 여부 false를 반환") + @ParameterizedTest + @CsvSource(value = {"1, true", "2, false"}) + void playAgainTest(String inputValue, boolean expected) { + WhetherReplay whetherReplay = WhetherReplay.of(inputValue); + assertThat(whetherReplay.isReplay()).isEqualTo(expected); + } +} \ No newline at end of file