Skip to content
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

[1주차] 변해빈 #1

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
5e5ff7e
docs: 기능 구현 목록 최초 작성
h-beeen Oct 19, 2023
285df98
feat: Couputer 랜덤 숫자 뽑기 기능 구현
h-beeen Oct 19, 2023
87fa078
feat: Player 숫자 입력 및 검증 로직 추가
h-beeen Oct 19, 2023
403e53a
feat: Game 필수 기능 구현 (Player 입력 / Computer 난수 생성)
h-beeen Oct 19, 2023
cd65c41
docs: 구현 기능 목록 수정
h-beeen Oct 19, 2023
29a7d86
feat: Hint 필수 기능 구현 (볼, 스트라이크 카운트)
h-beeen Oct 20, 2023
d5e2f95
feat: 게임 breakPoint 설정
h-beeen Oct 20, 2023
d01607d
refactor: Constant 값 Config 분리 및 축약 문법 수정
h-beeen Oct 20, 2023
45e0473
feat: 힌트 출력 반복 루프 추가
h-beeen Oct 20, 2023
1447d6f
feat: 사용자의 중복 숫자 입력 예외 처리 추가
h-beeen Oct 20, 2023
02ff445
docs: 기능 요구사항 진행사항 갱신
h-beeen Oct 20, 2023
3cd75ee
refactor : MVC 패턴 리팩토링
h-beeen Oct 20, 2023
f65861b
feat: 시스템 설정(Config) 제약조건 추가
h-beeen Oct 20, 2023
641d1d3
refactor: Model Layer / View Layer 분리
h-beeen Oct 20, 2023
dbcdc54
fix: 게임을 새로시작하지 않는 버그 수정
h-beeen Oct 20, 2023
2bd8d97
fix: 숫자 입력 간 개행 오류 수정
h-beeen Oct 20, 2023
8306983
feat: PlayerNumber / ComputerNumber를 Number 객체로 병합
h-beeen Oct 20, 2023
f837542
refactor: Hint 로직 도메인 주도 설계로 개선
h-beeen Oct 20, 2023
daad19e
feat: 생성자 접근제한자 조정
h-beeen Oct 20, 2023
e16b264
refactor: 메소드명 직관적 수정
h-beeen Oct 20, 2023
058853e
refactor: 요구사항 기능 구현 완료 및 MVC 패턴 리팩토링
h-beeen Oct 20, 2023
c672022
fix: 테스트 실패 케이스 핸들링(메소드 변경)
h-beeen Oct 21, 2023
dd8cba7
refactor: 프로그램 종료 플래그 상수 선언
h-beeen Oct 21, 2023
990b7c7
refactor: 상수 변수 Enum으로 리팩토링
h-beeen Oct 21, 2023
f5fa1bc
refactor: validator 로직 개선
h-beeen Oct 21, 2023
3e84307
refactor: 미사용 함수 삭제
h-beeen Oct 21, 2023
6a46e8c
refactor: Enum을 활용한 불변 상수 패키징
h-beeen Oct 21, 2023
04d9229
refactor: 클래스 - 상수 선언부 개행 제거
h-beeen Oct 21, 2023
5e9b6c2
docs: 구현 기능 목록 update
h-beeen Oct 21, 2023
8962557
feat: Result 멤버변수 final 선언
h-beeen Oct 21, 2023
b84f780
style: 코드 포맷팅
h-beeen Oct 21, 2023
7acc36b
chore: 패키지 이름 변경 (model -> domain)
h-beeen Oct 22, 2023
ba6bd2c
feat: Number 일급 컬렉션 적용
h-beeen Oct 22, 2023
b1dd4e1
fix: BallCount / StrikeCount를 제대로 카운트 하지 못하는 이슈 수정
h-beeen Oct 22, 2023
fa90c35
refactor: 전역으로 선언되어있던 Config 설정을 수정
h-beeen Oct 22, 2023
3f7bb2e
feat: Getter 메소드 삭제
h-beeen Oct 22, 2023
7f7d7e8
refactor: 지역변수 재참조 이슈 해결을 위해 do-while문 제거
h-beeen Oct 22, 2023
ac8b5bd
refactor: 메소드명 직관적으로 개선
h-beeen Oct 22, 2023
4a03375
refactor: 게임 종료 플래그 지역변수로 변경
h-beeen Oct 22, 2023
febd594
refactor: 불필요 viewConfig 제거
h-beeen Oct 22, 2023
1999b2c
refactor: 변수, 메소드 명 직관적으로 개선 (request -> ask)
h-beeen Oct 22, 2023
99aa219
refactor: Result 출력 방식 개선을 위한 Enum 추가 및 로직 수정
h-beeen Oct 22, 2023
22f1aef
refactor: 출력 메소드 재사용성 확장 및 출력 메세지 열거형으로 변경
h-beeen Oct 22, 2023
575e4a5
refactor: 공통 정보 출력 메소드 명 변경(printGameInformation -> printInformation)
h-beeen Oct 22, 2023
a1780f0
docs: README.md 프로젝트 개요 작성
h-beeen Oct 22, 2023
26561ff
fix: 빌드 에러, 개행을 정상적으로 출력하지 않는 오류 수정
h-beeen Oct 23, 2023
7ac0f58
chore: 패키지명 변경 (config -> global)
h-beeen Oct 23, 2023
473e3cd
docs: README.md 패키지 변동사항 업데이트
h-beeen Oct 23, 2023
ab42278
refactor: model <-> view 의존 관계 제거
h-beeen Oct 23, 2023
0755554
refactor: 변수명 직관적 개선 (PrintMessage -> StaticNotice)
h-beeen Oct 23, 2023
c56887c
refactor: 정적 팩토리 메소드를 활용한 예외처리 메소드 커스텀
h-beeen Oct 23, 2023
a1307d6
refactor: BaseballException.of 형태 적용 예외처리
h-beeen Oct 23, 2023
9de442b
fix: 변수명 통일 (player, user -> player)
h-beeen Oct 23, 2023
fecb933
refactor: static import를 활용한 코드 간소화
h-beeen Oct 23, 2023
2b505a0
refactor: SRP 준수를 위해서 메소드 추가 분리
h-beeen Oct 23, 2023
f9a297c
feat: 멤버변수가 없는 클래스의 생성자 제한
h-beeen Oct 23, 2023
5fdb512
chore: 패키지 변경 (domain/exception -> global/exception)
h-beeen Oct 23, 2023
3fc9cd2
docs: README.md 업데이트
h-beeen Oct 23, 2023
caa0bd3
feat: 메소드 분리 및 else-if문 구조 변경
h-beeen Oct 23, 2023
1fa1b0b
feat: 변수명 변경 및 종료조건 예외처리 추가(1, 2 외의 값)
h-beeen Oct 23, 2023
0422d7b
feat: 메세지 출력이 GameConfig 전역 설정에 의존하도록 수정
h-beeen Oct 23, 2023
a51764b
docs: README.md 업데이트
h-beeen Oct 23, 2023
5d9eaf2
docs: README.md 업데이트
h-beeen Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 📝&nbsp;&nbsp;Precourse-Week1 Mission **[숫자 야구]**

