diff --git a/src/main/java/baseball/BaseballApp.java b/src/main/java/baseball/BaseballApp.java new file mode 100644 index 000000000..3b450c113 --- /dev/null +++ b/src/main/java/baseball/BaseballApp.java @@ -0,0 +1,10 @@ +package baseball; + +import baseball.controller.BaseballController; + +public class BaseballApp { + public static void main(String[] args) { + BaseballController baseballController = BaseballConfig.set(); + baseballController.start(); + } +} diff --git a/src/main/java/baseball/BaseballConfig.java b/src/main/java/baseball/BaseballConfig.java new file mode 100644 index 000000000..eec223ec4 --- /dev/null +++ b/src/main/java/baseball/BaseballConfig.java @@ -0,0 +1,16 @@ +package baseball; + +import baseball.controller.BaseballController; +import baseball.model.entity.AnswerNumberImpl; +import baseball.model.service.BaseballService; +import baseball.model.service.BaseballServiceImpl; +import baseball.model.validator.InputValidatorImpl; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class BaseballConfig { + public static BaseballController set() { + BaseballService baseballService = new BaseballServiceImpl(new InputValidatorImpl(), new AnswerNumberImpl()); + return new BaseballController(new InputView(), new OutputView(), baseballService); + } +} diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java new file mode 100644 index 000000000..2ffad0466 --- /dev/null +++ b/src/main/java/baseball/controller/BaseballController.java @@ -0,0 +1,60 @@ +package baseball.controller; + +import baseball.model.dto.BaseballGameResultDto; +import baseball.model.service.BaseballService; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class BaseballController { + + private final InputView inputView; + + private final OutputView outputView; + + private final BaseballService baseballService; + + public BaseballController(InputView inputView, OutputView outputView, BaseballService baseballService) { + this.inputView = inputView; + this.outputView = outputView; + this.baseballService = baseballService; + } + + public void start() { + try { + String numbers = inputView.number(); + baseballService.validateInputValue(numbers); + BaseballGameResultDto gameResult = baseballService.getGameResult(numbers); + outputView.result(gameResult); + end(gameResult.getComplete()); + } catch (IllegalArgumentException e) { + outputView.exception(e.getMessage()); + start(); + } + } + + private void end(Boolean complete) { + if (complete) { + retry(); + } + + if (!complete) { + start(); + } + } + + private void retry() { + String retry = inputView.retry(); + + try { + baseballService.validateRetryInput(retry); + } catch (NumberFormatException e) { + outputView.exception(e.getMessage()); + retry(); + } + + if (retry.equals("1")) { + baseballService.resetAnswerNumber(); + start(); + } + } +} diff --git a/src/main/java/baseball/model/dto/BaseballGameResultDto.java b/src/main/java/baseball/model/dto/BaseballGameResultDto.java new file mode 100644 index 000000000..f1720ccc4 --- /dev/null +++ b/src/main/java/baseball/model/dto/BaseballGameResultDto.java @@ -0,0 +1,35 @@ +package baseball.model.dto; + +public class BaseballGameResultDto { + + private final Integer strikes; + + private final Integer balls; + + private final Boolean nothing; + + private final Boolean complete; + + public BaseballGameResultDto(Integer strikes, Integer balls, Boolean nothing, Boolean complete) { + this.strikes = strikes; + this.balls = balls; + this.nothing = nothing; + this.complete = complete; + } + + public Integer getStrikes() { + return strikes; + } + + public Integer getBalls() { + return balls; + } + + public Boolean getNothing() { + return nothing; + } + + public Boolean getComplete() { + return complete; + } +} diff --git a/src/main/java/baseball/model/entity/AnswerNumber.java b/src/main/java/baseball/model/entity/AnswerNumber.java new file mode 100644 index 000000000..41ed126b6 --- /dev/null +++ b/src/main/java/baseball/model/entity/AnswerNumber.java @@ -0,0 +1,14 @@ +package baseball.model.entity; + +public interface AnswerNumber { + + Integer countStrikes(String number); + + Integer countBalls(String number); + + Boolean isNothing(String number); + + Boolean isComplete(String number); + + void resetAnswerNumber(); +} diff --git a/src/main/java/baseball/model/entity/AnswerNumberImpl.java b/src/main/java/baseball/model/entity/AnswerNumberImpl.java new file mode 100644 index 000000000..789759208 --- /dev/null +++ b/src/main/java/baseball/model/entity/AnswerNumberImpl.java @@ -0,0 +1,86 @@ +package baseball.model.entity; + +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class AnswerNumberImpl implements AnswerNumber { + + private String answerNumber; + + private final Integer limit = 3; + + public AnswerNumberImpl() { + this.answerNumber = generateRandomNumber(); + } + + private String generateRandomNumber() { + Set randomNumbers = getRandomNumberSet(1, 11, limit); + + return randomNumbers.stream() + .map(String::valueOf) + .collect(Collectors.joining()); + } + + private Set getRandomNumberSet(int origin, int bound, int limit) { + Random random = new Random(); + + return random.ints(origin, bound) + .distinct() + .limit(limit) + .boxed() + .collect(Collectors.toSet()); + } + + @Override + public Integer countStrikes(String number) { + String[] answerNumbers = splitInput(answerNumber); + String[] numbers = splitInput(number); + + return (int) IntStream.range(0, limit) + .filter(i -> answerNumbers[i].equals(numbers[i])) + .count(); + } + + @Override + public Integer countBalls(String number) { + String[] answerNumbers = splitInput(answerNumber); + String[] numbers = splitInput(number); + + return (int) IntStream.range(0, limit) + .filter(i -> isBall(numbers[i], i, answerNumbers)) + .count(); + } + + private boolean isBall(String str, int index, String[] stringArr) { + + return IntStream.range(0, limit) + .anyMatch(i -> i != index && stringArr[i].equals(str)); + } + + @Override + public Boolean isNothing(String number) { + if (countStrikes(number).equals(0) && countBalls(number).equals(0)) { + return true; + } + return false; + } + + @Override + public Boolean isComplete(String number) { + if (countStrikes(number).equals(limit)) { + return true; + } + return false; + } + + @Override + public void resetAnswerNumber() { + this.answerNumber = generateRandomNumber(); + } + + private String[] splitInput(String input) { + return input.split(""); + } +} diff --git a/src/main/java/baseball/model/service/BaseballService.java b/src/main/java/baseball/model/service/BaseballService.java new file mode 100644 index 000000000..606d99fde --- /dev/null +++ b/src/main/java/baseball/model/service/BaseballService.java @@ -0,0 +1,14 @@ +package baseball.model.service; + +import baseball.model.dto.BaseballGameResultDto; + +public interface BaseballService { + + void validateInputValue(String input); + + BaseballGameResultDto getGameResult(String input); + + void resetAnswerNumber(); + + void validateRetryInput(String input); +} diff --git a/src/main/java/baseball/model/service/BaseballServiceImpl.java b/src/main/java/baseball/model/service/BaseballServiceImpl.java new file mode 100644 index 000000000..df9e2ed95 --- /dev/null +++ b/src/main/java/baseball/model/service/BaseballServiceImpl.java @@ -0,0 +1,44 @@ +package baseball.model.service; + +import baseball.model.dto.BaseballGameResultDto; +import baseball.model.entity.AnswerNumber; +import baseball.model.validator.InputValidator; + +public class BaseballServiceImpl implements BaseballService { + + private final InputValidator inputValidator; + + private final AnswerNumber answerNumber; + + public BaseballServiceImpl(InputValidator inputValidator, AnswerNumber answerNumber) { + this.inputValidator = inputValidator; + this.answerNumber = answerNumber; + } + + @Override + public void validateInputValue(String input) { + inputValidator.validateDistinctInput(input); + inputValidator.validateThreeInput(input); + inputValidator.validateNaturalNumbers(input); + } + + @Override + public BaseballGameResultDto getGameResult(String input) { + Integer strikes = answerNumber.countStrikes(input); + Integer balls = answerNumber.countBalls(input); + Boolean nothing = answerNumber.isNothing(input); + Boolean complete = answerNumber.isComplete(input); + + return new BaseballGameResultDto(strikes, balls, nothing, complete); + } + + @Override + public void resetAnswerNumber() { + answerNumber.resetAnswerNumber(); + } + + @Override + public void validateRetryInput(String input) { + inputValidator.validateOneOrTwo(input); + } +} diff --git a/src/main/java/baseball/model/validator/InputValidator.java b/src/main/java/baseball/model/validator/InputValidator.java new file mode 100644 index 000000000..3992c9e33 --- /dev/null +++ b/src/main/java/baseball/model/validator/InputValidator.java @@ -0,0 +1,12 @@ +package baseball.model.validator; + +public interface InputValidator { + + void validateNaturalNumbers(String input); + + void validateDistinctInput(String input); + + void validateThreeInput(String input); + + void validateOneOrTwo(String input); +} diff --git a/src/main/java/baseball/model/validator/InputValidatorImpl.java b/src/main/java/baseball/model/validator/InputValidatorImpl.java new file mode 100644 index 000000000..709789a48 --- /dev/null +++ b/src/main/java/baseball/model/validator/InputValidatorImpl.java @@ -0,0 +1,62 @@ +package baseball.model.validator; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public class InputValidatorImpl implements InputValidator { + + private final String NATURAL_NUMBERS_MESSAGE = "각 자리는 자연수로만 이루어져야 합니다."; + private final String DISTINCT_INPUT_MESSAGE = "서로 다른 수로 이루어져야 합니다."; + private final String THREE_INPUT_MESSAGE = "3자리 수로 이루어져야 합니다."; + private final String ONE_OR_TWO_MESSAGE = "입력은 1 또는 2만 가능합니다."; + + @Override + public void validateNaturalNumbers(String input) { + boolean result = Arrays.stream(splitInput(input)) + .mapToInt(this::inputToInteger) + .allMatch(n -> n > 0); + + if (!result) { + throw new IllegalArgumentException(NATURAL_NUMBERS_MESSAGE); + } + } + + private Integer inputToInteger(String s) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(NATURAL_NUMBERS_MESSAGE); + } + } + + @Override + public void validateDistinctInput(String input) { + String[] splitInput = splitInput(input); + + Set distinctValues = Arrays.stream(splitInput) + .collect(Collectors.toSet()); + + if (distinctValues.size() != splitInput.length) { + throw new IllegalArgumentException(DISTINCT_INPUT_MESSAGE); + } + } + + @Override + public void validateThreeInput(String input) { + if (input.length() != 3) { + throw new IllegalArgumentException(THREE_INPUT_MESSAGE); + } + } + + @Override + public void validateOneOrTwo(String input) { + if (!input.equals("1") && !input.equals("2")) { + throw new IllegalArgumentException(ONE_OR_TWO_MESSAGE); + } + } + + private String[] splitInput(String input) { + return input.split(""); + } +} diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 000000000..1475cf7a2 --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,23 @@ +package baseball.view; + + +import java.util.Scanner; + +public class InputView { + + private final Scanner scanner; + + public InputView() { + this.scanner = new Scanner(System.in); + } + + public String number() { + System.out.print("숫자를 입력해 주세요 : "); + return scanner.nextLine(); + } + + public String retry() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + return scanner.nextLine(); + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 000000000..2c735716e --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,43 @@ +package baseball.view; + +import baseball.model.dto.BaseballGameResultDto; + +public class OutputView { + + public void result(BaseballGameResultDto gameResult) { + String outputMessage = ballsToString(gameResult.getBalls()) + + strikesToString(gameResult.getStrikes()) + + nothingToString(gameResult.getNothing()); + + System.out.println(outputMessage); + } + + public void complete() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } + + public void exception(String outputMessage) { + System.out.println(outputMessage); + } + + private String ballsToString(Integer balls) { + if (balls > 0) { + return balls + "볼 "; + } + return ""; + } + + private String strikesToString(Integer strikes) { + if (strikes > 0) { + return strikes + "스트라이크"; + } + return ""; + } + + private String nothingToString(Boolean nothing) { + if (nothing) { + return "낫싱"; + } + return ""; + } +} diff --git a/src/test/java/baseball/model/entity/AnswerNumberImplTest.java b/src/test/java/baseball/model/entity/AnswerNumberImplTest.java new file mode 100644 index 000000000..88e24ccf3 --- /dev/null +++ b/src/test/java/baseball/model/entity/AnswerNumberImplTest.java @@ -0,0 +1,13 @@ +package baseball.model.entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AnswerNumberImplTest { + + @Test + public void countStrikesTest() throws Exception { + + } +} diff --git a/src/test/java/baseball/model/validator/InputValidatorTest.java b/src/test/java/baseball/model/validator/InputValidatorTest.java new file mode 100644 index 000000000..6e9ae2e30 --- /dev/null +++ b/src/test/java/baseball/model/validator/InputValidatorTest.java @@ -0,0 +1,48 @@ +package baseball.model.validator; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.*; + +class InputValidatorTest { + + private final InputValidator inputValidator; + + public InputValidatorTest() { + this.inputValidator = new InputValidatorImpl(); + } + + @ParameterizedTest + @ValueSource(strings = {"a12", "320"}) + public void validateNaturalNumbersTest(String value) { + assertThatThrownBy(() -> inputValidator.validateNaturalNumbers(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("각 자리는 자연수로만 이루어져야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"112", "323"}) + public void validateDistinctInputTest(String value) { + assertThatThrownBy(() -> inputValidator.validateDistinctInput(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("서로 다른 수로 이루어져야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"1", "3231", ""}) + public void validateThreeInputTest(String value) { + assertThatThrownBy(() -> inputValidator.validateThreeInput(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("3자리 수로 이루어져야 합니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"123", "0", "a", "", "------"}) + public void validateOneOrTwoTest(String value) { + assertThatThrownBy(() -> inputValidator.validateOneOrTwo(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("입력은 1 또는 2만 가능합니다."); + } + +}