diff --git a/README.md b/README.md index 8d7e8aee..56e93664 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +## ⚾ 숫자 야구 게임 + +### ✏️ 기능 요구 사항 + +기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. + +- 같은 수가 같은 자리에 있으면 + + → 스트라이크 + +- 다른 자리에 있으면 + + → 볼 + +- 같은 수가 전혀 없으면 + + → 낫싱이란 힌트를 얻는다. + + +이 3가지 힌트로 3자리 수를 맞추면 승리한다. + +```java +e.g. +상대방(컴퓨터)의 수가 425일 때, +- 123을 제시한 경우: 1스트라이크 +- 456을 제시한 경우: 1볼 1스트라이크 +- 789를 제시한 경우: 낫싱 +``` + +- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다. +- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. +- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. +- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 을 발생시킨 후 애플리케이션은 종료되어야 한다. +### 구현 체크 리스트 +- [x] random 3자리 수 반환 가능 `Generator` +- [x] 사용자로 부터 `input` 받는 기능 +- [x] 사용자로 부터 받은 input 값과 정답 `비교/검증` 기능 +- [x] 잘못된 값을 입력한 경우 `IllegalArgumentException` 을 발생시키는 기능 +- 사용자가 입력한 값이 정답과 같은지 판단 + - [x] 같다면 ASK REPLAY + - [x] 정답이 아니라면 HINT 제공 + - 스트라이크 + - 볼 + - 낫싱 \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..7ea9da9d --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,9 @@ +import controller.BaseballGameController; + +public class Application { + + public static void main(String[] args) { + BaseballGameController controller = new BaseballGameController(); + controller.progress(); + } +} \ No newline at end of file diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java new file mode 100644 index 00000000..cfa62f72 --- /dev/null +++ b/src/main/java/controller/BaseballGameController.java @@ -0,0 +1,44 @@ +package controller; + +import model.AnswerChecker; +import model.GenerateRandomNumber; +import model.Hint; +import view.InputView; +import view.OutputView; + +import java.util.List; + +public class BaseballGameController { + + private final static GenerateRandomNumber generateRandomNumber = new GenerateRandomNumber(); + private final static InputView input = new InputView(); + private final AnswerChecker answerChecker = new AnswerChecker(); + private final OutputView outputView = new OutputView(); + + public void progress() { + boolean isFinish; + do { + List answerNum = generateRandomNumber.getRandomNumberList(); + + oneGame(answerNum); + + isFinish = input.isReplay(); + + } while (isFinish); + } + + private void oneGame(List answerNum) { + while (true) { + // feat: input & validation + List inputNum = input.input(); + // feat: checkAnswer + Hint hint = answerChecker.checkAnswer(answerNum, inputNum); + // feat: print Console + outputView.printResult(hint); + if (answerChecker.isFinished(hint)) { + break; + } + } + } + +} diff --git a/src/main/java/model/AnswerChecker.java b/src/main/java/model/AnswerChecker.java new file mode 100644 index 00000000..bb678034 --- /dev/null +++ b/src/main/java/model/AnswerChecker.java @@ -0,0 +1,32 @@ +package model; + +import java.util.List; + +import static model.GenerateRandomNumber.DIGIT; + +/** + * correct -> Ask replay + * incorrect -> call Hint class + */ +public class AnswerChecker { + + public Hint checkAnswer (List answer, List userInput) { + Hint hint = new Hint(); + for (int i = 0; i < userInput.size(); i++) { + int answerI = answer.get(i); + int userI = userInput.get(i); + if (answerI == userI) { + hint.increaseStrike(); + } + else if(userInput.contains(answerI)) { // contains + hint.increaseBall(); + } + } + return hint; + } + + public boolean isFinished (Hint hint) { + return hint.getStrike() == DIGIT; + } + +} diff --git a/src/main/java/model/GenerateRandomNumber.java b/src/main/java/model/GenerateRandomNumber.java new file mode 100644 index 00000000..8b09c569 --- /dev/null +++ b/src/main/java/model/GenerateRandomNumber.java @@ -0,0 +1,22 @@ +package model; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 1~9 사이의 정수 값 - DIGIT 자리수를 랜덤으로 반환 + * DIGIT 조절로 자리 수 변경 + */ +public class GenerateRandomNumber { + + public static final int DIGIT = 3; + + public List getRandomNumberList() { + List randomNumbers = new ArrayList<>(); + for (int i = 0; i < DIGIT; i++) { + randomNumbers.add(ThreadLocalRandom.current().nextInt(1, 10)); + } + return randomNumbers; + } +} diff --git a/src/main/java/model/Hint.java b/src/main/java/model/Hint.java new file mode 100644 index 00000000..c2599539 --- /dev/null +++ b/src/main/java/model/Hint.java @@ -0,0 +1,29 @@ +package model; + +public class Hint { + + private int strike; + private int ball; + + public Hint() { + strike = 0; + ball = 0; + } + + public int getStrike() { + return strike; + } + + public int getBall() { + return ball; + } + + public void increaseStrike() { + strike++; + } + + public void increaseBall() { + ball++; + } + +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..42606094 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,102 @@ +package view; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class InputView { + + Scanner scanner; + public static final int DIGIT = 3; // 자리수 + private static final String ASK_INPUT_NUM = "숫자를 입력해 주세요: "; + private static final String INPUT_ERROR_INT = "입력한 값이 숫자가 아닙니다. "; + private static final String INPUT_ERROR_BOUND = "1과 2중 값을 입력해주세요. "; + + public InputView() { + scanner = new Scanner(System.in); + } + + /** + * 1. input number + * 2. validation + * 3. return number + */ + public List input() { + System.out.print(ASK_INPUT_NUM); + String inputValue = scanner.nextLine(); + inputValidation(inputValue); + return parseToInputList(inputValue); + } + + /** + * 두 가지 validation 진행 + * 1. 자리수 확인 + * 2. input type 확인 + */ + public void inputValidation(String inputValue) { + if (!isValidateDigitCount(inputValue)) { + throw new IllegalArgumentException("입력한 숫자의 자릿수를 " + DIGIT + "자리 수로 입력해주세요."); + } + if (!isValidateInt(inputValue)) { + throw new IllegalArgumentException(INPUT_ERROR_INT); + } + } + + /** + * @param inputValue: input() 메소드에서 Scanner로 받은 값 + * @return 자리 수 확인 후 true/false + */ + public boolean isValidateDigitCount(String inputValue) { + int digitCount = String.valueOf(inputValue).length(); + return digitCount == DIGIT; + } + + /** + * @param inputValue: input() 메소드에서 Scanner로 받은 값 + * @return int 확인 후 true/false + */ + public boolean isValidateInt(String inputValue) { + try { + Integer.parseInt(String.valueOf(inputValue)); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * List로 변환하는 메소드 + * @param inputValue int + * @return List + */ + public List parseToInputList(String inputValue) { + List intList = new ArrayList<>(); + for (int i = 0; i 0) { + result.append(ball).append(HINT_BALL); + } + if (strike > 0) { + result.append(strike).append(HINT_STRIKE); + } + if (ball == 0 && strike == 0) { + result.append(HINT_NOTHING); + } + } + + System.out.println(result); + } + +} diff --git a/src/test/java/controller/BaseballGameControllerTest.java b/src/test/java/controller/BaseballGameControllerTest.java new file mode 100644 index 00000000..054a0dc5 --- /dev/null +++ b/src/test/java/controller/BaseballGameControllerTest.java @@ -0,0 +1,53 @@ +package controller; + +import model.AnswerChecker; +import model.GenerateRandomNumber; +import model.Hint; +import org.junit.jupiter.api.Test; +import view.InputView; +import view.OutputView; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class BaseballGameControllerTest { + + private final static GenerateRandomNumber generateRandomNumber = new GenerateRandomNumber(); + private final static InputView input = new InputView(); + private final static AnswerChecker answerChecker = new AnswerChecker(); + private final static OutputView outputView = new OutputView(); + + private BaseballGameController baseballGameController; + + @Test + void progress() { + // Generate a random number + List answerNum = List.of(1, 2, 3); + + // Simulate user input for replay + boolean isReplay = false; + + // Simulate answer checking result + Hint hint = answerChecker.checkAnswer(answerNum, List.of(1, 2, 3)); + + // Verify that the game ends after one iteration + assertThat(answerChecker.isFinished(hint)).isTrue(); + } + + @Test + void testOneGame() { + // Generate a random number + List answerNum = List.of(5, 8, 4); + + // Simulate user input + List userInput = List.of(5, 8, 4); + + // Simulate answer checking result + Hint hint = answerChecker.checkAnswer(answerNum, userInput); + + // Verify that the correct hint is returned + assertThat(hint.getStrike()).isEqualTo(3); + assertThat(hint.getBall()).isZero(); + } +} \ No newline at end of file diff --git a/src/test/java/model/AnswerCheckerTest.java b/src/test/java/model/AnswerCheckerTest.java new file mode 100644 index 00000000..2729c062 --- /dev/null +++ b/src/test/java/model/AnswerCheckerTest.java @@ -0,0 +1,43 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class AnswerCheckerTest { + + @Test + @DisplayName("정답 확인 - strike, ball 테스트") + void testCheckAnswer() { + List answer = Arrays.asList(1, 2, 3); + List userInput = Arrays.asList(1, 4, 2); + + AnswerChecker answerChecker = new AnswerChecker(); + Hint hint = answerChecker.checkAnswer(answer, userInput); + + // 반환된 Hint 객체의 strike, ball 값 확인 + assertThat(hint.getStrike()).isEqualTo(1); + assertThat(hint.getBall()).isEqualTo(1); + } + + @Test + @DisplayName("게임 종료 확인") + void testIsFinished() { + // strike가 DIGIT과 같을 경우 테스트 + Hint hint1 = new Hint(); + hint1.increaseStrike(); + hint1.increaseStrike(); + hint1.increaseStrike(); + assertThat(new AnswerChecker().isFinished(hint1)).isTrue(); + + // strike가 DIGIT과 같지 않은 경우 테스트 + Hint hint2 = new Hint(); + hint2.increaseStrike(); + hint2.increaseStrike(); + assertThat(new AnswerChecker().isFinished(hint2)).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/model/GenerateRandomNumberTest.java b/src/test/java/model/GenerateRandomNumberTest.java new file mode 100644 index 00000000..f4dc5e46 --- /dev/null +++ b/src/test/java/model/GenerateRandomNumberTest.java @@ -0,0 +1,25 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class GenerateRandomNumberTest { + + @Test + @DisplayName("1~9까지의 랜덤 정수 세 자리 값 생성") + void getRandomNumberList() { + GenerateRandomNumber generator = new GenerateRandomNumber(); + List randomNumbers = generator.getRandomNumberList(); + + assertThat(randomNumbers).isNotNull(); + assertThat(randomNumbers).hasSize(3); + assertThat(randomNumbers).doesNotContain(0); + + for (int number : randomNumbers) { + assertThat(number).isBetween(1, 9); + } + } +} \ No newline at end of file diff --git a/src/test/java/view/InputViewTest.java b/src/test/java/view/InputViewTest.java new file mode 100644 index 00000000..2860bb79 --- /dev/null +++ b/src/test/java/view/InputViewTest.java @@ -0,0 +1,32 @@ +package view; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class InputViewTest { + + private InputView inputView; + + @BeforeEach + public void setUp() { + inputView = new InputView(); + } + + @Test + @DisplayName("자리 수 확인") + void testIsValidateDigitCount() { + assertThatThrownBy(() -> inputView.inputValidation("12345")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("정수 확인") + void testIsValidateInt() { + assertThatThrownBy(() -> inputView.inputValidation("12a")) + .isInstanceOf(IllegalArgumentException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/view/OutputViewTest.java b/src/test/java/view/OutputViewTest.java new file mode 100644 index 00000000..f2b237a1 --- /dev/null +++ b/src/test/java/view/OutputViewTest.java @@ -0,0 +1,98 @@ +package view; + +import model.Hint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.assertj.core.api.Assertions.*; + +class OutputViewTest { + + private static final String MESSAGE_FINISH = "3개의 숫자를 모두 맞히셨습니다! 게임종료\n게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; + private static final String HINT_STRIKE = "스트라이크"; + private static final String HINT_BALL = "볼"; + private static final String HINT_NOTHING = "낫싱"; + private final ByteArrayOutputStream output = new ByteArrayOutputStream(); + private OutputView outputView; + private Hint hint; + + @BeforeEach + void setUp() { + System.setOut(new PrintStream(output)); + outputView = new OutputView(); + hint = new Hint(); + } + + @Test + @DisplayName("출력 결과 확인 - 게임 종료") + void testPrintResult_GameFinish() { + // given + // strike 값이 DIGIT 과 같아야 맞춘 것 -> 게임 종료 + hint.increaseStrike(); + hint.increaseStrike(); + hint.increaseStrike(); + + // when + outputView.printResult(hint); + + // then +// assertThat(output.toString().trim()).isEqualTo(MESSAGE_FINISH); + } + + @Test + @DisplayName("출력 결과 확인 - 스트라이크") + void testPrintResult_Strike() { + // given + hint.increaseStrike(); + + // when + outputView.printResult(hint); + + // then +// assertThat(output.toString().trim()).isEqualTo("1" + HINT_STRIKE); + } + + @Test + @DisplayName("출력 결과 확인 - 볼") + void testPrintResult_Ball() { + // given + hint.increaseBall(); + + // when + outputView.printResult(hint); + + // then +// assertThat(output.toString().trim()).isEqualTo("1" + HINT_BALL); + } + + @Test + @DisplayName("출력 결과 확인 - 볼 스트라이크") + void testPrintResult_BallStrike() { + // given + hint.increaseBall(); + hint.increaseStrike(); + + // when + outputView.printResult(hint); + + // then +// assertThat(output.toString().trim()).isEqualTo("1" +HINT_BALL +" " + "1" + HINT_STRIKE); + } + + @Test + @DisplayName("출력 결과 확인 - 낫싱") + void testPrintResult_Nothing() { + // given + // (nothing to increase) + + // when + outputView.printResult(hint); + + // then +// assertThat(output.toString().trim()).isEqualTo(HINT_NOTHING); + } +} \ No newline at end of file