diff --git a/README.md b/README.md index fcf3f057..03b16445 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,19 @@ ## 과제 제출 과정 * [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse) + +## 구현할 기능 목록 + +- [x] 게임이 시작하면 컴퓨터는 서로 다른 3자리의 수를 랜덤 생성한다. + - 숫자의 범위는 1-9 이다. +- [x] 유저가 서로 다른 3자리의 수를 입력한다. + - 입력 포맷 : "숫자를 입력해주세요 : %s" +- [x] 결과를 계산한다. + - 스트라이크, 볼의 갯수를 계산하여 return 한다. +- [x] 결과를 종합한다. + - 스트라이크, 볼의 갯수가 둘 다 0이 아닐 시 그대로 출력 + - 스트라이크, 볼의 갯수가 둘 다 0일 경우 낫싱 출력 + - 스트라이크가 3개일 경우 정답처리 +- [x] 정답 처리 + - '1' 입력시에 게임 재실행 + - '2' 입력시에 게임 종료 \ No newline at end of file diff --git a/build.gradle b/build.gradle index d6eb8110..d702a9e2 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { group = 'camp.nextstep' version = '1.0.0' -sourceCompatibility = '11' +sourceCompatibility = "16" repositories { mavenCentral() @@ -18,3 +18,4 @@ dependencies { tasks.named('test') { useJUnitPlatform() } +targetCompatibility = JavaVersion.VERSION_16 diff --git a/commit-message-template b/commit-message-template new file mode 100644 index 00000000..29e3eef9 --- /dev/null +++ b/commit-message-template @@ -0,0 +1,27 @@ +# <타입>: <제목> + +##### 제목은 최대 50 글자까지만 입력 ############## -> | + + +# 본문은 위에 작성 +######## 본문은 한 줄에 최대 72 글자까지만 입력 ########################### -> | + +# 꼬릿말은 아래에 작성: ex) #이슈 번호 + +# --- COMMIT END --- +# <타입> 리스트 +# feat : 기능 (새로운 기능) +# fix : 버그 (버그 수정) +# refactor : 리팩토링 +# style : 스타일 (코드 형식, 세미콜론 추가: 비즈니스 로직에 변경 없음) +# docs : 문서 (문서 추가, 수정, 삭제) +# test : 테스트 (테스트 코드 추가, 수정, 삭제: 비즈니스 로직에 변경 없음) +# chore : 기타 변경사항 (빌드 스크립트 수정 등) +# ------------------ +# 제목 첫 글자를 대문자로 +# 제목은 명령문으로 +# 제목 끝에 마침표(.) 금지 +# 제목과 본문을 한 줄 띄워 분리하기 +# 본문은 "어떻게" 보다 "무엇을", "왜"를 설명한다. +# 본문에 여러줄의 메시지를 작성할 땐 "-"로 구분 +# ------------------ diff --git a/java-google-style.xml b/java-google-style.xml new file mode 100644 index 00000000..45bf14cc --- /dev/null +++ b/java-google-style.xml @@ -0,0 +1,598 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/baseball/Main.java b/src/main/java/baseball/Main.java new file mode 100644 index 00000000..07c7e4d1 --- /dev/null +++ b/src/main/java/baseball/Main.java @@ -0,0 +1,16 @@ +package baseball; +import baseball.controller.BaseballController; +import baseball.domain.BaseballNumber; +import baseball.service.BaseballService; +import baseball.view.BaseballView; + + +public class Main { + public static void main(String[] args) { + BaseballNumber baseballNumber = new BaseballNumber(); + BaseballService baseballService = new BaseballService(baseballNumber); + BaseballController baseballController = new BaseballController(baseballService); + BaseballView baseballView = new BaseballView(baseballController); + baseballView.startGame(); + } +} diff --git a/src/main/java/baseball/common/BaseballEnum.java b/src/main/java/baseball/common/BaseballEnum.java new file mode 100644 index 00000000..3e13890f --- /dev/null +++ b/src/main/java/baseball/common/BaseballEnum.java @@ -0,0 +1,8 @@ +package baseball.common; + +public class BaseballEnum { + public final static int CONTINUE_THE_GAME = 1; + public final static int FINISH_THE_GAME = 2; + public final static int BASEBALL_NUMBER_LENGTH = 3; + +} diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java new file mode 100644 index 00000000..03dd33d1 --- /dev/null +++ b/src/main/java/baseball/controller/BaseballController.java @@ -0,0 +1,33 @@ +package baseball.controller; + +import baseball.dto.BaseballResultDto; +import baseball.service.BaseballService; +import java.util.List; + +public class BaseballController { + + BaseballService baseballService; + + public BaseballController(BaseballService baseballService) { + this.baseballService = baseballService; + } + + public BaseballResultDto getBaseballResult(List userBaseballNumber) { + Integer strikeCount = baseballService.getStrikeCount(userBaseballNumber); + Integer ballCount = baseballService.getBallCount(userBaseballNumber); + return new BaseballResultDto(strikeCount, ballCount); + } + + public boolean isPlaying() { + return baseballService.isPlaying(); + } + + public void startGame() { + baseballService.startGame(); + } + + public void finishGame() { + baseballService.finishGame(); + } + +} diff --git a/src/main/java/baseball/domain/BaseballNumber.java b/src/main/java/baseball/domain/BaseballNumber.java new file mode 100644 index 00000000..22db8c6a --- /dev/null +++ b/src/main/java/baseball/domain/BaseballNumber.java @@ -0,0 +1,34 @@ +package baseball.domain; + +import static baseball.common.BaseballEnum.BASEBALL_NUMBER_LENGTH; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BaseballNumber { + private final List candidateNumbers = new ArrayList<>(); + private List number = new ArrayList<>(); + + public BaseballNumber() { + for (int i = 1; i <= 9; i++) { + candidateNumbers.add(i); + } + } + + public void generateBaseballNumber() { + number.clear(); + Collections.shuffle(candidateNumbers); + for (int i = 0; i < BASEBALL_NUMBER_LENGTH; i++) { + number.add(candidateNumbers.get(i)); + } + } + + public List getNumber() { + return number; + } + + public void setNumber(List number) { + this.number = number; + } +} diff --git a/src/main/java/baseball/dto/BaseballResultDto.java b/src/main/java/baseball/dto/BaseballResultDto.java new file mode 100644 index 00000000..1ec99001 --- /dev/null +++ b/src/main/java/baseball/dto/BaseballResultDto.java @@ -0,0 +1,3 @@ +package baseball.dto; + +public record BaseballResultDto(Integer strikeCount, Integer ballCount){} diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java new file mode 100644 index 00000000..b1079b6b --- /dev/null +++ b/src/main/java/baseball/service/BaseballService.java @@ -0,0 +1,61 @@ +package baseball.service; + +import baseball.domain.BaseballNumber; +import java.util.List; + +public class BaseballService { + + private final int RUNNING = 1; + private final int READY = 0; + private final BaseballNumber baseballNumber; + private int status; + + public BaseballService(BaseballNumber baseballNumber) { + this.baseballNumber = baseballNumber; + } + + public int getBallCount(List userBaseballNumber) { + List computerBaseballNumber = baseballNumber.getNumber(); + int ballCount = 0; + + for (int i = 0; i < userBaseballNumber.size(); i++) { + for (int j = 0; j < computerBaseballNumber.size(); j++){ + if (i != j & userBaseballNumber.get(i).equals(computerBaseballNumber.get(j))) { + ballCount++; + } + } + } + + return ballCount; + } + + public int getStrikeCount(List userBaseballNumber) { + List computerBaseballNumber = baseballNumber.getNumber(); + int strikeCount = 0; + + for (int i = 0; i < userBaseballNumber.size(); i++) { + if (userBaseballNumber.get(i).equals(computerBaseballNumber.get(i))) { + strikeCount++; + } + } + + return strikeCount; + } + + public boolean isPlaying() { + return status == RUNNING; + } + + public void startGame(){ + status = RUNNING; + baseballNumber.generateBaseballNumber(); + } + + public void finishGame(){ + status = READY; + } + + public void setBaseballNumber(List baseballNumber) { + this.baseballNumber.setNumber(baseballNumber); + } +} diff --git a/src/main/java/baseball/view/BaseballView.java b/src/main/java/baseball/view/BaseballView.java new file mode 100644 index 00000000..f357360f --- /dev/null +++ b/src/main/java/baseball/view/BaseballView.java @@ -0,0 +1,136 @@ +package baseball.view; + +import static baseball.common.BaseballEnum.BASEBALL_NUMBER_LENGTH; +import static baseball.common.BaseballEnum.CONTINUE_THE_GAME; +import static baseball.common.BaseballEnum.FINISH_THE_GAME; + +import baseball.controller.BaseballController; +import baseball.dto.BaseballResultDto; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; +import java.util.stream.Collectors; + +public class BaseballView { + + private final Scanner scanner; + private final BaseballController baseballController; + + public BaseballView(BaseballController baseballController) { + this.baseballController = baseballController; + this.scanner = new Scanner(System.in); + } + + public void startGame() { + baseballController.startGame(); + while (baseballController.isPlaying()) { + playGame(); + } + } + + private void playGame() { + List playerBaseballNumber = getPlayerBaseballNumber(); + + BaseballResultDto baseballResultDto = baseballController + .getBaseballResult(playerBaseballNumber); + + System.out.println(getBaseballResultString(baseballResultDto)); + checkGameStatus(baseballResultDto); + } + + private void checkGameStatus(BaseballResultDto baseballResultDto) { + if (baseballResultDto.strikeCount() == BASEBALL_NUMBER_LENGTH) { + baseballController.finishGame(); + System.out.printf("%d개의 숫자를 모두 맞히셨습니다! 게임 종료 \n", BASEBALL_NUMBER_LENGTH); + checkToContinueGame(); + } + } + + private void checkToContinueGame() { + System.out.printf("게임을 새로 시작하려면 %d, 종료하려면 %d를 입력하세요.\n", + CONTINUE_THE_GAME, FINISH_THE_GAME); + int playerStatusInput = scanner.nextInt(); + try { + validatePlayerStatusInput(playerStatusInput); + restartGameIfContinue(playerStatusInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + checkToContinueGame(); + } + } + + private void validatePlayerStatusInput(int playerInput) { + if (playerInput != CONTINUE_THE_GAME && playerInput != FINISH_THE_GAME) { + throw new IllegalArgumentException( + String.format("%d 또는 %d만 입력 가능합니다.", CONTINUE_THE_GAME, FINISH_THE_GAME) + ); + } + } + + private void restartGameIfContinue(int playerStatusInput) { + if (playerStatusInput == CONTINUE_THE_GAME) { + baseballController.startGame(); + } + } + private List getPlayerBaseballNumber() { + System.out.print("숫자를 입력해주세요 : "); + String playerInput = scanner.nextLine(); + try { + validatePlayerInput(playerInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return getPlayerBaseballNumber(); + } + return Arrays.stream(playerInput.split("")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public void validatePlayerInput(String playerInput) { + isValidLength(playerInput); + isOnlyNumber(playerInput); + isNotDuplicated(playerInput); + } + + private void isValidLength(String playerInput) { + if (playerInput.length() != BASEBALL_NUMBER_LENGTH) { + throw new IllegalArgumentException("3자리로 입력해주세요."); + } + } + + private void isOnlyNumber(String playerInput) { + playerInput.chars().mapToObj(x -> (char)x).forEach(this::isNumber); + } + + + private void isNumber(char ch){ + if (!Character.isDigit(ch)) { + throw new IllegalArgumentException("숫자만 입력 가능합니다."); + } + } + + private void isNotDuplicated(String playerInput) { + Set playerInputSet = new HashSet<> (Arrays.asList(playerInput.split(""))); + if (playerInputSet.size() != playerInput.length()) { + throw new IllegalArgumentException("숫자는 중복되지 않게 입력해주세요."); + } + } + + public String getBaseballResultString(BaseballResultDto baseballResultDto) { + String baseballResultString = getStrikeString(baseballResultDto) + + getBallString(baseballResultDto); + return baseballResultString.isBlank() ? "낫싱" : baseballResultString; + } + + private String getStrikeString(BaseballResultDto baseballResultDto) { + int strikeCount = baseballResultDto.strikeCount(); + return strikeCount != 0 ? String.format("%d 스트라이크 ", strikeCount) : ""; + } + + private String getBallString(BaseballResultDto baseballResultDto) { + int ballCount = baseballResultDto.ballCount(); + return ballCount != 0 ? String.format("%d 볼", ballCount) : ""; + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/BaseballNumberTest.java b/src/test/java/baseball/domain/BaseballNumberTest.java new file mode 100644 index 00000000..c40e9f73 --- /dev/null +++ b/src/test/java/baseball/domain/BaseballNumberTest.java @@ -0,0 +1,31 @@ +package baseball.domain; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +public class BaseballNumberTest { + + BaseballNumber baseballNumber; + + @BeforeEach + public void beforeEach() { + baseballNumber = new BaseballNumber(); + } + + @DisplayName("생성되는 숫자의 범위는 1-9이다.") + @Test + void GenerateBaseballNumbersTest() { + // given + baseballNumber.generateBaseballNumber(); + + // when + List numbers = baseballNumber.getNumber(); + + // then + assertThat(numbers.stream().allMatch(v -> 1 <= v && v <= 9)).isTrue(); + + } +} diff --git a/src/test/java/baseball/service/BaseballServiceTest.java b/src/test/java/baseball/service/BaseballServiceTest.java new file mode 100644 index 00000000..ed69e1fd --- /dev/null +++ b/src/test/java/baseball/service/BaseballServiceTest.java @@ -0,0 +1,49 @@ +package baseball.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import baseball.domain.BaseballNumber; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BaseballServiceTest { + + private BaseballNumber baseballNumber; + private BaseballService baseballService; + + @BeforeEach + public void beforeEach() { + baseballNumber = new BaseballNumber(); + baseballService = new BaseballService(baseballNumber); + baseballService.startGame(); + } + + @DisplayName("볼의 갯수를 계산할 수 있습니다.") + @Test + void getBallCount() { + // given + List userBaseballNumber = new ArrayList<>(Arrays.asList(1, 2, 3)); + List computerBaseballNumber = new ArrayList<>(Arrays.asList(1, 3, 2)); + baseballService.setBaseballNumber(computerBaseballNumber); + + // when & then + assertThat(baseballService.getBallCount(userBaseballNumber)).isEqualTo(2); + + } + + @DisplayName("스트라이크의 갯수를 계산할 수 있습니다.") + @Test + void getStrikeCount() { + // given + List userBaseballNumber = new ArrayList<>(Arrays.asList(1, 2, 4)); + List computerBaseballNumber = new ArrayList<>(Arrays.asList(1, 2, 3)); + baseballService.setBaseballNumber(computerBaseballNumber); + + // when & then + assertThat(baseballService.getStrikeCount(userBaseballNumber)).isEqualTo(2); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/view/BaseballViewTest.java b/src/test/java/baseball/view/BaseballViewTest.java new file mode 100644 index 00000000..3bb9be9f --- /dev/null +++ b/src/test/java/baseball/view/BaseballViewTest.java @@ -0,0 +1,102 @@ +package baseball.view; + +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import baseball.controller.BaseballController; +import baseball.domain.BaseballNumber; +import baseball.dto.BaseballResultDto; +import baseball.service.BaseballService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BaseballViewTest { + + private BaseballView baseballView; + + @BeforeEach + public void beforeEach() { + BaseballNumber baseballNumber = new BaseballNumber(); + BaseballService baseballService = new BaseballService(baseballNumber); + BaseballController baseballController = new BaseballController(baseballService); + baseballView = new BaseballView(baseballController); + } + + @DisplayName("입력이 3글자가 아니면 Exception이 발생합니다.") + @Test + void isValidLength() { + // given + String fourLengthInput = "1234"; + + // when & then + assertThrows( + IllegalArgumentException.class, + () -> baseballView.validatePlayerInput(fourLengthInput) + ); + } + + @DisplayName("숫자가 아닌 다른 값이 input으로 들어가면 Exception이 발생합니다.") + @Test + void isOnlyNumber() { + // given + String notOnlyNumberInput = "12#"; + + // when & then + assertThrows( + IllegalArgumentException.class, + () -> baseballView.validatePlayerInput(notOnlyNumberInput) + ); + } + + @DisplayName("중복된 숫자를 입력하면 Exception이 발생합니다.") + @Test + void isNotDuplicated() { + // given + String duplicatedNumberInput = "112"; + + // when & then + assertThrows( + IllegalArgumentException.class, + () -> baseballView.validatePlayerInput(duplicatedNumberInput) + ); + } + + @DisplayName("하나도 맞지 않으면 '낫싱'이 반환됩니다.") + @Test + void getBaseballResultString() { + // given + BaseballResultDto baseballResultDto = new BaseballResultDto(0, 0); + + // when + String resultString = baseballView.getBaseballResultString(baseballResultDto); + + // then + assertThat(resultString).isEqualTo("낫싱"); + } + + @DisplayName("스트라이크가 0이 아니면 스트라이크에 대한 정보가 포함됩니다.") + @Test + void getStrikeString() { + // given + BaseballResultDto baseballResultDto = new BaseballResultDto(1, 0); + + // when + String resultString = baseballView.getBaseballResultString(baseballResultDto); + + // then + assertThat(resultString).contains("스트라이크"); + } + + @DisplayName("볼이 0이 아니면 볼에 대한 정보가 포함됩니다.") + @Test + void getBallString() { + // given + BaseballResultDto baseballResultDto = new BaseballResultDto(0, 1); + + // when + String resultString = baseballView.getBaseballResultString(baseballResultDto); + + // then + assertThat(resultString).contains("볼"); + } +} \ No newline at end of file