## ✨&nbsp;&nbsp;기능 구현 List <프로그램 진행 순서>

- ✅1~9 사이의 서로 값이 다른 3자리의 정수를 랜덤으로 생성한다.
- ✅서로 중복되지 않는 숫자 3개
- ✅게임 시작 문구 출력 : `숫자 야구 게임을 시작합니다.`
- ✅사용자에게 `1-9 사이의 서로 값이 다른 3자리의 정수`를 입력 받고 저장한다.
- ✅입력받은 input이 3자리가 아닐 경우 예외처리
- ✅입력받은 input이 숫자가 아닌 문자가 포함될 경우 예외처리
- ✅입력받은 input에 중복된 숫자가 있을 경우 예외처리
- ✅사용자 input과 랜덤 생성 정수를 비교해 출력할 힌트를 계산한다.
- ✅다른 자리 같은 숫자의 갯수 n개 : `n볼`
- ✅같은 자리 같은 숫자 n개: `n스트라이크`
- ✅스트라이크와 볼이 같이 존재할 때 : `n볼 n스트라이크`
- ✅같은 숫자가 1개도 없으면 : `낫싱`
- ✅게임 클리어 여부 판단
- ✅3스트라이크 상황 : `3개의 숫자를 모두 맞히셨습니다! 게임 종료
✅게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`
- ✅사용자에게 게임 재시작 여부를 입력받고, 입력값에 따라 프로그램 진행 여부를 결정한다.
- ✅3스트라이크가 아니라면, 다시 사용자에게 입력을 숫자를 받고, 힌트를 출력한다.
5 changes: 4 additions & 1 deletion src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package baseball;

import baseball.controller.Game;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
Game game = new Game();
game.start();
}
}
23 changes: 23 additions & 0 deletions src/main/java/baseball/config/GlobalConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package baseball.config;

public enum GlobalConfig {
NUMBER_LENGTH(3),
RANDOM_NUMBER_MINIMUM(1),
RANDOM_NUMBER_MAXIMUM(9),
GAME_RESTART_FLAG(2);

private final int value;

GlobalConfig(int value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}

public int getValue() {
return value;
}
}
35 changes: 35 additions & 0 deletions src/main/java/baseball/controller/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package baseball.controller;

