From 3dca6ec0b75b2873554d26b0f8599acbc0b2a912 Mon Sep 17 00:00:00 2001 From: geonhui-kim Date: Sun, 2 Jul 2023 18:51:26 +0900 Subject: [PATCH 1/2] feat: number baseball game --- src/main/java/baseball/Application.java | 4 +- .../java/baseball/BaseBallNumberManager.java | 69 ++++++++ src/main/java/baseball/GameController.java | 50 ++++++ src/main/java/baseball/Number.java | 30 ++++ src/main/java/baseball/NumberManager.java | 9 + src/main/java/baseball/NumberRange.java | 20 +++ src/main/java/baseball/Score.java | 48 +++++ .../baseball/BaseBallNumberManagerTest.java | 167 ++++++++++++++++++ src/test/java/baseball/ScoreTest.java | 66 +++++++ 9 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 src/main/java/baseball/BaseBallNumberManager.java create mode 100644 src/main/java/baseball/GameController.java create mode 100644 src/main/java/baseball/Number.java create mode 100644 src/main/java/baseball/NumberManager.java create mode 100644 src/main/java/baseball/NumberRange.java create mode 100644 src/main/java/baseball/Score.java create mode 100644 src/test/java/baseball/BaseBallNumberManagerTest.java create mode 100644 src/test/java/baseball/ScoreTest.java diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index 7f1901b8b..80e3cd099 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -2,6 +2,8 @@ public class Application { public static void main(String[] args) { - //TODO: 숫자 야구 게임 구현 + NumberManager numberManager = new BaseBallNumberManager(); + GameController game = new GameController(numberManager); + game.start(); } } diff --git a/src/main/java/baseball/BaseBallNumberManager.java b/src/main/java/baseball/BaseBallNumberManager.java new file mode 100644 index 000000000..6ac05135e --- /dev/null +++ b/src/main/java/baseball/BaseBallNumberManager.java @@ -0,0 +1,69 @@ +package baseball; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BaseBallNumberManager implements NumberManager { + @Override + public Number generate(int start, int end, int count) { + List numbers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + int num = Randoms.pickNumberInRange(start, end); + while (numbers.contains(num)) { + num = Randoms.pickNumberInRange(start, end); + } + numbers.add(num); + } + return new Number(numbers); + } + + @Override + public Number parseInput(String input) throws IllegalArgumentException { + if (input.length() <= 0 || input.length() > 3) { + throw new IllegalArgumentException("입력 값은 세 자리 정수여야 합니다."); + } + + try { + int num1 = Integer.parseInt(input.substring(0, 1)); + int num2 = Integer.parseInt(input.substring(1, 2)); + int num3 = Integer.parseInt(input.substring(2, 3)); + + if (num1 == 0 || num2 == 0 || num3 == 0) { + throw new IllegalArgumentException("0은 입력할 수 없습니다."); + } + + if (num1 == num2 || num2 == num3 || num3 == num1) { + throw new IllegalArgumentException("서로 다른 세 자리 정수를 입력해 주세요."); + } + return new Number(new ArrayList<>(Arrays.asList(num1, num2, num3))); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("입력 값은 정수여야 합니다."); + } + } + + @Override + public Score calculate(Number user, Number computer) { + int ball = 0; + int strike = 0; + + List userNumbers = user.getNumbers(); + List computerNumbers = computer.getNumbers(); + + for (int i = 0; i < userNumbers.size(); i++) { + for (int j = 0; j < computerNumbers.size(); j++) { + if (userNumbers.get(i).equals(computerNumbers.get(j))) { + if (i == j) { + strike++; + } else { + ball++; + } + } + } + } + + return new Score(ball, strike); + } +} diff --git a/src/main/java/baseball/GameController.java b/src/main/java/baseball/GameController.java new file mode 100644 index 000000000..eca664e07 --- /dev/null +++ b/src/main/java/baseball/GameController.java @@ -0,0 +1,50 @@ +package baseball; + +import camp.nextstep.edu.missionutils.Console; + +import static baseball.NumberRange.*; + +public class GameController { + + private final NumberManager numberManager; + + public GameController(NumberManager numberManager) { + this.numberManager = numberManager; + } + + public void start() { + while (true) { + Number generatedNumber = numberManager.generate(START.value(), END.value(), COUNT.value()); + + while (true) { + Number inputNumber = readInput(); + Score score = numberManager.calculate(inputNumber, generatedNumber); + println(score.toString()); + if (score.isDone()) { + println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + break; + } + } + + println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + if (isClose()) { + println("게임 종료"); + break; + } + } + } + + private void println(String str) { + System.out.println(str); + } + + private Number readInput() { + String input = Console.readLine(); + return numberManager.parseInput(input); + } + + private boolean isClose() { + String input = Console.readLine(); + return input.equals("2"); + } +} diff --git a/src/main/java/baseball/Number.java b/src/main/java/baseball/Number.java new file mode 100644 index 000000000..317032505 --- /dev/null +++ b/src/main/java/baseball/Number.java @@ -0,0 +1,30 @@ +package baseball; + +import java.util.List; +import java.util.Objects; + +public class Number { + + private final List numbers; + + public Number(List numbers) { + this.numbers = numbers; + } + + public List getNumbers() { + return numbers; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Number number = (Number) o; + return Objects.equals(numbers, number.numbers); + } + + @Override + public int hashCode() { + return Objects.hash(numbers); + } +} diff --git a/src/main/java/baseball/NumberManager.java b/src/main/java/baseball/NumberManager.java new file mode 100644 index 000000000..ba627d712 --- /dev/null +++ b/src/main/java/baseball/NumberManager.java @@ -0,0 +1,9 @@ +package baseball; + +public interface NumberManager { + Number generate(int start, int end, int count); + + Number parseInput(String input) throws IllegalArgumentException; + + Score calculate(Number user, Number computer); +} diff --git a/src/main/java/baseball/NumberRange.java b/src/main/java/baseball/NumberRange.java new file mode 100644 index 000000000..b1923c296 --- /dev/null +++ b/src/main/java/baseball/NumberRange.java @@ -0,0 +1,20 @@ +package baseball; + +public enum NumberRange { + START(1), END(9), COUNT(3); + + public int value() { + return value; + } + + private final int value; + + NumberRange(int value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(value); + } +} diff --git a/src/main/java/baseball/Score.java b/src/main/java/baseball/Score.java new file mode 100644 index 000000000..b7958466b --- /dev/null +++ b/src/main/java/baseball/Score.java @@ -0,0 +1,48 @@ +package baseball; + +import java.util.Objects; + +public class Score { + int ball; + int strike; + + public Score(int ball, int strike) { + this.ball = ball; + this.strike = strike; + } + + public boolean isDone() { + return this.strike == 3; + } + + @Override + public String toString() { + if (ball == 0 && strike == 0) { + return "낫싱"; + } + + if (ball == 0) { + return strike + "스트라이크"; + } + + if (strike == 0) { + return ball + "볼"; + } + + return ball + "볼 " + strike + "스트라이크"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Score score = (Score) o; + return ball == score.ball && strike == score.strike; + } + + @Override + public int hashCode() { + return Objects.hash(ball, strike); + } + +} diff --git a/src/test/java/baseball/BaseBallNumberManagerTest.java b/src/test/java/baseball/BaseBallNumberManagerTest.java new file mode 100644 index 000000000..7ffc9b0e3 --- /dev/null +++ b/src/test/java/baseball/BaseBallNumberManagerTest.java @@ -0,0 +1,167 @@ +package baseball; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static baseball.NumberRange.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaseBallNumberManagerTest { + + NumberManager numberManager = new BaseBallNumberManager(); + + @Test + void TestGeneratedNumbersAreDifferent() { + // given + int start = START.value(); + int end = END.value(); + int count = COUNT.value(); + + // when + Number number = numberManager.generate(start, end, count); + List numbers = number.getNumbers(); + int length = numbers.size(); + + // then + boolean duplicated = false; + for (int i = 0; i < length - 1; i++) { + for (int j = i + 1; j < length; j++) { + if (numbers.get(i).equals(numbers.get(j))) { + duplicated = true; + break; + } + } + } + assertThat(duplicated).isFalse(); + } + + @Test + void TestGeneratedNumbersAreInRange() { + // given + int start = START.value(); + int end = END.value(); + int count = COUNT.value(); + + // when + Number number = numberManager.generate(start, end, count); + + // then + for (int n : number.getNumbers()) { + assertThat(n) + .isGreaterThanOrEqualTo(start) + .isLessThanOrEqualTo(end); + } + } + + @Test + void TestInputIsThreeInteger() { + // given + String input = "123"; + Number expected = new Number(Arrays.asList(1, 2, 3)); + + // when + Number actual = numberManager.parseInput(input); + + // then + assertThat(actual).isEqualTo(expected); + } + + + @Test + void TestInputIsNotThreeInteger() { + assertThatThrownBy(() -> numberManager.parseInput("")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("1234")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void TestInputIsZero() { + assertThatThrownBy(() -> numberManager.parseInput("023")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("103")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("120")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void TestInputIsDuplicated() { + assertThatThrownBy(() -> numberManager.parseInput("113")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("122")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("121")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void TestInputIsNotInteger() { + assertThatThrownBy(() -> numberManager.parseInput("x23")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("1x3")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> numberManager.parseInput("12x")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void TestCalculate3Strike() { + // given + Score expected = new Score(0, 3); + Number generatedNumber = new Number(new ArrayList<>(Arrays.asList(1, 2, 3))); + Number inputNumber = new Number(new ArrayList<>(Arrays.asList(1, 2, 3))); + + // when + Score actual = numberManager.calculate(inputNumber, generatedNumber); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void TestCalculate1Ball1Strike() { + // given + Score expected = new Score(1, 1); + Number generatedNumber = new Number(new ArrayList<>(Arrays.asList(1, 2, 3))); + Number inputNumber = new Number(new ArrayList<>(Arrays.asList(1, 3, 4))); + + // when + Score actual = numberManager.calculate(inputNumber, generatedNumber); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void TestCalculate3Ball() { + // given + Score expected = new Score(3, 0); + Number generatedNumber = new Number(new ArrayList<>(Arrays.asList(1, 2, 3))); + Number inputNumber = new Number(new ArrayList<>(Arrays.asList(2, 3, 1))); + + // when + Score actual = numberManager.calculate(inputNumber, generatedNumber); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void TestCalculateNothing() { + // given + Score expected = new Score(0, 0); + Number generatedNumber = new Number(new ArrayList<>(Arrays.asList(1, 2, 3))); + Number inputNumber = new Number(new ArrayList<>(Arrays.asList(4, 5, 6))); + + // when + Score actual = numberManager.calculate(inputNumber, generatedNumber); + + // then + assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/ScoreTest.java b/src/test/java/baseball/ScoreTest.java new file mode 100644 index 000000000..b56dea2ba --- /dev/null +++ b/src/test/java/baseball/ScoreTest.java @@ -0,0 +1,66 @@ +package baseball; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class ScoreTest { + + private class ScoreWithResult { + Score score; + boolean isDone; + String toString; + + public ScoreWithResult(Score score, boolean isDone) { + this.score = score; + this.isDone = isDone; + } + + public ScoreWithResult(Score score, String toString) { + this.score = score; + this.toString = toString; + } + } + + @Test + void TestIsDone() { + // given + List tests = new ArrayList<>(); + tests.add(new ScoreWithResult(new Score(0, 3), true)); + tests.add(new ScoreWithResult(new Score(1, 2), false)); + tests.add(new ScoreWithResult(new Score(2, 1), false)); + tests.add(new ScoreWithResult(new Score(3, 0), false)); + tests.add(new ScoreWithResult(new Score(2, 0), false)); + tests.add(new ScoreWithResult(new Score(1, 0), false)); + tests.add(new ScoreWithResult(new Score(0, 0), false)); + + for (ScoreWithResult test : tests) { + // when + boolean actual = test.score.isDone(); + + // then + assertThat(actual).isEqualTo(test.isDone); + } + } + + @Test + void TestToString() { + // given + List tests = new ArrayList<>(); + tests.add(new ScoreWithResult(new Score(0, 0), "낫싱")); + tests.add(new ScoreWithResult(new Score(1, 0), "1볼")); + tests.add(new ScoreWithResult(new Score(0, 1), "1스트라이크")); + tests.add(new ScoreWithResult(new Score(1, 1), "1볼 1스트라이크")); + + for (ScoreWithResult test : tests) { + // when + String actual = test.score.toString(); + + // then + assertThat(actual).isEqualTo(test.toString); + } + } +} \ No newline at end of file From 09ea4999b7e255764e94551b1b89f4db6ef05eac Mon Sep 17 00:00:00 2001 From: geonhui-kim Date: Sun, 2 Jul 2023 19:09:23 +0900 Subject: [PATCH 2/2] feat: error handling for input value --- src/main/java/baseball/GameController.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/baseball/GameController.java b/src/main/java/baseball/GameController.java index eca664e07..2b7dbfd45 100644 --- a/src/main/java/baseball/GameController.java +++ b/src/main/java/baseball/GameController.java @@ -17,13 +17,18 @@ public void start() { Number generatedNumber = numberManager.generate(START.value(), END.value(), COUNT.value()); while (true) { - Number inputNumber = readInput(); - Score score = numberManager.calculate(inputNumber, generatedNumber); - println(score.toString()); - if (score.isDone()) { - println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); - break; + try { + Number inputNumber = readInput(); + Score score = numberManager.calculate(inputNumber, generatedNumber); + println(score.toString()); + if (score.isDone()) { + println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + break; + } + } catch (IllegalArgumentException ex) { + println("[ERROR] " + ex.getMessage()); } + } println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); @@ -38,7 +43,7 @@ private void println(String str) { System.out.println(str); } - private Number readInput() { + private Number readInput() throws IllegalArgumentException { String input = Console.readLine(); return numberManager.parseInput(input); }