-
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 10 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,58 @@ | ||||||
| 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를 입력하세요."); | ||||||
| if (Console.readLine().equals("2")) { | ||||||
| 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(result.getStrike()); | ||||||
|
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.
Suggested change
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. 실수 했네요 . 디게 꼼꼼히 보신듯 :) |
||||||
| answer.append("스트라이크"); | ||||||
| } | ||||||
|
|
||||||
| return answer.toString(); | ||||||
| } | ||||||
| } | ||||||
|
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,64 @@ | ||
| package baseball.domain; | ||
|
|
||
| import baseball.domain.gamenumbercreator.GameNumberCreator; | ||
| 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 = 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. 일급 컬렉션 생성 시 참조를 끊어주면 의도하지 않은 값의 수정을 막을 수 있을 것 같아요!
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. 매의 눈이시네요. 수정 했습니다~ |
||
| } | ||
|
|
||
| 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,35 @@ | ||
| 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) { | ||
| if (values.length() != maxLength) { | ||
| throw new IllegalArgumentException(String.format("[ERROR] %s는 %d 자리수가 아닙니다.", values, maxLength)); | ||
| } | ||
|
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. 메서드 분리 반영했습니다~ |
||
|
|
||
| return Arrays.stream(values.split("")) | ||
| .map(this::parseInt) | ||
| .map(GameNumber::new) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| 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,21 @@ | ||
| package baseball.service; | ||
|
|
||
| import baseball.domain.GameNumbers; | ||
| import baseball.domain.GameResult; | ||
| import baseball.domain.gamenumbercreator.RandomIntegerToGameNumberCreator; | ||
| import baseball.domain.gamenumbercreator.StringToGameNumberCreator; | ||
|
|
||
| public class GameService { | ||
|
|
||
| private final GameNumbers answerNumbers; | ||
|
|
||
| public GameService() { | ||
| this.answerNumbers = GameNumbers.from(new RandomIntegerToGameNumberCreator()); | ||
|
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. 게임 서비스가 생성될 때마다 RandomIntegerToGameNumberCreator를 계속 생성할 필요는 없을 것 같은데 해당 클래스는 유틸 클래스로 만든건 어떨까요?
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. 이 부분은 전략 패턴을 사용한 부분이라서 객체의 상태로 존재해야합니다. |
||
| } | ||
|
|
||
| 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 쪽 로직은 크게 신경쓰지 않도록 하겠습니다..ㅎ