diff --git a/README.md b/README.md index fcf3f057..228d0d77 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,14 @@ ## 과제 제출 과정 * [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse) + +## 구현할 기능 목록 +- [x] 숫자 입력 +- [x] 입력값 검증 (1 ~ 9 사이의 숫자 3개) +- [x] 값 Parsing ("234" => [2, 3, 4]) +- [x] 랜덤 숫자 생성 +- [x] 값 비교 ([2, 3, 4] vs [4, 5, 6] => 1개 겹침) +- [x] 결과 판단 (1 => 1 스트라이크 2 볼) +- [x] 결과 출력 +- [x] 게임 반복 진행 가능 +- [ ] 테스트 구현 diff --git a/src/main/java/study/baseball/App.java b/src/main/java/study/baseball/App.java new file mode 100644 index 00000000..d0160c6c --- /dev/null +++ b/src/main/java/study/baseball/App.java @@ -0,0 +1,20 @@ +package study.baseball; + +import java.util.regex.Pattern; +import study.baseball.engine.Baseball; +import study.baseball.engine.InputParser; +import study.baseball.engine.InputValidator; +import study.baseball.engine.NumberGenerator; + +public class App { + public static void main(String[] args) { + Console console = new Console(); + Pattern regex = Pattern.compile("[0-9]{3}"); + InputValidator validator = new InputValidator(regex); + InputParser parser = new InputParser(); + NumberGenerator numberGenerator = new NumberGenerator(); + Baseball baseball = new Baseball(console, validator, parser, numberGenerator); + + baseball.run(); + } +} diff --git a/src/main/java/study/baseball/Console.java b/src/main/java/study/baseball/Console.java new file mode 100644 index 00000000..f68513ea --- /dev/null +++ b/src/main/java/study/baseball/Console.java @@ -0,0 +1,25 @@ +package study.baseball; + +import java.util.Scanner; +import study.baseball.engine.io.Input; +import study.baseball.engine.io.Output; + +public class Console implements Input, Output { + private final Scanner scanner = new Scanner(System.in); + + @Override + public String input(String message) { + System.out.print(message); + return scanner.nextLine(); + } + + @Override + public void printError(String message) { + System.out.println("[ERROR] : " + message); + } + + @Override + public void printOutput(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/study/baseball/engine/Baseball.java b/src/main/java/study/baseball/engine/Baseball.java new file mode 100644 index 00000000..efca3b54 --- /dev/null +++ b/src/main/java/study/baseball/engine/Baseball.java @@ -0,0 +1,80 @@ +package study.baseball.engine; + +import java.util.List; +import study.baseball.Console; +import study.baseball.engine.model.BallCount; +import study.baseball.engine.model.Numbers; + +public class Baseball implements Runnable { + private final Console console; + private final InputValidator validator; + private final InputParser parser; + private final NumberGenerator numberGenerator; + + public Baseball(Console console, InputValidator inputValidator, InputParser inputParser, + NumberGenerator numberGenerator) { + this.console = console; + this.validator = inputValidator; + this.parser = inputParser; + this.numberGenerator = numberGenerator; + } + + @Override + public void run() { + while (true) { + Numbers answer = numberGenerator.generateRandomNumber(); + while (saveGame(answer)) + ; + if (askContinue()) { + continue; + } + break; + } + } + + private boolean saveGame(Numbers answer) { + try { + return proceedGame(answer); + } catch (RuntimeException exception) { + console.printError(exception.getMessage()); + return true; + } + } + + private boolean proceedGame(Numbers answer) { + String input = console.input("숫자를 입력해주세요 : "); + validator.checkValidFormat(input); + List parsedInput = parser.parseStringToIntegerList(input); + BallCount result = new BallCount(answer.getStrike(parsedInput), answer.getBall(parsedInput)); + if (result.getStrike() == 3) { + console.printOutput("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + return false; + } + showResult(result); + return true; + } + + private boolean askContinue() { + while (true) { + String input = console.input("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.\n"); + if (input.equals("1")) { + return true; + } + if (input.equals("2")) { + return false; + } + console.printError("잘못된 입력입니다."); + } + } + + private void showResult(BallCount ballCount) { + StringBuilder output = new StringBuilder(); + if (ballCount.getStrike() > 0) { + output.append(ballCount.getStrike()).append(" 스트라이크 "); + } + if (ballCount.getBall() > 0) { + output.append(ballCount.getBall()).append(" 볼 "); + } + console.printOutput(output.toString().length() > 0 ? output.toString() : "낫싱"); + } +} diff --git a/src/main/java/study/baseball/engine/InputParser.java b/src/main/java/study/baseball/engine/InputParser.java new file mode 100644 index 00000000..37c12a6f --- /dev/null +++ b/src/main/java/study/baseball/engine/InputParser.java @@ -0,0 +1,20 @@ +package study.baseball.engine; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class InputParser { + public List parseStringToIntegerList(String input) { + String[] inputArray = input.split(""); + Set result = new LinkedHashSet<>(); + for (String number : inputArray) { + result.add(Integer.parseInt(number)); + } + if (result.size() < 3) { + throw new RuntimeException("중복된 숫자가 존재합니다."); + } + return new ArrayList<>(result); + } +} diff --git a/src/main/java/study/baseball/engine/InputValidator.java b/src/main/java/study/baseball/engine/InputValidator.java new file mode 100644 index 00000000..ebb56d93 --- /dev/null +++ b/src/main/java/study/baseball/engine/InputValidator.java @@ -0,0 +1,19 @@ +package study.baseball.engine; + +import java.util.regex.Pattern; + +public class InputValidator { + + private final Pattern regex; + + public InputValidator(Pattern regex) { + this.regex = regex; + } + + public boolean checkValidFormat(String input) { + if (!regex.matcher(input).matches()) { + throw new RuntimeException("입력의 형식이 잘못되었습니다."); + } + return true; + } +} diff --git a/src/main/java/study/baseball/engine/NumberGenerator.java b/src/main/java/study/baseball/engine/NumberGenerator.java new file mode 100644 index 00000000..b306d83c --- /dev/null +++ b/src/main/java/study/baseball/engine/NumberGenerator.java @@ -0,0 +1,18 @@ +package study.baseball.engine; + +import java.util.LinkedHashSet; +import java.util.Random; +import java.util.Set; +import study.baseball.engine.model.Numbers; + +public class NumberGenerator { + private final Random random = new Random(); + + public Numbers generateRandomNumber() { + Set randomNumbers = new LinkedHashSet<>(); + while (randomNumbers.size() < 3) { + randomNumbers.add(random.nextInt(10)); + } + return new Numbers(randomNumbers); + } +} diff --git a/src/main/java/study/baseball/engine/io/Input.java b/src/main/java/study/baseball/engine/io/Input.java new file mode 100644 index 00000000..0004c54b --- /dev/null +++ b/src/main/java/study/baseball/engine/io/Input.java @@ -0,0 +1,5 @@ +package study.baseball.engine.io; + +public interface Input { + String input(String s); +} diff --git a/src/main/java/study/baseball/engine/io/Output.java b/src/main/java/study/baseball/engine/io/Output.java new file mode 100644 index 00000000..aeee3d0a --- /dev/null +++ b/src/main/java/study/baseball/engine/io/Output.java @@ -0,0 +1,7 @@ +package study.baseball.engine.io; + +public interface Output { + void printError(String s); + + void printOutput(String s); +} diff --git a/src/main/java/study/baseball/engine/model/BallCount.java b/src/main/java/study/baseball/engine/model/BallCount.java new file mode 100644 index 00000000..75db768f --- /dev/null +++ b/src/main/java/study/baseball/engine/model/BallCount.java @@ -0,0 +1,19 @@ +package study.baseball.engine.model; + +public class BallCount { + private int strike; + private int ball; + + public BallCount(int strike, int ball) { + this.strike = strike; + this.ball = ball; + } + + public int getStrike() { + return strike; + } + + public int getBall() { + return ball; + } +} diff --git a/src/main/java/study/baseball/engine/model/Numbers.java b/src/main/java/study/baseball/engine/model/Numbers.java new file mode 100644 index 00000000..548a2de8 --- /dev/null +++ b/src/main/java/study/baseball/engine/model/Numbers.java @@ -0,0 +1,39 @@ +package study.baseball.engine.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class Numbers { + + private final List numbers; + + public Numbers(Set numbers) { + this.numbers = new ArrayList<>(numbers); + } + + public List getNumbers() { + return numbers; + } + + public Integer getBall(List input) { + Set sum = new HashSet<>(input); + sum.addAll(numbers); + return input.size() + numbers.size() - sum.size() - getStrike(input); + } + + public Integer getStrike(List input) { + Iterator numbersIterator = numbers.iterator(); + int index = 0; + int strike = 0; + while (numbersIterator.hasNext()) { + if (Objects.equals(numbersIterator.next(), input.get(index++))) { + strike++; + } + } + return strike; + } +} diff --git a/src/test/java/study/baseball/engine/InputParserTest.java b/src/test/java/study/baseball/engine/InputParserTest.java new file mode 100644 index 00000000..0abde42e --- /dev/null +++ b/src/test/java/study/baseball/engine/InputParserTest.java @@ -0,0 +1,27 @@ +package study.baseball.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class InputParserTest { + private final InputParser parser = new InputParser(); + + @ParameterizedTest + @ValueSource(strings = {"123", "076", "234", "543", "932"}) + public void testParseValidInput(String input) { + List numbers = parser.parseStringToIntegerList(input); + assertThat(numbers).hasSize(3); + } + + @ParameterizedTest + @ValueSource(strings = {"777", "112", "911", "707"}) + public void testParseDuplicatedInput(String input) { + assertThatThrownBy(() -> { + List numbers = parser.parseStringToIntegerList(input); + }).isInstanceOf(RuntimeException.class).hasMessage("중복된 숫자가 존재합니다."); + } +} \ No newline at end of file diff --git a/src/test/java/study/baseball/engine/InputValidatorTest.java b/src/test/java/study/baseball/engine/InputValidatorTest.java new file mode 100644 index 00000000..fc835570 --- /dev/null +++ b/src/test/java/study/baseball/engine/InputValidatorTest.java @@ -0,0 +1,27 @@ +package study.baseball.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.regex.Pattern; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class InputValidatorTest { + Pattern regex = Pattern.compile("[0-9]{3}"); + private final InputValidator validator = new InputValidator(regex); + + @ParameterizedTest + @ValueSource(strings = {"123", "076", "234", "543", "932"}) + public void testValidInput(String input) { + assertThat(validator.checkValidFormat(input)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = {"321312", "", " ", "dsfjasdflk", "fdf dfd !@#!@ d"}) + public void testInvalidFormatInput(String input) { + assertThatThrownBy(() -> { + validator.checkValidFormat(input); + }).isInstanceOf(RuntimeException.class).hasMessage("입력의 형식이 잘못되었습니다."); + } +} \ No newline at end of file diff --git a/src/test/java/study/baseball/engine/NumberGeneratorTest.java b/src/test/java/study/baseball/engine/NumberGeneratorTest.java new file mode 100644 index 00000000..e1bb1711 --- /dev/null +++ b/src/test/java/study/baseball/engine/NumberGeneratorTest.java @@ -0,0 +1,21 @@ +package study.baseball.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import study.baseball.engine.model.Numbers; + +class NumberGeneratorTest { + private final NumberGenerator numberGenerator = new NumberGenerator(); + + @Test + public void testGenerateRandomNumbers() { + for (int i = 0; i < 10; i++) { + Numbers random = numberGenerator.generateRandomNumber(); + assertThat(random.getNumbers()).hasSize(3); + for (Integer integer : random.getNumbers()) { + assertThat(integer).isLessThan(10).isGreaterThanOrEqualTo(0); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/study/baseball/engine/model/NumbersTest.java b/src/test/java/study/baseball/engine/model/NumbersTest.java new file mode 100644 index 00000000..85b48fda --- /dev/null +++ b/src/test/java/study/baseball/engine/model/NumbersTest.java @@ -0,0 +1,95 @@ +package study.baseball.engine.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class NumbersTest { + @Test + public void testNothing() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(4, 5, 6)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(0); + assertThat(numbers.getBall(input)).isEqualTo(0); + } + + @Test + public void testOneBall() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(4, 1, 6)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(0); + assertThat(numbers.getBall(input)).isEqualTo(1); + } + + @Test + public void testTwoBall() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(2, 1, 6)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(0); + assertThat(numbers.getBall(input)).isEqualTo(2); + } + + @Test + public void testThreeBall() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(3, 1, 2)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(0); + assertThat(numbers.getBall(input)).isEqualTo(3); + } + + @Test + public void testOneStrike() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(1, 5, 6)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(1); + assertThat(numbers.getBall(input)).isEqualTo(0); + } + + @Test + public void testOneStrikeOneBall() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(1, 5, 2)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(1); + assertThat(numbers.getBall(input)).isEqualTo(1); + } + + @Test + public void testOneStrikeTwoBall() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(1, 3, 2)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(1); + assertThat(numbers.getBall(input)).isEqualTo(2); + } + + @Test + public void testTwoStrike() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(1, 2, 4)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(2); + assertThat(numbers.getBall(input)).isEqualTo(0); + } + + @Test + public void testThreeStrike() { + List input = Arrays.asList(1, 2, 3); + Set answer = new LinkedHashSet<>(Arrays.asList(1, 2, 3)); + Numbers numbers = new Numbers(answer); + assertThat(numbers.getStrike(input)).isEqualTo(3); + assertThat(numbers.getBall(input)).isEqualTo(0); + } +} \ No newline at end of file