diff --git a/README.md b/README.md index 8d7e8aee..7b0fc39e 100644 --- a/README.md +++ b/README.md @@ -1 +1,30 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +### 기능 요구사항 목록 + +- [x] 컴퓨터가 1에서 9까지 서로 다른 임의의 수 3개를 선택 +- [x] 사용자에게 수 입력받기 +- [x] 입력받은 수와 비교하여 힌트 생성 + - [x] 스트라이크 : 같은 수가 같은 자리에 있을 때 + - [x] 볼 : 같은 수가 다른 자리에 있을 때 + - [x] 낫싱 : 같은 수가 전혀 없을 때 +- [x] 힌트 출력하기 +- [x] 사용자가 잘못된 값을 입력한 경우 `IllegalArgumentException` 발생시키기 + - [x] 게임 종료후, 재시작을 물어볼 때 1, 2 외에 잘못한 값을 입력했을 때 + - [x] 게임 중, 세자리 숫자를 잘못 입력 했을 때 +- [x] 사용자가 수를 맞췄을 때, 게임을 다시 할 건지 물어보기 + +### 테스트 목록 + +- [x] 랜덤 3자리수를 생성하는지 확인 + - [x] 3자리 숫자를 생성하는지 확인 + - [x] 중복이 없는 숫자를 생성하는지 확인 +- [x] 사용자가 잘못된 값을 입력한 경우 Validation Class 에서 검증 되는지 확인 + - [x] 숫자가 아닌 값을 입력했을 때 + - [x] 3자리가 아닌 숫자를 입력했을 때 + - [x] 같은 숫자 여러개를 입력했을 때 + - [x] 종료할 때, 1과 2 외에 다른 값을 입력했을 때 +- [x] strike와 ball에 따라 적절한 Hint String을 반환하는지 확인 + - [x] 스트라이크 + - [x] 볼 + - [x] 낫싱 diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..48e807aa --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,7 @@ +public class Application { + + public static void main(String[] args) { + Game game = new Game(); + game.run(); + } +} diff --git a/src/main/java/Game.java b/src/main/java/Game.java new file mode 100644 index 00000000..faa43407 --- /dev/null +++ b/src/main/java/Game.java @@ -0,0 +1,75 @@ +import constant.GameConstant; +import constant.GameMessageConstant; +import data.GameResult; +import java.util.Scanner; + +public class Game { + + Player computer; + boolean isEnd; + + public void run() throws IllegalArgumentException { + Scanner scanner = new Scanner(System.in); + String inputCommand; + do { + game(); + System.out.println(GameMessageConstant.GAME_RESTART_QUESTION); + inputCommand = scanner.next(); + if (!Validation.checkEndCommand(inputCommand)) { + throw new IllegalArgumentException(); + } + } while (Integer.parseInt(inputCommand) != GameConstant.END_GAME_NUMBER); + System.out.println(GameMessageConstant.GAME_END); + } + + public void init() { + computer = new Player(); + isEnd = false; + } + + public void game() throws IllegalArgumentException { + init(); + System.out.println(GameMessageConstant.START_GAME); + Scanner scanner = new Scanner(System.in); + String inputNumber; + GameResult result; + Hint hint; + do { + System.out.print(GameMessageConstant.INPUT_NUMBER); + inputNumber = scanner.next(); + checkInputNumber(inputNumber); + result = getResult(inputNumber); + hint = new Hint(result); + System.out.println(hint.getHint()); + } while (!isEnd); + System.out.println(GameMessageConstant.GAME_CLEAR); + } + + private GameResult getResult(String inputNumber) { + GameResult result = computer.checkMatching(input2IntArray(inputNumber)); + checkEnd(result); + return result; + } + + private void checkInputNumber(String inputNumber) throws IllegalArgumentException { + if (!Validation.checkThreeDigitNumber(inputNumber) || !Validation.checkDuplicateNumber( + inputNumber)) { + throw new IllegalArgumentException(); + } + } + + private void checkEnd(GameResult result) { + if (result.strike() == GameConstant.NUMBER_COUNT) { + isEnd = true; + } + } + + private int[] input2IntArray(String inputNumber) { + int[] guess = new int[GameConstant.NUMBER_COUNT]; + for (int i = 0; i < GameConstant.NUMBER_COUNT; i++) { + guess[i] = Character.getNumericValue(inputNumber.charAt(i)); + } + return guess; + } + +} diff --git a/src/main/java/Hint.java b/src/main/java/Hint.java new file mode 100644 index 00000000..e8ff97a1 --- /dev/null +++ b/src/main/java/Hint.java @@ -0,0 +1,38 @@ +import constant.GameMessageConstant; +import data.GameResult; + +public class Hint { + + GameResult result; + + public Hint(GameResult result) { + this.result = result; + } + + public String getHint() { + if (result.ball() == 0 && result.strike() == 0) { + return GameMessageConstant.NOTHING; + } + if (result.ball() == 0) { + return strike2String(result.strike()); + } + if (result.strike() == 0) { + return ball2String(result.ball()); + } + return ball2String(result.ball()) + " " + strike2String(result.strike()); + } + + private String ball2String(int ball) { + if (ball > 0) { + return String.format("%d%s", ball, GameMessageConstant.BALL); + } + return ""; + } + + private String strike2String(int strike) { + if (strike > 0) { + return String.format("%d%s", strike, GameMessageConstant.STRIKE); + } + return ""; + } +} diff --git a/src/main/java/Player.java b/src/main/java/Player.java new file mode 100644 index 00000000..337c6f13 --- /dev/null +++ b/src/main/java/Player.java @@ -0,0 +1,50 @@ +import constant.GameConstant; +import data.GameResult; +import java.util.HashSet; +import java.util.Set; + +public class Player { + + private int[] answer = new int[GameConstant.NUMBER_COUNT]; + Set numbers = new HashSet<>(); + + public Player() { + createRandomAnswer(); + } + + private void createRandomAnswer() { + answer = RandomNumberGenerator.generate(GameConstant.NUMBER_COUNT); + for (int i = 0; i < GameConstant.NUMBER_COUNT; i++) { + numbers.add(answer[i]); + } + } + + public GameResult checkMatching(int[] guess) { + int ball = checkBall(guess); + int strike = checkStrike(guess); + return new GameResult(ball, strike); + } + + private int checkBall(int[] guess) { + int count = 0; + for (int i = 0; i < GameConstant.NUMBER_COUNT; i++) { + if (answer[i] == guess[i]) { + continue; + } + if (numbers.contains(guess[i])) { + count++; + } + } + return count; + } + + private int checkStrike(int[] guess) { + int count = 0; + for (int i = 0; i < GameConstant.NUMBER_COUNT; i++) { + if (answer[i] == guess[i]) { + count++; + } + } + return count; + } +} diff --git a/src/main/java/RandomNumberGenerator.java b/src/main/java/RandomNumberGenerator.java new file mode 100644 index 00000000..8423f873 --- /dev/null +++ b/src/main/java/RandomNumberGenerator.java @@ -0,0 +1,25 @@ +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +public class RandomNumberGenerator { + + public static int[] generate(int length) { + int[] result = new int[length]; + Set numbers = new HashSet<>(); + while (numbers.size() < length) { + int number = createRandomNumber(); + numbers.add(number); + } + int index = 0; + for (int number : numbers) { + result[index++] = number; + } + return result; + } + + private static int createRandomNumber() { + Random random = new Random(); + return random.nextInt(10); + } +} diff --git a/src/main/java/Validation.java b/src/main/java/Validation.java new file mode 100644 index 00000000..8439ffe1 --- /dev/null +++ b/src/main/java/Validation.java @@ -0,0 +1,31 @@ +import constant.GameConstant; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public final class Validation { + + static final String TREE_DIGIT_REGEX = "^[0-9]{3}$"; + static final String ONE_DIGIT_REGEX = "^[0-9]{1}$"; + + public static boolean checkThreeDigitNumber(String inputNumber) { + return Pattern.compile(TREE_DIGIT_REGEX).matcher(inputNumber).matches(); + } + + public static boolean checkDuplicateNumber(String inputNumber) { + Set chars = new HashSet<>(); + for (char c : inputNumber.toCharArray()) { + chars.add(c); + } + return chars.size() == GameConstant.NUMBER_COUNT; + } + + public static boolean checkEndCommand(String inputCommand) { + if (!Pattern.compile(ONE_DIGIT_REGEX).matcher(inputCommand).matches()) { + return false; + } + int command = Integer.parseInt(inputCommand); + return command == GameConstant.END_GAME_NUMBER + || command == GameConstant.RESTART_GAME_NUMBER; + } +} diff --git a/src/main/java/constant/GameConstant.java b/src/main/java/constant/GameConstant.java new file mode 100644 index 00000000..38b95b3a --- /dev/null +++ b/src/main/java/constant/GameConstant.java @@ -0,0 +1,8 @@ +package constant; + +public final class GameConstant { + + public static final int NUMBER_COUNT = 3; + public static final int RESTART_GAME_NUMBER = 1; + public static final int END_GAME_NUMBER = 2; +} diff --git a/src/main/java/constant/GameMessageConstant.java b/src/main/java/constant/GameMessageConstant.java new file mode 100644 index 00000000..10ee1169 --- /dev/null +++ b/src/main/java/constant/GameMessageConstant.java @@ -0,0 +1,16 @@ +package constant; + +public final class GameMessageConstant { + + public static final String BALL = "볼"; + public static final String STRIKE = "스트라이크"; + public static final String NOTHING = "낫싱"; + public static final String START_GAME = "숫자 야구 게임을 시작합니다. 제가 생각한 숫자를 맞춰보세요!\n"; + public static final String INPUT_NUMBER = "숫자를 입력해주세요 : "; + public static final String GAME_CLEAR = String.format("%d개의 숫자를 정확히 맞히셨습니다! 축하드립니다.", + GameConstant.NUMBER_COUNT); + public static final String GAME_RESTART_QUESTION = String.format( + "게임을 새로 시작하려면 %d, 종료하려면 %d를 입력하세요.", + GameConstant.RESTART_GAME_NUMBER, GameConstant.END_GAME_NUMBER); + public static final String GAME_END = "게임을 종료합니다."; +} diff --git a/src/main/java/data/GameResult.java b/src/main/java/data/GameResult.java new file mode 100644 index 00000000..00c125fa --- /dev/null +++ b/src/main/java/data/GameResult.java @@ -0,0 +1,5 @@ +package data; + +public record GameResult(int ball, int strike) { + +} \ No newline at end of file diff --git a/src/test/java/HintTest.java b/src/test/java/HintTest.java new file mode 100644 index 00000000..03842eb6 --- /dev/null +++ b/src/test/java/HintTest.java @@ -0,0 +1,38 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import constant.GameMessageConstant; +import data.GameResult; +import org.junit.jupiter.api.Test; + +class HintTest { + + @Test + void getHint_nothing() { + Hint hint = new Hint(new GameResult(0, 0)); + assertEquals(hint.getHint(), GameMessageConstant.NOTHING); + } + + @Test + void getHint_onlyBall() { + int ball = 1; + Hint hint = new Hint(new GameResult(ball, 0)); + assertEquals(hint.getHint(), String.format("%d%s", ball, GameMessageConstant.BALL)); + } + + @Test + void getHint_onlyStrike() { + int strike = 3; + Hint hint = new Hint(new GameResult(0, strike)); + assertEquals(hint.getHint(), String.format("%d%s", strike, GameMessageConstant.STRIKE)); + } + + @Test + void getHint_ballAndStrike() { + int ball = 1; + int strike = 1; + Hint hint = new Hint(new GameResult(ball, strike)); + assertEquals(hint.getHint(), + String.format("%d%s %d%s", ball, GameMessageConstant.BALL, strike, + GameMessageConstant.STRIKE)); + } +} \ No newline at end of file diff --git a/src/test/java/RandomNumberGeneratorTest.java b/src/test/java/RandomNumberGeneratorTest.java new file mode 100644 index 00000000..ac1e204c --- /dev/null +++ b/src/test/java/RandomNumberGeneratorTest.java @@ -0,0 +1,27 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class RandomNumberGeneratorTest { + + @Test + void generate_lengthCheck_true() { + int length = 3; + int[] result = RandomNumberGenerator.generate(length); + assertEquals(result.length, length); + } + + @Test + void generate_duplicateCheck_true() { + int length = 3; + Set numbers = new HashSet<>(); + int[] result = RandomNumberGenerator.generate(length); + for (int i : result) { + numbers.add(i); + } + assertEquals(numbers.size(), length); + } + +} \ No newline at end of file diff --git a/src/test/java/ValidationTest.java b/src/test/java/ValidationTest.java new file mode 100644 index 00000000..6f97df53 --- /dev/null +++ b/src/test/java/ValidationTest.java @@ -0,0 +1,53 @@ +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ValidationTest { + + @Test + void checkThreeDigitNumber_rightInput_true() { + assertTrue(Validation.checkThreeDigitNumber("136")); + assertTrue(Validation.checkThreeDigitNumber("111")); + } + + @Test + void checkThreeDigitNumber_wrongLengthInput_false() { + assertFalse(Validation.checkThreeDigitNumber("14")); + assertFalse(Validation.checkThreeDigitNumber("")); + assertFalse(Validation.checkThreeDigitNumber("6789")); + } + + @Test + void checkThreeDigitNumber_notNumberInput_false() { + assertFalse(Validation.checkThreeDigitNumber("1a2")); + assertFalse(Validation.checkThreeDigitNumber("14*")); + assertFalse(Validation.checkThreeDigitNumber("9$0")); + } + + @Test + void checkDuplicateNumber_rightInput_true() { + assertTrue(Validation.checkDuplicateNumber("136")); + assertTrue(Validation.checkDuplicateNumber("567")); + } + + @Test + void checkDuplicateNumber_duplicateInput_false() { + assertFalse(Validation.checkDuplicateNumber("118")); + assertFalse(Validation.checkDuplicateNumber("399")); + assertFalse(Validation.checkDuplicateNumber("080")); + } + + @Test + void checkEndCommand_rightInput_true() { + assertTrue(Validation.checkEndCommand("1")); + assertTrue(Validation.checkEndCommand("2")); + } + + @Test + void checkEndCommand_otherInput_false() { + assertFalse(Validation.checkEndCommand("0")); + assertFalse(Validation.checkEndCommand("a")); + assertFalse(Validation.checkEndCommand("4")); + } +} \ No newline at end of file