import baseball.domain.Number;
import baseball.domain.Result;
import baseball.view.InputView;
import baseball.view.OutputView;

import java.util.Objects;

import static baseball.config.GlobalConfig.GAME_RESTART_FLAG;
import static baseball.view.constants.PrintMessage.GAME_START;

public class Game {
public void start() {
OutputView.printGameInformation(GAME_START);
do {
play(Number.generateRandomNumbers());
} while (!isRestart());
}

private void play(Number computerNumber) {
while (true) {
Number playerNumber = Number.inputPlayerNumbers();
Result result = Result.create(playerNumber, computerNumber);
result.print();
if (result.checkGameOver()) {
break;
}
}
}

private static boolean isRestart() {
return Objects.equals(InputView.askRestartOrExit(), GAME_RESTART_FLAG.toString());
}
}
76 changes: 76 additions & 0 deletions src/main/java/baseball/domain/Number.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package baseball.domain;

import baseball.view.InputView;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import static baseball.config.GlobalConfig.*;
import static baseball.validator.NumberValidator.*;
import static camp.nextstep.edu.missionutils.Randoms.pickNumberInRange;

public class Number {
private final List<Integer> numbers;

// Player Number Constructor
private Number(String input) {
validateEmpty(input);
validateNumberLength(input);
validateContainOnlyNumber(input);
validateContainDuplicatedNumber(input);

this.numbers = convertInputNumber(input);
}
Comment on lines +17 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 검증 메소드를 다 사용 안 한 이유가 있나요? 분명 7개 만든 것 같은데 4개 밖에 없어서ㅋㅋㅋ

Copy link
Member Author

@h-beeen h-beeen Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    public static void validateContainOnlyNumber(final String number) {
        if (!isValidNumber(number)) {
            throw new IllegalArgumentException("number cannot contain any letters");
        }
    }

isValid 메소드는 실제 검증을 수행한 결과를 boolean으로 리턴합니다.
그리고 이 메소드 호출 결과에 따른 포스트 컨디션으로
validate 메소드를 활용해는 검증 -> 예외를 던지는 역할로 분리했습니다.

따라서 상기 4개의 메소드를 호출하면
7개의 메소드를 전부 활용한 검증이 가능한거죠!


// Computer Number Constructor
private Number(List<Integer> computerNumber) {
this.numbers = computerNumber;
}

// Player Number Static Factory Method
public static Number inputPlayerNumbers() {
String playerNumbers = InputView.askUserNumbers();
return new Number(playerNumbers);
}

// Computer Number Static Factory Method
public static Number generateRandomNumbers() {
List<Integer> randomNumbers = new ArrayList<>();
while (randomNumbers.size() < NUMBER_LENGTH.getValue()) {
int number = pickNumberInRange(RANDOM_NUMBER_MAXIMUM.getValue(), RANDOM_NUMBER_MINIMUM.getValue());
if (!randomNumbers.contains(number)) {
randomNumbers.add(number);
}
}
return new Number(randomNumbers);
}

private List<Integer> convertInputNumber(String input) {
return input.chars()
.mapToObj(Character::getNumericValue)
.toList();
}
Comment on lines +53 to +57

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 식으로 문자열을 리스트로 바꾸는 거는 생각을 못 했는데 간결하고 좋은 것 같습니다👍


public int countBallCount(final Number comparableNumber) {
return (int) IntStream.range(0, numbers.size())
.filter(i -> comparableNumber.isBall(numbers.get(i), i))
.count();
}

public int countStrikeCount(final Number comparableNumber) {
return (int) IntStream.range(0, numbers.size())
.filter(i -> comparableNumber.isStrike(numbers.get(i), i))
.count();
}

// Ball : Contain their number at other position
private boolean isBall(int number, int digit) {
return !isStrike(number, digit) && numbers.contains(number);
}

// Strike : Contain their number at same digit
private boolean isStrike(int number, int digit) {
return number == numbers.get(digit);
}
}
61 changes: 61 additions & 0 deletions src/main/java/baseball/domain/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package baseball.domain;

import baseball.domain.constants.ResultType;
import baseball.view.OutputView;

import static baseball.config.GlobalConfig.NUMBER_LENGTH;
import static baseball.domain.constants.ResultType.*;
import static java.lang.String.format;

