-
Notifications
You must be signed in to change notification settings - Fork 0
[박성우] 야구 게임 미션 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ca9e7af
bdd44d3
e4e71af
3d209ad
de776a1
b0b21a5
79a4760
7a4efb6
8a59a93
201effb
a059027
b719d6c
31a0914
5573e23
33a288f
0cf7b4c
06b7e3c
2d7c265
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,39 @@ | ||
| package baseball; | ||
|
|
||
| import baseball.domain.GameResult; | ||
| import baseball.service.GameService; | ||
| import camp.nextstep.edu.missionutils.Console; | ||
|
|
||
| public class Application { | ||
|
|
||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| while (true) { | ||
| playGame(); | ||
|
|
||
| System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); | ||
|
|
||
| final RestartMessage restartMessage = new RestartMessage(Console.readLine()); | ||
| if (restartMessage.isEnd()) { | ||
| System.out.println("게임 종료"); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static void playGame() { | ||
| GameService gameService = new GameService(); | ||
|
|
||
| System.out.println("숫자 야구 게임을 시작합니다."); | ||
| while (true) { | ||
| System.out.print("숫자를 입력해주세요 : "); | ||
| String playerInput = Console.readLine(); | ||
|
|
||
| GameResult result = gameService.findResult(playerInput); | ||
|
|
||
| System.out.println(result.getResultComment()); | ||
| if (result.isEnd()) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package baseball; | ||
|
|
||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public class RestartMessage { | ||
|
|
||
| private static final String RESTART_MESSAGE = "1"; | ||
| private static final String END_MESSAGE = "2"; | ||
| private static final Pattern PATTERN = Pattern.compile(String.format("[%s%s]", RESTART_MESSAGE, END_MESSAGE)); | ||
| private final String value; | ||
|
|
||
| public RestartMessage(String value) { | ||
| validate(value); | ||
| this.value = value; | ||
| } | ||
|
|
||
| private void validate(String value) { | ||
| final Matcher matcher = PATTERN.matcher(value); | ||
| if (!matcher.matches()) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] 재시작 입력 값은 %s 일 수 없습니다.", value)); | ||
| } | ||
| } | ||
|
|
||
| public boolean isEnd() { | ||
| return this.value.equals(END_MESSAGE); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package baseball.domain; | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms; | ||
| import java.util.Objects; | ||
|
|
||
| public class GameNumber { | ||
|
|
||
| private static final int MIN_VALUE = 1; | ||
| private static final int MAX_VALUE = 9; | ||
|
|
||
| private final int value; | ||
|
|
||
| public GameNumber(int value) { | ||
| validateRange(value); | ||
| this.value = value; | ||
| } | ||
|
|
||
| private void validateRange(int value) { | ||
| if (value < MIN_VALUE || value > MAX_VALUE) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] 야구게임 숫자는 %d일 수 없습니다", value)); | ||
| } | ||
| } | ||
|
|
||
| public static GameNumber createRandomNumber() { | ||
| final int number = Randoms.pickNumberInRange(MIN_VALUE, MAX_VALUE); | ||
| return new GameNumber(number); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| GameNumber that = (GameNumber) o; | ||
| return value == that.value; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(value); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package baseball.domain; | ||
|
|
||
| import baseball.domain.gamenumbercreator.GameNumberCreator; | ||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| public class GameNumbers { | ||
|
|
||
| private static final int MAX_LENGTH = 3; | ||
| private final List<GameNumber> numbers; | ||
|
|
||
| private GameNumbers(List<GameNumber> numbers) { | ||
| validateLength(numbers); | ||
| validateDuplicate(numbers); | ||
| this.numbers = new ArrayList<>(numbers); | ||
| } | ||
|
|
||
| public static GameNumbers from(GameNumberCreator numberCreator) { | ||
| return new GameNumbers(numberCreator.create(MAX_LENGTH)); | ||
| } | ||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 생성자를 노출하지 않고, 이런 방식으로 객체를 생성하는 이유가 있을까요?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 생성자는 생성의 역할만 해야된다고 생각해서 이렇게 구현했습니다. |
||
|
|
||
| private void validateLength(List<GameNumber> numbers) { | ||
| if (numbers.size() != MAX_LENGTH) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] 게임 숫자는 %d 자리여야합니다.", MAX_LENGTH)); | ||
| } | ||
| } | ||
|
|
||
| private void validateDuplicate(List<GameNumber> numbers) { | ||
| if (numbers.size() != new HashSet<>(numbers).size()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HashSet을 사용하니 훨씬 깔끔하군요..! 참고하겠습니다! |
||
| throw new IllegalArgumentException("[ERROR] 게임 숫자는 중복된 값을 가질 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| public GameResult calculateResult(GameNumbers other) { | ||
| int strike = (int) IntStream.range(0, other.numbers.size()) | ||
| .filter(i -> other.numbers.get(i).equals(this.numbers.get(i))) | ||
| .count(); | ||
|
|
||
| int ball = (int) other.numbers.stream() | ||
| .filter(this.numbers::contains) | ||
| .count() - strike; | ||
|
|
||
| return GameResult.find(strike, ball); | ||
| } | ||
|
Comment on lines
+37
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stream을 잘 활용하시네요! 저도 더 공부해봐야겠습니다. |
||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| GameNumbers that = (GameNumbers) o; | ||
| return Objects.equals(numbers, that.numbers); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(numbers); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package baseball.domain; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| public enum GameResult { | ||
| ZERO(0, 0), | ||
| ZERO_STRIKE_ONE_BALL(0, 1), | ||
| ZERO_STRIKE_TWO_BALL(0, 2), | ||
| ZERO_STRIKE_THREE_BALL(0, 3), | ||
| ONE_STRIKE_ZERO_BALL(1, 0), | ||
| ONE_STRIKE_ONE_BALL(1, 1), | ||
| ONE_STRIKE_TWO_BALL(1, 2), | ||
| TWO_STRIKE_ZERO_BALL(2, 0), | ||
| TWO_STRIKE_ONE_BALL(2, 1), | ||
| THREE_STRIKE(3, 0); | ||
|
|
||
| private final int strike; | ||
| private final int ball; | ||
|
|
||
| GameResult(int strike, int ball) { | ||
| this.strike = strike; | ||
| this.ball = ball; | ||
| } | ||
|
|
||
| public static GameResult find(int strike, int ball) { | ||
| return Arrays.stream(GameResult.values()) | ||
| .filter(gameResult -> gameResult.strike == strike && gameResult.ball == ball) | ||
| .findAny() | ||
| .orElseThrow(() -> new IllegalArgumentException( | ||
| String.format("[ERROR] %d 스트라이크 %d 볼에 해달하는 결과는 존재하지 않습니다.", strike, ball))); | ||
| } | ||
|
|
||
| public boolean isEnd() { | ||
| return this == THREE_STRIKE; | ||
| } | ||
|
|
||
| public String getResultComment() { | ||
| if (strike == 0 && ball == 0) { | ||
| return "낫싱"; | ||
| } | ||
|
|
||
| StringBuilder answer = new StringBuilder(); | ||
| if (ball != 0) { | ||
| answer.append(ball); | ||
| answer.append("볼 "); | ||
| } | ||
|
|
||
| if (strike != 0) { | ||
| answer.append(strike); | ||
| answer.append("스트라이크"); | ||
| } | ||
|
|
||
| return answer.toString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package baseball.domain.gamenumbercreator; | ||
|
|
||
| import baseball.domain.GameNumber; | ||
| import java.util.List; | ||
|
|
||
| public interface GameNumberCreator { | ||
| List<GameNumber> create(int maxLength); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package baseball.domain.gamenumbercreator; | ||
|
|
||
| import baseball.domain.GameNumber; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
| public class RandomIntegerToGameNumberCreator implements GameNumberCreator { | ||
|
|
||
| @Override | ||
| public List<GameNumber> create(int maxLength) { | ||
| return Stream.generate(GameNumber::createRandomNumber) | ||
| .distinct() | ||
| .limit(maxLength) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package baseball.domain.gamenumbercreator; | ||
|
|
||
| import baseball.domain.GameNumber; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class StringToGameNumberCreator implements GameNumberCreator { | ||
|
|
||
| private final String values; | ||
|
|
||
| public StringToGameNumberCreator(String values) { | ||
| this.values = values; | ||
| } | ||
|
|
||
| @Override | ||
| public List<GameNumber> create(int maxLength) { | ||
| validateLength(maxLength); | ||
|
|
||
| return Arrays.stream(values.split("")) | ||
| .map(this::parseInt) | ||
| .map(GameNumber::new) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private void validateLength(int maxLength) { | ||
| if (values.length() != maxLength) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] %s는 %d 자리수가 아닙니다.", values, maxLength)); | ||
| } | ||
| } | ||
|
|
||
| private Integer parseInt(String value) { | ||
| try { | ||
| return Integer.parseInt(value); | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] 입력값 %s는 숫자가 아닙니다.", value)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package baseball.service; | ||
|
|
||
| import baseball.domain.GameNumbers; | ||
| import baseball.domain.GameResult; | ||
| import baseball.domain.gamenumbercreator.GameNumberCreator; | ||
| import baseball.domain.gamenumbercreator.RandomIntegerToGameNumberCreator; | ||
| import baseball.domain.gamenumbercreator.StringToGameNumberCreator; | ||
|
|
||
| public class GameService { | ||
|
|
||
| private static final GameNumberCreator NUMBER_CREATOR = new RandomIntegerToGameNumberCreator(); | ||
| private final GameNumbers answerNumbers; | ||
|
|
||
| public GameService() { | ||
| this.answerNumbers = GameNumbers.from(NUMBER_CREATOR); | ||
| } | ||
|
|
||
| public GameResult findResult(String playerRequest) { | ||
| final StringToGameNumberCreator creator = new StringToGameNumberCreator(playerRequest); | ||
| final GameNumbers playerNumbers = GameNumbers.from(creator); | ||
| return answerNumbers.calculateResult(playerNumbers); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package baseball.domain; | ||
|
|
||
| import org.assertj.core.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| class GameNumberTest { | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(ints = {0, 10}) | ||
| void 게임_숫자가_1이상_9이하의_자연수가_아니면_예외를_던진다(int number) { | ||
| Assertions.assertThatThrownBy(() -> new GameNumber(number)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9}) | ||
| void 게임_숫자는_1이상_9이하의_자연수여야한다(int number) { | ||
| Assertions.assertThatCode(() -> new GameNumber(number)) | ||
| .doesNotThrowAnyException(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
콘솔 입출력과 게임 진행이 모두 App 클래스에 모여있네요.
Controller와 Service를 분리하듯이, 입출력과 게임 진행 로직을 분리하면 어떨까 하는 의견입니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Application 클래스에서 게임 진행 로직과 view 로직을 분리하더라도 진행 흐름에는 변함이 없을 것 같네요.
view 쪽 로직은 크게 신경쓰지 않도록 하겠습니다..ㅎ