-
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 15 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,62 @@ | ||
| 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; | ||
| } | ||
|
Comment on lines
+13
to
+19
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. 해당 입력에 대해선 검증을 안하고 있어서 2가 아닌 값만 입력하면 게임을 새로 시작할 수 있겠네요.
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 static void playGame() { | ||
| GameService gameService = new GameService(); | ||
|
|
||
| System.out.println("숫자 야구 게임을 시작합니다."); | ||
| while (true) { | ||
| System.out.print("숫자를 입력해주세요 : "); | ||
| String playerInput = Console.readLine(); | ||
|
|
||
| GameResult result = gameService.findResult(playerInput); | ||
|
|
||
| String answer = getResultComment(result); | ||
| System.out.println(answer); | ||
| if (result.equals(GameResult.THREE_STRIKE)) { | ||
| break; | ||
|
Comment on lines
+28
to
+35
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 static String getResultComment(GameResult result) { | ||
| final int strike = result.getStrike(); | ||
| final int ball = result.getBall(); | ||
|
|
||
| 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,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); | ||
| } | ||
| } |
|
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. GameNumber라는 클래스를 정의해서 숫자와 관련된 로직을 여기에 모아두셨군요. 이렇게 하는게 더 코드를 관리하는 데에 편할 것 같네요! 배워갑니다~ 다만 궁금한 점은, 번호를 Integer로 관리할 때 보다 차지하는 메모리가 커서 비효율적이지는 않을까요?
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. Integer도 객체라서 별 차이 없지 않을까요..?? |
| 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); | ||
| } | ||
| } | ||
|
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. Application의 getResultComment가 여기에 위치하면 어떨까 싶습니다.
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. 사실 이 부분은 view 로직이라 크게 신경쓰진않았는데요. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| 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 int getStrike() { | ||
| return strike; | ||
| } | ||
|
|
||
| public int getBall() { | ||
| return ball; | ||
| } | ||
| } |
|
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. GameNumberCreator를 이용해서 랜덤과 사용자 입력을 동일한 방식으로 처리해주는 게 너무 깔끔한 것 같아요! 배워갑니다 👀 |
| 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) { | ||
| validteLength(maxLength); | ||
|
|
||
| return Arrays.stream(values.split("")) | ||
| .map(this::parseInt) | ||
| .map(GameNumber::new) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private void validteLength(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 쪽 로직은 크게 신경쓰지 않도록 하겠습니다..ㅎ