diff --git a/README.md b/README.md index 8d7e8aee..0519cd89 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# java-baseball-precourse \ No newline at end of file +# 숫자 야구 게임 +1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임. + +## 기능 정리 + +### 게임 진행 +- 사용자는 3개의 숫자를 입력하여 컴퓨터의 수를 맞추기 +- 3개의 숫자를 모두 맞히면 게임이 종료 +- 게임 종료 후 재시작(1)하거나 완전히 종료(2) + +### 컴퓨터의 수 맞추기 검증 +- 위 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임 종료 + +### 입력 및 출력 +- 게임 시작 문구, 결과, 오류 메시지 등을 출력 + +### 힌트 +- 같은 수가 같은 자리에 있으면 `스트라이크` +- 같은 수가 다른 자리에 있으면 `볼` +- 같은 수가 전혀 없으면 `낫싱` + +### 예외 처리 +- 사용자가 잘못된 값을 입력할 경우 에러 발생 + - `[ERROR]`로 시작하는 에러 메시지를 출력 + - 에러 발생 후에도 게임을 계속 진행할 수 있도록 구현 + - 3자리가 아닌 경우 + - 숫자가 아닌 문자가 포함된 경우 + - 중복된 숫자가 있는 경우 + - 1~9 범위를 벗어난 경우 diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 00000000..79ffed76 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,9 @@ +import controller.BaseballGame; +import java.io.IOException; + +public class Main { + public static void main(String[] args) throws IOException { + BaseballGame game = new BaseballGame(); + game.run(); + } +} diff --git a/src/main/java/controller/BaseballGame.java b/src/main/java/controller/BaseballGame.java new file mode 100644 index 00000000..dfaea2fa --- /dev/null +++ b/src/main/java/controller/BaseballGame.java @@ -0,0 +1,83 @@ +package controller; + +import model.Computer; +import model.GameResult; +import model.Numbers; +import view.InputView; +import view.OutputView; + +import java.util.ArrayList; +import java.util.List; + +public class BaseballGame { + private final InputView inputView; + private final OutputView outputView; + private final Computer computer; + + public BaseballGame() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + this.computer = new Computer(); + } + + public void run() { + boolean isRunning = true; + while (isRunning) { + playRound(); + isRunning = checkRestart(); + } + } + + private void playRound() { + Numbers target = computer.generate(); + boolean isCorrect = false; + while (!isCorrect) { + isCorrect = playTurn(target); + } + outputView.printGameEnd(); + } + + private boolean playTurn(Numbers target) { + try { + Numbers userNumbers = getUserInput(); + GameResult result = calculateResult(target, userNumbers); + outputView.printResult(result); + return result.isThreeStrike(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + return false; + } + } + + private Numbers getUserInput() { + String input = inputView.inputNumber(); + return new Numbers(parse(input)); + } + + private List parse(String input) { + List numbers = new ArrayList<>(); + for (char c : input.toCharArray()) { + numbers.add(c - '0'); + } + return numbers; + } + + private GameResult calculateResult(Numbers target, Numbers user) { + int strike = target.countStrikes(user); + int ball = target.countBalls(user); + return new GameResult(strike, ball); + } + + private boolean checkRestart() { + while (true) { + String input = inputView.askRestart(); + if (input.equals("1")) { + return true; + } + if (input.equals("2")) { + return false; + } + outputView.printError("[ERROR] 1 또는 2만 입력 가능합니다."); + } + } +} diff --git a/src/main/java/model/Computer.java b/src/main/java/model/Computer.java new file mode 100644 index 00000000..de4458ce --- /dev/null +++ b/src/main/java/model/Computer.java @@ -0,0 +1,23 @@ +package model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Computer { + private static final int NUMBER_SIZE = 3; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 9; + + public Numbers generate() { + Random random = new Random(); + List computer = new ArrayList<>(); + while (computer.size() < NUMBER_SIZE) { + int randomNumber = random.nextInt(MAX_NUMBER) + MIN_NUMBER; + if (!computer.contains(randomNumber)) { + computer.add(randomNumber); + } + } + return new Numbers(computer); + } +} diff --git a/src/main/java/model/GameResult.java b/src/main/java/model/GameResult.java new file mode 100644 index 00000000..c1e590c0 --- /dev/null +++ b/src/main/java/model/GameResult.java @@ -0,0 +1,30 @@ +package model; + +public class GameResult { + private final int strike; + private final int ball; + + public GameResult(int strike, int ball) { + this.strike = strike; + this.ball = ball; + } + + public boolean isThreeStrike() { + return strike == 3; + } + + @Override + public String toString() { + if (strike == 0 && ball == 0) { + return "낫싱"; + } + String result = ""; + if (ball > 0) { + result += ball + "볼 "; + } + if (strike > 0) { + result += strike + "스트라이크"; + } + return result.trim(); + } +} diff --git a/src/main/java/model/Numbers.java b/src/main/java/model/Numbers.java new file mode 100644 index 00000000..9ca69667 --- /dev/null +++ b/src/main/java/model/Numbers.java @@ -0,0 +1,65 @@ +package model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Numbers { + private final List numbers; + + public Numbers(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + if (numbers.size() != 3) { + throw new IllegalArgumentException("[ERROR] 3자리의 숫자를 입력해야 합니다."); + } + for (int number : numbers) { + validateRange(number); + } + validateUnique(numbers); + } + + private void validateRange(int number) { + if (number < 1 || number > 9) { + throw new IllegalArgumentException("[ERROR] 1~9 사이의 숫자만 입력 가능합니다."); + } + } + + private void validateUnique(List numbers) { + Set uniqueNumbers = new HashSet<>(numbers); + if (uniqueNumbers.size() != numbers.size()) { + throw new IllegalArgumentException("[ERROR] 중복된 숫자는 입력할 수 없습니다."); + } + } + + public int countStrikes(Numbers other) { + int count = 0; + for (int i = 0; i < numbers.size(); i++) { + if (numbers.get(i).equals(other.get(i))) { + count++; + } + } + return count; + } + + public int countBalls(Numbers other) { + int count = 0; + for (int i = 0; i < numbers.size(); i++) { + if (!numbers.get(i).equals(other.get(i)) && numbers.contains(other.get(i))) { + count++; + } + } + return count; + } + + public Integer get(int index) { + return numbers.get(index); + } + + public boolean contains(int number) { + return numbers.contains(number); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..c82bc29d --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,31 @@ +package view; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class InputView { + private final BufferedReader br; + + public InputView() { + this.br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String inputNumber() { + System.out.print("숫자를 입력해주세요 : "); + try { + return br.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String askRestart() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + try { + return br.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..01a12f3c --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,17 @@ +package view; + +import model.GameResult; + +public class OutputView { + public void printResult(GameResult result) { + System.out.println(result.toString()); + } + + public void printGameEnd() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); + } + + public void printError(String message) { + System.out.println(message); + } +} diff --git a/src/test/java/model/GameResultTest.java b/src/test/java/model/GameResultTest.java new file mode 100644 index 00000000..7c3be487 --- /dev/null +++ b/src/test/java/model/GameResultTest.java @@ -0,0 +1,23 @@ +package model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GameResultTest { + + @Test + void 결과_출력_형식_확인() { + GameResult result = new GameResult(1, 1); + assertEquals("1볼 1스트라이크", result.toString()); + + result = new GameResult(3, 0); + assertEquals("3스트라이크", result.toString()); + + result = new GameResult(0, 3); + assertEquals("3볼", result.toString()); + + result = new GameResult(0, 0); + assertEquals("낫싱", result.toString()); + } +} diff --git a/src/test/java/model/NumbersTest.java b/src/test/java/model/NumbersTest.java new file mode 100644 index 00000000..54d25259 --- /dev/null +++ b/src/test/java/model/NumbersTest.java @@ -0,0 +1,64 @@ +package model; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class NumbersTest { + @Test + void 숫자_리스트_생성_정상() { + assertDoesNotThrow(() -> new Numbers(Arrays.asList(1, 2, 3))); + } + + @Test + void 예외_테스트_숫자_길이오류() { + assertThrows(IllegalArgumentException.class, () -> new Numbers(Arrays.asList(1, 2))); + assertThrows(IllegalArgumentException.class, () -> new Numbers(Arrays.asList(1, 2, 3, 4))); + } + + @Test + void 예외_테스트_숫자_범위오류() { + assertThrows(IllegalArgumentException.class, () -> new Numbers(Arrays.asList(1, 0, 3))); + } + + @Test + void 예외_테스트_숫자_중복오류() { + assertThrows(IllegalArgumentException.class, () -> new Numbers(Arrays.asList(1, 2, 1))); + } + + @Test + void 스트라이크_계산_3스트라이크() { + Numbers computer = new Numbers(Arrays.asList(1, 2, 3)); + Numbers user = new Numbers(Arrays.asList(1, 2, 3)); + assertEquals(3, computer.countStrikes(user)); + assertEquals(0, computer.countBalls(user)); + } + + @Test + void 볼_계산_3볼() { + Numbers computer = new Numbers(Arrays.asList(1, 2, 3)); + Numbers user = new Numbers(Arrays.asList(2, 3, 1)); + assertEquals(0, computer.countStrikes(user)); + assertEquals(3, computer.countBalls(user)); + } + + @Test + void 낫싱_계산() { + Numbers computer = new Numbers(Arrays.asList(1, 2, 3)); + Numbers user = new Numbers(Arrays.asList(4, 5, 6)); + assertEquals(0, computer.countStrikes(user)); + assertEquals(0, computer.countBalls(user)); + } + + @Test + void 스트라이크_1_볼_1_계산() { + Numbers computer = new Numbers(Arrays.asList(1, 2, 3)); + Numbers user = new Numbers(Arrays.asList(1, 3, 4)); + assertEquals(1, computer.countStrikes(user)); + assertEquals(1, computer.countBalls(user)); + } +}