public class Result {
private final int ballCount;
private final int strikeCount;

private Result(final Number playerNumber, final Number computerNumber) {
ballCount = playerNumber.countBallCount(computerNumber);
strikeCount = playerNumber.countStrikeCount(computerNumber);
}

public static Result create(final Number playerNumber, final Number computerNumber) {
return new Result(playerNumber, computerNumber);
}

private ResultType inspectResultType() {
if (!hasBall() && !hasStrike()) {
return NOTHING;
} else if (hasBall() && hasStrike()) {
return BALL_AND_STRIKE;
} else if (hasBall()) {
return ONLY_BALL;
} else if (hasStrike()) {
return ONLY_STRIKE;
}
throw new IllegalArgumentException("result type error");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금도 간결한 구조이긴 한데 else if는 빼고 if만 4번 넣어도 될 것 같습니당
어디서 자꾸 else if 쓰지 말라고 들었는데 이렇게 하는 게 맞는진 모르겠네요..ㅋㅋㅋ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

껄껄 else 없는 삶..서럽


private String generateResultMessage(ResultType resultType) {
return switch (resultType) {
case NOTHING -> resultType.getValue();
case BALL_AND_STRIKE -> format(BALL_AND_STRIKE.getValue(), ballCount, strikeCount);
case ONLY_BALL -> format(ONLY_BALL.getValue(), ballCount);
case ONLY_STRIKE -> format(ONLY_STRIKE.getValue(), strikeCount);
};
}

public void print() {
ResultType resultType = inspectResultType();
OutputView.printResult(generateResultMessage(resultType));
}

public boolean hasBall() {
return ballCount > 0;
}

public boolean hasStrike() {
return strikeCount > 0;
}

public boolean checkGameOver() {
return strikeCount == NUMBER_LENGTH.getValue();
}
}
19 changes: 19 additions & 0 deletions src/main/java/baseball/domain/constants/ResultType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package baseball.domain.constants;

public enum ResultType {

NOTHING("낫싱"),
ONLY_BALL("%d볼"),
ONLY_STRIKE("%d스트라이크"),
BALL_AND_STRIKE("%d볼 %d스트라이크");

private final String value;

ResultType(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Comment on lines +3 to +19

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum 타입은 쓸 생각을 못 했는데 저렇게 %d까지 넣어서 처리하는 건 좀 sexy한 것 같습니다👍

44 changes: 44 additions & 0 deletions src/main/java/baseball/validator/NumberValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package baseball.validator;


public class NumberValidator {
public static void validateNumberLength(final String number) {
if (!isValidLength(number)) {
throw new IllegalArgumentException("number Length is different from the system setting");
}
}

public static void validateContainDuplicatedNumber(final String number) {
if (!isUniqueNumber(number)) {
throw new IllegalArgumentException("number cannot contain duplicated numbers");
}
}

public static void validateContainOnlyNumber(final String number) {
if (!isValidNumber(number)) {
throw new IllegalArgumentException("number cannot contain any letters");
}
}

public static void validateEmpty(final String number) {
if (number.isEmpty()) {
throw new IllegalArgumentException("number cannot be empty");
}
}

private static boolean isValidLength(final String number) {
return number.length() == 3;
}

private static boolean isValidNumber(final String number) {
return number
.chars()
.allMatch(c -> Character.isDigit(c) && c >= '1' && c <= '9');
}

private static boolean isUniqueNumber(final String number) {
return number.chars()
.distinct()
.count() == number.length();
}
}
18 changes: 18 additions & 0 deletions src/main/java/baseball/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package baseball.view;

import camp.nextstep.edu.missionutils.Console;

import static baseball.view.constants.PrintMessage.*;

public class InputView {
public static String askUserNumbers() {
OutputView.printGameInformation(ASK_PLAYER_NUMBER);
return Console.readLine();
}

public static String askRestartOrExit() {
OutputView.printGameInformation(GAME_OVER);
OutputView.printGameInformation(ASK_RESTART_OR_EXIT);
return Console.readLine();
}
}
13 changes: 13 additions & 0 deletions src/main/java/baseball/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package baseball.view;

import baseball.view.constants.PrintMessage;

public class OutputView {
public static void printGameInformation(PrintMessage message) {
System.out.print(message.getMessage());
}

public static void printResult(String resultMessage) {
System.out.println(resultMessage);
}
}
18 changes: 18 additions & 0 deletions src/main/java/baseball/view/constants/PrintMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package baseball.view.constants;

public enum PrintMessage {
GAME_START("숫자 야구 게임을 시작합니다."),
ASK_PLAYER_NUMBER("숫자를 입력해주세요 : "),
ASK_RESTART_OR_EXIT("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."),
GAME_OVER("3개의 숫자를 모두 맞히셨습니다! 게임 종료");

private final String message;

PrintMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}
6 changes: 3 additions & 3 deletions src/test/java/baseball/ApplicationTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package baseball;

import camp.nextstep.edu.missionutils.test.NsTest;
import org.junit.jupiter.api.Test;

import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest;
import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import camp.nextstep.edu.missionutils.test.NsTest;
import org.junit.jupiter.api.Test;

class ApplicationTest extends NsTest {
@Test
void 게임종료_후_재시작() {
Expand Down