diff --git a/README.md b/README.md index 8d7e8aee..871d2041 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ -# java-baseball-precourse \ No newline at end of file +# 숫자 야구 게임 (Baseball) + +## 📌 구현할 기능 목록 + +### 1) 게임 실행/흐름 +- 프로그램 시작 시 숫자 야구 게임을 실행한다. +- 한 게임은 “정답 3자리 생성 → 입력 반복 → 정답 시 종료 문구 출력” 흐름으로 진행한다. +- 정답을 맞히면 “3개의 숫자를 모두 맞히셨습니다! 게임 끝”을 출력하고 재시작/종료 입력 단계로 이동한다. +- 재시작(1) 선택 시 새로운 정답을 생성하고 게임을 다시 진행한다. +- 종료(2) 선택 시 프로그램을 종료한다. + +### 2) 컴퓨터 숫자 생성 +- 0~9 사이의 숫자 중 서로 다른 3개를 무작위로 뽑아 정답으로 사용한다. +- 생성된 정답은 3자리 List(Integer) 형태로 유지한다. + +### 3) 사용자 입력(숫자 3자리) +- “숫자를 입력해주세요 : ” 안내 문구를 출력한다. +- 사용자 입력은 정확히 3자리여야 한다. +- 사용자 입력은 모두 숫자여야 한다. +- 사용자 입력은 중복 숫자가 없어야 한다. +- 위 조건을 만족하지 않으면 [ERROR] Invalid input을 출력하고 다시 입력을 받는다. +- 검증이 끝나면 문자열 입력을 정수 리스트로 변환한다. + +### 4) 결과 판정(스트라이크/볼) +- 같은 위치에 같은 숫자가 있으면 스트라이크로 센다. +- 숫자는 존재하지만 위치가 다르면 볼로 센다. +- 판정 결과를 Result(strikeCount, ballCount)로 반환한다. +- 스트라이크가 3이면 정답으로 처리한다. + +### 5) 결과 출력 +- 스트라이크가 1 이상이면 “{n}스트라이크 ”를 출력한다. +- 볼이 1 이상이면 “{n}볼 ”을 출력한다. +- 스트라이크/볼이 모두 0이면 “낫싱”을 출력한다. +- 각 결과 출력은 줄바꿈으로 마무리한다. + +### 6) 재시작/종료 입력 +- “게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.”를 출력한다. +- 입력은 한 자리 숫자여야 한다. +- 입력값은 1 또는 2만 허용한다. +- 유효하지 않으면 [ERROR] Invalid input을 출력하고 다시 입력을 받는다. +- 1이면 true(계속), 2이면 false(종료)를 반환한다. + +## ▶️ 실행 방법 (예시) +1. 프로그램을 실행한다. +2. 안내 문구에 따라 3자리 숫자를 입력한다. +3. 판정 결과(볼/스트라이크/낫싱)를 확인하며 반복한다. +4. 정답을 맞히면 종료 문구가 출력된다. +5. 1을 입력하면 재시작, 2를 입력하면 종료된다. diff --git a/src/main/java/Baseball.java b/src/main/java/Baseball.java new file mode 100644 index 00000000..40c8b803 --- /dev/null +++ b/src/main/java/Baseball.java @@ -0,0 +1,38 @@ +import java.util.ArrayList; +import java.util.Scanner; + +public class Baseball { + private final Input input; + + public Baseball() { + Scanner scanner = new Scanner(System.in); + this.input = new Input(scanner);; + } + + public void run() { + boolean isContinue = true; + while(isContinue){ + isContinue = playGame(); + } + } + + private boolean playGame() { + NumberGenerator generator = new RandomNumberGenerator(); + Computer computer = new Computer(generator); + + boolean isContinue; + while (true) { + ArrayList numbers = input.inputNumbers(); + Result result = computer.checkNumbers(numbers); + + if (result.isAllStrike()) { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); + break; + } + Output.printResult(result); + } + + isContinue = input.inputContinue(); + return isContinue; + } +} diff --git a/src/main/java/Computer.java b/src/main/java/Computer.java new file mode 100644 index 00000000..866ec707 --- /dev/null +++ b/src/main/java/Computer.java @@ -0,0 +1,38 @@ +import java.util.List; +import java.util.Objects; + +public class Computer { + private final List numbers; + + public Computer(NumberGenerator generator) { + this.numbers = generator.generate(); + } + + public Result checkNumbers(List inputNumbers) { + int strikeCount = countStrike(inputNumbers); + int ballCount = countBall(inputNumbers); + + return new Result(strikeCount, ballCount); + } + + private int countStrike(List inputNumbers) { + int count = 0; + for (int i = 0; i < numbers.size(); i++) { + if (Objects.equals(numbers.get(i), inputNumbers.get(i))) { + count++; + } + } + return count; + } + + private int countBall(List inputNumbers) { + int count = 0; + for (int i = 0; i < inputNumbers.size(); i++) { + int num = inputNumbers.get(i); + if (numbers.contains(num) && numbers.get(i) != num) { + count++; + } + } + return count; + } +} diff --git a/src/main/java/Input.java b/src/main/java/Input.java new file mode 100644 index 00000000..8e3cebaf --- /dev/null +++ b/src/main/java/Input.java @@ -0,0 +1,92 @@ +import java.util.ArrayList; +import java.util.Scanner; + +public class Input { + + private final Scanner scanner; + + public Input(Scanner scanner) { + this.scanner = scanner; + } + + public ArrayList inputNumbers() { + while (true) { + try { + System.out.print("숫자를 입력해주세요 : "); + String input = scanner.next(); + return validateInputNumber(input); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + public boolean inputContinue() { + while (true) { + try { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + String input = scanner.next(); + return validateContinue(input); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + private ArrayList validateInputNumber(String input) { + if (input == null || input.length() != 3) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + if (!isAllNumbers(input)) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + if (hasDuplicateNumbers(input)) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + return convertStringToNumbers(input); + } + + private boolean isAllNumbers(String input) { + for (int i = 0; i < input.length(); i++) { + if (!Character.isDigit(input.charAt(i))) { + return false; + } + } + return true; + } + + private boolean hasDuplicateNumbers(String input) { + boolean[] used = new boolean[10]; + + for (int i = 0; i < input.length(); i++) { + int num = Character.getNumericValue(input.charAt(i)); + if (used[num]) { + return true; + } + used[num] = true; + } + return false; + } + + private ArrayList convertStringToNumbers(String input) { + ArrayList numbers = new ArrayList<>(); + for (int i = 0; i < input.length(); i++) { + numbers.add(Character.getNumericValue(input.charAt(i))); + } + return numbers; + } + + private boolean validateContinue(String input) { + if (input == null || input.length() != 1) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + if (!Character.isDigit(input.charAt(0))) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + int inputNumber = Character.getNumericValue(input.charAt(0)); + if (inputNumber != 1 && inputNumber != 2) { + throw new IllegalArgumentException("[ERROR] Invalid input"); + } + return inputNumber == 1; + } +} \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 00000000..0016013f --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,6 @@ +public class Main { + public static void main(String[] args) { + Baseball baseball = new Baseball(); + baseball.run(); + } +} diff --git a/src/main/java/NumberGenerator.java b/src/main/java/NumberGenerator.java new file mode 100644 index 00000000..4a3caa24 --- /dev/null +++ b/src/main/java/NumberGenerator.java @@ -0,0 +1,5 @@ +import java.util.List; + +public interface NumberGenerator { + List generate(); +} diff --git a/src/main/java/Output.java b/src/main/java/Output.java new file mode 100644 index 00000000..a5cf5d77 --- /dev/null +++ b/src/main/java/Output.java @@ -0,0 +1,16 @@ +public class Output { + public static void printResult(Result result){ + int strikeCount = result.strikeCount(); + int ballCount = result.ballCount(); + if(strikeCount > 0){ + System.out.printf("%d스트라이크 ", strikeCount); + } + if(ballCount > 0){ + System.out.printf("%d볼 ", ballCount); + } + if(strikeCount == 0 && ballCount == 0){ + System.out.print("낫싱"); + } + System.out.println(); + } +} diff --git a/src/main/java/RandomNumberGenerator.java b/src/main/java/RandomNumberGenerator.java new file mode 100644 index 00000000..12ea9a78 --- /dev/null +++ b/src/main/java/RandomNumberGenerator.java @@ -0,0 +1,16 @@ +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RandomNumberGenerator implements NumberGenerator { + + @Override + public List generate() { + List numbers = new ArrayList<>(); + for (int i = 0; i <= 9; i++) { + numbers.add(i); + } + Collections.shuffle(numbers); + return numbers.subList(0, 3); + } +} \ No newline at end of file diff --git a/src/main/java/Result.java b/src/main/java/Result.java new file mode 100644 index 00000000..4bdb356b --- /dev/null +++ b/src/main/java/Result.java @@ -0,0 +1,6 @@ +public record Result(int strikeCount, int ballCount) { + + public Boolean isAllStrike() { + return strikeCount == 3; + } +} diff --git a/src/test/java/ComputerTest.java b/src/test/java/ComputerTest.java new file mode 100644 index 00000000..f0ef00cb --- /dev/null +++ b/src/test/java/ComputerTest.java @@ -0,0 +1,84 @@ +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ComputerTest { + @Test + void allStrike_returns3Strike0Ball() { + // given + Computer computer = new Computer( + new FixedNumberGenerator(Arrays.asList(1, 2, 3)) + ); + List input = Arrays.asList(1, 2, 3); + + // when + Result result = computer.checkNumbers(input); + + // then + assertEquals(3, result.strikeCount()); + assertEquals(0, result.ballCount()); + } + + @Test + void oneStrike_oneBall() { + // secret: 4 2 5 + // guess : 4 5 6 + Computer computer = new Computer( + new FixedNumberGenerator(Arrays.asList(4, 2, 5)) + ); + List input = Arrays.asList(4, 5, 6); + + Result result = computer.checkNumbers(input); + + assertEquals(1, result.strikeCount()); + assertEquals(1, result.ballCount()); + } + + @Test + void onlyBalls_returns0Strike2Ball() { + // secret: 4 2 5 + // guess : 2 4 6 + Computer computer = new Computer( + new FixedNumberGenerator(Arrays.asList(4, 2, 5)) + ); + List input = Arrays.asList(2, 4, 6); + + Result result = computer.checkNumbers(input); + + assertEquals(0, result.strikeCount()); + assertEquals(2, result.ballCount()); + } + + @Test + void nothing_returns0Strike0Ball() { + // secret: 4 2 5 + // guess : 7 8 9 + Computer computer = new Computer( + new FixedNumberGenerator(Arrays.asList(4, 2, 5)) + ); + List input = Arrays.asList(7, 8, 9); + + Result result = computer.checkNumbers(input); + + assertEquals(0, result.strikeCount()); + assertEquals(0, result.ballCount()); + } + + @Test + void mixedCase_returnsCorrectCounts() { + // secret: 1 2 3 + // guess : 1 3 2 + Computer computer = new Computer( + new FixedNumberGenerator(Arrays.asList(1, 2, 3)) + ); + List input = Arrays.asList(1, 3, 2); + + Result result = computer.checkNumbers(input); + + assertEquals(1, result.strikeCount()); + assertEquals(2, result.ballCount()); + } +} \ No newline at end of file diff --git a/src/test/java/FixedNumberGenerator.java b/src/test/java/FixedNumberGenerator.java new file mode 100644 index 00000000..b0a3168d --- /dev/null +++ b/src/test/java/FixedNumberGenerator.java @@ -0,0 +1,15 @@ +import java.util.List; + +public class FixedNumberGenerator implements NumberGenerator { + + private final List numbers; + + public FixedNumberGenerator(List numbers) { + this.numbers = numbers; + } + + @Override + public List generate() { + return numbers; + } +} \ No newline at end of file diff --git a/src/test/java/InputTest.java b/src/test/java/InputTest.java new file mode 100644 index 00000000..96eab837 --- /dev/null +++ b/src/test/java/InputTest.java @@ -0,0 +1,106 @@ +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.*; + +class InputTest { + + @Test + void inputNumbers_validInput_returnsDigits() { + // given + Input input = new Input(new Scanner("123\n")); + + // when + ArrayList result = input.inputNumbers(); + + // then + assertEquals(Arrays.asList(1, 2, 3), result); + } + + @Test + void inputNumbers_duplicate_thenValid_returnsValidDigits() { + // given + Input input = new Input(new Scanner("112\n789\n")); + + // when + ArrayList result = input.inputNumbers(); + + // then + assertEquals(Arrays.asList(7, 8, 9), result); + } + + @Test + void inputNumbers_nonDigit_thenValid_returnsValidDigits() { + // given + Input input = new Input(new Scanner("12a\n456\n")); + + // when + ArrayList result = input.inputNumbers(); + + // then + assertEquals(Arrays.asList(4, 5, 6), result); + } + + @Test + void inputNumbers_wrongLength_thenValid_returnsValidDigits() { + // given + Input input = new Input(new Scanner("12\n987\n")); + + // when + ArrayList result = input.inputNumbers(); + + // then + assertEquals(Arrays.asList(9, 8, 7), result); + } + + @Test + void inputContinue_input1_returnsTrue() { + // given + Input input = new Input(new Scanner("1\n")); + + // when + boolean result = input.inputContinue(); + + // then + assertTrue(result); + } + + @Test + void inputContinue_input2_returnsFalse() { + // given + Input input = new Input(new Scanner("2\n")); + + // when + boolean result = input.inputContinue(); + + // then + assertFalse(result); + } + + @Test + void inputContinue_invalid_then1_returnsTrue() { + // given + Input input = new Input(new Scanner("3\n1\n")); + + // when + boolean result = input.inputContinue(); + + // then + assertTrue(result); + } + + @Test + void inputContinue_nonDigit_then2_returnsFalse() { + // given + Input input = new Input(new Scanner("a\n2\n")); + + // when + boolean result = input.inputContinue(); + + // then + assertFalse(result); + } +} \ No newline at end of file