diff --git a/README.md b/README.md index fcf3f057..86471d94 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,10 @@ ## 과제 제출 과정 * [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse) + +## 기능 목록 +1. 게임시작 환경 셋업 +2. Input 받아서 Validation 하기 +3. 숫자야구 규칙에 따른 핵심로직 (스트라이크, 볼 판단) +4. 재시작 기능 +5. 단위테스트 구현 \ No newline at end of file diff --git a/build.gradle b/build.gradle index d6eb8110..04b69707 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,12 @@ repositories { dependencies { testImplementation 'org.assertj:assertj-core:3.23.1' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' + + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' } tasks.named('test') { diff --git a/src/main/java/game/AnswerEntity.java b/src/main/java/game/AnswerEntity.java new file mode 100644 index 00000000..b42ee3af --- /dev/null +++ b/src/main/java/game/AnswerEntity.java @@ -0,0 +1,39 @@ +package game; + +import java.util.Random; + +public class AnswerEntity { + private int[] answer; + private boolean[] check; + AnswerEntity(){ + this.answer = new int[3]; + } + public void init(){ + Random random = new Random(); + check = new boolean[10]; + + for(int idx = 0; idx < 3; idx++){ + int num = random.nextInt(9) + 1; + if(checkDuplicationAndInsert(num, idx)) idx--; + } + } + private boolean checkDuplicationAndInsert(int num, int idx){ + if(!check[num]){ + answer[idx] = num; + check[num] = true; + return false; + } + return true; + } + + public int[] getAnswer(){ + return this.answer; + } + + public void setAnswer(int n1, int n2, int n3){ + this.answer[0] = n1; + this.answer[1] = n2; + this.answer[2] = n3; + } + +} diff --git a/src/main/java/game/GameInputScanner.java b/src/main/java/game/GameInputScanner.java new file mode 100644 index 00000000..bc7cca25 --- /dev/null +++ b/src/main/java/game/GameInputScanner.java @@ -0,0 +1,41 @@ +package game; + +import java.util.Scanner; + +public class GameInputScanner { + private final Scanner scanner; + private final GameInputValidator validator; + + GameInputScanner(){ + this.scanner = new Scanner(System.in); + this.validator = new GameInputValidator(); + } + public int[] getGameInput(){ + String input; + do{ + System.out.print(Message.INPUT_MSG.getMsgStr()); + input = scanner.nextLine().trim(); + }while(!validator.validateGameInput(input)); + + return strToIntArr(input); + } + private int[] strToIntArr(String input){ + int[] ret = new int[3]; + char[] inputCharArr = input.toCharArray(); + + for(int i = 0; i < 3; i++){ + ret[i] = inputCharArr[i] - '0'; + } + + return ret; + } + public int getRestartInput(){ + String input; + do{ + System.out.println(Message.RESTART_MSG.getMsgStr()); + input = scanner.nextLine().trim(); + }while(!validator.validateRestartInput(input)); + + return Integer.parseInt(input); + } +} diff --git a/src/main/java/game/GameInputValidator.java b/src/main/java/game/GameInputValidator.java new file mode 100644 index 00000000..fd6685c7 --- /dev/null +++ b/src/main/java/game/GameInputValidator.java @@ -0,0 +1,41 @@ +package game; + +public class GameInputValidator { + public boolean validateGameInput(String input){ + //input 문자열의 길이 & 0이 포함되어 있는지 검사 + if(input.length() != 3 || input.contains("0")){ + System.out.println(Message.INVALID_INPUT_WARNING.getMsgStr()); + return false; + } + + return isNumber(input); + }; + + //input 문자열이 숫자로만 이루어져 있는지 검사 + private boolean isNumber(String input){ + try{ + Integer.parseInt(input); + }catch (NumberFormatException numberFormatException){ + System.out.println(Message.INVALID_INPUT_WARNING.getMsgStr()); + return false; + } + + return true; + } + + public boolean validateRestartInput(String input) { + if(!isNumber(input)){ + System.out.println(Message.INVALID_INPUT_WARNING.getMsgStr()); + return false; + } + + int n = Integer.parseInt(input); + if(n != 1 && n != 2){ + System.out.println(Message.INVALID_INPUT_WARNING.getMsgStr()); + return false; + } + + return true; + } + +} diff --git a/src/main/java/game/GameService.java b/src/main/java/game/GameService.java new file mode 100644 index 00000000..70dec240 --- /dev/null +++ b/src/main/java/game/GameService.java @@ -0,0 +1,39 @@ +package game; + +import java.util.Arrays; + +public class GameService { + + public ResultEntity calcResult(int[] answer, int[] input){ + ResultEntity result = new ResultEntity(0, 0); + //answer 배열의 각 원소 위치를 저장하는 배열, 없는 원소면 -1 + int[] answerIdxArr = makeAnswerIdxArr(answer); + + for(int inputIdx = 0; inputIdx < 3; inputIdx++){ + int answerIdx = answerIdxArr[input[inputIdx]]; + if(answerIdx == inputIdx) result.addStrike(); + if(answerIdx != inputIdx && answerIdx != -1) result.addBall(); + } + + System.out.println(result); + return result; + } + private int[] makeAnswerIdxArr(int[] answer){ + int[] ret = new int[10]; + Arrays.fill(ret, -1); + + for(int idx = 0; idx < 3; idx++){ + ret[answer[idx]] = idx; + } + + return ret; + } + + public boolean isFinish(ResultEntity result){ + if(result.getStrike() == 3){ + System.out.println(Message.END_MSG.getMsgStr()); + return true; + } + return false; + } +} diff --git a/src/main/java/game/Main.java b/src/main/java/game/Main.java new file mode 100644 index 00000000..dd87f902 --- /dev/null +++ b/src/main/java/game/Main.java @@ -0,0 +1,39 @@ +package game; + +public class Main { + private static AnswerEntity answer; + private static int[] input; + private static boolean isFinish; + private static GameInputScanner gameInputScanner; + private static GameService gameService; + + public static void main(String[] args) { + do{ + setUp(); + startGame(); + }while(restartOrNot()); + } + private static void setUp(){ + isFinish = false; + answer = new AnswerEntity(); + gameInputScanner = new GameInputScanner(); + gameService = new GameService(); + + answer.init(); + } + private static void startGame(){ + while(!isFinish){ + input = gameInputScanner.getGameInput(); + ResultEntity result = gameService.calcResult(answer.getAnswer(), input); + isFinish = gameService.isFinish(result); + } + } + private static boolean restartOrNot(){ + int cmd = gameInputScanner.getRestartInput(); + if(cmd == 1) return true; + + return false; + } + + +} diff --git a/src/main/java/game/Message.java b/src/main/java/game/Message.java new file mode 100644 index 00000000..ad060290 --- /dev/null +++ b/src/main/java/game/Message.java @@ -0,0 +1,15 @@ +package game; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum Message { + INPUT_MSG("숫자를 입력해주세요 : "), + END_MSG("3개의 숫자를 모두 맞히셨습니다! 게임 종료"), + RESTART_MSG("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요"), + INVALID_INPUT_WARNING("잘못된 입력입니다."); + + private final String msgStr; +} diff --git a/src/main/java/game/ResultEntity.java b/src/main/java/game/ResultEntity.java new file mode 100644 index 00000000..10d1a35f --- /dev/null +++ b/src/main/java/game/ResultEntity.java @@ -0,0 +1,29 @@ +package game; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@EqualsAndHashCode +public class ResultEntity { + private Integer strike; + private Integer ball; + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + if(this.strike > 0) sb.append(this.strike).append(" 스트라이크 "); + if(this.ball > 0) sb.append(this.ball).append(" 볼"); + if(this.strike == 0 && this.ball == 0) sb.append("낫싱"); + + return sb.toString(); + } + + public void addStrike(){ + this.strike++; + } + public void addBall(){ + this.ball++; + } +} diff --git a/src/test/java/gametest/GameInputValidatorTest.java b/src/test/java/gametest/GameInputValidatorTest.java new file mode 100644 index 00000000..aff9255e --- /dev/null +++ b/src/test/java/gametest/GameInputValidatorTest.java @@ -0,0 +1,57 @@ +package gametest; + +import game.GameInputValidator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GameInputValidatorTest { + private final GameInputValidator gameInputValidator = new GameInputValidator(); + + @DisplayName("[GameInputValidator] 게임 인풋 유효성 테스트") + @ParameterizedTest + @MethodSource("validateGameInputTestGenerator") + void validateGameInputTest(String input, boolean expected){ + boolean result = gameInputValidator.validateGameInput(input); + + assertThat(result).isEqualTo(expected); + } + private static Stream validateGameInputTestGenerator(){ + return Stream.of( + Arguments.of("123", true), + Arguments.of("253", true), + Arguments.of("12", false), + Arguments.of("1829", false), + Arguments.of("12a", false), + Arguments.of("12 a", false) + ); + } + + @DisplayName("[GameInputValidator] 재시작 입력값 유효성 테스트") + @ParameterizedTest + @MethodSource("validateRestartInputTestGenerator") + void validateRestartInputTest(String input, boolean expected){ + boolean result = gameInputValidator.validateRestartInput(input); + + assertThat(result).isEqualTo(expected); + } + + private static Stream validateRestartInputTestGenerator(){ + return Stream.of( + Arguments.of("1", true), + Arguments.of("2", true), + Arguments.of("3", false), + Arguments.of("0", false), + Arguments.of("", false), + Arguments.of("123", false), + Arguments.of("a", false), + Arguments.of("7h", false) + ); + } + +} diff --git a/src/test/java/gametest/GameServiceTest.java b/src/test/java/gametest/GameServiceTest.java new file mode 100644 index 00000000..4a59ec07 --- /dev/null +++ b/src/test/java/gametest/GameServiceTest.java @@ -0,0 +1,37 @@ +package gametest; + +import game.GameService; +import game.ResultEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GameServiceTest { + private final GameService gameService = new GameService(); + + @DisplayName("[GameService] 결과 계산 로직 테스트") + @ParameterizedTest + @MethodSource("calcResultTestGenerator") + void calcResultTest(int[] answer, int[] input, ResultEntity expected) { + ResultEntity result = gameService.calcResult(answer, input); + + assertThat(result).isEqualTo(expected); + } + + private static Stream calcResultTestGenerator(){ + return Stream.of( + Arguments.of(new int[]{1,2,3}, new int[]{1,2,3}, new ResultEntity(3, 0)), + Arguments.of(new int[]{1,2,3}, new int[]{3,1,2}, new ResultEntity(0, 3)), + Arguments.of(new int[]{1,2,3}, new int[]{4,5,6}, new ResultEntity(0, 0)), + Arguments.of(new int[]{1,2,3}, new int[]{1,3,2}, new ResultEntity(1, 2)), + Arguments.of(new int[]{1,2,3}, new int[]{1,2,4}, new ResultEntity(2, 0)) + ); + } + + +}