-
Notifications
You must be signed in to change notification settings - Fork 0
숫자 야구 게임 [sjiwon] #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
67acfb9
d1eef0e
7077cce
bd0fecc
2da9bf0
312f766
55bee8e
6ffb501
1dde87d
7a3481e
ed74d6e
cb910f1
7150851
f28211a
7f0707b
b0dabea
ba99c7e
36107ff
10961ce
e619cf8
95f69e0
0e47d89
d71a429
56cf0e5
991a5d9
bdb2346
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 |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| ## Feature | ||
|
|
||
| - [X] 컴퓨터는 1..9 범위에서 서로 다른 임의의 수 3개 선택 | ||
| - [X] 유저는 1..9 범위에서 서로 다른 숫자 3개를 입력한다 | ||
| - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생 | ||
| - [X] 심판은 `컴퓨터 <-> 유저의 3가지 숫자`를 토대로 숫자 야구 게임 결과를 도출한다 | ||
| - 비교 후 `strikeCount & ballCount` 정보를 `Result`로 Wrapping한 후 결과를 발송한다 | ||
| - [X] 컴퓨터가 선택한 모든 숫자를 맞히면 게임은 클리어된다 | ||
| - [X] 사용자는 재시작[1], 종료[2] 중 하나를 입력한다 | ||
| - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생 | ||
|
|
||
| <br> | ||
| <hr> | ||
|
|
||
| ## Model | ||
|
|
||
| ### `Baseballs` | ||
|
|
||
| - 입력한 3개의 숫자들을 추상화시킨 `Baseballs` | ||
| - Baseballs에 속한 `List<Integer>`은 | ||
| - [X] 각 원소가 `1..9` 범위 사이여야 한다 | ||
| - [X] 원소의 크기가 3이여야 한다 | ||
| - [X] 중복된 원소가 없어야 한다 | ||
|
|
||
| <br> | ||
|
|
||
| ### `Computer: 컴퓨터` | ||
|
|
||
| - 1..9 범위에서 서로 다른 임의의 수 3개 선택 | ||
| - Computer는 중복 숫자를 선택하지 않도록 구현 | ||
| - User 선택을 통한 결과 산출의 기준 | ||
|
|
||
| <br> | ||
|
|
||
| ### `User: 사용자` | ||
|
|
||
| - 숫자 3개 입력 | ||
| - 숫자를 입력하지 않을 경우 (IllegalArgumentException) | ||
| - 게임 재시작/종료와 관련된 커맨드 입력 | ||
| - 숫자를 입력하지 않을 경우 (IllegalArgumentException) | ||
|
|
||
| <br> | ||
|
|
||
| ### `Referee: 심판` | ||
|
|
||
| - 플레이어 점수 채점 | ||
| - 숫자 겹침 + 동일 위치 = 스트라이크 | ||
| - 숫자 겹침 + 다른 위치 = 볼 | ||
| - 숫자 안겹침 = 낫싱 | ||
|
|
||
| <br> | ||
|
|
||
| ### `Result: 채점 결과` | ||
|
|
||
| - 스트라이크 & 볼에 대한 count field 보유 | ||
|
|
||
| <br> | ||
|
|
||
| ### `GameStatus: 게임 상태` | ||
|
|
||
| - 진행중인 게임에 대한 상태 | ||
| - `GAME_RUN` -> 게임 실행 | ||
| - `GAME_TERMINATE` -> 게임 종료 | ||
|
|
||
| <br> | ||
|
|
||
| ### `GameProcessDecider: 재시작 or 종료 결정자` | ||
|
|
||
| - 게임 클리어 후 종료 or 재시작에 대한 커맨더 | ||
|
|
||
| <br> | ||
| <hr> | ||
|
|
||
| ## Utils | ||
|
|
||
| ### `ExceptionConstants` | ||
|
|
||
| - 전역 예외 메시지 통합 컴포넌트 | ||
|
|
||
| ### `BaseballConstants` | ||
|
|
||
| - Baseball에 대한 상수 전용 컴포넌트 | ||
| - 숫자 범위 | ||
| - Baseball 개수 | ||
|
|
||
| <br> | ||
| <hr> | ||
|
|
||
| ## View | ||
|
|
||
| ### `InputView` | ||
|
|
||
| - 사용자 Input을 받기 위한 컴포넌트 | ||
|
|
||
| ### `OutputView` | ||
|
|
||
| - 게임 진행과 관련된 출력 컴포넌트 | ||
|
|
||
| <br> | ||
| <hr> | ||
|
|
||
| ## Controller | ||
|
|
||
| ### `GameController` | ||
|
|
||
| - 게임 진행과 관련된 컨트롤러 | ||
|
|
||
| <br> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| package baseball; | ||
|
|
||
| import baseball.controller.GameController; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| new GameController().run(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package baseball.controller; | ||
|
|
||
| import baseball.model.*; | ||
| import baseball.view.InputView; | ||
| import baseball.view.OutputView; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static baseball.model.GameProcessDecider.GAME_RESTART; | ||
| import static baseball.model.GameStatus.GAME_RUNNING; | ||
| import static baseball.model.GameStatus.GAME_TERMINATE; | ||
|
|
||
| public class GameController { | ||
| private static GameStatus gameStatus; | ||
| private Computer computer; | ||
| private User user; | ||
|
|
||
| public GameController() { | ||
| computer = new Computer(); | ||
| gameStatus = GAME_RUNNING; | ||
| } | ||
|
|
||
| public void run() { | ||
| // 게임 시작 | ||
| OutputView.printGameStart(); | ||
|
|
||
| while (gameStatus.isGameNotTerminated()) { | ||
| // User - Baseball 입력 | ||
| readUserBaseballInput(); | ||
|
|
||
| // 게임 결과 확인 | ||
| Result result = judgeGameByReferee(); | ||
| OutputView.printGameResult(result); | ||
|
|
||
| // 게임 클리어 확인 | ||
| checkGameClear(result); | ||
| } | ||
| } | ||
|
|
||
| private void readUserBaseballInput() { | ||
| List<Integer> userBaseballs = InputView.readUserBaseballs(); | ||
| user = new User(userBaseballs); | ||
| } | ||
|
|
||
| private Result judgeGameByReferee() { | ||
| return Referee.judge(computer.getBaseballs(), user.getBaseballs()); | ||
| } | ||
|
|
||
| private void checkGameClear(final Result result) { | ||
| if (result.isGameClear()) { | ||
| OutputView.printGameClear(); | ||
| determineGameRestartOrEnd(); | ||
| } | ||
| } | ||
|
|
||
| private void determineGameRestartOrEnd() { | ||
| int userCommand = InputView.readUserRestartCommand(); | ||
| GameProcessDecider decider = GameProcessDecider.getDecider(userCommand); | ||
|
|
||
| if (decider == GAME_RESTART) { | ||
| computer = new Computer(); | ||
| gameStatus = GAME_RUNNING; | ||
| } else { | ||
| gameStatus = GAME_TERMINATE; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package baseball.model; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| import static baseball.utils.BaseballConstants.*; | ||
| import static baseball.utils.ExceptionConstants.BaseballException.*; | ||
|
|
||
| public class Baseballs { | ||
| private final List<Integer> baseballs; | ||
|
|
||
| public Baseballs(final List<Integer> baseballs) { | ||
| validateEachBaseballElementIsInRange(baseballs); | ||
| validateTotalBaseballSize(baseballs); | ||
| validateBaseballHasDuplicateNumber(baseballs); | ||
| this.baseballs = baseballs; | ||
| } | ||
|
|
||
| private void validateEachBaseballElementIsInRange(final List<Integer> baseballs) { | ||
| if (hasOutOfRange(baseballs)) { | ||
| throw new IllegalArgumentException(BASEBALL_IS_NOT_IN_RANGE.message); | ||
| } | ||
| } | ||
|
|
||
| private boolean hasOutOfRange(final List<Integer> baseballs) { | ||
| return baseballs.stream() | ||
| .anyMatch(baseball -> baseball < MIN_BASEBALL || baseball > MAX_BASEBALL); | ||
| } | ||
|
|
||
| private void validateTotalBaseballSize(final List<Integer> baseballs) { | ||
| if (baseballs.size() != BASEBALL_SIZE) { | ||
| throw new IllegalArgumentException(BASEBALL_SIZE_IS_NOT_FULFILL.message); | ||
| } | ||
| } | ||
|
|
||
| private void validateBaseballHasDuplicateNumber(final List<Integer> baseballs) { | ||
| if (hasDuplicateNumber(baseballs)) { | ||
| throw new IllegalArgumentException(BASEBALL_MUST_BE_UNIQUE.message); | ||
| } | ||
| } | ||
|
|
||
| private boolean hasDuplicateNumber(final List<Integer> baseballs) { | ||
| return baseballs.stream() | ||
| .distinct() | ||
| .count() != BASEBALL_SIZE; | ||
| } | ||
|
|
||
| public List<Integer> getBaseballs() { | ||
| return Collections.unmodifiableList(baseballs); | ||
| } | ||
|
Comment on lines
+48
to
+50
Collaborator
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. Collections.unmodifiableList는 Read-only라도 원본 컬렉션의 불변성을 보장하지 않는다고 합니다. CopyOf가 아닌 unmodifiableList를 사용하신 이유가 있을까요?
Collaborator
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. unmodifiableList를 사용하는거는 원본 객체에 대해서 immutable을 보장하지는 않는데 구조상 Baseballs의 원본 객체를 외부에서 접근할 수 없고 외부에서 얻는
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package baseball.model; | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| import static baseball.utils.BaseballConstants.*; | ||
|
|
||
| public class Computer { | ||
| private final Baseballs baseballs; | ||
|
|
||
| public Computer() { | ||
| this.baseballs = generateRandomBaseballs(); | ||
| } | ||
|
|
||
| private Baseballs generateRandomBaseballs() { | ||
| List<Integer> baseballs = new ArrayList<>(); | ||
|
|
||
| while (isNotReachedLimitSize(baseballs)) { | ||
| int randomNumber = getRandomNumberInRange(); | ||
| if (isRandomNumberAbsentInBaseballs(baseballs, randomNumber)) { | ||
| baseballs.add(randomNumber); | ||
| } | ||
| } | ||
|
|
||
| return new Baseballs(baseballs); | ||
| } | ||
|
|
||
| private boolean isNotReachedLimitSize(final List<Integer> baseballs) { | ||
| return baseballs.size() < BASEBALL_SIZE; | ||
| } | ||
|
|
||
| private int getRandomNumberInRange() { | ||
| return Randoms.pickNumberInRange(MIN_BASEBALL, MAX_BASEBALL); | ||
| } | ||
|
|
||
| private boolean isRandomNumberAbsentInBaseballs( | ||
| final List<Integer> baseballs, | ||
| final int randomNumber | ||
| ) { | ||
| return !baseballs.contains(randomNumber); | ||
| } | ||
|
|
||
| public List<Integer> getBaseballs() { | ||
| return baseballs.getBaseballs(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package baseball.model; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| import static baseball.utils.ExceptionConstants.GameProcessCommandException.INVALID_COMMAND; | ||
|
|
||
| public enum GameProcessDecider { | ||
| GAME_RESTART(1), | ||
| GAME_END(2), | ||
| ; | ||
|
|
||
| private final int command; | ||
|
|
||
| GameProcessDecider(final int command) { | ||
| this.command = command; | ||
| } | ||
|
|
||
| public static GameProcessDecider getDecider(final int userCommand) { | ||
| return Arrays.stream(values()) | ||
| .filter(restartDecider -> restartDecider.command == userCommand) | ||
| .findFirst() | ||
| .orElseThrow(() -> new IllegalArgumentException(INVALID_COMMAND.message)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package baseball.model; | ||
|
|
||
| public enum GameStatus { | ||
| GAME_RUNNING, | ||
| GAME_TERMINATE, | ||
| ; | ||
|
|
||
| public boolean isGameNotTerminated() { | ||
| return this != GAME_TERMINATE; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package baseball.model; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| import static baseball.utils.BaseballConstants.BASEBALL_SIZE; | ||
|
|
||
| public class Referee { | ||
| private Referee() { | ||
| } | ||
|
|
||
| public static Result judge( | ||
| final List<Integer> computerBaseballs, | ||
| final List<Integer> userBaseballs | ||
| ) { | ||
| final int strikeCount = calculateStrikeCount(computerBaseballs, userBaseballs); | ||
| final int ballCount = calculateBallCount(computerBaseballs, userBaseballs); | ||
|
|
||
| return new Result(strikeCount, ballCount); | ||
| } | ||
|
|
||
| private static int calculateStrikeCount( | ||
| final List<Integer> computerBaseballs, | ||
| final List<Integer> userBaseballs | ||
| ) { | ||
| return (int) IntStream.range(0, BASEBALL_SIZE) | ||
| .filter(index -> isExactlyMatchAtIndex(computerBaseballs, userBaseballs, index)) | ||
| .count(); | ||
| } | ||
|
|
||
| private static boolean isExactlyMatchAtIndex( | ||
| final List<Integer> computerBaseballs, | ||
| final List<Integer> userBaseballs, | ||
| final int index | ||
| ) { | ||
| return computerBaseballs.get(index).equals(userBaseballs.get(index)); | ||
| } | ||
|
|
||
| private static int calculateBallCount( | ||
| final List<Integer> computerBaseballs, | ||
| final List<Integer> userBaseballs | ||
| ) { | ||
| return getContainedElementsCount(computerBaseballs, userBaseballs) | ||
| - calculateStrikeCount(computerBaseballs, userBaseballs); | ||
| } | ||
|
|
||
| private static int getContainedElementsCount( | ||
| final List<Integer> computerBaseballs, | ||
| final List<Integer> userBaseballs | ||
| ) { | ||
| return (int) userBaseballs.stream() | ||
| .filter(computerBaseballs::contains) | ||
| .count(); | ||
| } | ||
| } |
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.
https://github.com/kgu-woowa/woowa-java-baseball/blob/ba99c7e7ad9b5457bf2937dd3b69bfc7066dbdb7/src/main/java/baseball/controller/GameController.java#L40-L43
위 코드를 보면 while문을 돌면서 readUserBaseballInput()에서 입력을 받을 때마다 User 객체를 생성하고
https://github.com/kgu-woowa/woowa-java-baseball/blob/ba99c7e7ad9b5457bf2937dd3b69bfc7066dbdb7/src/main/java/baseball/model/User.java#L8-L10
위 코드에서 User 객체를 만들 때마다 Baseballs 객체를 생성하게 되는데 이러면 사용자가 숫자를 입력 할 때마다 메모리를 먹게 될 것 같고
또 한 번의 게임에서 숫자 입력마다 사용자를 새로 생성하는 것은 요구사항에 안 좋게 보일 수 있을 거 같은데 어떻게 생각하시나요?
Uh oh!
There was an error while loading. Please reload this page.
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.
코드 구조상 게임을 클리어한 후 사용자가
재시작 [1] Command를 요청했을 경우 게임을 다시 시작하는데 여기서 다시 시작하는 부분은 요구사항 이해에 따라서 다르긴한데 저는완전히 새로운 게임으로 판단하였고 그에 따라서determineGameRestartOrEnd -> new Computer() / readUserBaseballInput -> new User(~~)로 초기화를 하는게 깔끔하다고 생각했습니다.메모리 관련 부분은 사실 Computer나 User나 그렇게 큰 리소스를 잡아먹는다고 생각하지 않고 내부에서 관리하는 List또한 어차피 필드 자체는 3개로 제한되기 때문에 이 부분이 메모리에 큰 영향을 준다고 생각은 하지 않습니다