diff --git a/README.md b/README.md index 8d7e8aee..45d9515a 100644 --- a/README.md +++ b/README.md @@ -1 +1,28 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse +## 기능 구현 +### 1. 1~9까지의 서로 다른 임의의 수 3개 생성 +### 2. 게임 플레이어에게 수를 입력 받음 + 1) 사용자 입력 검증 + 1-1) 사용자가 잘못된 값을 입력할 경우, IllegalArgumentException 을 발생시킨 후 애플리케이션은 종료 + - 1 ~ 9가 아닌 다른 문자를 입력한 경우 + - 1 ~ 9 사이의 숫자를 입력했지만 중복되는 수가 있는 경우 + + 1-2) 사용자가 제대로 된 값을 입력한 경우, 게임을 진행함 + - 1 ~ 9 사이의 서로 다른 임의의 수 3개인 경우 + + 게임이 종료되기 전 까지 사용자에게 계속 값을 입력 받음 + +### 3. 게임 로직 처리 + 1) 컴퓨터가 생성한 값과 사용자가 입력한 값 중 일치하는 것이 있나 확인함 + - 반복문을 통해 먼저 스트라이크가 존재하는지 확인 + - 스트라이크가 있다면, 다음 index의 스트라이크 판단 + - 스트라이크가 아닌 경우, 볼인지 판단 + - 볼도 아니라면 다음 값으로 넘어가서 위의 과정 반복 + + 2) 사용자가 입력한 값의 스트라이크 개수와 볼 개수 확인 + - 스트라이크와 볼 개수를 더한 값이 0 : 낫싱 + - 스트라이크와 볼 개수를 더한 값이 0이 아니라면, 스트라이크와 볼 수 출력 + +### 4. 게임을 종료한 후 게임을 다시 시작하거나 완전 종료할 수 있게 함 + - 게임을 새로 시작하려면 1, 종료하려면 2룰 입력하개 함 + - 1,2 가 아닌 값을 입력할 경우, 다시 입력하게 함 diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 00000000..e6713556 --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,10 @@ +package baseball; + +import baseball.controller.BaseballGameController; + +public class Application { + public static void main(String[] args) { + BaseballGameController controller = new BaseballGameController(); + controller.run(); + } +} \ No newline at end of file diff --git a/src/main/java/baseball/controller/BaseballGameController.java b/src/main/java/baseball/controller/BaseballGameController.java new file mode 100644 index 00000000..dcd4b36a --- /dev/null +++ b/src/main/java/baseball/controller/BaseballGameController.java @@ -0,0 +1,57 @@ +package baseball.controller; + +import baseball.service.GameService; +import baseball.view.GameMessage; +import baseball.view.RequestInput; +import java.util.Scanner; + +public class BaseballGameController { + final int SIZE = 3; + final int START= 1; + final int END = 9; + final int RETRY = 1; + final int GAME_OVER = 2; + + GameService gameService = new GameService(); + private Scanner scanner = new Scanner(System.in); + + public void run() throws IllegalArgumentException { + setGame(); + startGame(); + endGame(); + askRetry(); + } + + private void setGame() { + gameService.setGame(SIZE, START, END); + } + + private void startGame() throws IllegalArgumentException { + gameService.playGame(); + } + + private void endGame() { + GameMessage.printGameOverMessage(); + } + + /* + - 유저입력이 (문자 or 개수가 0 or 3 이상) : Exception + - 유저입력이 (1) : 재시작 + - 유저입력이 (2) : 종료 + */ + private void askRetry() throws IllegalArgumentException { + RequestInput.printRetryMessage(); + if (getInputNum() == RETRY) { + run(); + } + } + + private int getInputNum() throws IllegalArgumentException { + int inputNum = Integer.parseInt(scanner.nextLine()); + + if (inputNum == 0 || inputNum > GAME_OVER) { + throw new IllegalArgumentException(); + } + return inputNum; + } +} diff --git a/src/main/java/baseball/domain/Game.java b/src/main/java/baseball/domain/Game.java new file mode 100644 index 00000000..b77725e9 --- /dev/null +++ b/src/main/java/baseball/domain/Game.java @@ -0,0 +1,36 @@ +package baseball.domain; + +public class Game { + int strikeCount; + int ballCount; + int[] gameNumbers; + + public Game(int[] numbers) { + gameNumbers = numbers; + } + + public void initBaseBall() { + strikeCount = 0; + ballCount = 0; + } + + public int getStrikeCount() { + return strikeCount; + } + + public int getBallCount() { + return ballCount; + } + + public int[] getGameNumbers() { + return gameNumbers; + } + + public void increaseStrikeCount() { + strikeCount += 1; + } + + public void increaseBallCount() { + ballCount += 1; + } +} \ No newline at end of file diff --git a/src/main/java/baseball/domain/User.java b/src/main/java/baseball/domain/User.java new file mode 100644 index 00000000..8d23b21c --- /dev/null +++ b/src/main/java/baseball/domain/User.java @@ -0,0 +1,13 @@ +package baseball.domain; + +public class User { + int[] userNumbers; + + public int[] getUserNumbers() { + return userNumbers; + } + + public void setUserNumbers(int[] userNumbers) { + this.userNumbers = userNumbers; + } +} \ No newline at end of file diff --git a/src/main/java/baseball/service/GameService.java b/src/main/java/baseball/service/GameService.java new file mode 100644 index 00000000..3fcee947 --- /dev/null +++ b/src/main/java/baseball/service/GameService.java @@ -0,0 +1,70 @@ +package baseball.service; + +import baseball.domain.Game; +import baseball.domain.User; +import baseball.utils.Parse; +import baseball.utils.GenerateRandomNum; +import baseball.view.RequestInput; +import baseball.view.GameMessage; +import java.util.Scanner; + +public class GameService { + + int size; + Game game; + User user = new User(); + Parse parser = new Parse(); + GameMessage systemMessage = new GameMessage(); + private final Scanner scanner = new Scanner(System.in); + public void setGame(int size, int start, int end) { + this.size = size; + game = new Game(GenerateRandomNum.getRandomNumbers(size, start, end)); + } + + public void playGame() { + int strike = 0; + while (strike != 3) { + play(); + systemMessage.printScoreMessage(game.getBallCount(), game.getStrikeCount()); + strike = game.getStrikeCount(); + } + } + + private void play() { + game.initBaseBall(); + user.setUserNumbers(getUserNumber()); + computeScore(); + } + + private int[] getUserNumber() throws IllegalArgumentException { + RequestInput.requestInputData(); + String input = scanner.nextLine(); + return parser.parseUserInput(input, size); + } + + private void computeScore() { + for (int i = 0; i < size; i++) { + compute(game.getGameNumbers(), user.getUserNumbers(), i); + } + } + + private void compute(int[] gameNumber, int[] userNumber, int index) { + int temp = -1; + for (int i = 0; i < gameNumber.length; i++) { + if (gameNumber[i] == userNumber[index]) { + temp = i; + break; + } + } + increaseCount(index, temp); + } + + private void increaseCount(int index, int temp) { + if (temp != index && temp != -1) { + game.increaseBallCount(); + } + if (temp == index) { + game.increaseStrikeCount(); + } + } +} diff --git a/src/main/java/baseball/utils/GenerateRandomNum.java b/src/main/java/baseball/utils/GenerateRandomNum.java new file mode 100644 index 00000000..07f0e6a8 --- /dev/null +++ b/src/main/java/baseball/utils/GenerateRandomNum.java @@ -0,0 +1,33 @@ +package baseball.utils; + +import java.util.Random; +public class GenerateRandomNum { + private static final Random random = new Random(); + public static int[] getRandomNumbers(int size, int start, int end) { + int[] numbers = new int[size]; + + for (int i = 0; i < size; i++) { + numbers[i] = getUniqueRandomNumber(numbers, start, end, i); + } + return numbers; + } + + private static int getUniqueRandomNumber(int[] numbers, int start, int end, int i) { + // start와 end 사이의 랜덤 숫자 생성 + int randomNumber = start + random.nextInt(end - start + 1); + while (!isUnique(numbers, i, randomNumber)) { + // 랜덤 숫자가 유니크하지 않으면 새로운 랜덤 숫자 생성 + randomNumber = start + random.nextInt(end - start + 1); + } + return randomNumber; + } + + private static Boolean isUnique(int[] numbers, int i, int randomNumber) { + for (int j = 0; j < i; j++) { + if (numbers[j] == randomNumber) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/baseball/utils/Parse.java b/src/main/java/baseball/utils/Parse.java new file mode 100644 index 00000000..467eb8f7 --- /dev/null +++ b/src/main/java/baseball/utils/Parse.java @@ -0,0 +1,35 @@ +package baseball.utils; + +public class Parse { + + public int[] parseUserInput(String input, int size) throws IllegalArgumentException { + checkSize(input, size); + return getParseInt(input, size); + } + + private void checkSize(String input, int size) throws IllegalArgumentException { + if (input.length() != size) { + throw new IllegalArgumentException(); + } + } + + private int[] getParseInt(String input, int size) throws IllegalArgumentException { + int[] parseInt = new int[size]; + + for (int i = 0; i < input.length(); i++) { + if (!checkDigit(input, i)) { + throw new IllegalArgumentException(); + } + parseInt[i] = convertCharToInt(input, i); + } + return parseInt; + } + + private int convertCharToInt(String input, int i) { + return input.charAt(i) - '0'; + } + + private Boolean checkDigit(String input, int i) { + return '0' <= input.charAt(i) && input.charAt(i) <= '9'; + } +} \ No newline at end of file diff --git a/src/main/java/baseball/view/GameMessage.java b/src/main/java/baseball/view/GameMessage.java new file mode 100644 index 00000000..8d3d04b4 --- /dev/null +++ b/src/main/java/baseball/view/GameMessage.java @@ -0,0 +1,22 @@ +package baseball.view; + +public class GameMessage { + public void printScoreMessage(int ball, int strike) { + if (ball == 0 && strike == 0) { + System.out.println("낫싱"); + } + if (ball == 0 && strike != 0) { + System.out.println(strike + "스트라이크"); + } + if (ball != 0 && strike == 0) { + System.out.println(ball + "볼"); + } + if (ball != 0 && strike != 0) { + System.out.println(ball + "볼 " + strike + "스트라이크"); + } + } + + public static void printGameOverMessage() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } +} diff --git a/src/main/java/baseball/view/RequestInput.java b/src/main/java/baseball/view/RequestInput.java new file mode 100644 index 00000000..8f045055 --- /dev/null +++ b/src/main/java/baseball/view/RequestInput.java @@ -0,0 +1,12 @@ +package baseball.view; + +public class RequestInput { + + public static void requestInputData() { + System.out.print("숫자를 입력해 주세요 : "); + } + + public static void printRetryMessage() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/ApplicationTest.java b/src/test/java/baseball/ApplicationTest.java new file mode 100644 index 00000000..2bf30f75 --- /dev/null +++ b/src/test/java/baseball/ApplicationTest.java @@ -0,0 +1,48 @@ +package baseball; + +import baseball.view.GameMessage; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ApplicationTest { + + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @Test + public void testPrintScoreMessage() { + GameMessage gameMessage = new GameMessage(); + gameMessage.printScoreMessage(0, 0); + assertTrue(outputStreamCaptor.toString().trim().equals("낫싱")); + + outputStreamCaptor.reset(); // 스트림 초기화 + + gameMessage.printScoreMessage(1, 2); + assertTrue(outputStreamCaptor.toString().trim().equals("1볼 2스트라이크")); + } + + @Test + public void testPrintGameOverMessage() { + GameMessage.printGameOverMessage(); + assertTrue(outputStreamCaptor.toString().trim().equals("3개의 숫자를 모두 맞히셨습니다! 게임 종료")); + } + + // RequestInput에 대한 테스트는 복잡성으로 인해 여기서 다루지 않습니다. + // 일반적으로 표준 입력을 직접 다루는 것은 테스트에서 권장되지 않습니다. +} diff --git a/src/test/java/baseball/utils/GenerateRandomNumTest.java b/src/test/java/baseball/utils/GenerateRandomNumTest.java new file mode 100644 index 00000000..64a1d7df --- /dev/null +++ b/src/test/java/baseball/utils/GenerateRandomNumTest.java @@ -0,0 +1,22 @@ +package baseball.utils; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GenerateRandomNumTest { + + @Test + @DisplayName("랜덤 숫자 생성 테스트") + void testGetRandomNumbers() { + int size = 3; + int[] numbers = GenerateRandomNum.getRandomNumbers(size, 1, 9); + + assertEquals(size, numbers.length); + assertTrue(numbers[0] != numbers[1]); + assertTrue(numbers[1] != numbers[2]); + assertTrue(numbers[0] != numbers[2]); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/utils/ParseTest.java b/src/test/java/baseball/utils/ParseTest.java new file mode 100644 index 00000000..d6d07dec --- /dev/null +++ b/src/test/java/baseball/utils/ParseTest.java @@ -0,0 +1,28 @@ +package baseball.utils; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ParseTest { + + @Test + void parseUserInputInvalidLength() { + // 길이가 유효하지 않을 때 IllegalArgumentException을 던져야 합니다. + Parse parse = new Parse(); + assertThrows(IllegalArgumentException.class, () -> parse.parseUserInput("12345", 3)); + } + + @Test + void parseUserInputNonDigit() { + // 입력에 숫자가 아닌 문자가 포함되어 있을 때 IllegalArgumentException을 던져야 합니다. + Parse parse = new Parse(); + assertThrows(IllegalArgumentException.class, () -> parse.parseUserInput("12a4", 4)); + } + + @Test + void parseUserInputValid() { + // 정상적인 입력을 처리할 수 있어야 합니다. + Parse parse = new Parse(); + assertArrayEquals(new int[]{1, 2, 3}, parse.parseUserInput("123", 3)); + } +} diff --git a/src/test/java/baseball/view/GameMessageTest.java b/src/test/java/baseball/view/GameMessageTest.java new file mode 100644 index 00000000..e0818c54 --- /dev/null +++ b/src/test/java/baseball/view/GameMessageTest.java @@ -0,0 +1,60 @@ +package baseball.view; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GameMessageTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + + @Test + public void testPrintScoreMessageNothing() { + GameMessage gameMessage = new GameMessage(); + gameMessage.printScoreMessage(0, 0); + assertEquals("낫싱\n", outContent.toString()); + } + + @Test + public void testPrintScoreMessageOnlyStrikes() { + GameMessage gameMessage = new GameMessage(); + gameMessage.printScoreMessage(0, 3); + assertEquals("3스트라이크\n", outContent.toString()); + } + + @Test + public void testPrintScoreMessageOnlyBalls() { + GameMessage gameMessage = new GameMessage(); + gameMessage.printScoreMessage(2, 0); + assertEquals("2볼\n", outContent.toString()); + } + + @Test + public void testPrintScoreMessageBallsAndStrikes() { + GameMessage gameMessage = new GameMessage(); + gameMessage.printScoreMessage(1, 2); + assertEquals("1볼 2스트라이크\n", outContent.toString()); + } + + @Test + public void testPrintGameOverMessage() { + GameMessage.printGameOverMessage(); + assertEquals("3개의 숫자를 모두 맞히셨습니다! 게임 종료\n", outContent.toString()); + } +}