diff --git a/README.md b/README.md index fcf3f057..8c2c2643 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,23 @@ ## 과제 제출 과정 * [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse) + +## 기능 요구사항 분석 +- 임의의 정답 생성 + - 컴퓨터는 게임 시작 시 1부터 9까지 서로 다른 수로 이루어진 3자리 수를 임의로 생성한다. +- 정답 입력 + - 사용자는 1부터 9까지 서로 다른 수로 이루어진 3자리 수를 입력할 수 있다. + - 정답을 맞출 때까지 사용자는 반복해서 정답을 입력한다. +- 정답 비교 + - 사용자가 입력을 수행할 때마다 컴퓨터는 임의로 생성된 수와 비교하여 결과를 출력한다. + - 같은 수가 같은 자리에 있으면 **스트라이크** + - 같은 수가 다른 자리에 있으면 **볼** + - 같은 수가 전혀 없으면 **낫싱** + - 출력의 우선순위는 스트라이크가 볼보다 높다. ex) 1 스트라이크 1볼 +- 게임 재시작 or 종료 + - 사용자가 정답을 맞출 경우 게임을 재시작하거나 완전히 종료할 수 있다. + +## 고려사항 +- 정답의 길이가 항상 3일까? -> 3자리 이상의 숫자 야구 가능 +- 항상 정답을 임의의 수로만 생성할까? -> 직접 입력으로 변경 가능 +- 항상 서로 다른 수로만 이루어질까? -> 규칙이기 때문에 바뀌지 않음 \ No newline at end of file diff --git a/src/main/java/AnswerMaker.java b/src/main/java/AnswerMaker.java new file mode 100644 index 00000000..a12c1b01 --- /dev/null +++ b/src/main/java/AnswerMaker.java @@ -0,0 +1,3 @@ +public interface AnswerMaker { + String makeAnswer(int length); +} diff --git a/src/main/java/GamePlayer.java b/src/main/java/GamePlayer.java new file mode 100644 index 00000000..81392933 --- /dev/null +++ b/src/main/java/GamePlayer.java @@ -0,0 +1,76 @@ +import UI.InputUI; + +public class GamePlayer { + + private final int length; + private String answer; + private String userInput; + + public GamePlayer(int length) { + this.length = length; + } + + public void start() { + boolean isRestart = false; + while (!isRestart) { + playGame(); + String userInput = InputUI.getRestartFlag(); + isRestart = userInput.equals("2"); + System.out.println(); + } + System.out.println("게임을 완전히 종료합니다."); + } + + private void playGame() { + System.out.println("게임을 시작합니다."); + // 정답 생성 + AnswerMaker answerMaker = new RandomAnswerMaker(); + answer = answerMaker.makeAnswer(length); + + boolean isCorrect = false; + while (!isCorrect) { + // 정답 입력 + userInput = InputUI.getAnswerByUser(length); + isCorrect = compareInputWithAnswer(); + } + } + + private boolean compareInputWithAnswer() { + int strikeCnt = 0; + int ballCnt = 0; + for (int i = 0; i < userInput.length(); i++) { + strikeCnt += isStrike(i); + ballCnt += isBall(i); + } + + printResult(strikeCnt, ballCnt); + if (strikeCnt == length) return true; + return false; + } + + private int isStrike(int idx) { + if (answer.indexOf(userInput.charAt(idx)) == idx) + return 1; + return 0; + } + + private int isBall(int idx) { + if (answer.indexOf(userInput.charAt(idx)) != -1 && answer.indexOf(userInput.charAt(idx)) != idx) + return 1; + return 0; + } + + private void printResult(int strikeCnt, int ballCnt) { + if (strikeCnt > 0) + System.out.print(strikeCnt + "스트라이크 "); + if (ballCnt > 0) + System.out.print(ballCnt + "볼"); + if (strikeCnt == 0 && ballCnt == 0) + System.out.print("낫싱"); + System.out.println(); + + if (strikeCnt == length) { + System.out.println(length + "개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } + } +} diff --git a/src/main/java/GameStarter.java b/src/main/java/GameStarter.java new file mode 100644 index 00000000..a1fb7868 --- /dev/null +++ b/src/main/java/GameStarter.java @@ -0,0 +1,6 @@ +public class GameStarter { + public static void main(String[] args) { + GamePlayer gamePlayer = new GamePlayer(3); + gamePlayer.start(); + } +} diff --git a/src/main/java/RandomAnswerMaker.java b/src/main/java/RandomAnswerMaker.java new file mode 100644 index 00000000..60322358 --- /dev/null +++ b/src/main/java/RandomAnswerMaker.java @@ -0,0 +1,18 @@ +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RandomAnswerMaker implements AnswerMaker { + + @Override + public String makeAnswer(int length) { + List numbers = new ArrayList<>(); + for (int i = 1; i < 10; i++) { + numbers.add(String.valueOf(i)); + } + Collections.shuffle(numbers); + + return String.join("", numbers.subList(0, length)); + } + +} diff --git a/src/main/java/UI/InputUI.java b/src/main/java/UI/InputUI.java new file mode 100644 index 00000000..1a252a3d --- /dev/null +++ b/src/main/java/UI/InputUI.java @@ -0,0 +1,45 @@ +package UI; + +import java.util.Scanner; + +public class InputUI { + private static final Scanner sc = new Scanner(System.in); + + public static String getAnswerByUser(int length) { + System.out.print("서로 다른 수로 이루어진 세자리 수를 입력해주세요 : "); + String userInput = sc.next(); + while (userInput.length() != length || !isValidInput(userInput)) { + System.out.print("\n올바르지 않은 입력입니다.\n" + + "서로 다른 수로 이루어진 세자리 수를 입력해주세요 : "); + userInput = sc.next(); + } + + return userInput; + } + + public static String getRestartFlag() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + String userInput = sc.next(); + while (!userInput.equals("1") && !userInput.equals("2")) { + System.out.println("올바르지 않은 입력입니다.\n" + + "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + userInput = sc.next(); + } + return userInput; + } + + private static boolean isValidInput(String input) { + StringBuilder sb = new StringBuilder(); + for (char c : input.toCharArray()) { + appendUniqueChar(sb, c); + } + + return input.equals(sb.toString()); + } + + private static void appendUniqueChar(StringBuilder sb, char c) { + if (!sb.toString().contains(String.valueOf(c)) && Character.isDigit(c)) { + sb.append(c); + } + } +} diff --git a/src/test/java/AnswerMakerTest.java b/src/test/java/AnswerMakerTest.java new file mode 100644 index 00000000..18c6432a --- /dev/null +++ b/src/test/java/AnswerMakerTest.java @@ -0,0 +1,36 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AnswerMakerTest { + private final AnswerMaker answerMaker; + + public AnswerMakerTest() { + this.answerMaker = new RandomAnswerMaker(); + } + + @Test + @DisplayName("생성된 수는 모두 다른 수로 이루어져있는가?") + public void checkDuplicationInAnswer() { + String answer = answerMaker.makeAnswer(3); + + assertThat(checkDuplication(answer)).isTrue(); + } + + @Test + @DisplayName("생성된 수는 세자리 수인가?") + public void checkAnswerSize() { + String answer = answerMaker.makeAnswer(3); + + assertThat(answer.length()).isEqualTo(3); + } + + private boolean checkDuplication(String str) { + if (str.charAt(0) == str.charAt(1)) return false; + if (str.charAt(1) == str.charAt(2)) return false; + if (str.charAt(0) == str.charAt(2)) return false; + return true; + } + +} diff --git a/src/test/java/GamePlayerTest.java b/src/test/java/GamePlayerTest.java new file mode 100644 index 00000000..ff744c94 --- /dev/null +++ b/src/test/java/GamePlayerTest.java @@ -0,0 +1,82 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.assertj.core.api.Assertions.assertThat; + +class GamePlayerTest { + + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + private final GamePlayer gamePlayer; + + public GamePlayerTest() { + this.gamePlayer = new GamePlayer(3); + } + + @Test + @DisplayName("정답 여부가 맞게 결정되는가?") + void test1() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException { + // private 메서드 접근 허용 + Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer"); + Field answerField = GamePlayer.class.getDeclaredField("answer"); + Field userInputField = GamePlayer.class.getDeclaredField("userInput"); + testMethod.setAccessible(true); + answerField.setAccessible(true); + userInputField.setAccessible(true); + + answerField.set(gamePlayer, "123"); + userInputField.set(gamePlayer, "321"); + + boolean actual = (boolean) testMethod.invoke(gamePlayer); + + assertThat(actual).isFalse(); + } + + @Test + @DisplayName("힌트가 맞게 출력되는가?") + void test2() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException { + // private 메서드 접근 허용 + Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer"); + Field answerField = GamePlayer.class.getDeclaredField("answer"); + Field userInputField = GamePlayer.class.getDeclaredField("userInput"); + testMethod.setAccessible(true); + answerField.setAccessible(true); + userInputField.setAccessible(true); + + System.setOut(new PrintStream(out)); + + answerField.set(gamePlayer, "123"); + userInputField.set(gamePlayer, "321"); + + testMethod.invoke(gamePlayer); + + assertThat(out.toString().strip()).isEqualTo("1스트라이크 2볼"); + } + + @Test + @DisplayName("일치하는 수가 없는 경우 '낫싱'이 출력되는가?") + void test3() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException { + // private 메서드 접근 허용 + Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer"); + Field answerField = GamePlayer.class.getDeclaredField("answer"); + Field userInputField = GamePlayer.class.getDeclaredField("userInput"); + testMethod.setAccessible(true); + answerField.setAccessible(true); + userInputField.setAccessible(true); + + System.setOut(new PrintStream(out)); + + answerField.set(gamePlayer, "123"); + userInputField.set(gamePlayer, "456"); + + testMethod.invoke(gamePlayer); + + assertThat(out.toString().strip()).isEqualTo("낫싱"); + } +} \ No newline at end of file diff --git a/src/test/java/UI/InputUITest.java b/src/test/java/UI/InputUITest.java new file mode 100644 index 00000000..c7154c00 --- /dev/null +++ b/src/test/java/UI/InputUITest.java @@ -0,0 +1,53 @@ +package UI; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class InputUITest { + + @Test + @DisplayName("사용자가 입력한 답은 숫자로만 이루어져 있는가?") + void checkUserInputIsNum() { + inputStringToStream("123"); + String userInput = InputUI.getAnswerByUser(3); + + for (int i = 0; i < userInput.length(); i++) { + assertThat(userInput.charAt(i)).isBetween('1', '9'); + } + } + + @Test + @DisplayName("사용자가 입력한 답은 세자리 수인가?") + void checkUserInput() { + inputStringToStream("456"); + String userInput = InputUI.getAnswerByUser(3); + + assertThat(userInput.length()).isEqualTo(3); + } + + @Test + @DisplayName("사용자가 입력한 답은 서로 다른 수로 이루어져 있는가?") + void test() { + inputStringToStream("789"); + String userInput = InputUI.getAnswerByUser(3); + + assertThat(checkDuplication(userInput)).isTrue(); + } + + private void inputStringToStream(String input) { + InputStream in = new ByteArrayInputStream(input.getBytes()); + System.setIn(in); + } + + private boolean checkDuplication(String str) { + if (str.charAt(0) == str.charAt(1)) return false; + if (str.charAt(1) == str.charAt(2)) return false; + if (str.charAt(0) == str.charAt(2)) return false; + return true; + } +} \ No newline at end of file