diff --git a/README.md b/README.md index 8d7e8aee..24949910 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# java-baseball-precourse \ No newline at end of file +# 구현할 기능 목록 + +- 컴퓨터는 1~9 사이의 3개의 숫자를 선택한다. +- 생성된 숫자는 한 라운드 동안 유지된다. +- 사용자는 3자리 숫자를 입력한다. +- 입력 받은 문자열을 검증한 후, 숫자 리스트로 반환한다. + - 검증 조건 + - 입력값이 3자리인지 확인한다. + - 각각의 입력값은 숫자이며 1~9 사이어야 한다. + - 각각의 입력값은 중복이 불가하다. +- 잘못된 입력이 들어오면 '[ERROR] 잘못된 입력입니다. 중복없이 3자리 숫자를 1~9 범위로 입력해주세요.' 메시지를 출력하고 게임을 재개한다. +- 사용자의 입력과 컴퓨터가 생성한 숫자의 위치와 값을 비교하여 스트라이크/볼/낫싱 여부를 판단한다. + - 판정 규칙 + - 스트라이크 : 값이 같고 위치도 같을 때 + - 볼 : 값은 같으나 위치가 다를 때 + - 낫싱 : 3자리의 숫자모두 컴퓨터가 생성한 숫자와 값이 일치한 것이 없을 때 +- 판정 결과에 따라 스트라이크/볼/낫싱을 다음과 같은 형태로 출력한다. + - 1스트라이크 1볼 + - 2볼 + - 2스트라이크 + - 낫싱 + - 3스트라이크 +- 3스트라이크 일때는 '3개의 숫자를 모두 맞히셨습니다! 게임 끝\n게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.' 메시지를 출력한다 + - 이때, 입력받은 숫자가 1이면 컴퓨터는 새로ㅎ운 3개의 숫자를 선택하고 게임을 처음부터 다시 실행한다. + - 입력받은 숫자가 2라면 게임을 종료한다. + - 1 또는 2 외의 문자 혹은 숫자가 입력되면 '[ERROR] 잘못된 입력입니다. 게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.'를 출력하고 사용자의 입력을 받는다. +- MVC 패턴으로 개발을 진행하며, 테스트 코드는 Model을 중심으로 단위테스트로 작성한다. diff --git a/build.gradle b/build.gradle index 20a92c9e..a3614f5f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,7 @@ plugins { id 'java' + id 'org.ec4j.editorconfig' version '0.0.3' + id 'checkstyle' } group = 'camp.nextstep.edu' @@ -11,6 +13,10 @@ java { } } +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + repositories { mavenCentral() } @@ -23,3 +29,16 @@ dependencies { test { useJUnitPlatform() } + +editorconfig { + excludes = ['build'] +} + +check.dependsOn tasks.named("editorconfigCheck") + +checkstyle { + maxWarnings = 0 // 규칙이 어긋나는 코드가 하나라도 있을 경우 빌드 fail + configFile = file("${rootDir}/naver-checkstyle-rules.xml") + configProperties = ["suppressionFile" : "${rootDir}/naver-checkstyle-suppressions.xml"] + toolVersion = "8.24" +} diff --git a/src/main/java/com/kakao/baseball/Main.java b/src/main/java/com/kakao/baseball/Main.java new file mode 100644 index 00000000..a633800a --- /dev/null +++ b/src/main/java/com/kakao/baseball/Main.java @@ -0,0 +1,13 @@ +package com.kakao.baseball; + +import com.kakao.baseball.controller.BaseballController; +import com.kakao.baseball.model.ComputerGenerator; +import com.kakao.baseball.model.InputParser; +import com.kakao.baseball.model.Judge; +import com.kakao.baseball.view.BaseballView; + +public class Main { + public static void main(String[] args) { + new BaseballController(new BaseballView(), new ComputerGenerator(), new InputParser(), new Judge()).run(); + } +} diff --git a/src/main/java/com/kakao/baseball/controller/BaseballController.java b/src/main/java/com/kakao/baseball/controller/BaseballController.java new file mode 100644 index 00000000..359d82d5 --- /dev/null +++ b/src/main/java/com/kakao/baseball/controller/BaseballController.java @@ -0,0 +1,94 @@ +package com.kakao.baseball.controller; + +import com.kakao.baseball.model.BaseballNumbers; +import com.kakao.baseball.model.ComputerGenerator; +import com.kakao.baseball.model.InputParser; +import com.kakao.baseball.model.Judge; +import com.kakao.baseball.model.Result; +import com.kakao.baseball.view.BaseballView; + +public class BaseballController { + + private static final int RESTART = 1; + private static final int EXIT = 2; + + private final BaseballView view; + private final ComputerGenerator generator; + private final InputParser parser; + private final Judge judge; + + public BaseballController(BaseballView view, ComputerGenerator generator, InputParser parser, Judge judge) { + this.view = view; + this.generator = generator; + this.parser = parser; + this.judge = judge; + } + + public void run() { + while (true) { + playOneGame(); + if (isExit()) { + return; + } + } + } + + private void playOneGame() { + BaseballNumbers computer = generator.generate(); + while (true) { + Result result = playOneTurn(computer); + if (isThreeStrike(result)) { + view.printGameEnd(); + return; + } + } + } + + private Result playOneTurn(BaseballNumbers computer) { + BaseballNumbers guess = readGuessNumbers(); + Result result = judge.judge(computer, guess); + view.printResult(result); + return result; + } + + private BaseballNumbers readGuessNumbers() { + while (true) { + try { + return parser.parse(view.readGuess()); + } catch (IllegalArgumentException e) { + view.printInputError(); + } + } + } + + private boolean isThreeStrike(Result result) { + return result.getStrike() == 3; + } + + private boolean isExit() { + while (true) { + try { + int command = parseCommand(view.readRestartCommand()); + return command == EXIT; + } catch (IllegalArgumentException e) { + view.printRestartError(); + } + } + } + + private int parseCommand(String input) { + int command = Integer.parseInt(input); + validateCommand(command); + return command; + } + + private void validateCommand(int command) { + if (command == RESTART) { + return; + } + if (command == EXIT) { + return; + } + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/com/kakao/baseball/model/BaseballNumbers.java b/src/main/java/com/kakao/baseball/model/BaseballNumbers.java new file mode 100644 index 00000000..b2975b98 --- /dev/null +++ b/src/main/java/com/kakao/baseball/model/BaseballNumbers.java @@ -0,0 +1,23 @@ +package com.kakao.baseball.model; + +import java.util.List; + +public class BaseballNumbers { + private final List numbers; + + public BaseballNumbers(int first, int second, int third) { + this.numbers = List.of(first, second, third); + } + + public int size() { + return numbers.size(); + } + + public int get(int idx) { + return numbers.get(idx); + } + + public boolean contains(int num) { + return numbers.contains(num); + } +} diff --git a/src/main/java/com/kakao/baseball/model/ComputerGenerator.java b/src/main/java/com/kakao/baseball/model/ComputerGenerator.java new file mode 100644 index 00000000..d81a9a82 --- /dev/null +++ b/src/main/java/com/kakao/baseball/model/ComputerGenerator.java @@ -0,0 +1,30 @@ +package com.kakao.baseball.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class ComputerGenerator { + private static final int NUMBER_COUNT = 3; + private static final int MAX_NUMBER = 9; + + private final Random random = new Random(); + + public BaseballNumbers generate() { + List numbers = new ArrayList<>(); + + while (numbers.size() < NUMBER_COUNT) { + int number = random.nextInt(MAX_NUMBER) + 1; + addIfNotExists(numbers, number); + } + + return new BaseballNumbers(numbers.get(0), numbers.get(1), numbers.get(2)); + } + + private void addIfNotExists(List numbers, int number) { + if (numbers.contains(number)) { + return; + } + numbers.add(number); + } +} diff --git a/src/main/java/com/kakao/baseball/model/InputParser.java b/src/main/java/com/kakao/baseball/model/InputParser.java new file mode 100644 index 00000000..041db08f --- /dev/null +++ b/src/main/java/com/kakao/baseball/model/InputParser.java @@ -0,0 +1,46 @@ +package com.kakao.baseball.model; + +public class InputParser { + + public BaseballNumbers parse(String input) { + validateLength(input); + validateDigit(input); + + int first = toNumber(input.charAt(0)); + int second = toNumber(input.charAt(1)); + int third = toNumber(input.charAt(2)); + + validateRange(first); + validateRange(second); + validateRange(third); + validateDuplicate(first, second, third); + + return new BaseballNumbers(first, second, third); + } + + private void validateLength(String input) { + if (input.length() != 3) + throw new IllegalArgumentException(); + } + + private void validateDigit(String input) { + for (int i = 0; i < input.length(); i++) { + if (!Character.isDigit(input.charAt(i))) + throw new IllegalArgumentException(); + } + } + + private int toNumber(char d) { + return d - '0'; + } + + private void validateRange(int num) { + if (num < 1 || num > 9) + throw new IllegalArgumentException(); + } + + private void validateDuplicate(int first, int second, int third) { + if (first == second || first == third || second == third) + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/com/kakao/baseball/model/Judge.java b/src/main/java/com/kakao/baseball/model/Judge.java new file mode 100644 index 00000000..2e95dc8c --- /dev/null +++ b/src/main/java/com/kakao/baseball/model/Judge.java @@ -0,0 +1,35 @@ +package com.kakao.baseball.model; + +public class Judge { + + public Result judge(BaseballNumbers computer, BaseballNumbers user) { + int strike = countStrike(computer, user); + int ball = countBall(computer, user, strike); + return new Result(strike, ball); + } + + private int countStrike(BaseballNumbers computer, BaseballNumbers user) { + int strike = 0; + + for (int i = 0; i < computer.size(); i++) { + if (computer.get(i) == user.get(i)) + ++strike; + } + return strike; + } + + private int countBall(BaseballNumbers computer, BaseballNumbers user, int strike) { + int matchCount = countMatch(computer, user); + return matchCount - strike; + } + + private int countMatch(BaseballNumbers computer, BaseballNumbers user) { + int matchCount = 0; + + for (int i = 0; i < computer.size(); i++) { + if (computer.contains(user.get(i))) + ++matchCount; + } + return matchCount; + } +} diff --git a/src/main/java/com/kakao/baseball/model/Result.java b/src/main/java/com/kakao/baseball/model/Result.java new file mode 100644 index 00000000..4866c512 --- /dev/null +++ b/src/main/java/com/kakao/baseball/model/Result.java @@ -0,0 +1,19 @@ +package com.kakao.baseball.model; + +public class Result { + private final int strike; + private final int ball; + + public Result(int strike, int ball) { + this.strike = strike; + this.ball = ball; + } + + public int getStrike() { + return strike; + } + + public int getBall() { + return ball; + } +} diff --git a/src/main/java/com/kakao/baseball/view/BaseballView.java b/src/main/java/com/kakao/baseball/view/BaseballView.java new file mode 100644 index 00000000..2cdf3c20 --- /dev/null +++ b/src/main/java/com/kakao/baseball/view/BaseballView.java @@ -0,0 +1,72 @@ +package com.kakao.baseball.view; + +import java.util.Scanner; + +import com.kakao.baseball.model.Result; + +public class BaseballView { + + private static final String INPUT_MESSAGE = "숫자를 입력해주세요 : "; + private static final String GAME_END_MESSAGE = + "3개의 숫자를 모두 맞히셨습니다! 게임 끝\n게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; + private static final String INPUT_ERROR_MESSAGE = + "[ERROR] 잘못된 입력입니다. 중복없이 3자리 숫자를 1~9 범위로 입력해주세요."; + private static final String RESTART_ERROR_MESSAGE = + "[ERROR] 잘못된 입력입니다. 게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; + + private final Scanner scanner = new Scanner(System.in); + + public String readGuess() { + System.out.print(INPUT_MESSAGE); + return scanner.nextLine(); + } + + public void printResult(Result result) { + if (isNothing(result)) { + System.out.println("낫싱"); + return; + } + System.out.println(formatResult(result)); + } + + public void printGameEnd() { + System.out.println(GAME_END_MESSAGE); + } + + public String readRestartCommand() { + return scanner.nextLine(); + } + + public void printInputError() { + System.out.println(INPUT_ERROR_MESSAGE); + } + + public void printRestartError() { + System.out.println(RESTART_ERROR_MESSAGE); + } + + private boolean isNothing(Result result) { + return result.getStrike() == 0 && result.getBall() == 0; + } + + private String formatResult(Result result) { + String message = ""; + message = appendStrike(message, result); + message = appendBall(message, result); + return message.trim(); + } + + private String appendStrike(String message, Result result) { + if (result.getStrike() == 0) { + return message; + } + return message + result.getStrike() + "스트라이크 "; + } + + private String appendBall(String message, Result result) { + if (result.getBall() == 0) { + return message; + } + return message + result.getBall() + "볼"; + } +} diff --git a/src/test/java/com/kakao/baseball/model/InputParserTest.java b/src/test/java/com/kakao/baseball/model/InputParserTest.java new file mode 100644 index 00000000..b4e8e26b --- /dev/null +++ b/src/test/java/com/kakao/baseball/model/InputParserTest.java @@ -0,0 +1,57 @@ +package com.kakao.baseball.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class InputParserTest { + + @DisplayName("입력 문자열은 3자리 숫자로 파싱한다.") + @Test + void parseValidInput() { + InputParser parser = new InputParser(); + + BaseballNumbers numbers = parser.parse("123"); + + assertThat(numbers.get(0)).isEqualTo(1); + assertThat(numbers.get(1)).isEqualTo(2); + assertThat(numbers.get(2)).isEqualTo(3); + } + + @DisplayName("입력 문자열이 3자리가 아니라면 예외가 발생한다.") + void throwExceptionLengthIsNotThree() { + InputParser parser = new InputParser(); + + assertThatThrownBy(() -> parser.parse("12")) + .isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> parser.parse("1234")) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("입력 문자열에 문자가 포함되어 있으면 예외가 발생한다.") + void throwExceptionWhenNotNumber() { + InputParser parser = new InputParser(); + + assertThatThrownBy(() -> parser.parse("12a")) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("입력 문자열에 0이 포함되면 예외가 발생한다.") + void throwExceptionWhenContainsZero() { + InputParser parser = new InputParser(); + + assertThatThrownBy(() -> parser.parse("12a")) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("중복 숫자가 있으면 예외가 발생한다") + @Test + void throwExceptionWhenDuplicated() { + InputParser parser = new InputParser(); + + assertThatThrownBy(() -> parser.parse("112")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/com/kakao/baseball/model/JudgeTest.java b/src/test/java/com/kakao/baseball/model/JudgeTest.java new file mode 100644 index 00000000..1199bc46 --- /dev/null +++ b/src/test/java/com/kakao/baseball/model/JudgeTest.java @@ -0,0 +1,48 @@ +package com.kakao.baseball.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class JudgeTest { + + @DisplayName("값과 위치가 같으면 스트라이크로 판정한다.") + @Test + void countStrike() { + BaseballNumbers computer = new BaseballNumbers(1, 2, 3); + BaseballNumbers user = new BaseballNumbers(1, 4, 5); + + Judge judge = new Judge(); + Result result = judge.judge(computer, user); + + assertThat(result.getStrike()).isEqualTo(1); + assertThat(result.getBall()).isEqualTo(0); + } + + @DisplayName("값은 같고 위치가 다르면 볼로 판정한다.") + @Test + void countBall() { + BaseballNumbers computer = new BaseballNumbers(1, 2, 3); + BaseballNumbers user = new BaseballNumbers(4, 1, 5); + + Judge judge = new Judge(); + Result result = judge.judge(computer, user); + + assertThat(result.getStrike()).isEqualTo(0); + assertThat(result.getBall()).isEqualTo(1); + } + + @DisplayName("값이 전부 일치하지 않으면 스트라이크와 볼은 0이다") + @Test + void nothing() { + BaseballNumbers computer = new BaseballNumbers(1, 2, 3); + BaseballNumbers user = new BaseballNumbers(4, 5, 6); + + Judge judge = new Judge(); + Result result = judge.judge(computer, user); + + assertThat(result.getStrike()).isEqualTo(0); + assertThat(result.getBall()).isEqualTo(0); + } +}