diff --git a/docs/README.md b/docs/README.md index e69de29bb2..bb12ca4260 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,30 @@ +# 숫자야구 +기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. + +- [x] 게임 시작 Controller#startGame() +- [x] 야구 게임 시작 메시지 출력 Print +- [x] 랜덤 숫자 3개 생성 NumberGenerator#numberGenerate() + - [x] 각 숫자는 중복되지 않는가? Baseball#checkDuplication() + - [x] 각 숫자는 1~9까지 숫자인가? Baseball#checkValidRange() +- [x] 숫자 입력 UserInput#askNumbers() + - [x] 숫자로 파싱 UserInput#readInt() + - [x] 숫자 배열로 파싱 UserInput#getSeparate() + - [x] 받은 숫자로 baseball 반환 UserInput#askInputBaseball() + - 예외처리 + - [x] 숫자인가? UserInput#vaildNumber() + - [x] 각 숫자는 중복되지 않는가? Baseball#checkDuplication() + - [x] 각 숫자는 1~9까지 숫자인가? Baseball#checkValidRange() + - [x] 숫자가 3개인가? Baseball#checkSize() + - [x] 예외 발생시 다시 입력 +- [x] 정답 숫자와 비교 BaseballJudge#judgement + - [x] 숫자가 같고, 자리수가 다르면 '볼' BaseballJudge#getBall + - [x] 숫자가 같고, 자리수도 같으면 '스트라이크' BaseballJudge#getStrike + - [x] ball, strike가 둘다 없으면 '낫싱' JudgementType#isNothing + - [x] 에러 발생시 다시 입력받기 +- [x] 정답인지 확인 BaseballJudge#checkAnswer + - [x] 3스트라이크 시 정답 +- [x] 게임 재시작 BaseballController#retry + - [x] 1 입력시 재시작 Controller#gameStart + - [x] 2 입력시 종료 + - [x] 그 외 입력시 다시 입력받기 + diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index dd95a34214..c903afd0c7 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,10 @@ package baseball; +import baseball.controller.BaseballController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + BaseballController baseballController = new BaseballController(); + baseballController.startGame(); } } diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java new file mode 100644 index 0000000000..1149a4328e --- /dev/null +++ b/src/main/java/baseball/controller/BaseballController.java @@ -0,0 +1,60 @@ +package baseball.controller; + +import baseball.domain.Baseball; +import baseball.domain.JudgementType; +import baseball.util.BaseballJudge; +import baseball.util.NumberGenerator; +import baseball.view.InputView; + +public class BaseballController { + + Baseball computer; + BaseballJudge baseballJudge; + NumberGenerator numberGenerator; + InputView userInput; + Boolean endFlag; + + public BaseballController() { + this.numberGenerator = new NumberGenerator(); + this.userInput = new InputView(); + } + + public boolean startGame() { + initNewGame(); + + while (!endFlag) { + playRound(); + } + endGame(); + return false; + } + + private void endGame() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + retry(); + } + + private boolean retry() { + int command = userInput.askRetry(); + if (command==1) { + return startGame(); + } + return false; + } + + private void playRound() { + Baseball userBaseball = userInput.askInputBaseball(); + JudgementType judgement = baseballJudge.judgement(userBaseball); + System.out.println(judgement.toString()); + endFlag = baseballJudge.checkAnswer(judgement); + } + + + + private void initNewGame() { + System.out.println("숫자 야구 게임을 시작합니다."); + endFlag = false; + computer = numberGenerator.baseballGenerate(); + baseballJudge = new BaseballJudge(computer); + } +} diff --git a/src/main/java/baseball/domain/Baseball.java b/src/main/java/baseball/domain/Baseball.java new file mode 100644 index 0000000000..8336338d32 --- /dev/null +++ b/src/main/java/baseball/domain/Baseball.java @@ -0,0 +1,56 @@ +package baseball.domain; + +import baseball.view.Error; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Baseball { + private List numbers; + private final int NUMBERS_SIZE = 3; + + public Baseball(List numbers) { + checkSize(numbers); + checkValidRange(numbers); + checkDuplication(numbers); + this.numbers = numbers; + } + + public Baseball(int num1, int num2, int num3) { + List numbers = Arrays.asList(num1, num2, num3); + checkSize(numbers); + checkValidRange(numbers); + checkDuplication(numbers); + this.numbers = numbers; + } + + public List getNumbers() { + return numbers; + } + + private void checkSize(List numbers) { + if (numbers.size() != NUMBERS_SIZE) { + throw new IllegalArgumentException(Error.NUMBER_SIZE_EXCEPTION.toString()); + } + } + + private void checkDuplication(List numbers) { + Set numbersSet = new HashSet<>(numbers); + if (numbersSet.size() != numbers.size()) { + throw new IllegalArgumentException(Error.NUMBER_DUPLICATION_EXCEPTION.toString()); + } + } + + private void checkValidRange(List numbers) { + for (int num : numbers) { + if (1 > num || num > 9) { + throw new IllegalArgumentException(Error.NUMBER_RANGE_EXCEPTION.toString()); + } + } + } + + + +} diff --git a/src/main/java/baseball/domain/JudgementType.java b/src/main/java/baseball/domain/JudgementType.java new file mode 100644 index 0000000000..6b260d6705 --- /dev/null +++ b/src/main/java/baseball/domain/JudgementType.java @@ -0,0 +1,55 @@ +package baseball.domain; + +public class JudgementType { + private int strike; + private int ball; + private boolean homerun; + private final String STRIKE_MESSAGE = "%d스트라이크 "; + private final String BALL_MESSAGE = "%d볼 "; + private final String NOTHING_MESSAGE = "낫싱"; + + public JudgementType(int strike, int ball) { + this.strike = strike; + this.ball = ball-strike; + this.homerun = false; + } + + public boolean isHomerun() { + if(strike==3) { + homerun = true; + } + return homerun; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append(getBall(ball)); + sb.append(getStrike(strike)); + sb.append(isNothing()); + + return sb.toString().trim(); + } + + private String getBall(int ball) { + if (ball != 0) { + return String.format(BALL_MESSAGE, ball); + } + return ""; + } + + private String getStrike(int strike) { + if (strike != 0) { + return String.format(STRIKE_MESSAGE, strike); + } + return ""; + } + + private String isNothing() { + if (ball==0 && strike==0) { + return NOTHING_MESSAGE; + } + return ""; + } +} diff --git a/src/main/java/baseball/util/BaseballJudge.java b/src/main/java/baseball/util/BaseballJudge.java new file mode 100644 index 0000000000..07d0f012d1 --- /dev/null +++ b/src/main/java/baseball/util/BaseballJudge.java @@ -0,0 +1,50 @@ +package baseball.util; + +import baseball.domain.Baseball; +import baseball.domain.JudgementType; + +import java.util.List; + +public class BaseballJudge { + Baseball computer; + + public BaseballJudge(Baseball computer) { + this.computer = computer; + } + + public JudgementType judgement(Baseball user) { + List userNumbers = user.getNumbers(); + List computerNumbers = computer.getNumbers(); + int ball = getBall(userNumbers, computerNumbers); + int strike = getStrike(userNumbers, computerNumbers); + return new JudgementType(strike, ball); + } + + private int getStrike(List userNumbers, List computerNumbers) { + int result = 0; + for (int i =0; i userNumbers, List computerNumbers) { + int result = 0; + for (int userNumber : userNumbers) { + if (computerNumbers.contains(userNumber)) { + result ++; + } + } + return result; + } + + public boolean checkAnswer(JudgementType judgement) { + if (judgement.isHomerun()) { + return true; + } + return false; + } + +} diff --git a/src/main/java/baseball/util/NumberGenerator.java b/src/main/java/baseball/util/NumberGenerator.java new file mode 100644 index 0000000000..f143fb830a --- /dev/null +++ b/src/main/java/baseball/util/NumberGenerator.java @@ -0,0 +1,28 @@ +package baseball.util; + +import baseball.domain.Baseball; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static camp.nextstep.edu.missionutils.Randoms.*; + +public class NumberGenerator { + + public Baseball baseballGenerate() { + return new Baseball(getNumbers()); + } + + private List getNumbers() { + Set numbers = new HashSet<>(); + + while (numbers.size()<3) { + numbers.add(pickNumberInRange(1,9)); + } + + return new ArrayList<>(numbers); + } + +} diff --git a/src/main/java/baseball/view/Error.java b/src/main/java/baseball/view/Error.java new file mode 100644 index 0000000000..29808cbffa --- /dev/null +++ b/src/main/java/baseball/view/Error.java @@ -0,0 +1,22 @@ +package baseball.view; + +public enum Error { + NUMBER_SIZE_EXCEPTION("3자리 숫자를 입력해주세요.\n"), + NUMBER_DUPLICATION_EXCEPTION("중복된 숫자가 없어야 합니다.\n"), + NUMBER_RANGE_EXCEPTION("각 숫자는 1이상 9이하로 입력해주세요.\n"), + NUMBER_PARSE_EXCEPTION("숫자를 입력해 주세요.\n"), + COMMAND_NOT_VAILD_EXCEPTION("올바른 명령어를 입력해주세요.\n"); + + + String tag = "[ERROR] "; + String message; + + Error(String message) { + this.message = message; + } + + @Override + public String toString() { + return tag + message; + } +} diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 0000000000..7dfed1f9d9 --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,86 @@ +package baseball.view; + +import baseball.domain.Baseball; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import static camp.nextstep.edu.missionutils.Console.readLine; + +public class InputView { + private final String INT_PATTERN = "^[0-9]+$"; + private final String COMMAND_PATTERN = "^[1-2]$"; + private final String ASK_INPUT_NUMBERS = "숫자를 입력해주세요 : "; + private final String ASK_RETRY = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; + + public Baseball askInputBaseball() { + int input = askNumbers(); + return getBaseball(input); + } + + private int askNumbers() { + System.out.print(ASK_INPUT_NUMBERS); + return readInt(); + } + + private Baseball getBaseball(int input) { + try { + List numbers = getSeparate(input); + return new Baseball(numbers); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return askInputBaseball(); + } + } + + private int readInt() { + String inputStr = readLine(); + try { + vaildNumber(inputStr); + return Integer.parseInt(inputStr); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return askNumbers(); + } + } + + private void vaildNumber(String inputStr) { + if (!Pattern.matches(INT_PATTERN, inputStr)) { + throw new IllegalArgumentException(Error.NUMBER_PARSE_EXCEPTION.toString()); + } + } + + private List getSeparate(int input) { + List numbers = new ArrayList<>(); + + while (input>0) { + numbers.add(input%10); + input /= 10; + } + + return numbers; + } + + public int askRetry() { + System.out.println(ASK_RETRY); + return readRetry(); + } + + private int readRetry() { + String inputStr = readLine(); + try { + vaildCommand(inputStr); + return Integer.parseInt(inputStr); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return askRetry(); + } + } + + private void vaildCommand(String inputStr) { + if (!Pattern.matches(COMMAND_PATTERN, inputStr)) { + throw new IllegalArgumentException(Error.COMMAND_NOT_VAILD_EXCEPTION.toString()); + } + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 0000000000..d5b05e7335 --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,4 @@ +package baseball.view; + +public class Message { +} diff --git a/src/test/java/baseball/BaseballTest.java b/src/test/java/baseball/BaseballTest.java new file mode 100644 index 0000000000..3e83b0e6a0 --- /dev/null +++ b/src/test/java/baseball/BaseballTest.java @@ -0,0 +1,28 @@ +package baseball; + +import baseball.domain.Baseball; +import baseball.view.Error; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaseballTest { + + @Test + void 야구숫자유효성검사_기본기능() { + assertThat(new Baseball(1,2,3).getNumbers()).hasSize(3); + } + + @Test + void 야구숫자유효성검사_숫자_배열() { + assertThat(new Baseball(1,2,5).getNumbers()).contains(1,2,5); + } + + @Test + void 야구숫자에러처리() { + assertThatThrownBy(() -> new Baseball(0,1,2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Error.NUMBER_RANGE_EXCEPTION.toString()); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/util/NumberGeneratorTest.java b/src/test/java/baseball/util/NumberGeneratorTest.java new file mode 100644 index 0000000000..b3da03b9eb --- /dev/null +++ b/src/test/java/baseball/util/NumberGeneratorTest.java @@ -0,0 +1,19 @@ +package baseball.util; + +import baseball.domain.Baseball; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class NumberGeneratorTest { + + @Test + void numberGenerate테스트() { + NumberGenerator numberGenerator = new NumberGenerator(); + Baseball baseball = numberGenerator.numberGenerate(); + System.out.printf(baseball.getNumbers().toString()); + + assertThat(baseball.getNumbers()).hasSize(3); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/view/UserInputTest.java b/src/test/java/baseball/view/UserInputTest.java new file mode 100644 index 0000000000..2f038cefd0 --- /dev/null +++ b/src/test/java/baseball/view/UserInputTest.java @@ -0,0 +1,29 @@ +package baseball.view; + +import baseball.domain.Baseball; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class UserInputTest { + +// @Test +// @ValueSource(strings = "123") +// void askNumber테스트(String input) { +// InputStream in = generatoteUserInput(input); +// System.setIn(in); +// +// UserInput userInput = new UserInput(); +// Baseball baseball = userInput.askNumbers(); +// assertThat(baseball.getNumbers()).hasSize(3); +// } + + public static InputStream generatoteUserInput(String input) { + return new ByteArrayInputStream(input.getBytes()); + } +} \ No newline at end of file